All Downloads are FREE. Search and download functionalities are using the official Maven repository.

META-INF.dirigible.dev-tools.emulation.MediaQueryInspector.js Maven / Gradle / Ivy

There is a newer version: 10.6.27
Show newest version
// Copyright 2014 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 Bindings from '../bindings/bindings.js';
import * as Common from '../common/common.js';
import * as Platform from '../platform/platform.js';
import * as SDK from '../sdk/sdk.js';
import * as UI from '../ui/ui.js';
import * as Workspace from '../workspace/workspace.js';  // eslint-disable-line no-unused-vars

/**
 * @implements {SDK.SDKModel.SDKModelObserver}
 * @unrestricted
 */
export class MediaQueryInspector extends UI.Widget.Widget {
  /**
   * @param {function():number} getWidthCallback
   * @param {function(number)} setWidthCallback
   */
  constructor(getWidthCallback, setWidthCallback) {
    super(true);
    this.registerRequiredCSS('emulation/mediaQueryInspector.css');
    this.contentElement.classList.add('media-inspector-view');
    this.contentElement.addEventListener('click', this._onMediaQueryClicked.bind(this), false);
    this.contentElement.addEventListener('contextmenu', this._onContextMenu.bind(this), false);
    this._mediaThrottler = new Common.Throttler.Throttler(0);

    this._getWidthCallback = getWidthCallback;
    this._setWidthCallback = setWidthCallback;
    this._scale = 1;

    SDK.SDKModel.TargetManager.instance().observeModels(SDK.CSSModel.CSSModel, this);
    UI.ZoomManager.ZoomManager.instance().addEventListener(
        UI.ZoomManager.Events.ZoomChanged, this._renderMediaQueries.bind(this), this);
  }

