[Display Layout] Allow multiple selection, duplication, and changing types (#3083)

* enable multiple selection

* enable object duplication

* enable copy styles

* enable converting plots and tables to alpha numerics

* enable merging multiple alpha numerics

* change icon for viewSwitcher

* allow users to merge overlay plots into a stacked plot

* New glyph for alphanumeric

Co-authored-by: charlesh88 <charlesh88@gmail.com>
This commit is contained in:
Deep Tailor 2020-06-19 11:44:17 -07:00 committed by GitHub
parent 055cf2b118
commit 771fb9c044
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 609 additions and 190 deletions

View File

@ -29,11 +29,15 @@ define([
function isTelemetryObject(selectionPath) {
let selectedObject = selectionPath[0].context.item;
let parentObject = selectionPath[1].context.item;
let selectedLayoutItem = selectionPath[0].context.layoutItem;
return parentObject &&
parentObject.type === 'layout' &&
selectedObject &&
selectedLayoutItem &&
selectedLayoutItem.type === 'telemetry-view' &&
openmct.telemetry.isTelemetryObject(selectedObject) &&
!options.showAsView.includes(selectedObject.type)
!options.showAsView.includes(selectedObject.type);
}
return {

View File

@ -1,5 +1,5 @@
/*****************************************************************************
* Open MCT, Copyright (c) 2014-2018, United States Government
* Open MCT, Copyright (c) 2014-2020, United States Government
* as represented by the Administrator of the National Aeronautics and Space
* Administration. All rights reserved.
*
@ -41,38 +41,84 @@ define(['lodash'], function (_) {
},
toolbar: function (selectedObjects) {
const DIALOG_FORM = {
'text': {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
'text': {
name: "Text Element Properties",
sections: [
{
rows: [
{
key: "text",
control: "textfield",
name: "Text",
required: true
}
]
}
]
},
'image': {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
]
}
},
'image': {
name: "Image Properties",
sections: [
{
rows: [
{
key: "url",
control: "textfield",
name: "Image URL",
"cssClass": "l-input-lg",
required: true
}
]
}
viewTypes = {
'telemetry-view': {
value: 'telemetry-view',
name: 'Alphanumeric',
class: 'icon-alphanumeric'
},
'telemetry.plot.overlay': {
value: 'telemetry.plot.overlay',
name: 'Overlay Plot',
class: "icon-plot-overlay"
},
'telemetry.plot.stacked': {
value: "telemetry.plot.stacked",
name: "Stacked Plot",
class: "icon-plot-stacked"
},
'table': {
value: 'table',
name: 'Table',
class: 'icon-tabular-realtime'
}
},
applicableViews = {
'telemetry-view': [
viewTypes['telemetry.plot.overlay'],
viewTypes.table
],
'telemetry.plot.overlay': [
viewTypes['telemetry.plot.stacked'],
viewTypes.table,
viewTypes['telemetry-view']
],
'table': [
viewTypes['telemetry.plot.overlay'],
viewTypes['telemetry.plot.stacked'],
viewTypes['telemetry-view']
],
'telemetry-view-multi': [
viewTypes['telemetry.plot.overlay'],
viewTypes['telemetry.plot.stacked'],
viewTypes.table
],
'telemetry.plot.overlay-multi': [
viewTypes['telemetry.plot.stacked']
]
}
};
};
function getUserInput(form) {
return openmct.$injector.get('dialogService').getUserInput(form, {});
@ -415,6 +461,100 @@ define(['lodash'], function (_) {
}
}
function getDuplicateButton(selectedParent, selectionPath, selection) {
return {
control: "button",
domainObject: selectedParent,
icon: "icon-duplicate",
title: "Duplicate the selected object",
method: function () {
let duplicateItem = selectionPath[1].context.duplicateItem;
duplicateItem(selection);
}
};
}
function getPropertyFromPath(object, path) {
let splitPath = path.split('.'),
property = Object.assign({}, object);
while (splitPath.length && property) {
property = property[splitPath.shift()];
}
return property;
}
function areAllViews(type, path, selection) {
let allTelemetry = true;
selection.forEach(selectedItem => {
let selectedItemContext = selectedItem[0].context;
if (getPropertyFromPath(selectedItemContext, path) !== type) {
allTelemetry = false;
}
});
return allTelemetry;
}
function getViewSwitcherMenu(selectedParent, selectionPath, selection) {
if (selection.length === 1) {
let displayLayoutContext = selectionPath[1].context,
selectedItemContext = selectionPath[0].context,
selectedItemType = selectedItemContext.item.type;
if (selectedItemContext.layoutItem.type === 'telemetry-view') {
selectedItemType = 'telemetry-view';
}
let viewOptions = applicableViews[selectedItemType];
if (viewOptions) {
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-object",
title: "Switch the way this telemetry is displayed",
options: viewOptions,
method: function (option) {
displayLayoutContext.switchViewType(selectedItemContext, option.value, selection);
}
};
}
} else if (selection.length > 1) {
if (areAllViews('telemetry-view', 'layoutItem.type', selection)) {
let displayLayoutContext = selectionPath[1].context;
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-object",
title: "Merge into a telemetry table or plot",
options: applicableViews['telemetry-view-multi'],
method: function (option) {
displayLayoutContext.mergeMultipleTelemetryViews(selection, option.value);
}
};
} else if (areAllViews('telemetry.plot.overlay', 'item.type', selection)) {
let displayLayoutContext = selectionPath[1].context;
return {
control: "menu",
domainObject: selectedParent,
icon: "icon-object",
title: "Merge into a stacked plot",
options: applicableViews['telemetry.plot.overlay-multi'],
method: function (option) {
displayLayoutContext.mergeMultipleOverlayPlots(selection, option.value);
}
};
}
}
}
function getSeparator() {
return {
control: "separator"
@ -435,12 +575,14 @@ define(['lodash'], function (_) {
'add-menu': [],
'text': [],
'url': [],
'viewSwitcher': [],
'toggle-frame': [],
'display-mode': [],
'telemetry-value': [],
'style': [],
'text-style': [],
'position': [],
'duplicate': [],
'remove': []
};
@ -471,6 +613,9 @@ define(['lodash'], function (_) {
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
if (toolbar.viewSwitcher.length === 0) {
toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'telemetry-view') {
if (toolbar['display-mode'].length === 0) {
toolbar['display-mode'] = [getDisplayModeMenu(selectedParent, selectedObjects)];
@ -495,6 +640,9 @@ define(['lodash'], function (_) {
if (toolbar.remove.length === 0) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
if (toolbar.viewSwitcher.length === 0) {
toolbar.viewSwitcher = [getViewSwitcherMenu(selectedParent, selectionPath, selectedObjects)];
}
} else if (layoutItem.type === 'text-view') {
if (toolbar['text-style'].length === 0) {
toolbar['text-style'] = [
@ -556,6 +704,9 @@ define(['lodash'], function (_) {
toolbar.remove = [getRemoveButton(selectedParent, selectionPath, selectedObjects)];
}
}
if(toolbar.duplicate.length === 0) {
toolbar.duplicate = [getDuplicateButton(selectedParent, selectionPath, selectedObjects)];
}
});
let toolbarArray = Object.values(toolbar);

View File

@ -91,6 +91,13 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@ -47,6 +47,7 @@
:is="item.type"
v-for="(item, index) in layoutItems"
:key="item.id"
:ref="`layout-item-${item.id}`"
:item="item"
:grid-size="gridSize"
:init-select="initSelectIndex === index"
@ -92,6 +93,7 @@ const ORDERS = {
bottom: Number.NEGATIVE_INFINITY
};
const DRAG_OBJECT_TRANSFER_PREFIX = 'openmct/domain-object/';
const DUPLICATE_OFFSET = 3;
let components = ITEM_TYPE_VIEW_MAP;
components['edit-marquee'] = EditMarquee;
@ -301,9 +303,9 @@ export default {
if (this.isTelemetry(domainObject)) {
this.addItem('telemetry-view', domainObject, droppedObjectPosition);
} else {
let identifier = this.openmct.objects.makeKeyString(domainObject.identifier);
let keyString = this.openmct.objects.makeKeyString(domainObject.identifier);
if (!this.objectViewMap[identifier]) {
if (!this.objectViewMap[keyString]) {
this.addItem('subobject-view', domainObject, droppedObjectPosition);
} else {
let prompt = this.openmct.overlays.dialog({
@ -365,7 +367,8 @@ export default {
let count = this.telemetryViewMap[keyString] || 0;
this.telemetryViewMap[keyString] = ++count;
} else if (item.type === "subobject-view") {
this.objectViewMap[keyString] = true;
let count = this.objectViewMap[keyString] || 0;
this.objectViewMap[keyString] = ++count;
}
},
removeItem(selectedItems) {
@ -384,17 +387,25 @@ export default {
return;
}
let keyString = this.openmct.objects.makeKeyString(item.identifier);
let keyString = this.openmct.objects.makeKeyString(item.identifier),
telemetryViewCount = this.telemetryViewMap[keyString],
objectViewCount = this.objectViewMap[keyString];
if (item.type === 'telemetry-view') {
let count = --this.telemetryViewMap[keyString];
telemetryViewCount = --this.telemetryViewMap[keyString];
if (count === 0) {
if (telemetryViewCount === 0) {
delete this.telemetryViewMap[keyString];
this.removeFromComposition(keyString);
}
} else if (item.type === 'subobject-view') {
delete this.objectViewMap[keyString];
objectViewCount = --this.objectViewMap[keyString];
if (objectViewCount === 0) {
delete this.objectViewMap[keyString];
}
}
if (!telemetryViewCount && !objectViewCount) {
this.removeFromComposition(keyString);
}
},
@ -411,12 +422,12 @@ export default {
this.layoutItems.forEach(this.trackItem);
},
addChild(child) {
let identifier = this.openmct.objects.makeKeyString(child.identifier);
let keyString = this.openmct.objects.makeKeyString(child.identifier);
if (this.isTelemetry(child)) {
if (!this.telemetryViewMap[identifier]) {
if (!this.telemetryViewMap[keyString] && !this.objectViewMap[keyString]) {
this.addItem('telemetry-view', child);
}
} else if (!this.objectViewMap[identifier]) {
} else if (!this.objectViewMap[keyString]) {
this.addItem('subobject-view', child);
}
},
@ -515,6 +526,180 @@ export default {
let index = this.layoutItems.findIndex(item);
item.format = format;
this.mutate(`configuration.items[${index}]`, item);
},
createNewDomainObject(domainObject, composition, viewType, nameExtension, model) {
let identifier = {
key: uuid(),
namespace: domainObject.identifier.namespace
},
type = this.openmct.types.get(viewType),
parentKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier),
objectName = nameExtension ? `${domainObject.name}-${nameExtension}` : domainObject.name,
object = {};
if (model) {
object = _.cloneDeep(model);
} else {
object.type = viewType;
type.definition.initialize(object);
object.composition.push(...composition);
}
object.name = objectName;
object.identifier = identifier;
object.location = parentKeyString;
this.openmct.objects.mutate(object, 'persisted', Date.now());
return object;
},
convertToTelemetryView(identifier, position) {
this.openmct.objects.get(identifier).then((domainObject) => {
this.composition.add(domainObject);
this.addItem('telemetry-view', domainObject, position);
});
},
dispatchMultipleSelection(selectItemsArray) {
let event = new MouseEvent('click', {
bubbles: true,
shiftKey: true,
cancelable: true,
view: window
})
selectItemsArray.forEach((id) => {
let refId = `layout-item-${id}`,
component = this.$refs[refId] && this.$refs[refId][0];
if (component) {
component.immediatelySelect = event;
component.$el.dispatchEvent(event);
}
});
},
duplicateItem(selectedItems) {
let objectStyles = this.internalDomainObject.configuration.objectStyles || {},
selectItemsArray = [],
newDomainObjectsArray = [];
selectedItems.forEach(selectedItem => {
let layoutItem = selectedItem[0].context.layoutItem,
domainObject = selectedItem[0].context.item,
layoutItemStyle = objectStyles[layoutItem.id],
copy = _.cloneDeep(layoutItem);
copy.id = uuid();
selectItemsArray.push(copy.id);
let offsetKeys = ['x', 'y'];
if (copy.type === 'line-view') {
offsetKeys = offsetKeys.concat(['x2', 'y2']);
}
if (copy.type === 'subobject-view') {
let newDomainObject = this.createNewDomainObject(domainObject, domainObject.composition, domainObject.type, 'duplicate', domainObject);
newDomainObjectsArray.push(newDomainObject);
copy.identifier = newDomainObject.identifier;
}
offsetKeys.forEach(key => {
copy[key] += DUPLICATE_OFFSET
});
if (layoutItemStyle) {
objectStyles[copy.id] = layoutItemStyle;
}
this.trackItem(copy);
this.layoutItems.push(copy);
});
this.$nextTick(() => {
this.openmct.objects.mutate(this.internalDomainObject, "configuration.items", this.layoutItems);
this.openmct.objects.mutate(this.internalDomainObject, "configuration.objectStyles", objectStyles);
this.$el.click(); //clear selection;
newDomainObjectsArray.forEach(domainObject => {
this.composition.add(domainObject);
});
this.dispatchMultipleSelection(selectItemsArray);
});
},
mergeMultipleTelemetryViews(selection, viewType) {
let identifiers = selection.map(selectedItem => {
return selectedItem[0].context.layoutItem.identifier;
}),
firstDomainObject = selection[0][0].context.item,
firstLayoutItem = selection[0][0].context.layoutItem,
position = [firstLayoutItem.x, firstLayoutItem.y],
mockDomainObject = {
name: 'Merged Telemetry Views',
identifier: firstDomainObject.identifier
},
newDomainObject = this.createNewDomainObject(mockDomainObject, identifiers, viewType);
this.composition.add(newDomainObject);
this.addItem('subobject-view', newDomainObject, position);
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1;
},
mergeMultipleOverlayPlots(selection, viewType) {
let overlayPlots = selection.map(selectedItem => selectedItem[0].context.item),
overlayPlotIdentifiers = overlayPlots.map(overlayPlot => overlayPlot.identifier),
firstOverlayPlot = overlayPlots[0],
firstLayoutItem = selection[0][0].context.layoutItem,
position = [firstLayoutItem.x, firstLayoutItem.y],
mockDomainObject = {
name: 'Merged Overlay Plots',
identifier: firstOverlayPlot.identifier
},
newDomainObject = this.createNewDomainObject(mockDomainObject, overlayPlotIdentifiers, viewType),
newDomainObjectKeyString = this.openmct.objects.makeKeyString(newDomainObject.identifier),
internalDomainObjectKeyString = this.openmct.objects.makeKeyString(this.internalDomainObject.identifier);
this.composition.add(newDomainObject);
this.addItem('subobject-view', newDomainObject, position);
overlayPlots.forEach(overlayPlot => {
if (overlayPlot.location === internalDomainObjectKeyString) {
this.openmct.objects.mutate(overlayPlot, 'location', newDomainObjectKeyString);
}
});
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1;
},
switchViewType(context, viewType, selection) {
let domainObject = context.item,
layoutItem = context.layoutItem,
position = [layoutItem.x, layoutItem.y],
newDomainObject,
layoutType = 'subobject-view';
if (layoutItem.type === 'telemetry-view') {
newDomainObject = this.createNewDomainObject(domainObject, [domainObject.identifier], viewType);
} else {
if (viewType !== 'telemetry-view') {
newDomainObject = this.createNewDomainObject(domainObject, domainObject.composition, viewType);
} else {
domainObject.composition.forEach((identifier , index) => {
let positionX = position[0] + (index * DUPLICATE_OFFSET),
positionY = position[1] + (index * DUPLICATE_OFFSET);
this.convertToTelemetryView(identifier, [positionX, positionY]);
});
}
}
if (newDomainObject) {
this.composition.add(newDomainObject);
this.addItem(layoutType, newDomainObject, position);
}
this.removeItem(selection);
this.initSelectIndex = this.layoutItems.length - 1; //restore selection
}
}
}

View File

@ -95,6 +95,13 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@ -194,6 +194,13 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@ -61,7 +61,7 @@ function hasFrameByDefault(type) {
}
export default {
makeDefinition(openmct, gridSize, domainObject, position) {
makeDefinition(openmct, gridSize, domainObject, position, viewKey) {
let defaultDimensions = getDefaultDimensions(gridSize);
position = position || DEFAULT_POSITION;
@ -71,7 +71,8 @@ export default {
x: position[0],
y: position[1],
identifier: domainObject.identifier,
hasFrame: hasFrameByDefault(domainObject.type)
hasFrame: hasFrameByDefault(domainObject.type),
viewKey
};
},
inject: ['openmct', 'objectPath'],
@ -109,6 +110,13 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {
@ -131,7 +139,8 @@ export default {
childContext.index = this.index;
this.context = childContext;
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
});
}
}

View File

@ -168,6 +168,10 @@ export default {
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
@ -242,7 +246,8 @@ export default {
updateTelemetryFormat: this.updateTelemetryFormat
};
this.removeSelectable = this.openmct.selection.selectable(
this.$el, this.context, this.initSelect);
this.$el, this.context, this.immediatelySelect || this.initSelect);
delete this.immediatelySelect;
},
updateTelemetryFormat(format) {
this.$emit('formatChanged', this.item, format);

View File

@ -91,6 +91,13 @@ export default {
}
this.context.index = newIndex;
},
item(newItem) {
if (!this.context) {
return;
}
this.context.layoutItem = newItem;
}
},
mounted() {

View File

@ -66,8 +66,12 @@ export default function DisplayLayoutPlugin(options) {
supportsMultiSelect: true,
addElement: component && component.$refs.displayLayout.addElement,
removeItem: component && component.$refs.displayLayout.removeItem,
orderItem: component && component.$refs.displayLayout.orderItem
}
orderItem: component && component.$refs.displayLayout.orderItem,
duplicateItem: component && component.$refs.displayLayout.duplicateItem,
switchViewType: component && component.$refs.displayLayout.switchViewType,
mergeMultipleTelemetryViews: component && component.$refs.displayLayout.mergeMultipleTelemetryViews,
mergeMultipleOverlayPlots: component && component.$refs.displayLayout.mergeMultipleOverlayPlots
};
},
destroy() {
component.$destroy();

View File

@ -28,8 +28,10 @@ define([
}
ConfigStore.prototype.deleteStore = function (id) {
this.store[id].destroy();
delete this.store[id];
if (this.store[id]) {
this.store[id].destroy();
delete this.store[id];
}
};
ConfigStore.prototype.add = function (id, config) {

View File

@ -238,9 +238,12 @@ define(
context.item = newItem;
});
}
if (select) {
element.click();
if (typeof select === 'object') {
element.dispatchEvent(select);
} else if (typeof select === 'boolean') {
element.click();
}
}
return function () {

View File

@ -239,6 +239,7 @@ $glyph-icon-spectra-telemetry: '\eb25';
$glyph-icon-command: '\eb26';
$glyph-icon-conditional: '\eb27';
$glyph-icon-condition-widget: '\eb28';
$glyph-icon-alphanumeric: '\eb29';
/************************** GLYPHS AS DATA URI */
// Only objects have been converted, for use in Create menu and folder views

View File

@ -175,6 +175,7 @@
.icon-command { @include glyphBefore($glyph-icon-command); }
.icon-conditional { @include glyphBefore($glyph-icon-conditional); }
.icon-condition-widget { @include glyphBefore($glyph-icon-condition-widget); }
.icon-alphanumeric { @include glyphBefore($glyph-icon-alphanumeric); }
/************************** 12 PX CLASSES */
// TODO: sync with 16px redo as of 10/25/18

File diff suppressed because it is too large Load Diff

View File

@ -143,4 +143,5 @@
<glyph unicode="&#xeb26;" glyph-name="icon-pushbutton" d="M370.2 372.6c9.326-8.53 19.666-16.261 30.729-22.914l0.871-0.486c-11.077 19.209-17.664 42.221-17.8 66.76v0.040c0 39.6 17.8 77.6 50.2 107.4 37 34 87.4 52.6 141.8 52.6 40.2 0 78.2-10.2 110.2-29.2-8.918 15.653-19.693 29.040-32.268 40.482l-0.132 0.118c-37 34-87.4 52.6-141.8 52.6s-104.8-18.6-141.8-52.6c-32.4-29.8-50.2-67.8-50.2-107.4s17.8-77.6 50.2-107.4zM885.4 562.4c-40.6 154.6-192.4 269.6-373.4 269.6s-332.8-115-373.4-269.6c-86-80-138.6-187.8-138.6-306.4 0-247.4 229.2-448 512-448s512 200.6 512 448c0 118.6-52.6 226.4-138.6 306.4zM512 704c141.2 0 256-100.4 256-224s-114.8-224-256-224-256 100.4-256 224 114.8 224 256 224zM512 0c-175.4 0-318.4 127.8-320 285.4 68.8-94.8 186.4-157.4 320-157.4s251.2 62.6 320 157.4c-1.6-157.6-144.6-285.4-320-285.4z" />
<glyph unicode="&#xeb27;" glyph-name="icon-conditional" d="M512 832c-282.76 0-512-229.24-512-512s229.24-512 512-512 512 229.24 512 512-229.24 512-512 512zM512 64l-384 256 384 256 384-256z" />
<glyph unicode="&#xeb28;" glyph-name="icon-condition-widget" d="M832 832h-640c-105.6 0-192-86.4-192-192v-640c0-105.6 86.4-192 192-192h640c105.6 0 192 86.4 192 192v640c0 105.6-86.4 192-192 192zM512 64l-384 256 384 256 384-256z" />
<glyph unicode="&#xeb29;" glyph-name="icon-alphanumeric" d="M535.6 301.4c-8.4-1.6-17.2-3-26.2-4s-18.2-2.4-27.2-4c-10.196-1.861-18.808-4.010-27.21-6.633l1.61 0.433c-8.609-2.674-16.105-6.348-22.89-10.987l0.29 0.187c-6.693-4.517-12.283-10.107-16.663-16.585l-0.137-0.215c-4.6-6.8-7.4-15.6-8.8-26s-0.4-18.4 2.4-25.2c2.746-6.688 7.224-12.195 12.881-16.122l0.119-0.078c5.967-4.053 13.057-6.94 20.704-8.161l0.296-0.039c7.592-1.527 16.319-2.4 25.25-2.4 0.123 0 0.246 0 0.369 0h-0.019c22.2 0 39.6 3.6 52.6 11s23.2 16.2 30.2 26.4c6.273 8.873 11.271 19.191 14.426 30.285l0.174 0.715c1.853 6.809 3.601 15.41 4.855 24.169l0.145 1.231 5.2 41.6c-5.4-4.217-11.723-7.564-18.583-9.689l-0.417-0.111c-6.489-2.241-14.362-4.255-22.444-5.662l-0.956-0.138zM1024 448v192h-152l24 192h-192l-24-192h-256l24 192h-192l-24-192h-232v-192h208l-32-256h-176v-192h152l-24-192h192l24 192h256l-24-192h192l24 192h232v192h-208l32 256zM702.8 420.2l-26.4-211.8c-2.231-15.809-3.537-34.122-3.6-52.727v-0.073c0-16.8 2.2-29.4 6.4-37.8h-113.4c-1.342 5.556-2.338 12.122-2.781 18.84l-0.019 0.36c-0.261 3.524-0.409 7.634-0.409 11.778 0 2.962 0.076 5.907 0.226 8.832l-0.017-0.41c-18.663-17.401-41.395-30.694-66.597-38.289l-1.203-0.311c-22.627-6.956-48.639-10.974-75.586-11h-0.014c-0.764-0.011-1.666-0.018-2.569-0.018-18.098 0-35.598 2.563-52.156 7.345l1.325-0.328c-15.991 4.512-29.851 12.090-41.545 22.122l0.145-0.122c-11.233 9.982-19.792 22.733-24.624 37.192l-0.176 0.608c-5.2 15.2-6.4 33.4-3.8 54.4s9.4 42.2 19.4 57.2c9.524 14.399 21.535 26.346 35.532 35.512l0.468 0.288c13.387 8.662 28.922 15.533 45.512 19.765l1.088 0.235c13.436 3.792 30.801 7.554 48.47 10.41l2.93 0.39c17 2.6 33.8 4.6 50.4 6.2 16.628 1.527 31.69 4.070 46.349 7.643l-2.149-0.443c13 3 23.6 7.6 31.6 13.6s12.6 15 13.6 26.4 0.8 21.8-2.4 28.8c-2.849 6.902-7.542 12.56-13.468 16.517l-0.132 0.083c-6.217 4.011-13.604 6.78-21.543 7.774l-0.257 0.026c-7.897 1.277-17 2.007-26.274 2.007-0.537 0-1.073-0.002-1.609-0.007l0.082 0.001c-22 0-40-4.6-53.8-14.2s-23-25.2-28-47.2h-111.8c4.8 26.2 14.2 48 27.8 65.4 13.475 16.978 29.89 30.968 48.574 41.377l0.826 0.423c18.192 10.038 39.297 17.806 61.619 22.175l1.381 0.225c20.488 4.162 44.053 6.563 68.171 6.6h0.029c21.8-0.005 43.239-1.532 64.222-4.479l-2.422 0.279c20.641-2.809 39.324-8.783 56.401-17.461l-1.001 0.461c15.909-8.108 28.858-20.031 37.967-34.601l0.233-0.399c9-15 12.2-34.8 9-59.6z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 54 KiB