Add PluginManager (#4672)

* Add PluginManager.
This commit is contained in:
Monica Kozbial
2021-03-04 16:51:06 -08:00
committed by GitHub
parent 4e5cecf90c
commit ef8a5a1fe1
9 changed files with 247 additions and 55 deletions

View File

@@ -95,13 +95,15 @@ goog.addDependency('../../core/interfaces/i_deletearea.js', ['Blockly.IDeleteAre
goog.addDependency('../../core/interfaces/i_flyout.js', ['Blockly.IFlyout'], [], {});
goog.addDependency('../../core/interfaces/i_metrics_manager.js', ['Blockly.IMetricsManager'], [], {});
goog.addDependency('../../core/interfaces/i_movable.js', ['Blockly.IMovable'], [], {});
goog.addDependency('../../core/interfaces/i_positionable.js', ['Blockly.IPositionable'], [], {});
goog.addDependency('../../core/interfaces/i_plugin.js', ['Blockly.IPlugin'], [], {});
goog.addDependency('../../core/interfaces/i_positionable.js', ['Blockly.IPositionable'], ['Blockly.IPlugin'], {});
goog.addDependency('../../core/interfaces/i_registrable.js', ['Blockly.IRegistrable'], [], {});
goog.addDependency('../../core/interfaces/i_registrable_field.js', ['Blockly.IRegistrableField'], [], {});
goog.addDependency('../../core/interfaces/i_selectable.js', ['Blockly.ISelectable'], [], {});
goog.addDependency('../../core/interfaces/i_styleable.js', ['Blockly.IStyleable'], [], {});
goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [], {});
goog.addDependency('../../core/interfaces/i_toolbox_item.js', ['Blockly.ICollapsibleToolboxItem', 'Blockly.ISelectableToolboxItem', 'Blockly.IToolboxItem'], [], {});
goog.addDependency('../../core/interfaces/i_workspace_plugin.js', ['Blockly.IWorkspacePlugin'], [], {});
goog.addDependency('../../core/keyboard_nav/ast_node.js', ['Blockly.ASTNode'], ['Blockly.connectionTypes', 'Blockly.constants', 'Blockly.utils.Coordinate'], {'lang': 'es5'});
goog.addDependency('../../core/keyboard_nav/basic_cursor.js', ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor', 'Blockly.registry'], {'lang': 'es5'});
goog.addDependency('../../core/keyboard_nav/cursor.js', ['Blockly.Cursor'], ['Blockly.ASTNode', 'Blockly.Marker', 'Blockly.registry', 'Blockly.utils.object'], {'lang': 'es5'});
@@ -115,6 +117,7 @@ goog.addDependency('../../core/msg.js', ['Blockly.Msg'], ['Blockly.utils.global'
goog.addDependency('../../core/mutator.js', ['Blockly.Mutator'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.BubbleOpen', 'Blockly.Icon', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.object', 'Blockly.utils.toolbox', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg', 'Blockly.constants'], {});
goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.registry', 'Blockly.utils.IdGenerator', 'Blockly.utils.Metrics', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {});
goog.addDependency('../../core/plugin_manager.js', ['Blockly.PluginManager'], [], {});
goog.addDependency('../../core/procedures.js', ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.Names', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/registry.js', ['Blockly.registry'], [], {});
goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.connectionTypes', 'Blockly.constants', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Svg', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
@@ -212,7 +215,7 @@ goog.addDependency('../../core/workspace_comment_render_svg.js', ['Blockly.Works
goog.addDependency('../../core/workspace_comment_svg.js', ['Blockly.WorkspaceCommentSvg'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.CommentCreate', 'Blockly.Events.CommentDelete', 'Blockly.Events.CommentMove', 'Blockly.Events.Selected', 'Blockly.WorkspaceComment', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
goog.addDependency('../../core/workspace_drag_surface_svg.js', ['Blockly.WorkspaceDragSurfaceSvg'], ['Blockly.utils', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {});
goog.addDependency('../../core/workspace_dragger.js', ['Blockly.WorkspaceDragger'], ['Blockly.utils.Coordinate'], {});
goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.ThemeChange', 'Blockly.Events.ViewportChange', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.MarkerManager', 'Blockly.MetricsManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es5'});
goog.addDependency('../../core/workspace_svg.js', ['Blockly.WorkspaceSvg'], ['Blockly.BlockSvg', 'Blockly.ConnectionDB', 'Blockly.ContextMenu', 'Blockly.ContextMenuRegistry', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.ThemeChange', 'Blockly.Events.ViewportChange', 'Blockly.Gesture', 'Blockly.Grid', 'Blockly.IPositionable', 'Blockly.MarkerManager', 'Blockly.MetricsManager', 'Blockly.Msg', 'Blockly.Options', 'Blockly.PluginManager', 'Blockly.ThemeManager', 'Blockly.Themes.Classic', 'Blockly.TouchGesture', 'Blockly.Workspace', 'Blockly.WorkspaceAudio', 'Blockly.WorkspaceDragSurfaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.Metrics', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es5'});
goog.addDependency('../../core/xml.js', ['Blockly.Xml'], ['Blockly.Events', 'Blockly.constants', 'Blockly.inputTypes', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.global', 'Blockly.utils.xml'], {});
goog.addDependency('../../core/zoom_controls.js', ['Blockly.ZoomControls'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.Click', 'Blockly.IPositionable', 'Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom'], {'lang': 'es5'});
goog.addDependency("base.js", [], []);

View File

@@ -0,0 +1,22 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Interface for a plugin.
* @author kozbial@google.com (Monica Kozbial)
*/
'use strict';
goog.provide('Blockly.IPlugin');
/**
* The interface for a workspace plugin.
* @interface
*/
Blockly.IPlugin = function() {};

View File

@@ -13,21 +13,19 @@
goog.provide('Blockly.IPositionable');
goog.require('Blockly.IPlugin');
/**
* Interface for a component that is positioned on top of the workspace.
* @extends {Blockly.IPlugin}
* @interface
*/
Blockly.IPositionable = function() {};
/**
* Positions the element. Called when the window is resized.
* @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics The workspace
* viewMetrics.
* @param {!Blockly.MetricsManager.AbsoluteMetrics} absoluteMetrics The absolute
* metrics for the workspace.
* @param {!Blockly.MetricsManager.ToolboxMetrics} toolboxMetrics The toolbox
* metrics for the workspace.
* @param {!Blockly.MetricsManager.UiMetrics} metrics The workspace metrics.
* @param {!Array<Blockly.utils.Rect>} savedPositions List of rectangles that
* are already on the workspace.
*/
@@ -39,5 +37,3 @@ Blockly.IPositionable.prototype.position;
* @returns {!Blockly.utils.Rect} The plugins bounding box.
*/
Blockly.IPositionable.prototype.getBoundingRectangle;

View File

@@ -0,0 +1,20 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Interface for plugins that can be registered on the workspace.
* @author kozbial@google.com (Monica Kozbial)
*/
'use strict';
goog.provide('Blockly.IWorkspacePlugin');
/**
* Base interface for a plugin that can be registered on the workspace.
* @interface
*/
Blockly.IWorkspacePlugin = function() {};

View File

@@ -82,6 +82,16 @@ Blockly.MetricsManager.ContainerRegion;
*/
Blockly.MetricsManager.FixedEdges;
/**
* Common metrics used for ui elements.
* @typedef {{
* viewMetrics: !Blockly.MetricsManager.ContainerRegion,
* absoluteMetrics: !Blockly.MetricsManager.AbsoluteMetrics,
* toolboxMetrics: !Blockly.MetricsManager.ToolboxMetrics
* }}
*/
Blockly.MetricsManager.UiMetrics;
/**
* Gets the dimensions of the given workspace component, in pixel coordinates.
* @param {?Blockly.IToolbox|?Blockly.IFlyout} elem The element to get the
@@ -373,6 +383,18 @@ Blockly.MetricsManager.prototype.getScrollMetrics = function(
};
};
/**
* Returns common metrics used by ui elements.
* @return {!Blockly.MetricsManager.UiMetrics} The ui metrics.
*/
Blockly.MetricsManager.prototype.getUiMetrics = function() {
return {
viewMetrics: this.getViewMetrics(),
absoluteMetrics: this.getAbsoluteMetrics(),
toolboxMetrics: this.getToolboxMetrics()
};
};
/**
* Returns an object with all the metrics required to size scrollbars for a
* top level workspace. The following properties are computed:

122
core/plugin_manager.js Normal file
View File

@@ -0,0 +1,122 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @fileoverview Manager for all items registered with the workspace.
* @author kozbial@google.com (Monica Kozbial)
*/
'use strict';
goog.provide('Blockly.PluginManager');
/**
* Manager for all items registered with the workspace.
* @constructor
*/
Blockly.PluginManager = function() {
/**
* A map of the plugins registered with the workspace, mapped to id.
* @type {!Object<string, !Blockly.PluginManager.PluginData>}
*/
this.pluginData_ = {};
/**
* A map of types to plugin ids.
* @type {!Object<string, Array<string>>}
*/
this.typeToPluginId_ = {};
};
/**
* An object storing plugin information.
* @typedef {{
* id: string,
* plugin: !Blockly.IPlugin,
* types: !Array<string|!Blockly.PluginManager.Type<Blockly.IPlugin>>,
* weight: number
* }}
*/
Blockly.PluginManager.PluginData;
/**
* Adds a plugin.
* @param {!Blockly.PluginManager.PluginData} pluginDataObject The plugin.
* @template T
*/
Blockly.PluginManager.prototype.addPlugin = function(pluginDataObject) {
this.pluginData_[pluginDataObject.id] = pluginDataObject;
for (var i = 0, type; (type = pluginDataObject.types[i]); i++) {
var typeKey = String(type).toLowerCase();
if (this.typeToPluginId_[typeKey] === undefined) {
this.typeToPluginId_[typeKey] = [pluginDataObject.id];
} else {
this.typeToPluginId_[typeKey].push(pluginDataObject.id);
}
}
};
/**
* Gets the plugin with the given id and the given type.
* @param {string} id The id of the plugin to get.
* @return {!Blockly.IPlugin|undefined} The plugin with the given name
* or undefined if not found.
*/
Blockly.PluginManager.prototype.getPlugin = function(id) {
return this.pluginData_[id] && this.pluginData_[id].plugin;
};
/**
* Gets all the plugins of the specified type.
* @param {!Blockly.PluginManager.Type<T>} type The type of the plugin.
* @param {boolean} sorted Whether to return list ordered by weights.
* @return {!Array<T>} The plugins that match the
* specified type.
* @template T
*/
Blockly.PluginManager.prototype.getPlugins = function(type, sorted) {
var plugins = [];
var typeKey = String(type).toLowerCase();
var pluginIds = this.typeToPluginId_[typeKey];
for (var i = 0, id; pluginIds && (id = pluginIds[i]); i++) {
plugins.push(this.pluginData_[id].plugin);
}
if (sorted) {
plugins.sort(function(a, b) {
return a.weight - b.weight;
});
}
return plugins;
};
/**
* A name with the type of the element stored in the generic.
* @param {string} name The name of the plugin type.
* @constructor
* @template T
*/
Blockly.PluginManager.Type = function(name) {
/**
* @type {string}
* @private
*/
this.name_ = name;
};
/**
* Returns the name of the type.
* @return {string} The name.
* @override
*/
Blockly.PluginManager.Type.prototype.toString = function() {
return this.name_;
};
/** @type {!Blockly.PluginManager.Type<!Blockly.IPositionable>} */
Blockly.PluginManager.Type.POSITIONABLE =
new Blockly.PluginManager.Type('positionable');

View File

@@ -434,26 +434,20 @@ Blockly.Trashcan.prototype.emptyContents = function() {
* Position the trashcan.
* It is positioned in the opposite corner to the corner the
* categories/toolbox starts at.
* @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics The workspace
* viewMetrics.
* @param {!Blockly.MetricsManager.AbsoluteMetrics} absoluteMetrics The absolute
* metrics for the workspace.
* @param {!Blockly.MetricsManager.ToolboxMetrics} toolboxMetrics The toolbox
* metrics for the workspace.
* @param {!Blockly.MetricsManager.UiMetrics} metrics The workspace metrics.
* @param {!Array<Blockly.utils.Rect>} savedPositions List of rectangles that
* are already on the workspace.
*/
Blockly.Trashcan.prototype.position = function(
viewMetrics, absoluteMetrics, toolboxMetrics, savedPositions) {
Blockly.Trashcan.prototype.position = function(metrics, savedPositions) {
// Not yet initialized.
if (!this.verticalSpacing_) {
return;
}
if (toolboxMetrics.position == Blockly.TOOLBOX_AT_LEFT ||
if (metrics.toolboxMetrics.position == Blockly.TOOLBOX_AT_LEFT ||
(this.workspace_.horizontalLayout && !this.workspace_.RTL)) {
// Toolbox starts in the left corner.
this.left_ = viewMetrics.width + absoluteMetrics.left -
this.left_ = metrics.viewMetrics.width + metrics.absoluteMetrics.left -
this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness;
} else {
// Toolbox starts in the right corner.
@@ -464,9 +458,10 @@ Blockly.Trashcan.prototype.position = function(
// Upper corner placement
var minTop = this.top_ = this.verticalSpacing_;
// Bottom corner placement
var maxTop = viewMetrics.height + absoluteMetrics.top - height -
this.verticalSpacing_;
var placeBottom = toolboxMetrics.position !== Blockly.TOOLBOX_AT_BOTTOM;
var maxTop = metrics.viewMetrics.height + metrics.absoluteMetrics.top -
height - this.verticalSpacing_;
var placeBottom =
metrics.toolboxMetrics.position !== Blockly.TOOLBOX_AT_BOTTOM;
this.top_ = placeBottom ? maxTop : minTop;
// Check for collision and bump if needed.

View File

@@ -25,10 +25,12 @@ goog.require('Blockly.Events.ThemeChange');
goog.require('Blockly.Events.ViewportChange');
goog.require('Blockly.Gesture');
goog.require('Blockly.Grid');
goog.require('Blockly.IPositionable');
goog.require('Blockly.MarkerManager');
goog.require('Blockly.MetricsManager');
goog.require('Blockly.Msg');
goog.require('Blockly.Options');
goog.require('Blockly.PluginManager');
goog.require('Blockly.registry');
goog.require('Blockly.ThemeManager');
goog.require('Blockly.Themes.Classic');
@@ -55,7 +57,6 @@ goog.requireType('Blockly.IASTNodeLocationSvg');
goog.requireType('Blockly.IBoundedElement');
goog.requireType('Blockly.IFlyout');
goog.requireType('Blockly.IMetricsManager');
goog.requireType('Blockly.IPositionable');
goog.requireType('Blockly.IToolbox');
goog.requireType('Blockly.Marker');
goog.requireType('Blockly.ScrollbarPair');
@@ -107,6 +108,12 @@ Blockly.WorkspaceSvg = function(
this.setMetrics =
options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_;
/**
* @type {!Blockly.PluginManager}
* @private
*/
this.pluginManager_ = new Blockly.PluginManager();
this.connectionDBList = Blockly.ConnectionDB.init(this.connectionChecker);
if (opt_blockDragSurface) {
@@ -511,6 +518,15 @@ Blockly.WorkspaceSvg.prototype.getMetricsManager = function() {
return this.metricsManager_;
};
/**
* Gets the plugin manager for this workspace.
* @return {!Blockly.PluginManager} The plugin manager.
* @public
*/
Blockly.WorkspaceSvg.prototype.getPluginManager = function() {
return this.pluginManager_;
};
/**
* Add the cursor svg to this workspaces svg group.
* @param {SVGElement} cursorSvg The svg root of the cursor to be added to the
@@ -953,6 +969,12 @@ Blockly.WorkspaceSvg.prototype.addTrashcan = function() {
this.trashcan = new Blockly.Trashcan(this);
var svgTrashcan = this.trashcan.createDom();
this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_);
this.pluginManager_.addPlugin({
id: 'trashcan',
plugin: this.trashcan,
weight: 0,
types: [Blockly.PluginManager.Type.POSITIONABLE]
});
};
/**
@@ -967,6 +989,12 @@ Blockly.WorkspaceSvg.prototype.addZoomControls = function() {
this.zoomControls_ = new Blockly.ZoomControls(this);
var svgZoomControls = this.zoomControls_.createDom();
this.svgGroup_.appendChild(svgZoomControls);
this.pluginManager_.addPlugin({
id: 'zoomControls',
plugin: this.zoomControls_,
weight: 0,
types: [Blockly.PluginManager.Type.POSITIONABLE]
});
};
/**
@@ -1078,25 +1106,14 @@ Blockly.WorkspaceSvg.prototype.resize = function() {
if (this.flyout_) {
this.flyout_.position();
}
/** @type {Array<Blockly.IPositionable>} */
var positionableEls = [];
if (this.trashcan) {
positionableEls.push(this.trashcan);
}
if (this.zoomControls_) {
positionableEls.push(this.zoomControls_);
}
if (positionableEls) {
var metricsManager = this.getMetricsManager();
var viewMetrics = metricsManager.getViewMetrics();
var absoluteMetrics = metricsManager.getAbsoluteMetrics();
var toolboxMetrics = metricsManager.getToolboxMetrics();
var savedPositions = [];
for (var i = 0, uiElement; (uiElement = positionableEls[i]); i++) {
uiElement.position(
viewMetrics, absoluteMetrics, toolboxMetrics, savedPositions);
savedPositions.push(uiElement.getBoundingRectangle());
}
var positionables = this.pluginManager_.getPlugins(
Blockly.PluginManager.Type.POSITIONABLE, true);
var metrics = this.getMetricsManager().getUiMetrics();
var savedPositions = [];
for (var i = 0, positionable; (positionable = positionables[i]); i++) {
positionable.position(metrics, savedPositions);
savedPositions.push(positionable.getBoundingRectangle());
}
if (this.scrollbar) {

View File

@@ -213,25 +213,19 @@ Blockly.ZoomControls.prototype.getBoundingRectangle = function() {
* Position the zoom controls.
* It is positioned in the opposite corner to the corner the
* categories/toolbox starts at.
* @param {!Blockly.MetricsManager.ContainerRegion} viewMetrics The workspace
* viewMetrics.
* @param {!Blockly.MetricsManager.AbsoluteMetrics} absoluteMetrics The absolute
* metrics for the workspace.
* @param {!Blockly.MetricsManager.ToolboxMetrics} toolboxMetrics The toolbox
* metrics for the workspace.
* @param {!Blockly.MetricsManager.UiMetrics} metrics The workspace metrics.
* @param {!Array<Blockly.utils.Rect>} savedPositions List of rectangles that
* are already on the workspace.
*/
Blockly.ZoomControls.prototype.position = function(
viewMetrics, absoluteMetrics, toolboxMetrics, savedPositions) {
Blockly.ZoomControls.prototype.position = function(metrics, savedPositions) {
// Not yet initialized.
if (!this.verticalSpacing_) {
return;
}
if (toolboxMetrics.position == Blockly.TOOLBOX_AT_LEFT ||
if (metrics.toolboxMetrics.position == Blockly.TOOLBOX_AT_LEFT ||
(this.workspace_.horizontalLayout && !this.workspace_.RTL)) {
// Toolbox starts in the left corner.
this.left_ = viewMetrics.width + absoluteMetrics.left -
this.left_ = metrics.viewMetrics.width + metrics.absoluteMetrics.left -
this.WIDTH_ - this.MARGIN_SIDE_ - Blockly.Scrollbar.scrollbarThickness;
} else {
// Toolbox starts in the right corner.
@@ -241,9 +235,10 @@ Blockly.ZoomControls.prototype.position = function(
// Upper corner placement
var minTop = this.top_ = this.verticalSpacing_;
// Bottom corner placement
var maxTop = viewMetrics.height + absoluteMetrics.top -
var maxTop = metrics.viewMetrics.height + metrics.absoluteMetrics.top -
this.HEIGHT_ - this.verticalSpacing_;
var placeBottom = toolboxMetrics.position !== Blockly.TOOLBOX_AT_BOTTOM;
var placeBottom =
metrics.toolboxMetrics.position !== Blockly.TOOLBOX_AT_BOTTOM;
this.top_ = placeBottom ? maxTop : minTop;
if (placeBottom) {
this.zoomInGroup_.setAttribute('transform', 'translate(0, 43)');