  /**
   * @override
   * @param {!SDK.CSSModel.CSSModel} cssModel
   */
  modelAdded(cssModel) {
    // FIXME: adapt this to multiple targets.
    if (this._cssModel) {
      return;
    }
    this._cssModel = cssModel;
    this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetAdded, this._scheduleMediaQueriesUpdate, this);
    this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetRemoved, this._scheduleMediaQueriesUpdate, this);
    this._cssModel.addEventListener(SDK.CSSModel.Events.StyleSheetChanged, this._scheduleMediaQueriesUpdate, this);
    this._cssModel.addEventListener(
        SDK.CSSModel.Events.MediaQueryResultChanged, this._scheduleMediaQueriesUpdate, this);
  }

  /**
   * @override
   * @param {!SDK.CSSModel.CSSModel} cssModel
   */
  modelRemoved(cssModel) {
    if (cssModel !== this._cssModel) {
      return;
    }
    this._cssModel.removeEventListener(SDK.CSSModel.Events.StyleSheetAdded, this._scheduleMediaQueriesUpdate, this);
    this._cssModel.removeEventListener(SDK.CSSModel.Events.StyleSheetRemoved, this._scheduleMediaQueriesUpdate, this);
    this._cssModel.removeEventListener(SDK.CSSModel.Events.StyleSheetChanged, this._scheduleMediaQueriesUpdate, this);
    this._cssModel.removeEventListener(
        SDK.CSSModel.Events.MediaQueryResultChanged, this._scheduleMediaQueriesUpdate, this);
    delete this._cssModel;
  }

  /**
   * @param {number} scale
   */
  setAxisTransform(scale) {
    if (Math.abs(this._scale - scale) < 1e-8) {
      return;
    }
    this._scale = scale;
    this._renderMediaQueries();
  }

  /**
   * @param {!Event} event
   */
  _onMediaQueryClicked(event) {
    const mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass('media-inspector-bar');
    if (!mediaQueryMarker) {
      return;
    }

    const model = mediaQueryMarker._model;
    if (model.section() === Section.Max) {
      this._setWidthCallback(model.maxWidthExpression().computedLength());
      return;
    }
    if (model.section() === Section.Min) {
      this._setWidthCallback(model.minWidthExpression().computedLength());
      return;
    }
    const currentWidth = this._getWidthCallback();
    if (currentWidth !== model.minWidthExpression().computedLength()) {
      this._setWidthCallback(model.minWidthExpression().computedLength());
    } else {
      this._setWidthCallback(model.maxWidthExpression().computedLength());
    }
  }

  /**
   * @param {!Event} event
   */
  _onContextMenu(event) {
    if (!this._cssModel || !this._cssModel.isEnabled()) {
      return;
    }

    const mediaQueryMarker = event.target.enclosingNodeOrSelfWithClass('media-inspector-bar');
    if (!mediaQueryMarker) {
      return;
    }

    const locations = mediaQueryMarker._locations;
    const uiLocations = new Map();
    for (let i = 0; i < locations.length; ++i) {
      const uiLocation =
          Bindings.CSSWorkspaceBinding.CSSWorkspaceBinding.instance().rawLocationToUILocation(locations[i]);
      if (!uiLocation) {
        continue;
      }
      const descriptor = Platform.StringUtilities.sprintf(
          '%s:%d:%d', uiLocation.uiSourceCode.url(), uiLocation.lineNumber + 1, uiLocation.columnNumber + 1);
      uiLocations.set(descriptor, uiLocation);
    }

    const contextMenuItems = [...uiLocations.keys()].sort();
    const contextMenu = new UI.ContextMenu.ContextMenu(event);
    const subMenuItem =
        contextMenu.defaultSection().appendSubMenuItem(Common.UIString.UIString('Reveal in source code'));
    for (let i = 0; i < contextMenuItems.length; ++i) {
      const title = contextMenuItems[i];
      subMenuItem.defaultSection().appendItem(
          title,
          this._revealSourceLocation.bind(
              this, /** @type {!Workspace.UISourceCode.UILocation} */ (uiLocations.get(title))));
    }
    contextMenu.show();
  }

  /**
   * @param {!Workspace.UISourceCode.UILocation} location
   */
  _revealSourceLocation(location) {
    Common.Revealer.reveal(location);
  }

  _scheduleMediaQueriesUpdate() {
    if (!this.isShowing()) {
      return;
    }
    this._mediaThrottler.schedule(this._refetchMediaQueries.bind(this));
  }

  _refetchMediaQueries() {
    if (!this.isShowing() || !this._cssModel) {
      return Promise.resolve();
    }

    return this._cssModel.mediaQueriesPromise().then(this._rebuildMediaQueries.bind(this));
  }

  /**
   * @param {!Array.} models
   * @return {!Array.}
   */
  _squashAdjacentEqual(models) {
    const filtered = [];
    for (let i = 0; i < models.length; ++i) {
      const last = filtered.peekLast();
      if (!last || !last.equals(models[i])) {
        filtered.push(models[i]);
      }
    }
    return filtered;
  }

  /**
   * @param {!Array.} cssMedias
   */
  _rebuildMediaQueries(cssMedias) {
    let queryModels = [];
    for (let i = 0; i < cssMedias.length; ++i) {
      const cssMedia = cssMedias[i];
      if (!cssMedia.mediaList) {
        continue;
      }
      for (let j = 0; j < cssMedia.mediaList.length; ++j) {
        const mediaQuery = cssMedia.mediaList[j];
        const queryModel = MediaQueryUIModel.createFromMediaQuery(cssMedia, mediaQuery);
        if (queryModel && queryModel.rawLocation()) {
          queryModels.push(queryModel);
        }
      }
    }
    queryModels.sort(compareModels);
    queryModels = this._squashAdjacentEqual(queryModels);

    let allEqual = this._cachedQueryModels && this._cachedQueryModels.length === queryModels.length;
    for (let i = 0; allEqual && i < queryModels.length; ++i) {
      allEqual = allEqual && this._cachedQueryModels[i].equals(queryModels[i]);
    }
    if (allEqual) {
      return;
    }
    this._cachedQueryModels = queryModels;
    this._renderMediaQueries();

    /**
     * @param {!MediaQueryUIModel} model1
     * @param {!MediaQueryUIModel} model2
     * @return {number}
     */
    function compareModels(model1, model2) {
      return model1.compareTo(model2);
    }
  }

  _renderMediaQueries() {
    if (!this._cachedQueryModels || !this.isShowing()) {
      return;
    }

    const markers = [];
    let lastMarker = null;
    for (let i = 0; i < this._cachedQueryModels.length; ++i) {
      const model = this._cachedQueryModels[i];
      if (lastMarker && lastMarker.model.dimensionsEqual(model)) {
        lastMarker.locations.push(model.rawLocation());
        lastMarker.active = lastMarker.active || model.active();
      } else {
        lastMarker = {active: model.active(), model: model, locations: [model.rawLocation()]};
        markers.push(lastMarker);
      }
    }

    this.contentElement.removeChildren();

    let container = null;
    for (let i = 0; i < markers.length; ++i) {
      if (!i || markers[i].model.section() !== markers[i - 1].model.section()) {
        container = this.contentElement.createChild('div', 'media-inspector-marker-container');
      }
      const marker = markers[i];
      const bar = this._createElementFromMediaQueryModel(marker.model);
      bar._model = marker.model;
      bar._locations = marker.locations;
      bar.classList.toggle('media-inspector-marker-inactive', !marker.active);
      container.appendChild(bar);
    }
  }

  /**
   * @return {number}
   */
  _zoomFactor() {
    return UI.ZoomManager.ZoomManager.instance().zoomFactor() / this._scale;
  }

  /**
   * @override
   */
  wasShown() {
    this._scheduleMediaQueriesUpdate();
  }

  /**
   * @param {!MediaQueryUIModel} model
   * @return {!Element}
   */
  _createElementFromMediaQueryModel(model) {
    const zoomFactor = this._zoomFactor();
    const minWidthValue = model.minWidthExpression() ? model.minWidthExpression().computedLength() / zoomFactor : 0;
    const maxWidthValue = model.maxWidthExpression() ? model.maxWidthExpression().computedLength() / zoomFactor : 0;
    const result = createElementWithClass('div', 'media-inspector-bar');

    if (model.section() === Section.Max) {
      result.createChild('div', 'media-inspector-marker-spacer');
      const markerElement = result.createChild('div', 'media-inspector-marker media-inspector-marker-max-width');
      markerElement.style.width = maxWidthValue + 'px';
      markerElement.title = model.mediaText();
      appendLabel(markerElement, model.maxWidthExpression(), false, false);
      appendLabel(markerElement, model.maxWidthExpression(), true, true);
      result.createChild('div', 'media-inspector-marker-spacer');
    }

    if (model.section() === Section.MinMax) {
      result.createChild('div', 'media-inspector-marker-spacer');
      const leftElement = result.createChild('div', 'media-inspector-marker media-inspector-marker-min-max-width');
      leftElement.style.width = (maxWidthValue - minWidthValue) * 0.5 + 'px';
      leftElement.title = model.mediaText();
      appendLabel(leftElement, model.minWidthExpression(), true, false);
      appendLabel(leftElement, model.maxWidthExpression(), false, true);
      result.createChild('div', 'media-inspector-marker-spacer').style.flex = '0 0 ' + minWidthValue + 'px';
      const rightElement = result.createChild('div', 'media-inspector-marker media-inspector-marker-min-max-width');
      rightElement.style.width = (maxWidthValue - minWidthValue) * 0.5 + 'px';
      rightElement.title = model.mediaText();
      appendLabel(rightElement, model.minWidthExpression(), true, false);
      appendLabel(rightElement, model.maxWidthExpression(), false, true);
      result.createChild('div', 'media-inspector-marker-spacer');
    }

    if (model.section() === Section.Min) {
      const leftElement = result.createChild(
          'div', 'media-inspector-marker media-inspector-marker-min-width media-inspector-marker-min-width-left');
      leftElement.title = model.mediaText();
      appendLabel(leftElement, model.minWidthExpression(), false, false);
      result.createChild('div', 'media-inspector-marker-spacer').style.flex = '0 0 ' + minWidthValue + 'px';
      const rightElement = result.createChild(
          'div', 'media-inspector-marker media-inspector-marker-min-width media-inspector-marker-min-width-right');
      rightElement.title = model.mediaText();
      appendLabel(rightElement, model.minWidthExpression(), true, true);
    }

    function appendLabel(marker, expression, atLeft, leftAlign) {
      marker
          .createChild(
              'div',
              'media-inspector-marker-label-container ' + (atLeft ? 'media-inspector-marker-label-container-left' :
                                                                    'media-inspector-marker-label-container-right'))
          .createChild(
              'span', 'media-inspector-marker-label ' +
                  (leftAlign ? 'media-inspector-label-left' : 'media-inspector-label-right'))
          .textContent = expression.value() + expression.unit();
    }

    return result;
  }
}

