| // Copyright 2021 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import * as i18n from '../i18n/i18n.js'; |
| import * as Components from '../ui/components/components.js'; |
| import * as UI from '../ui/ui.js'; |
| |
| import {EditableProperties, FlexboxEditor, PropertyDeselectedEvent, PropertySelectedEvent} from './FlexboxEditor.js'; |
| import {StylePropertyTreeElement} from './StylePropertyTreeElement.js'; |
| import {StylePropertiesSection, StylesSidebarPane} from './StylesSidebarPane.js'; |
| |
| const UIStrings = { |
| /** |
| * @description Title of the button that opens the flexbox editor in the Styles panel. |
| */ |
| editorButton: 'Open Flexbox editor', |
| }; |
| const str_ = i18n.i18n.registerUIStrings('elements/FlexboxEditorWidget.ts', UIStrings); |
| const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); |
| |
| let instance: FlexboxEditorWidget|null = null; |
| |
| /** |
| * Thin UI.Widget wrapper around FlexboxEditor to allow using it as a popover. |
| */ |
| export class FlexboxEditorWidget extends UI.Widget.VBox { |
| private editor: FlexboxEditor; |
| private pane?: StylesSidebarPane; |
| private section?: StylePropertiesSection; |
| |
| constructor() { |
| super(true); |
| this.contentElement.tabIndex = 0; |
| this.setDefaultFocusedElement(this.contentElement); |
| this.editor = new FlexboxEditor(); |
| this.editor.data = { |
| authoredProperties: new Map(), |
| computedProperties: new Map(), |
| }; |
| this.contentElement.appendChild(this.editor); |
| this.onPropertySelected = this.onPropertySelected.bind(this); |
| this.onPropertyDeselected = this.onPropertyDeselected.bind(this); |
| } |
| |
| async onPropertySelected(event: PropertySelectedEvent): Promise<void> { |
| if (!this.section) { |
| return; |
| } |
| const target = ensureTreeElementForProperty(this.section, event.data.name); |
| target.property.value = event.data.value; |
| target.updateTitle(); |
| await target.applyStyleText(target.renderedPropertyText(), false); |
| await this.render(); |
| } |
| |
| async onPropertyDeselected(event: PropertyDeselectedEvent): Promise<void> { |
| if (!this.section) { |
| return; |
| } |
| const target = ensureTreeElementForProperty(this.section, event.data.name); |
| await target.applyStyleText('', false); |
| await this.render(); |
| } |
| |
| bindContext(pane: StylesSidebarPane, section: StylePropertiesSection): void { |
| this.pane = pane; |
| this.section = section; |
| this.editor.addEventListener('property-selected', this.onPropertySelected); |
| this.editor.addEventListener('property-deselected', this.onPropertyDeselected); |
| } |
| |
| unbindContext(): void { |
| this.pane = undefined; |
| this.section = undefined; |
| this.editor.removeEventListener('property-selected', this.onPropertySelected); |
| this.editor.removeEventListener('property-deselected', this.onPropertyDeselected); |
| } |
| |
| async render(): Promise<void> { |
| this.editor.data = { |
| authoredProperties: this.section ? getAuthoredStyles(this.section) : new Map(), |
| computedProperties: this.pane ? await fetchComputedStyles(this.pane) : new Map(), |
| }; |
| } |
| |
| static instance(): FlexboxEditorWidget { |
| if (!instance) { |
| instance = new FlexboxEditorWidget(); |
| } |
| return instance; |
| } |
| |
| static createFlexboxEditorButton(pane: StylesSidebarPane, section: StylePropertiesSection): HTMLElement { |
| const flexboxEditorButton = createButton(); |
| |
| flexboxEditorButton.onclick = async(event): Promise<void> => { |
| event.stopPropagation(); |
| const popoverHelper = pane.swatchPopoverHelper(); |
| const widget = FlexboxEditorWidget.instance(); |
| widget.bindContext(pane, section); |
| await widget.render(); |
| const scrollerElement = flexboxEditorButton.enclosingNodeOrSelfWithClass('style-panes-wrapper'); |
| const onScroll = (): void => { |
| popoverHelper.hide(true); |
| }; |
| popoverHelper.show(widget, flexboxEditorButton, () => { |
| widget.unbindContext(); |
| if (scrollerElement) { |
| scrollerElement.removeEventListener('scroll', onScroll); |
| } |
| }); |
| if (scrollerElement) { |
| scrollerElement.addEventListener('scroll', onScroll); |
| } |
| }; |
| |
| return flexboxEditorButton; |
| } |
| } |
| |
| function createButton(): HTMLButtonElement { |
| const flexboxEditorButton = document.createElement('button'); |
| flexboxEditorButton.classList.add('styles-pane-button'); |
| flexboxEditorButton.tabIndex = 0; |
| flexboxEditorButton.title = i18nString(UIStrings.editorButton); |
| flexboxEditorButton.onmouseup = (event): void => { |
| // Stop propagation to prevent the property editor from being activated. |
| event.stopPropagation(); |
| }; |
| const flexboxIcon = new Components.Icon.Icon(); |
| flexboxIcon.data = {iconName: 'flex-wrap-icon', color: 'var(--color-text-secondary)', width: '12px', height: '12px'}; |
| flexboxEditorButton.appendChild(flexboxIcon); |
| return flexboxEditorButton; |
| } |
| |
| function ensureTreeElementForProperty(section: StylePropertiesSection, propertyName: string): StylePropertyTreeElement { |
| const target = section.propertiesTreeOutline.rootElement().children().find( |
| child => child instanceof StylePropertyTreeElement && child.property.name === propertyName); |
| if (target) { |
| return target as StylePropertyTreeElement; |
| } |
| const newTarget = section.addNewBlankProperty(); |
| newTarget.property.name = propertyName; |
| return newTarget; |
| } |
| |
| async function fetchComputedStyles(pane: StylesSidebarPane): Promise<Map<string, string>> { |
| const computedStyleModel = pane.computedStyleModel(); |
| const style = await computedStyleModel.fetchComputedStyle(); |
| return style ? style.computedStyle : new Map(); |
| } |
| |
| function getAuthoredStyles(section: StylePropertiesSection): Map<string, string> { |
| const authoredProperties = new Map(); |
| const flexboxProperties = new Set(EditableProperties.map(prop => prop.propertyName as string)); |
| for (const prop of section._style.leadingProperties()) { |
| if (flexboxProperties.has(prop.name)) { |
| authoredProperties.set(prop.name, prop.value); |
| } |
| } |
| return authoredProperties; |
| } |