/*! promise-polyfill 3.1.0 */
!function(a){function b(a,b){return function(){a.apply(b,arguments)}}function c(a){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof a)throw new TypeError("not a function");this._state=null,this._value=null,this._deferreds=[],i(a,b(e,this),b(f,this))}function d(a){var b=this;return null===this._state?void this._deferreds.push(a):void k(function(){var c=b._state?a.onFulfilled:a.onRejected;if(null===c)return void(b._state?a.resolve:a.reject)(b._value);var d;try{d=c(b._value)}catch(e){return void a.reject(e)}a.resolve(d)})}function e(a){try{if(a===this)throw new TypeError("A promise cannot be resolved with itself.");if(a&&("object"==typeof a||"function"==typeof a)){var c=a.then;if("function"==typeof c)return void i(b(c,a),b(e,this),b(f,this))}this._state=!0,this._value=a,g.call(this)}catch(d){f.call(this,d)}}function f(a){this._state=!1,this._value=a,g.call(this)}function g(){for(var a=0,b=this._deferreds.length;b>a;a++)d.call(this,this._deferreds[a]);this._deferreds=null}function h(a,b,c,d){this.onFulfilled="function"==typeof a?a:null,this.onRejected="function"==typeof b?b:null,this.resolve=c,this.reject=d}function i(a,b,c){var d=!1;try{a(function(a){d||(d=!0,b(a))},function(a){d||(d=!0,c(a))})}catch(e){if(d)return;d=!0,c(e)}}var j=setTimeout,k="function"==typeof setImmediate&&setImmediate||function(a){j(a,1)},l=Array.isArray||function(a){return"[object Array]"===Object.prototype.toString.call(a)};c.prototype["catch"]=function(a){return this.then(null,a)},c.prototype.then=function(a,b){var e=this;return new c(function(c,f){d.call(e,new h(a,b,c,f))})},c.all=function(){var a=Array.prototype.slice.call(1===arguments.length&&l(arguments[0])?arguments[0]:arguments);return new c(function(b,c){function d(f,g){try{if(g&&("object"==typeof g||"function"==typeof g)){var h=g.then;if("function"==typeof h)return void h.call(g,function(a){d(f,a)},c)}a[f]=g,0===--e&&b(a)}catch(i){c(i)}}if(0===a.length)return b([]);for(var e=a.length,f=0;f<a.length;f++)d(f,a[f])})},c.resolve=function(a){return a&&"object"==typeof a&&a.constructor===c?a:new c(function(b){b(a)})},c.reject=function(a){return new c(function(b,c){c(a)})},c.race=function(a){return new c(function(b,c){for(var d=0,e=a.length;e>d;d++)a[d].then(b,c)})},c._setImmediateFn=function(a){k=a},"undefined"!=typeof module&&module.exports?module.exports=c:a.Promise||(a.Promise=c)}(this);
/**
* The core JS file for the tag editor framework.
*/
(function (root, factory) {
if (typeof define === "function" && define.amd) {
// AMD. Register as a named module.
define("red-dingo-tag-preview-framework", [], factory);
} else {
// Browser globals. Install a global object named TagPreview.
root.TagPreview = factory();
}
} (this, function () {
// Red Dingo framework common definitions
var rdCommon = { }
rdCommon.buildNumber = "20241";
// Red Dingo static resources CDN root
rdCommon.cdnRoot = "https://cdn.tags.reddingo.com/";
rdCommon.tagEditorDir = "https://cdn.tags.reddingo.com/tag-preview-framework/lib/";
// Module global private variables
rdCommon.gwtBridge = null;
/**
* Checks if the given object is a browser DOM element object.
*/
rdCommon.isDomElement = function(obj) {
return typeof obj == "object" && "nodeType" in obj && obj.nodeType === 1;
}
/**
* Unwraps the underlying DOM element from a jQuery selector. If the object is already a DOM element
* it is simply returned.
*/
rdCommon.unwrapJQueryElement = function(el) {
if (rdCommon.isDomElement(el)) {
return el;
} else {
return el[0];
}
}
/**
* Copies own properties of the second and any additional arguments into the first argument in manner similar to
* jQuery's $.extend or Lodash's _.assign. Less clever than either of those but enough for our purposes.
*/
rdCommon.extend = function(obj) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (source.hasOwnProperty(key)) {
obj[key] = source[key];
}
}
}
}
/**
* Gets a URL to a static resource on the CDN.
*/
rdCommon.getStatic = function(path) {
return rdCommon.cdnRoot + path + "?v=" + rdCommon.buildNumber;
}
/**
* Namespace for functions used by the GWT code.
*/
rdCommon.GwtExports = {
getStatic: rdCommon.getStatic
};
/**
* Lazily loads the GWT module and instantiates the GWT bridge when first required.
*/
rdCommon.initGwtBridge = function() {
if (rdCommon.gwtBridge == null) {
return new Promise(function(resolve, reject) {
// Need to initialize the GWT glue code first. Through it, the JS code will talk to the GWT module.
window.__tagEditorGwtCallbacks = window.__tagEditorGwtCallbacks || [];
window.__tagEditorGwtCallbacks.push(function(err, result) {
if (err) {
reject(err);
} else {
rdCommon.gwtBridge = result;
rdCommon.gwtBridge.setJsExports(rdCommon.GwtExports);
rdCommon.gwtBridge.setTagDataPath(rdCommon.tagDataPath);
resolve(null);
}
});
var tagEditorDir;
// Add <script> tag for the GWT module.
var gwtScriptFile = "red_dingo_tag_editor_framework.nocache.js";
var scriptElement = document.createElement("script");
scriptElement.setAttribute("src",
rdCommon.tagEditorDir + gwtScriptFile + "?v=" + rdCommon.buildNumber);
document.getElementsByTagName("head")[0].appendChild(scriptElement);
});
} else {
// Already initialized
return Promise.resolve();
}
}
rdCommon.createSideView = function(options) {
return rdCommon.initGwtBridge().then(function() {
var canvas = rdCommon.unwrapJQueryElement(options.canvasElement);
if (!canvas) {
return Promise.reject(new TypeError("Invalid canvas element"), null);
}
return rdCommon.gwtBridge.loadEngraveFont().then(function(font) {
var side = options.side || "SIDE1";
var zoomer = options.zoomer || rdCommon.gwtBridge.createZoomer(rdCommon.minZoomLevel);
var view = rdCommon.gwtBridge.createSideView(
canvas, font, side.toUpperCase(), zoomer, rdCommon.imagePath);
// Repaint side on zoom level change
zoomer.addScaleChangeListener(function() {
view.repaint();
});
// RD-1565: Repaint on resize
var resizeHandler = rdCommon.gwtBridge.debounce(function() {
if (!document.contains(canvas)) {
// canvas no longer attached to DOM - stop listening to window resize
window.removeEventListener("resize", resizeHandler);
} else {
// repaint on window resize, just in case
view.repaint();
}
}, 50, false);
window.addEventListener("resize", resizeHandler);
// Install hook for replacing the default wheel handler if necessary
var wheelHandler = null;
view.setOnWheel = function(handler) {
if (wheelHandler) {
canvas.removeEventListener("wheel", wheelHandler);
wheelHandler = null;
}
if (canvas.__tagEditorDefaultOnWheel) {
canvas.removeEventListener(canvas.__tagEditorDefaultOnWheel);
canvas.__tagEditorDefaultOnWheel = null;
}
if (handler) {
wheelHandler = function(e) {
handler.call(view, e);
};
canvas.addEventListener("wheel", wheelHandler);
}
};
// Install arrow key handler
canvas.addEventListener("keydown", function(e) {
switch (e.keyCode) {
case 37: // left
view.handleEditCommand("MOVE_LEFT");
break;
case 38: // up
view.handleEditCommand("MOVE_UP");
break;
case 39: // right
view.handleEditCommand("MOVE_RIGHT");
break;
case 40: // down
view.handleEditCommand("MOVE_DOWN");
break;
default:
break;
}
});
return view;
});
});
}
/**
* A two-side view without toolbars.
*/
rdCommon.TwoSideView = function(side1, side2, zoomer) {
var changeListeners = [];
var selectionChangeListeners = [];
var outOfBoundsChangeListeners = [];
var currentAutoHideSide2 = false;
var isSide2Hidden = false;
function fireChange(side, otherSide) {
changeListeners.forEach(function(listener) {
listener(side, otherSide);
});
}
function fireSelectionChange(selectedLines, side, otherSide) {
selectionChangeListeners.forEach(function(listener) {
listener(selectedLines, side, otherSide);
});
}
function fireOutOfBoundsChange(outOfBoundsLines, side, otherSide) {
outOfBoundsChangeListeners.forEach(function(listener) {
listener(outOfBoundsLines, side, otherSide);
});
}
/**
* Gets the side 1 view used by this two-side view.
*
* @function TwoSideView#getSide1
* @return {SideView} the side 1 view
*/
this.getSide1 = function() {
return side1;
};
/**
* Gets the side 2 view used by this two-side view.
*
* @function TwoSideView#getSide2
* @return {SideView} the side 1 view
*/
this.getSide2 = function() {
return side2;
};
/**
* Gets the zoomer associated with this two-side view.
*
* @function TwoSideView#getZoomer
* @return {Zoomer} the zoomer
*/
this.getZoomer = function() {
return zoomer;
};
/**
* Listener for data change events.
*
* @param side {SideView} the side the event was fired for
* @param otherSide {SideView} the side the event was <b>not</b> fired for
* @callback TwoSideView~changeListener
*/
/**
* Registers a data change listener with this two-side view.
*
* @function TwoSideView#addChangeListener
* @param listener {TwoSideView~changeListener} listener to register
*/
this.addChangeListener = function(listener) {
changeListeners.push(listener);
};
/**
* Listener for selection change events.
*
* @param selectedLines {number[]} Indexes of selected lines (zero-based), empty if no selection
* @param side {SideView} the side the event was fired for
* @param otherSide {SideView} the side the event was <b>not</b> fired for
* @callback TwoSideView~selectionChangeListener
*/
/**
* Registers a selection change listener with this two-side view.
*
* @function TwoSideView#addSelectionChangeListener
* @param listener {TwoSideView~selectionChangeListener} listener to register
*/
this.addSelectionChangeListener = function(listener) {
selectionChangeListeners.push(listener);
};
/**
* Listener for out of bounds change events.
*
* @callback TwoSideView~outOfBoundsChangeListener
* @param outOfBoundsLines {number[]} Indexes of lines that cross the tag bounds (zero-based), empty if none
* @param side {SideView} the side the event was fired for
* @param otherSide {SideView} the side the event was <b>not</b> fired for
*/
/**
* Registers an out of bounds change listener with this two-side view.
*
* @function TwoSideView#addOutOfBoundsChangeListener
* @param listener {TwoSideView~outOfBoundsChangeListener} listener to register
*/
this.addOutOfBoundsChangeListener = function(listener) {
outOfBoundsChangeListeners.push(listener);
};
// Install data change, selection change and line intersection change events
function onDataChanged(side, otherSide) {
// Return the actual event handler
return function() {
// For now, do nothing, but in the future, we might need this. Fire the event, though.
fireChange(side, otherSide);
};
}
if (side1.addChangeListener) { side1.addChangeListener(onDataChanged(side1, side2)); }
if (side2.addChangeListener) { side2.addChangeListener(onDataChanged(side2, side1)); }
function onSelectionChanged(side, otherSide) {
// Return the actual event handler
return function(selectedLines) {
// Reset selection on the other side if all lines are deselected
if (selectedLines.length == 0) {
otherSide.resetSelection();
}
fireSelectionChange(selectedLines, side, otherSide);
};
}
side1.addSelectionChangeListener(onSelectionChanged(side1, side2));
side2.addSelectionChangeListener(onSelectionChanged(side2, side1));
function onOutOfBoundsChanged(side, otherSide) {
// Return the actual event handler
return function(outOfBoundsLines) {
fireOutOfBoundsChange(outOfBoundsLines, side, otherSide);
};
}
side1.addOutOfBoundsChangeListener(onOutOfBoundsChanged(side1, side2));
side2.addOutOfBoundsChangeListener(onOutOfBoundsChanged(side2, side1));
function setSide2Visible(visible) {
if (visible && isSide2Hidden) {
side2.getCanvasElement().style.display = "none";
isSide2Hidden = false;
} else if (!visible && !isSide2Hidden) {
side2.getCanvasElement().style.display = "";
isSide2Hidden = true;
}
}
/**
* Returns the tag options from the last successful call to {@link TwoSideView#setTag}. If <code>setTag</code>
* was not previously called, or no call succeeded, returns <code>null</code>.
*
* @function TwoSideView#getTag
* @return {?TagOptions} tag options used previously by <code>setTag</code>
*/
this.getTag = function() {
return this.getSide1().getTag();
};
/**
* Asynchronously sets the tag for the two-side view. Arguments are identical to single-side.
*
* If the side has text on it, and the default layout was not customized, the text is relaid out according to
* the default layout of the new tag. A custom layout is untouched, so the text will likely not fit the new tag.
* If desired, a custom layout can be manually reset afterwards using {@link TwoSideView#resetLayout}.
*
* @function TwoSideView#setTag
* @param options {TagOptions} tag options
* @return {Promise.<void>} promise resolved after the operation completes
*/
this.setTag = function(options) {
var that = this;
// Note that if we need to hide side 2, it is done BEFORE queuing tag update for side 2,
// whereas if we need to show side 2, it is done AFTER tag update for side 2 finishes.
// This is done to prevent flickering.
if (currentAutoHideSide2 && !options.doubleSided) {
setSide2Visible(false);
}
return Promise.all([
side1.setTag(options),
side2.setTag(options).then(function() {
if (currentAutoHideSide2 && options.doubleSided) {
setSide2Visible(true);
side2.repaint();
}
})
]);
};
/**
* Returns whether side 2 should be automatically hidden upon when this tag view displays a single-side tag.
*
* @function TwoSideView#isAutoHideSide2
* @return {boolean} whether to automatically hide side 2
*/
this.isAutoHideSide2 = function() {
return autoHideSide2;
}
/**
* Sets a flag specifying whether side 2 should be automatically hidden upon when this tag view displays a
* single-side tag. If necessary, shows or hides side 2 depending on the current tag and the new setting.
* <p>
* By default, side 2 is not automatically hidden.
* </p>
*
* @function TwoSideView#setAutoHideSide2
* @param autoHideSide2 {boolean} whether to automatically hide side 2
*/
this.setAutoHideSide2 = function(autoHideSide2) {
autoHideSide2 = !!autoHideSide2; // coerce to boolean
if (!currentAutoHideSide2 && autoHideSide2) {
var tag = this.getTag();
setSide2Visible(tag ? tag.doubleSided : false);
} else if (currentAutoHideSide2 && !autoHideSide2) {
setSide2Visible(true);
}
currentAutoHideSide2 = autoHideSide2;
}
}
/**
* A two-side view without toolbars.
*
* @interface TwoSideView
*/
rdCommon.TwoSideView.prototype = {
/**
* Returns the font set by the last successful call to {@link TwoSideView#setFont}. When a <code>TwoSideView</code>
* is created, the font is set to <code>ENGRAVE</code> by default.
*
* @function TwoSideView#getFont
* @return {module:TagEditor.Font} the current font
*/
getFont: function() {
return this.getSide1().getFont();
},
/**
* Asynchronously sets the font for the two-side view. Arguments are identical to single-side.
*
* @function TwoSideView#setFont
* @param font {module:TagEditor.Font} the font to set (e.g. "ENGRAVE" or "UNICODE")
* @return {Promise.<void>} promise resolved after the operation completes
*/
setFont: function(font) {
var that = this;
return Promise.all([ that.getSide1().setFont(font), that.getSide2().setFont(font) ]);
},
/**
* Repaints the two-side view.
*
* @function TwoSideView#repaint
*/
repaint: function() {
this.getSide1().repaint();
this.getSide2().repaint();
},
/**
* Returns the text lines set by the last successful call to {@link TwoSideView#setLines}. When a
* <code>TwoSideView</code> is created, it has no text lines set, and this method returns an empty array.
*
* @function TwoSideView#getLines
* @return {TwoSideLines} current text lines
*/
getLines: function() {
return {
side1: this.getSide1().getLines(),
side2: this.getSide2().getLines()
};
},
/**
* Asynchronously sets the text for the two-side view. Any custom layout changes are reset, and the text is
* relaid out according to the default layout rules.
*
* @function TwoSideView#setLines
* @param textLines {TwoSideLines} text lines to set
* @return {Promise.<void>} promise resolved after the operation completes
*/
setLines: function(textLines) {
var that = this;
return Promise.all([
that.getSide1().setLines(textLines.side1 || []),
that.getSide2().setLines(textLines.side2 || [])
]);
},
/**
* Checks if preview is enabled for this two-side view. When preview is enabled, a translucent version of the
* tag image or (for side 1, if set) the blank tag image is displayed under the rendered tag outline and text.
*
* @function TwoSideView#isPreviewEnabled
* @return {boolean} whether preview is enabled
*/
isPreviewEnabled: function() {
return this.getSide1().isPreviewEnabled() && this.getSide2().isPreviewEnabled();
},
/**
* Sets the preview flag for this two-side view. Repaints the view if necessary.
*
* @function TwoSideView#setPreviewEnabled
* @param previewEnabled true to enable preview, false to disable
*/
setPreviewEnabled: function(previewEnabled) {
this.getSide1().setPreviewEnabled(previewEnabled);
this.getSide2().setPreviewEnabled(previewEnabled);
},
/**
* Checks if the view has any selected lines.
*
* @function TwoSideView#hasSelection
* @return {boolean} Whether there are any selected lines on either side of the view
*/
hasSelection: function() {
return this.getSide1().hasSelection() || this.getSide2().hasSelection();
},
/**
* Unselects all lines on both sides of the view. Does not fire a selection change event.
*
* @function TwoSideView#resetSelection
*/
resetSelection: function() {
this.getSide1().resetSelection();
this.getSide2().resetSelection();
},
/**
* Forces the two-side view to use metric units (cm) or imperial units (inches) for rulers on both sides,
* replacing the current user selection (which can be different for each side).
*
* @function TwoSideView#showImperialUnits
* @param imperial true to use inches, false to use centimeters
*/
showImperialUnits: function(imperial) {
this.getSide1().showImperialUnits(imperial);
this.getSide2().showImperialUnits(imperial);
},
/**
* Replaces the default mouse wheel handler with <code>handler</code>. The handler listens to the browser
* <code>wheel</code> event on the canvas element. Inside the handler, <code>this</code> refers to the
* two-side view.
*
* @function TwoSideView#setOnWheel
* @param handler {jQueryEventHandler} the new wheel event handler
*/
setOnWheel: function(handler) {
var that = this;
var handlerWrapper = function(event) {
handler.call(that, event);
};
this.getSide1().setOnWheel(handlerWrapper);
this.getSide2().setOnWheel(handlerWrapper);
}
};
rdCommon.createTwoSideViewPrerequisites = function(options) {
return rdCommon.initGwtBridge().then(function() {
var zoomer = options.zoomer || rdCommon.gwtBridge.createZoomer(rdCommon.minZoomLevel);
var side1CanvasEl = rdCommon.unwrapJQueryElement(options.side1CanvasElement);
if (!side1CanvasEl) {
return Promise.reject(new TypeError("Invalid side 1 canvas element"), null);
}
var side2CanvasEl = rdCommon.unwrapJQueryElement(options.side2CanvasElement);
if (!side2CanvasEl) {
return Promise.reject(new TypeError("Invalid side 2 canvas element"), null);
}
return rdCommon.createSideView({
canvasElement: side1CanvasEl,
side: "SIDE1",
zoomer: zoomer
}).then(function(side1) {
return rdCommon.createSideView({
canvasElement: side2CanvasEl,
side: "SIDE2",
zoomer: zoomer
}).then(function(side2) {
return { side1: side1, side2: side2, zoomer: zoomer };
});
});
});
};
// The definitions below are not used in the code and are only relevant to JSDoc.
/**
* Event handler for jQuery events triggered by a {@link SideView} or {@link TwoSideView}. These view classes
* expose methods to override event handling. Inside a handler passed to such methods, the <code>this</code>
* parameter refers to the view (<code>SideView</code> or <code>TwoSideView</code>).
*
* @callback jQueryEventHandler
* @param event <a href="https://api.jquery.com/category/events/event-object/">jQuery event object</a>
*/
/**
* Object describing text lines for both sides of a {@link TwoSideView}.
*
* @typedef TwoSideLines
*
* @property side1 {string[]} text lines for side 1 (possibly an empty array)
* @property [side2=[]] {string[]} text lines for side 2 (possibly an empty array). Optional when passed to
* {@link TwoSideView#setLines}. Always returned by {@link TwoSideView#getLines}.
*/
/**
* Options object passed to {@link SideView#setTag} and {@link TwoSideView#setTag}.
*
* @typedef TagOptions
*
* @property tagSku {string} tag SKU string in the extended format (e.g. "01-AU-DB-LG");
* hyphens may be omitted (e.g. "01AUDBLG")
* @property [doubleSided=false] {boolean} allow engraving on both sides of the tag; note that the
* framework does not check if the tag is actually capable of double-side engraving
* @property [template=default] {string} template code
* @property [blankTagImageFileName] {string} File name to use to display the blank tag image in preview mode.
* If not set, the main tag image is used as a fallback. If the file name has no extension, ".png" is
* assumed. The framework will automatically insert the right scale suffix in the file name before the
* extension if necessary to draw higher-resolution tag images.
*/
/**
* Object associated with each single-side and two-side view, used to control and synchronize the zoom level.
*
* @interface Zoomer
*/
var _Zoomer = {
/**
* Gets the current zoom level.
*
* @function Zoomer#getZoom
* @return {number} the current zoom level
*/
getZoom: function() { },
/**
* Sets the current zoom level.
*
* @function Zoomer#setZoom
* @param scale {number} the new zoom level
*/
setZoom: function(scale) { },
/**
* Increases the zoom level.
*
* @function Zoomer#zoomIn
*/
zoomIn: function() { },
/**
* Decreases the zoom level.
*
* @function Zoomer#zoomOut
*/
zoomOut: function() { },
/**
* Resets the zoom level.
*
* @function Zoomer#resetZoom
*/
resetZoom: function() { },
/**
* Listener for scale change events. Has no parameters.
*
* @callback Zoomer~scaleChangeListener
*/
/**
* Registers a listener called when the zoom level changes.
*
* @function Zoomer#addScaleChangeListener
* @param listener {Zoomer~scaleChangeListener} listener to register
*/
addScaleChangeListener: function(listener) { }
};
// Definition of the SideView public API shared between the tag editor framework and the tag preview framework.
/**
* A single-side view without toolbars.
*
* @interface SideView
*/
rdCommon._SideView = {
/**
* Returns the tag options from the last successful call to {@link SideView#setTag}. If <code>setTag</code> was not
* previously called, or no call succeeded, returns <code>null</code>.
*
* @function SideView#getTag
* @return {?TagOptions} tag options used previously by <code>setTag</code>
*/
getTag: function() { },
/**
* Sets the tag for the single-side view.
*
* If the side has text on it, and the default layout was not customized, the text is relaid out according to
* the default layout of the new tag. A custom layout is untouched, so the text will likely not fit the new tag.
* If desired, a custom layout can be manually reset afterwards using {@link SideView#resetLayout}.
*
* @function SideView#setTag
* @param options {TagOptions} tag options
* @return {Promise.<void>} promise that is resolved when the operation completes
*/
setTag: function(options) { },
/**
* Returns the font set by the last successful call to {@link SideView#setFont}. When a <code>SideView</code> is
* created, the font is set to <code>ENGRAVE</code> by default.
*
* @function SideView#getFont
* @return {module:TagEditor.Font} the current font
*/
getFont: function() { },
/**
* Sets the font for the single-side view.
*
* @function SideView#setFont
* @param font {module:TagEditor.Font} font identifier
* @return {Promise.<void>} promise that is resolved when the operation completes
*/
setFont: function(font) { },
/**
* Returns the text lines set by the last successful call to {@link SideView#setLines}. When a
* <code>SideView</code> is created, it has no text lines set, and this method returns an empty array.
*
* @function SideView#getLines
* @return {string[]} current text lines
*/
getLines: function() { },
/**
* Sets the text lines for the single-side view. Any custom layout changes are reset, and the text is relaid
* out according to the default layout rules.
*
* @function SideView#setLines
* @param textLines {string[]} text lines to set
* @return {Promise.<void>} promise that is resolved when the operation completes
*/
setLines: function(textLines) { },
/**
* Checks if preview is enabled for this side view. When preview is enabled, a translucent version of the tag
* image or (for side 1, if set) the blank tag image is displayed under the rendered tag outline and text.
*
* @function SideView#isPreviewEnabled
* @return whether preview is enabled
*/
isPreviewEnabled: function() { },
/**
* Sets the preview flag for this side view. Repaints the view if necessary.
*
* @function SideView#setPreviewEnabled
* @param previewEnabled true to enable preview, false to disable
*/
setPreviewEnabled: function(previewEnabled) { },
/**
* Repaints the single-side view.
*
* @function SideView#repaint
*/
repaint: function() { },
/**
* Gets the canvas DOM element which this view uses to perform its drawing.
*
* @function SideView#getCanvasElement
* @return {Element} the canvas element
*/
getCanvasElement: function() { },
/**
* Convenience method. Sets input focus to the view's canvas element.
* Equivalent to <code>getCanvasElement().focus()</code>.
*
* @function SideView#focus
*/
focus: function() { },
/**
* Checks if the side has any selected lines.
*
* @function SideView#hasSelection
* @return {boolean} Whether there are any selected lines on this side
*/
hasSelection: function() { },
/**
* Unselects all lines on this side. Does not fire a selection change event.
*
* @function SideView#resetSelection
*/
resetSelection: function() { },
/**
* Forces the side view to use metric units (cm) or imperial units (inches) for rulers, replacing the current
* user selection.
*
* @function SideView#showImperialUnits
* @param imperial {boolean} true to use inches, false to use centimeters
*/
showImperialUnits: function(imperial) { },
/**
* Listener for selection change events.
*
* @callback SideView~selectionChangeListener
* @param selectedLines {number[]} Indexes of selected lines (zero-based), empty if no selection
*/
/**
* Registers a selection change listener with this single-side view.
*
* @function SideView#addSelectionChangeListener
* @param listener {SideView~selectionChangeListener} listener to register
*/
addSelectionChangeListener: function(listener) { },
/**
* Listener for out of bounds change events.
*
* @callback SideView~outOfBoundsChangeListener
* @param outOfBoundsLines {number[]} Indexes of lines that cross the tag bounds (zero-based), empty if none
*/
/**
* Registers a of bounds change listener with this single-side view.
*
* @function SideView#addOutOfBoundsChangeListener
* @param listener {SideView~outOfBoundsChangeListener} listener to register
*/
addOutOfBoundsChangeListener: function(listener) { },
/**
* Replaces the default mouse wheel handler with <code>handler</code>. The handler listens to the browser
* <code>wheel</code> event on the canvas element. Inside the handler, <code>this</code> refers to the
* side view.
*
* @function SideView#setOnWheel
* @param handler {jQueryEventHandler} the new wheel event handler
*/
// This function is actually defined in createSideView. It is only listed here for JSDoc uniformity.
setOnWheel: function(handler) { }
};
rdCommon.minZoomLevel = 1.0;
// Relative CDN paths to tag data and tag images
rdCommon.tagDataPath = "TagData2/";
rdCommon.imagePath = "img/tags2/";
// Wrapper that exposes only the public SideView API in the tag preview framework, not the full API exposed by GWT
function SideView(gwtDelegate) {
// Disable editing right away, and do not allow re-enabling it
gwtDelegate.setEditingEnabled(false);
// rdCommon._SideView contains the declarations of the shared API
var api = rdCommon._SideView;
function wrapFunc(obj, func) {
return function() {
return func.apply(obj, arguments);
}
}
for (var key in api) {
if (api.hasOwnProperty(key) && typeof api[key] === "function") {
// For every function name present in the public API, forward it to delegate
this[key] = wrapFunc(gwtDelegate, gwtDelegate[key]);
}
}
// Definitions specific to tag preview framework
/**
* Enables or disables line selection by user. If selection is disabled, current selection is cleared.
*
* @function SideView#setSelectionEnabled
* @param selectionEnabled {boolean} true to enable selection, false to disable
*/
this.setSelectionEnabled = function(selectionEnabled) {
gwtDelegate.setSelectionEnabled(selectionEnabled);
}
/**
* Specifies which rulers are visible and which are hidden.
*
* @function SideView#setVisibleRulers
* @param visibleRulers {RulerSides} flags specifying which rulers are visible
*/
this.setVisibleRulers = function(visibleRulers) {
gwtDelegate.setVisibleRulers(visibleRulers);
}
/**
* Specifies which corners will display the measurement unit switcher.
*
* @function SideView#setVisibleUnits
* @param visibleUnits {UnitCorners} flags specifying which corners show the unit switcher
*/
this.setVisibleUnits = function(visibleUnits) {
gwtDelegate.setVisibleUnits(visibleUnits);
}
/**
* Resets corners that display the measurement unit switcher to defaults.
*
* @function SideView#resetVisibleUnits
*/
this.resetVisibleUnits = function() {
gwtDelegate.resetVisibleUnits();
}
/**
* Returns the complete state of this side view.
*
* @function SideView#getData
* @return {SideViewData} the current view state
*/
this.getData = function() {
var data = gwtDelegate.getData();
// Filter layout out of result
return {
tag : data.tag,
font : data.font,
lines : data.lines
}
}
/**
* Sets the complete state of this side view. Any layout customizations are reset to default.
*
* @function SideView#setData
* @param data {SideViewData} the new view state
* @return {Promise.<void>} promise that is resolved when the operation completes
*/
this.setData = function(data) {
return gwtDelegate.setData({
tag : data.tag,
font : data.font,
lines : data.lines
});
}
}
// Monkey patch rdCommon.createSideView to use the wrapper instead of returning the underlying GWT object
var oldCreateSideView = rdCommon.createSideView;
rdCommon.createSideView = function() {
return oldCreateSideView.apply(this, arguments).then(function(view) {
return new SideView(view);
});
};
// Extra definitions used for TwoSideView in tag preview framework
rdCommon.extend(rdCommon.TwoSideView.prototype, {
/**
* Enables or disables line selection by user. If selection is disabled, current selection is cleared.
*
* @function TwoSideView#setSelectionEnabled
* @param selectionEnabled {boolean} true to enable selection, false to disable
*/
setSelectionEnabled : function(selectionEnabled) {
this.getSide1().setSelectionEnabled(selectionEnabled);
this.getSide2().setSelectionEnabled(selectionEnabled);
},
/**
* Specifies which rulers are visible and which are hidden.
*
* @function TwoSideView#setVisibleRulers
* @param visibleRulers {object} flags specifying which rulers are visible
* @param visibleRulers.side1 {RulerSides} ruler flags for side 1
* @param visibleRulers.side2 {RulerSides} ruler flags for side 2
*/
setVisibleRulers : function(visibleRulers) {
this.getSide1().setVisibleRulers(visibleRulers.side1 || {});
this.getSide2().setVisibleRulers(visibleRulers.side2 || {});
},
/**
* Specifies which corners will display the measurement unit switcher.
*
* @function TwoSideView#setVisibleUnits
* @param visibleUnits {object} flags specifying which corners show the unit switcher
* @param visibleUnits.side1 {UnitCorners} unit flags for side 1
* @param visibleUnits.side2 {UnitCorners} unit flags for side 2
*/
setVisibleUnits : function(visibleUnits) {
this.getSide1().setVisibleUnits(visibleUnits.side1 || {});
this.getSide2().setVisibleUnits(visibleUnits.side2 || {});
},
/**
* Resets corners that display the measurement unit switcher to defaults.
*
* @function TwoSideView#resetVisibleUnits
*/
resetVisibleUnits : function() {
this.getSide1().resetVisibleUnits();
this.getSide2().resetVisibleUnits();
},
/**
* Returns the complete state of this two-side view.
*
* @function TwoSideView#getData
* @return {TwoSideViewData} the current view state
*/
getData: function() {
var side1Data = this.getSide1().getData();
var side2Data = this.getSide2().getData();
return {
tag: side1Data.tag,
font: side1Data.font,
side1: {
lines: side1Data.lines
},
side2: {
lines: side2Data.lines
}
};
},
/**
* Asynchronously sets the complete state of this two-side view.
*
* @function TwoSideView#setData
* @param data {SideViewData} the new view state
* @return {Promise.<void>} promise that is resolved when the operation completes
*/
setData: function(data) {
if (!data.tag || !data.side1 || (data.tag.doubleSided && !data.side2)) {
return Promise.reject(new Error("invalid TwoSideView data"));
}
var side1Data = {
tag: data.tag,
font: data.font,
lines: data.side1.lines || []
};
var side2Data = {
tag: data.tag,
font: data.font,
lines: data.side2 ? data.side2.lines || [] : []
};
var that = this;
return Promise.all([
that.getSide1().setData(side1Data),
that.getSide2().setData(side2Data)
]);
}
});
/**
* Functions exported by the TagPreview module.
*
* @exports TagPreview
*/
var TagPreview = {
/**
* The complete state of a {@link SideView}.
*
* @typedef SideViewData
*
* @property tag {TagOptions} tag options (as in <code>setTag</code>}
* @property font {module:TagPreview.Font} font ID (as in <code>setFont</code>}
* @property lines {string[]} text lines
*/
/**
* The complete state of a {@link TwoSideView}.
*
* @typedef TwoSideViewData
*
* @property tag {TagOptions} tag options (as in <code>setTag</code>}
* @property font {module:TagPreview.Font} font ID (as in <code>setFont</code>}
* @property side1 {object} side 1 state
* @property side1.lines {string[]} side 1 text lines
* @property side2 {object} side 2 state
* @property side2.lines {string[]} side 2 text lines
*/
/**
* Flags controlling which rulers are visible and which are hidden.
*
* @typedef RulerSides
*
* @property [top=false] {boolean} whether the top ruler is visible
* @property [bottom=false] {boolean} whether the bottom ruler is visible
* @property [left=false] {boolean} whether the left ruler is visible
* @property [right=false] {boolean} whether the right ruler is visible
*/
/**
* Flags controlling which corners display the measurement unit switcher.
*
* @typedef UnitCorners
*
* @property [topLeft=false] {boolean} whether units are visible in the top left corner
* @property [topRight=false] {boolean} whether units are visible in the top right corner
* @property [bottomLeft=false] {boolean} whether units are visible in the bottom left corner
* @property [bottomRight=false] {boolean} whether units are visible in the bottom right corner
*/
/**
* A possible font that can be set in a tag view.
*
* @readonly
* @enum {string}
*/
Font: {
/** Default tag editor font, only reliable for the ASCII range using Latin letters. */
ENGRAVE: "ENGRAVE",
/** More complete font, featuring a wide selection of Unicode glyphs. */
UNICODE: "UNICODE"
},
/**
* Creates a Zoomer object, which provides zoom in/out/reset zoom functionality for tag views.
*
* @return {Promise.<Zoomer>} promise resolved with the created Zoomer after the operation completes
*/
createZoomer: function() {
return rdCommon.initGwtBridge().then(function() {
return rdCommon.gwtBridge.createZoomer(rdCommon.minZoomLevel);
});
},
/**
* Creates a new tag editor side view asynchronously, binding to the given canvas element.
*
* @function
* @param options {Object} the options for the side view
* @param options.canvasElement {Element|jQuery} the <canvas> DOM element or jQuery object
* @param [options.side=SIDE1] {string} the side; "SIDE1" for side 1 or "SIDE2" for side 2
* @param [options.zoomer] {Zoomer} the zoomer object to be used by this view; if not set, one is created
* @return {Promise.<SideView>} promise resolved with the created SideView after the operation completes
*/
createSideView: rdCommon.createSideView,
/**
* Creates a two-side view without toolbars, given two canvas elements for side 1 and side 2.
*
* @param options {Object} the two-side view creation options
* @param options.side1CanvasElement {Element|jQuery} the side 1 <canvas> DOM element or jQuery object
* @param options.side2CanvasElement {Element|jQuery} the side 2 <canvas> DOM element or jQuery object
* @param [options.zoomer] {Zoomer} the zoomer object to be used by this view; if not set, one is created
* @return {Promise.<TwoSideView>} promise resolved with the created TwoSideView after the operation completes
*/
createTwoSideView: function(options) {
return rdCommon.createTwoSideViewPrerequisites(options).then(function(result) {
return new rdCommon.TwoSideView(result.side1, result.side2, result.zoomer);
});
}
};
return TagPreview;
}));