/**
 * @enum {number}
 */
export const Section = {
  Max: 0,
  MinMax: 1,
  Min: 2
};

/**
 * @unrestricted
 */
export class MediaQueryUIModel {
  /**
   * @param {!SDK.CSSMedia.CSSMedia} cssMedia
   * @param {?SDK.CSSMedia.CSSMediaQueryExpression} minWidthExpression
   * @param {?SDK.CSSMedia.CSSMediaQueryExpression} maxWidthExpression
   * @param {boolean} active
   */
  constructor(cssMedia, minWidthExpression, maxWidthExpression, active) {
    this._cssMedia = cssMedia;
    this._minWidthExpression = minWidthExpression;
    this._maxWidthExpression = maxWidthExpression;
    this._active = active;
    if (maxWidthExpression && !minWidthExpression) {
      this._section = Section.Max;
    } else if (minWidthExpression && maxWidthExpression) {
      this._section = Section.MinMax;
    } else {
      this._section = Section.Min;
    }
  }

  /**
   * @param {!SDK.CSSMedia.CSSMedia} cssMedia
   * @param {!SDK.CSSMedia.CSSMediaQuery} mediaQuery
   * @return {?MediaQueryUIModel}
   */
  static createFromMediaQuery(cssMedia, mediaQuery) {
    let maxWidthExpression = null;
    let maxWidthPixels = Number.MAX_VALUE;
    let minWidthExpression = null;
    let minWidthPixels = Number.MIN_VALUE;
    const expressions = mediaQuery.expressions();
    for (let i = 0; i < expressions.length; ++i) {
      const expression = expressions[i];
      const feature = expression.feature();
      if (feature.indexOf('width') === -1) {
        continue;
      }
      const pixels = expression.computedLength();
      if (feature.startsWith('max-') && pixels < maxWidthPixels) {
        maxWidthExpression = expression;
        maxWidthPixels = pixels;
      } else if (feature.startsWith('min-') && pixels > minWidthPixels) {
        minWidthExpression = expression;
        minWidthPixels = pixels;
      }
    }
    if (minWidthPixels > maxWidthPixels || (!maxWidthExpression && !minWidthExpression)) {
      return null;
    }

    return new MediaQueryUIModel(cssMedia, minWidthExpression, maxWidthExpression, mediaQuery.active());
  }

