diff --git a/core/block.js b/core/block.js index df32b68202..48fd5b5eb8 100644 --- a/core/block.js +++ b/core/block.js @@ -622,7 +622,7 @@ Blockly.Block.prototype.getFieldValue = function(name) { * @deprecated December 2013 */ Blockly.Block.prototype.getTitleValue = function(name) { - console.warn('Deprecated call to getTitleValue, use getFieldValue instead.'); + console.log('Deprecated call to getTitleValue, use getFieldValue instead.'); return this.getFieldValue(name); }; @@ -644,7 +644,7 @@ Blockly.Block.prototype.setFieldValue = function(newValue, name) { * @deprecated December 2013 */ Blockly.Block.prototype.setTitleValue = function(newValue, name) { - console.warn('Deprecated call to setTitleValue, use setFieldValue instead.'); + console.log('Deprecated call to setTitleValue, use setFieldValue instead.'); this.setFieldValue(newValue, name); }; diff --git a/core/block_svg.js b/core/block_svg.js index 65a76c71aa..3fc7daf76a 100644 --- a/core/block_svg.js +++ b/core/block_svg.js @@ -484,7 +484,7 @@ Blockly.BlockSvg.prototype.showHelp_ = function() { * @private */ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { - if (this.workspace.options.readOnly || !this.contextMenu) { + if (Blockly.readOnly || !this.contextMenu) { return; } // Save the current block in a variable for use in closures. @@ -505,8 +505,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { } options.push(duplicateOption); - if (this.isEditable() && !this.collapsed_ && - this.workspace.options.comments) { + if (this.isEditable() && !this.collapsed_ && Blockly.comments) { // Option to add/remove a comment. var commentOption = {enabled: true}; if (this.comment) { @@ -540,7 +539,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { } } - if (this.workspace.options.collapse) { + if (Blockly.collapse) { // Option to collapse/expand block. if (this.collapsed_) { var expandOption = {enabled: true}; @@ -559,7 +558,7 @@ Blockly.BlockSvg.prototype.showContextMenu_ = function(e) { } } - if (this.workspace.options.disable) { + if (Blockly.disable) { // Option to disable/enable block. var disableOption = { text: this.disabled ? diff --git a/core/blockly.js b/core/blockly.js index 7359094c1b..fd81c28548 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -54,6 +54,12 @@ goog.require('goog.color'); goog.require('goog.userAgent'); +/** + * Path to Blockly's media directory. Can be relative, absolute, or remote. + * Used for loading sounds and sprites. Defaults to demo server. + */ +Blockly.pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; + /** * Required name space for SVG elements. * @const @@ -377,6 +383,7 @@ Blockly.onKeyDown_ = function(e) { // When focused on an HTML text input widget, don't trap any keys. return; } + // TODO: Add keyboard support for cursoring around the context menu. if (e.keyCode == 27) { // Pressing esc closes the context menu. Blockly.hideChaff(); @@ -487,18 +494,17 @@ Blockly.copy_ = function(block) { * @private */ Blockly.showContextMenu_ = function(e) { - var workspace = Blockly.mainWorkspace; - if (workspace.options.readOnly) { + if (Blockly.readOnly) { return; } var options = []; // Add a little animation to collapsing and expanding. var COLLAPSE_DELAY = 10; - if (workspace.options.collapse) { + if (Blockly.collapse) { var hasCollapsedBlocks = false; var hasExpandedBlocks = false; - var topBlocks = workspace.getTopBlocks(false); + var topBlocks = Blockly.mainWorkspace.getTopBlocks(false); for (var i = 0; i < topBlocks.length; i++) { var block = topBlocks[i]; while (block) { @@ -696,20 +702,19 @@ Blockly.playAudio = function(name, opt_volume) { * @private */ Blockly.getMainWorkspaceMetrics_ = function() { - var mainWorkspace = Blockly.mainWorkspace; var svgSize = Blockly.svgSize(); - if (mainWorkspace.toolbox_) { - svgSize.width -= mainWorkspace.toolbox_.width; + if (Blockly.mainWorkspace.toolbox_) { + svgSize.width -= Blockly.mainWorkspace.toolbox_.width; } var viewWidth = svgSize.width - Blockly.Scrollbar.scrollbarThickness; var viewHeight = svgSize.height - Blockly.Scrollbar.scrollbarThickness; try { - var blockBox = mainWorkspace.getCanvas().getBBox(); + var blockBox = Blockly.mainWorkspace.getCanvas().getBBox(); } catch (e) { // Firefox has trouble with hidden elements (Bug 528969). return null; } - if (mainWorkspace.scrollbar) { + if (Blockly.mainWorkspace.scrollbar) { // Add a border around the content that is at least half a screenful wide. // Ensure border is wide enough that blocks can scroll over entire screen. var MARGIN = 5; @@ -731,16 +736,16 @@ Blockly.getMainWorkspaceMetrics_ = function() { var bottomEdge = topEdge + blockBox.height; } var absoluteLeft = 0; - if (!Blockly.RTL && mainWorkspace.toolbox_) { - absoluteLeft = mainWorkspace.toolbox_.width; + if (!Blockly.RTL && Blockly.mainWorkspace.toolbox_) { + absoluteLeft = Blockly.mainWorkspace.toolbox_.width; } var metrics = { viewHeight: svgSize.height, viewWidth: svgSize.width, contentHeight: bottomEdge - topEdge, contentWidth: rightEdge - leftEdge, - viewTop: -mainWorkspace.scrollY, - viewLeft: -mainWorkspace.scrollX, + viewTop: -Blockly.mainWorkspace.scrollY, + viewLeft: -Blockly.mainWorkspace.scrollX, contentTop: topEdge, contentLeft: leftEdge, absoluteTop: 0, @@ -756,24 +761,23 @@ Blockly.getMainWorkspaceMetrics_ = function() { * @private */ Blockly.setMainWorkspaceMetrics_ = function(xyRatio) { - var mainWorkspace = Blockly.mainWorkspace; - if (!mainWorkspace.scrollbar) { + if (!Blockly.mainWorkspace.scrollbar) { throw 'Attempt to set main workspace scroll without scrollbars.'; } var metrics = Blockly.getMainWorkspaceMetrics_(); if (goog.isNumber(xyRatio.x)) { - mainWorkspace.scrollX = -metrics.contentWidth * xyRatio.x - + Blockly.mainWorkspace.scrollX = -metrics.contentWidth * xyRatio.x - metrics.contentLeft; } if (goog.isNumber(xyRatio.y)) { - mainWorkspace.scrollY = -metrics.contentHeight * xyRatio.y - + Blockly.mainWorkspace.scrollY = -metrics.contentHeight * xyRatio.y - metrics.contentTop; } - var x = mainWorkspace.scrollX + metrics.absoluteLeft; - var y = mainWorkspace.scrollY + metrics.absoluteTop; - mainWorkspace.translate(x, y); - mainWorkspace.gridPattern_.setAttribute('x', x); - mainWorkspace.gridPattern_.setAttribute('y', y); + var x = Blockly.mainWorkspace.scrollX + metrics.absoluteLeft; + var y = Blockly.mainWorkspace.scrollY + metrics.absoluteTop; + Blockly.mainWorkspace.translate(x, y); + Blockly.mainWorkspacePattern_.setAttribute('x', x); + Blockly.mainWorkspacePattern_.setAttribute('y', y); }; /** diff --git a/core/css.js b/core/css.js index 9d94db4ae9..451efee029 100644 --- a/core/css.js +++ b/core/css.js @@ -66,18 +66,15 @@ Blockly.Css.mediaPath_ = ''; * a) It loads synchronously and doesn't force a redraw later. * b) It speeds up loading by not blocking on a separate HTTP transfer. * c) The CSS content may be made dynamic depending on init options. - * @param {boolean} hasCss If false, don't inject CSS - * (providing CSS becomes the document's responsibility). - * @param {string} pathToMedia Path from page to the Blockly media directory. */ -Blockly.Css.inject = function(hasCss, pathToMedia) { +Blockly.Css.inject = function() { // Placeholder for cursor rule. Must be first rule (index 0). var text = '.blocklyDraggable {}\n'; - if (hasCss) { + if (Blockly.hasCss) { text += Blockly.Css.CONTENT.join('\n'); } // Strip off any trailing slash (either Unix or Windows). - Blockly.Css.mediaPath_ = pathToMedia.replace(/[\\\/]$/, ''); + Blockly.Css.mediaPath_ = Blockly.pathToMedia.replace(/[\\\/]$/, ''); text = text.replace(/<<>>/g, Blockly.Css.mediaPath_); Blockly.Css.styleSheet_ = goog.cssom.addCssText(text).sheet; Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN); diff --git a/core/inject.js b/core/inject.js index 9f83c2b1bb..ee3afebb89 100644 --- a/core/inject.js +++ b/core/inject.js @@ -34,32 +34,31 @@ goog.require('goog.userAgent'); /** - * Inject a Blockly editor into the specified container DIV. + * Initialize the SVG document with various handlers. * @param {!Element} container Containing element. * @param {Object} opt_options Optional dictionary of options. - * @return {!Blockly.Workspace} Newly created main workspace. */ Blockly.inject = function(container, opt_options) { // Verify that the container is in document. if (!goog.dom.contains(document, container)) { throw 'Error: container is not in current document.'; } - var options = Blockly.parseOptions_(opt_options || {}); - var workspace; + if (opt_options) { + Blockly.parseOptions_(opt_options); + } var startUi = function() { - workspace = Blockly.createDom_(container, options); - Blockly.init_(workspace); + Blockly.createDom_(container); + Blockly.init_(); }; - if (options.enableRealtime) { + if (Blockly.enableRealtime) { var realtimeElement = document.getElementById('realtime'); if (realtimeElement) { realtimeElement.style.display = 'block'; } - Blockly.Realtime.startRealtime(startUi, container, options.realtimeOptions); + Blockly.Realtime.startRealtime(startUi, container, Blockly.realtimeOptions); } else { startUi(); } - return workspace; }; /** @@ -88,9 +87,7 @@ Blockly.parseToolboxTree_ = function(tree) { /** * Configure Blockly to behave according to a set of options. - * @param {!Object} options Dictionary of options. Specification: - * https://developers.google.com/blockly/installation/overview#configuration - * @return {!Object} Dictionary of normalized options. + * @param {!Object} options Dictionary of options. * @private */ Blockly.parseOptions_ = function(options) { @@ -150,44 +147,38 @@ Blockly.parseOptions_ = function(options) { grid['length'] = parseFloat(grid['length']); } grid['snap'] = !!grid['snap']; - var pathToMedia = 'https://blockly-demo.appspot.com/static/media/'; + var enableRealtime = !!options['realtime']; + var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined; + + Blockly.RTL = !!options['rtl']; + Blockly.collapse = hasCollapse; + Blockly.comments = hasComments; + Blockly.disable = hasDisable; + Blockly.readOnly = readOnly; + Blockly.maxBlocks = options['maxBlocks'] || Infinity; if (options['media']) { - pathToMedia = options['media']; + Blockly.pathToMedia = options['media']; } else if (options['path']) { // 'path' is a deprecated option which has been replaced by 'media'. - pathToMedia = options['path'] + 'media/'; + Blockly.pathToMedia = options['path'] + 'media/'; } - var enableRealtime = !!options['realtime']; - var realtimeOptions = enableRealtime ? options['realtimeOptions'] : undefined; - - return { - RTL: !!options['rtl'], - collapse: hasCollapse, - comments: hasComments, - disable: hasDisable, - readOnly: readOnly, - maxBlocks: options['maxBlocks'] || Infinity, - pathToMedia: pathToMedia, - hasCategories: hasCategories, - hasScrollbars: hasScrollbars, - hasTrashcan: hasTrashcan, - hasSounds: hasSounds, - hasCss: hasCss, - languageTree: tree, - gridOptions: grid, - enableRealtime: enableRealtime, - realtimeOptions: realtimeOptions - }; + Blockly.hasCategories = hasCategories; + Blockly.hasScrollbars = hasScrollbars; + Blockly.hasTrashcan = hasTrashcan; + Blockly.hasSounds = hasSounds; + Blockly.hasCss = hasCss; + Blockly.languageTree = tree; + Blockly.gridOptions = grid; + Blockly.enableRealtime = enableRealtime; + Blockly.realtimeOptions = realtimeOptions; }; /** * Create the SVG image. * @param {!Element} container Containing element. - * @param {Object} options Dictionary of options. - * @return {!Blockly.Workspace} Newly created main workspace. * @private */ -Blockly.createDom_ = function(container, options) { +Blockly.createDom_ = function(container) { // Sadly browsers (Chrome vs Firefox) are currently inconsistent in laying // out content in RTL mode. Therefore Blockly forces the use of LTR, // then manually positions content in RTL as needed. @@ -196,7 +187,7 @@ Blockly.createDom_ = function(container, options) { goog.ui.Component.setDefaultRightToLeft(Blockly.RTL); // Load CSS. - Blockly.Css.inject(options.hasCss, options.pathToMedia); + Blockly.Css.inject(); // Build the SVG DOM. /* @@ -222,7 +213,7 @@ Blockly.createDom_ = function(container, options) { */ var defs = Blockly.createSvgElement('defs', {}, svg); - var filter, feSpecularLighting, feMerge; + var filter, feSpecularLighting, feMerge, pattern; /* @@ -267,13 +258,13 @@ Blockly.createDom_ = function(container, options) { */ - var disabledPattern = Blockly.createSvgElement('pattern', + pattern = Blockly.createSvgElement('pattern', {'id': 'blocklyDisabledPattern', 'patternUnits': 'userSpaceOnUse', 'width': 10, 'height': 10}, defs); Blockly.createSvgElement('rect', - {'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern); + {'width': 10, 'height': 10, 'fill': '#aaa'}, pattern); Blockly.createSvgElement('path', - {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern); + {'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, pattern); /* @@ -281,55 +272,52 @@ Blockly.createDom_ = function(container, options) { */ - var gridPattern = Blockly.createSvgElement('pattern', + pattern = Blockly.createSvgElement('pattern', {'id': 'blocklyGridPattern', 'patternUnits': 'userSpaceOnUse', - 'width': options.gridOptions['spacing'], - 'height': options.gridOptions['spacing']}, defs); - if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) { - var half = Math.floor(options.gridOptions['spacing'] / 2) + .5; - var start = half - options.gridOptions['length'] / 2; - var end = half + options.gridOptions['length'] / 2; + 'width': Blockly.gridOptions['spacing'], + 'height': Blockly.gridOptions['spacing']}, defs); + if (Blockly.gridOptions['length'] > 0) { + var half = Math.floor(Blockly.gridOptions['spacing'] / 2) + .5; + var start = half - Blockly.gridOptions['length'] / 2; + var end = half + Blockly.gridOptions['length'] / 2; Blockly.createSvgElement('line', {'x1': start, 'y1': half, 'x2': end, 'y2': half, - 'stroke': options.gridOptions['colour']}, - gridPattern); - if (options.gridOptions['length'] > 1) { + 'stroke': Blockly.gridOptions['colour']}, + pattern); + if (Blockly.gridOptions['length'] > 1) { Blockly.createSvgElement('line', {'x1': half, 'y1': start, 'x2': half, 'y2': end, - 'stroke': options.gridOptions['colour']}, - gridPattern); + 'stroke': Blockly.gridOptions['colour']}, + pattern); } + Blockly.mainWorkspacePattern_ = pattern; } - var mainWorkspace = new Blockly.WorkspaceSvg( + Blockly.mainWorkspace = new Blockly.WorkspaceSvg( Blockly.getMainWorkspaceMetrics_, Blockly.setMainWorkspaceMetrics_); - mainWorkspace.options = options; - goog.mixin(Blockly, options); // TODO: Delete this (#singletonHunt). - Blockly.mainWorkspace = mainWorkspace; // TODO: Delete this (#singletonHunt). - svg.appendChild(mainWorkspace.createDom('blocklyMainBackground')); - mainWorkspace.maxBlocks = options.maxBlocks; - mainWorkspace.gridPattern_ = gridPattern; + svg.appendChild(Blockly.mainWorkspace.createDom('blocklyMainBackground')); + Blockly.mainWorkspace.maxBlocks = Blockly.maxBlocks; - if (!options.readOnly) { + if (!Blockly.readOnly) { // Determine if there needs to be a category tree, or a simple list of // blocks. This cannot be changed later, since the UI is very different. - if (options.hasCategories) { - mainWorkspace.toolbox_ = new Blockly.Toolbox(svg, container); - } else if (options.languageTree) { - mainWorkspace.addFlyout(); + if (Blockly.hasCategories) { + Blockly.mainWorkspace.toolbox_ = new Blockly.Toolbox(svg, container); + } else if (Blockly.languageTree) { + Blockly.mainWorkspace.addFlyout(); } - if (!options.hasScrollbars) { + if (!Blockly.hasScrollbars) { var workspaceChanged = function() { if (Blockly.dragMode_ == 0) { - var metrics = mainWorkspace.getMetrics(); + var metrics = Blockly.mainWorkspace.getMetrics(); var edgeLeft = metrics.viewLeft + metrics.absoluteLeft; var edgeTop = metrics.viewTop + metrics.absoluteTop; if (metrics.contentTop < edgeTop || @@ -341,7 +329,7 @@ Blockly.createDom_ = function(container, options) { metrics.viewWidth : metrics.viewWidth + edgeLeft)) { // One or more blocks may be out of bounds. Bump them back in. var MARGIN = 25; - var blocks = mainWorkspace.getTopBlocks(false); + var blocks = Blockly.mainWorkspace.getTopBlocks(false); for (var b = 0, block; block = blocks[b]; b++) { var blockXY = block.getRelativeToSurfaceXY(); var blockHW = block.getHeightWidth(); @@ -386,16 +374,13 @@ Blockly.createDom_ = function(container, options) { Blockly.WidgetDiv.DIV = goog.dom.createDom('div', 'blocklyWidgetDiv'); Blockly.WidgetDiv.DIV.style.direction = Blockly.RTL ? 'rtl' : 'ltr'; document.body.appendChild(Blockly.WidgetDiv.DIV); - return mainWorkspace; }; /** * Initialize Blockly with various handlers. - * @param {!Blockly.Workspace} mainWorkspace Newly created main workspace. * @private */ -Blockly.init_ = function(mainWorkspace) { - var options = mainWorkspace.options; +Blockly.init_ = function() { // Bind events for scrolling the workspace. // Most of these events should be bound to the SVG's surface. // However, 'mouseup' has to be on the whole document so that a block dragged @@ -430,32 +415,34 @@ Blockly.init_ = function(mainWorkspace) { Blockly.documentEventsBound_ = true; } - if (options.languageTree) { - if (mainWorkspace.toolbox_) { - mainWorkspace.toolbox_.init(mainWorkspace); - } else if (mainWorkspace.flyout_) { + if (Blockly.languageTree) { + if (Blockly.mainWorkspace.toolbox_) { + Blockly.mainWorkspace.toolbox_.init(Blockly.mainWorkspace); + } else if (Blockly.mainWorkspace.flyout_) { // Build a fixed flyout with the root blocks. - mainWorkspace.flyout_.init(mainWorkspace); - mainWorkspace.flyout_.show(options.languageTree.childNodes); + Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace); + Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes); // Translate the workspace sideways to avoid the fixed flyout. - mainWorkspace.scrollX = mainWorkspace.flyout_.width_; - if (options.RTL) { - mainWorkspace.scrollX *= -1; + Blockly.mainWorkspace.scrollX = Blockly.mainWorkspace.flyout_.width_; + if (Blockly.RTL) { + Blockly.mainWorkspace.scrollX *= -1; } - var translation = 'translate(' + mainWorkspace.scrollX + ', 0)'; - mainWorkspace.getCanvas().setAttribute('transform', translation); - mainWorkspace.getBubbleCanvas().setAttribute('transform', translation); + var translation = 'translate(' + Blockly.mainWorkspace.scrollX + ', 0)'; + Blockly.mainWorkspace.getCanvas().setAttribute('transform', translation); + Blockly.mainWorkspace.getBubbleCanvas().setAttribute('transform', + translation); } } - if (options.hasScrollbars) { - mainWorkspace.scrollbar = new Blockly.ScrollbarPair(mainWorkspace); - mainWorkspace.scrollbar.resize(); + if (Blockly.hasScrollbars) { + Blockly.mainWorkspace.scrollbar = + new Blockly.ScrollbarPair(Blockly.mainWorkspace); + Blockly.mainWorkspace.scrollbar.resize(); } - mainWorkspace.addTrashcan(); + Blockly.mainWorkspace.addTrashcan(); // Load the sounds. - if (options.hasSounds) { + if (Blockly.hasSounds) { Blockly.loadAudio_( [Blockly.pathToMedia + 'click.mp3', Blockly.pathToMedia + 'click.wav', @@ -485,8 +472,30 @@ Blockly.init_ = function(mainWorkspace) { * Modify the block tree on the existing toolbox. * @param {Node|string} tree DOM tree of blocks, or text representation of same. */ -Blockly.updateToolbox = function(tree, workspace) { - console.warn('Deprecated call to Blockly.updateToolbox, ' + - 'use workspace.updateToolbox instead.'); - Blockly.getMainWorkspace().updateToolbox(tree); +Blockly.updateToolbox = function(tree) { + tree = Blockly.parseToolboxTree_(tree); + if (!tree) { + if (Blockly.languageTree) { + throw 'Can\'t nullify an existing toolbox.'; + } + // No change (null to null). + return; + } + if (!Blockly.languageTree) { + throw 'Existing toolbox is null. Can\'t create new toolbox.'; + } + var hasCategories = !!tree.getElementsByTagName('category').length; + if (hasCategories) { + if (!Blockly.hasCategories) { + throw 'Existing toolbox has no categories. Can\'t change mode.'; + } + Blockly.languageTree = tree; + Blockly.mainWorkspace.toolbox_.populate_(); + } else { + if (Blockly.hasCategories) { + throw 'Existing toolbox has categories. Can\'t change mode.'; + } + Blockly.languageTree = tree; + Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes); + } }; diff --git a/core/input.js b/core/input.js index 5b6209ee77..28610150b2 100644 --- a/core/input.js +++ b/core/input.js @@ -102,7 +102,7 @@ Blockly.Input.prototype.appendField = function(field, opt_name) { * @deprecated December 2013 */ Blockly.Input.prototype.appendTitle = function(field, opt_name) { - console.warn('Deprecated call to appendTitle, use appendField instead.'); + console.log('Deprecated call to appendTitle, use appendField instead.'); return this.appendField(field, opt_name); }; diff --git a/core/workspace.js b/core/workspace.js index 955aef9ebc..6ab7e6322a 100644 --- a/core/workspace.js +++ b/core/workspace.js @@ -186,35 +186,3 @@ Blockly.Workspace.prototype.remainingCapacity = function() { Blockly.Workspace.prototype.fireChangeEvent = function() { // NOP. }; - -/** - * Modify the block tree on the existing toolbox. - * @param {Node|string} tree DOM tree of blocks, or text representation of same. - */ -Blockly.Workspace.prototype.updateToolbox = function(tree) { - tree = Blockly.parseToolboxTree_(tree); - if (!tree) { - if (Blockly.languageTree) { - throw 'Can\'t nullify an existing toolbox.'; - } - // No change (null to null). - return; - } - if (!Blockly.languageTree) { - throw 'Existing toolbox is null. Can\'t create new toolbox.'; - } - var hasCategories = !!tree.getElementsByTagName('category').length; - if (hasCategories) { - if (!this.toolbox_) { - throw 'Existing toolbox has no categories. Can\'t change mode.'; - } - Blockly.languageTree = tree; - this.toolbox_.populate_(); - } else { - if (!this.flyout_) { - throw 'Existing toolbox has categories. Can\'t change mode.'; - } - Blockly.languageTree = tree; - this.flyout_.show(Blockly.languageTree.childNodes); - } -};