  /**
   * @param {!MediaQueryUIModel} other
   * @return {boolean}
   */
  equals(other) {
    return this.compareTo(other) === 0;
  }

  /**
   * @param {!MediaQueryUIModel} other
   * @return {boolean}
   */
  dimensionsEqual(other) {
    return this.section() === other.section() &&
        (!this.minWidthExpression() ||
         (this.minWidthExpression().computedLength() === other.minWidthExpression().computedLength())) &&
        (!this.maxWidthExpression() ||
         (this.maxWidthExpression().computedLength() === other.maxWidthExpression().computedLength()));
  }

  /**
   * @param {!MediaQueryUIModel} other
   * @return {number}
   */
  compareTo(other) {
    if (this.section() !== other.section()) {
      return this.section() - other.section();
    }
    if (this.dimensionsEqual(other)) {
      const myLocation = this.rawLocation();
      const otherLocation = other.rawLocation();
      if (!myLocation && !otherLocation) {
        return this.mediaText().compareTo(other.mediaText());
      }
      if (myLocation && !otherLocation) {
        return 1;
      }
      if (!myLocation && otherLocation) {
        return -1;
      }
      if (this.active() !== other.active()) {
        return this.active() ? -1 : 1;
      }
      return myLocation.url.compareTo(otherLocation.url) || myLocation.lineNumber - otherLocation.lineNumber ||
          myLocation.columnNumber - otherLocation.columnNumber;
    }
    if (this.section() === Section.Max) {
      return other.maxWidthExpression().computedLength() - this.maxWidthExpression().computedLength();
    }
    if (this.section() === Section.Min) {
      return this.minWidthExpression().computedLength() - other.minWidthExpression().computedLength();
    }
    return this.minWidthExpression().computedLength() - other.minWidthExpression().computedLength() ||
        other.maxWidthExpression().computedLength() - this.maxWidthExpression().computedLength();
  }

  /**
   * @return {!Section}
   */
  section() {
    return this._section;
  }

  /**
   * @return {string}
   */
  mediaText() {
    return this._cssMedia.text;
  }

  /**
   * @return {?SDK.CSSModel.CSSLocation}
   */
  rawLocation() {
    if (!this._rawLocation) {
      this._rawLocation = this._cssMedia.rawLocation();
    }
    return this._rawLocation;
  }

  /**
   * @return {?SDK.CSSMedia.CSSMediaQueryExpression}
   */
  minWidthExpression() {
    return this._minWidthExpression;
  }

  /**
   * @return {?SDK.CSSMedia.CSSMediaQueryExpression}
   */
  maxWidthExpression() {
    return this._maxWidthExpression;
  }

  /**
   * @return {boolean}
   */
  active() {
    return this._active;
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy