From d3162e49c2bce626cce52a51265b71a18b10ccda Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?"J.=20Rene=CC=81e=20Beach"?= <splendidnoise@gmail.com>
Date: Thu, 14 Feb 2013 20:26:13 -0500
Subject: [PATCH] Issue #1741498 by jessebeach, Wim Leers: Add a mobile
 preview bar to Drupal core
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: J. Renée Beach <splendidnoise@gmail.com>
---
 .../config/responsive_preview.devices.yml          |   61 ++++
 .../css/responsive-preview.base-rtl.css            |   38 +++
 .../css/responsive-preview.base.css                |  103 ++++++
 .../css/responsive-preview.theme-rtl.css           |   36 +++
 .../css/responsive-preview.theme.css               |  126 ++++++++
 core/modules/responsive_preview/images/close.png   |    3 +
 .../images/icon-responsive-preview-active.png      |    3 +
 .../images/icon-responsive-preview.png             |    4 +
 .../responsive_preview/js/responsive-preview.js    |  341 ++++++++++++++++++++
 .../responsive_preview/responsive_preview.info     |    5 +
 .../responsive_preview/responsive_preview.module   |  133 ++++++++
 11 files changed, 853 insertions(+)
 create mode 100644 core/modules/responsive_preview/config/responsive_preview.devices.yml
 create mode 100644 core/modules/responsive_preview/css/responsive-preview.base-rtl.css
 create mode 100644 core/modules/responsive_preview/css/responsive-preview.base.css
 create mode 100644 core/modules/responsive_preview/css/responsive-preview.theme-rtl.css
 create mode 100644 core/modules/responsive_preview/css/responsive-preview.theme.css
 create mode 100644 core/modules/responsive_preview/images/close.png
 create mode 100644 core/modules/responsive_preview/images/icon-responsive-preview-active.png
 create mode 100644 core/modules/responsive_preview/images/icon-responsive-preview.png
 create mode 100644 core/modules/responsive_preview/js/responsive-preview.js
 create mode 100644 core/modules/responsive_preview/responsive_preview.info
 create mode 100644 core/modules/responsive_preview/responsive_preview.module

diff --git a/core/modules/responsive_preview/config/responsive_preview.devices.yml b/core/modules/responsive_preview/config/responsive_preview.devices.yml
new file mode 100644
index 0000000..dd4f7a0
--- /dev/null
+++ b/core/modules/responsive_preview/config/responsive_preview.devices.yml
@@ -0,0 +1,61 @@
+devices:
+  iphone:
+    label: iPhone 5
+    dimensions:
+      width: 640
+      height: 1136
+  iphone4:
+    label: iPhone 4
+    dimensions:
+      width: 640
+      height: 960
+  iphone3gs:
+    label: iPhone 3GS
+    dimensions:
+      width: 320
+      height: 480
+  ipadmini:
+    label: iPad mini
+    dimensions:
+      width: 768
+      height: 1024
+  ipad4:
+    label: iPad 3/4
+    dimensions:
+      width: 1536
+      height: 2048
+  ipad2:
+    label: iPad 2
+    dimensions:
+      width: 768
+      height: 1024
+  kindlefire:
+    label: Kindle Fire
+    dimensions:
+      width: 600
+      height: 1024
+  kindlefirehd:
+    label: Kindle Fire HD
+    dimensions:
+      width: 800
+      height: 1280
+  nooktablet:
+    label: Nook Tablet
+    dimensions:
+      width: 600
+      height: 1024
+  nexus7:
+    label: Nexus 7
+    dimensions:
+      width: 800
+      height: 1280
+  surface:
+    label: Microsoft Surface
+    dimensions:
+      width: 768
+      height: 1366
+  desktop:
+    label: Typical desktop
+    dimensions:
+      width: 1366
+      height: 768
diff --git a/core/modules/responsive_preview/css/responsive-preview.base-rtl.css b/core/modules/responsive_preview/css/responsive-preview.base-rtl.css
new file mode 100644
index 0000000..eaf2504
--- /dev/null
+++ b/core/modules/responsive_preview/css/responsive-preview.base-rtl.css
@@ -0,0 +1,38 @@
+/**
+ * @file
+ * RTL base styling for responsive preview.
+ */
+
+/**
+ * Toolbar tab.
+ */
+
+/* At narrow screen widths, float the tab to the right so it falls in line with
+ * the rest of the toolbar tabs. */
+.js .toolbar .bar .responsive-preview-toolbar-tab.tab {
+  float: right;
+}
+/* At wide widths, float the tab to the left. */
+@media only screen and (min-width: 36em) {
+  .js .toolbar .bar .responsive-preview-toolbar-tab.tab {
+    float: left;
+  }
+}
+.responsive-preview-toolbar-tab .responsive-preview-options {
+  left: 0.3em;
+  right: auto;
+}
+
+/**
+ * Preview container.
+ *
+ * The container is kept offscreen after it is built and has been disabled.
+ */
+#responsive-preview-container {
+  left: auto;
+  right: -200%;
+}
+#responsive-preview-container.active {
+  left: auto;
+  right: 0;
+}
diff --git a/core/modules/responsive_preview/css/responsive-preview.base.css b/core/modules/responsive_preview/css/responsive-preview.base.css
new file mode 100644
index 0000000..6586f57
--- /dev/null
+++ b/core/modules/responsive_preview/css/responsive-preview.base.css
@@ -0,0 +1,103 @@
+/**
+ * @file
+ * Base styling for responsive preview.
+ */
+
+/**
+ * Constrain the window height to the client height when the preview is active.
+ */
+.responsive-preview-active {
+  height: 100%;
+  overflow: hidden;
+}
+
+/**
+ * Toolbar tab.
+ */
+.responsive-preview-toolbar-tab {
+  display: none;
+}
+/* At narrow screen widths, float the tab to the left so it falls in line with
+ * the rest of the toolbar tabs. */
+.js .toolbar .bar .responsive-preview-toolbar-tab.tab {
+  display: block;
+  float: left; /* LTR */
+  position: relative;
+}
+/* At wide widths, float the tab to the right. */
+@media only screen and (min-width: 36em) {
+  .js .toolbar .bar .responsive-preview-toolbar-tab.tab {
+    float: right; /* LTR */
+  }
+}
+.responsive-preview-toolbar-tab .responsive-preview-options {
+  display: none;
+  z-index: 1;
+}
+.responsive-preview-toolbar-tab.open .responsive-preview-options {
+  display: block;
+}
+.js .responsive-preview-toolbar-tab.tab .responsive-preview-options li {
+  float: none;
+}
+
+/**
+ * Preview container.
+ *
+ * The container is kept offscreen after it is built and has been disabled.
+ */
+#responsive-preview-container {
+  bottom: 0;
+  display: none;
+  height: 100%;
+  left: -200%; /* LTR */
+  position: fixed;
+  top: 0;
+  width: 100%;
+  z-index: 1050;
+}
+#responsive-preview-container.active {
+  display: block;
+  left: 0; /* LTR */
+}
+#responsive-preview-close {
+  position: absolute;
+  z-index: 75;
+}
+.responsive-preview-modal-background {
+  bottom: 0;
+  height: 100%;
+  left: 0;
+  position: fixed;
+  right: 0;
+  top: 3em;
+  width: 100%;
+  z-index: 1;
+}
+
+/**
+ * Preview iframe.
+ */
+#responsive-preview-container iframe {
+  height: 100%;
+  position: relative;
+  width: 100%;
+  z-index: 100;
+}
+
+/**
+ * Override Toolbar styling in the preview iframe.
+ */
+body.toolbar-tray-open.responsive-preview-frame {
+  margin-left: 0 !important;
+  margin-right: 0 !important;
+}
+.responsive-preview-frame {
+  overflow-x: hidden !important;
+}
+.responsive-preview-frame #toolbar-administration {
+  display: none !important;
+}
+.responsive-preview-frame .contextual {
+  display: none !important;
+}
diff --git a/core/modules/responsive_preview/css/responsive-preview.theme-rtl.css b/core/modules/responsive_preview/css/responsive-preview.theme-rtl.css
new file mode 100644
index 0000000..8add01e
--- /dev/null
+++ b/core/modules/responsive_preview/css/responsive-preview.theme-rtl.css
@@ -0,0 +1,36 @@
+/**
+ * @file
+ * RTL styling for responsive preview.
+ */
+
+/**
+ * Toolbar tab.
+ */
+
+/* Toolbar icon. */
+.toolbar .bar .responsive-preview-toolbar-tab .icon-responsive-preview:before {
+  left: auto;
+  right: 1em;
+}
+
+/* Toolbar tab triangle toggle. */
+.responsive-preview-toolbar-tab .trigger:after {
+  left: 1em;
+  right: auto;
+}
+.responsive-preview-toolbar-tab.open:before {
+  left: 0;
+  right: auto;
+}
+.responsive-preview-toolbar-tab.open .trigger:after {
+  left: 0.7em;
+  right: auto;
+}
+
+/**
+ * Preview container.
+ */
+#responsive-preview-close {
+  margin-left: 0;
+  margin-right: 10px;
+}
diff --git a/core/modules/responsive_preview/css/responsive-preview.theme.css b/core/modules/responsive_preview/css/responsive-preview.theme.css
new file mode 100644
index 0000000..3dce264
--- /dev/null
+++ b/core/modules/responsive_preview/css/responsive-preview.theme.css
@@ -0,0 +1,126 @@
+/**
+ * @file
+ * Styling for responsive preview.
+ */
+
+/**
+ * Toolbar tab.
+ */
+.responsive-preview-toolbar-tab .responsive-preview-options {
+  background-color: #0f0f0f;
+}
+/* Toolbar icon. */
+.toolbar .bar .icon.icon-responsive-preview {
+  margin-left: 0;
+  margin-right: 0;
+  padding-left: 0;
+  padding-right: 0;
+  width: 5em;
+}
+.icon-responsive-preview:before {
+  background-image: url("../images/icon-responsive-preview.png");
+}
+.toolbar .bar .responsive-preview-toolbar-tab .icon-responsive-preview:before {
+  left: 1em; /* LTR */
+}
+.responsive-preview-toolbar-tab.open .icon-responsive-preview:before,
+.responsive-preview-toolbar-tab .icon-responsive-preview.active:before {
+  background-image: url("../images/icon-responsive-preview-active.png");
+}
+@media only screen and (min-width: 16.5em) {
+  .toolbar .responsive-preview-toolbar-tab.tab .icon-responsive-preview:before {
+    width: 20px;
+  }
+}
+/* Device preview options. */
+.responsive-preview-toolbar-tab .responsive-preview-options {
+  box-shadow: 0 0.8em 2.5em -0.8em rgba(0, 0, 0, 0.75);
+  position: absolute;
+  white-space: nowrap;
+}
+.responsive-preview-toolbar-tab .responsive-preview-options li {
+  background-color: white;
+}
+.responsive-preview-toolbar-tab .trigger {
+  height: 3em;
+}
+.responsive-preview-toolbar-tab .trigger,
+.responsive-preview-toolbar-tab .responsive-preview-options a {
+  padding-bottom: 1em;
+  padding-top: 1em;
+}
+.toolbar .responsive-preview-toolbar-tab.tab .responsive-preview-options a {
+  color: #777;
+}
+.toolbar .responsive-preview-toolbar-tab.tab .responsive-preview-options a:hover {
+  color: black;
+}
+/* Toolbar tab triangle toggle. */
+.responsive-preview-toolbar-tab .trigger:after {
+  border-bottom-color: transparent;
+  border-left-color: transparent;
+  border-right-color: transparent;
+  border-style: solid;
+  border-width: 0.4545em 0.4em 0;
+  color: #a0a0a0;
+  content: ' ';
+  display: block;
+  height: 0;
+  line-height: 0;
+  overflow: hidden;
+  position: absolute;
+  right: 1em; /* LTR */
+  top: 50%;
+  margin-top: -0.1666em;
+  width: 0;
+  z-index: 1
+}
+.responsive-preview-toolbar-tab.open:before {
+  background-color: white;
+  bottom: 0;
+  content: ' ';
+  display: block;
+  position: absolute;
+  right: 0; /* LTR */
+  top: 0;
+  width: 2em;
+  z-index: 1;
+}
+.responsive-preview-toolbar-tab.open .trigger:after {
+  border-bottom: 0.4545em solid;
+  border-top-color: transparent;
+  color: black;
+  right: 0.7em; /* LTR */
+  top: 1.25em;
+}
+
+/**
+ * Preview container.
+ */
+#responsive-preview-container {
+  box-shadow: 0 0 10px 0 black;
+}
+#responsive-preview-close {
+  background-attachment: scroll;
+  background-color: #a0a0a0;
+  background-image: url("../images/close.png");
+  background-image: url("../images/close.png"), -webkit-linear-gradient(transparent, #787878 150%);
+  background-image: url("../images/close.png"), linear-gradient(transparent, #787878 150%);
+  background-position: center center;
+  background-repeat: no-repeat;
+  border: none;
+  border-radius: 3px;
+  cursor: pointer;
+  font-size: 1em;
+  height: 2.333em;
+  margin-left: 10px; /* LTR */
+  margin-top: 9px;
+  width: 2.333em;
+}
+#responsive-preview-close:hover {
+  background-image: url("../images/close.png");
+}
+.responsive-preview-modal-background {
+  background-color: black;
+  background-color: rgba(0,0,0,0.92);
+}
diff --git a/core/modules/responsive_preview/images/close.png b/core/modules/responsive_preview/images/close.png
new file mode 100644
index 0000000..538518e
--- /dev/null
+++ b/core/modules/responsive_preview/images/close.png
@@ -0,0 +1,3 @@
+PNG
+
+   IHDR         7   tEXtSoftware Adobe ImageReadyqe<  IDAT(SeKKBAx_i WjOhBREࣝ{E. BpEĽuy!hs VYkiֶ^pđ#^j[RPZaC̄>5Vdr2Q\mF*ToBKگtJr?wϋ||PqkUݞ,/s_n^z ({!-sSS3oRlYgE/S9d,H+_ϲ5}PrgY{ST⫑V k{,p2D *kfj5Nd2Z>%o=$4֘5FhhfPnoҗ1_M|6]!mf-!K}H2N|S\*6BQ1	#(&INʕr;Sd	#Z     IENDB`
\ No newline at end of file
diff --git a/core/modules/responsive_preview/images/icon-responsive-preview-active.png b/core/modules/responsive_preview/images/icon-responsive-preview-active.png
new file mode 100644
index 0000000..f6e8a16
--- /dev/null
+++ b/core/modules/responsive_preview/images/icon-responsive-preview-active.png
@@ -0,0 +1,3 @@
+PNG
+
+   IHDR         $   cIDAT8cL@HL0#!W?&m"mр0?|?!	?bG(!)dF,~(. te    IENDB`
\ No newline at end of file
diff --git a/core/modules/responsive_preview/images/icon-responsive-preview.png b/core/modules/responsive_preview/images/icon-responsive-preview.png
new file mode 100644
index 0000000..3e22cb8
--- /dev/null
+++ b/core/modules/responsive_preview/images/icon-responsive-preview.png
@@ -0,0 +1,4 @@
+PNG
+
+   IHDR         $   pIDAT8cZx O? f 380L-\1  L%ϦQM߿@ S;#ƐD
+iڈEBCit uJq4Q    IENDB`
\ No newline at end of file
diff --git a/core/modules/responsive_preview/js/responsive-preview.js b/core/modules/responsive_preview/js/responsive-preview.js
new file mode 100644
index 0000000..a58d040
--- /dev/null
+++ b/core/modules/responsive_preview/js/responsive-preview.js
@@ -0,0 +1,341 @@
+/**
+ * @file
+ *
+ * Provides a component that previews the a page in various device dimensions.
+ */
+
+(function ($, Drupal) {
+
+  "use strict";
+
+  Drupal.responsivePreview = Drupal.responsivePreview || {};
+
+  var $toolbarTab = $();
+  var $container; // The container of the page preview component.
+  var $frame; // The iframe that contains the previewed page.
+  var iframeDocument; // The document of the iframe that contains the preview.
+  var size; // The width of the iframe container.
+  var offset; // The left value of the iframe container.
+  var device = {
+    width: null, // The width of the device to preview.
+    height: null // The height of the device to preview.
+  };
+  var edgeTolerance = 60;
+  // Take RTL text direction into account.
+  var dir = document.getElementsByTagName('html')[0].getAttribute('dir');
+  var parentWindow;
+
+  /**
+   * Attaches behaviors to the toolbar tab and preview containers.
+   */
+  Drupal.behaviors.responsivePreview = {
+    attach: function (context, settings) {
+      // once() returns a jQuery set. It will be empty if no unprocessed
+      // elements are found. window and window.parent are equivalent unless the
+      // Drupal page is itself wrapped in an iframe.
+      var $body = $(window.parent.document.body).once('responsive-preview');
+
+      if ($body.length) {
+        // If this window is itself in an iframe it must be marked as processed.
+        // Its parent window will have been processed above.
+        // When attach() is called again for the preview iframe, it will check
+        // its parent window and find it has been processed. In most cases, the
+        // following code will have no effect.
+        $(window.document.body).once('responsive-preview');
+        // Retain a reference to the parent window.
+        parentWindow = window;
+        // Assign behaviors to the toolbar tab.
+        $toolbarTab = $('.responsive-preview-toolbar-tab')
+          .on('click.responsivePreview', toggleConfigurationOptions)
+          .on('click.responsivePreview', '#responsive-preview', toggleConfigurationOptions)
+          .on('mouseleave.responsivePreview', {open: false}, toggleConfigurationOptions)
+          .on('click.responsivePreview', '.responsive-preview-options .responsive-preview-device', {open: false}, toggleConfigurationOptions)
+          .on('click.responsivePreview', '.responsive-preview-device', loadDevicePreview);
+        // Hide layout options that are wider than the current screen
+        prunePreviewChoices.call($toolbarTab.find('.responsive-preview-device'), edgeTolerance);
+        // Register a handler on window resize to reposition the tab dropdown.
+        $(window)
+          .on('resize.responsivePreview.tab', handleWindowToolbarResize);
+      }
+      // The main window is equivalent to window.parent and window.self. Inside,
+      // an iframe, these objects are not equivalent. If the parent window is
+      // itself in an iframe, check that the parent window has been processed.
+      // If it has been, this invocation of attach() is being called on the
+      // preview iframe, not its parent.
+      if ((window.parent !== window.self) && !$body.length) {
+        var $frameBody = $(window.self.document.body).once('responsive-preview');
+        if ($frameBody.length > 0) {
+          $frameBody.get(0).className += ' responsive-preview-frame';
+        }
+      }
+    }
+  };
+
+  /**
+   * Toggles the list of devices available to preview from the toolbar tab.
+   *
+   * @param Object event
+   *   jQuery Event object.
+   */
+  function toggleConfigurationOptions (event) {
+    event.preventDefault();
+    event.stopPropagation();
+    var open = (event.data && typeof event.data.open === 'boolean') ? event.data.open : undefined;
+    var $list = $toolbarTab
+      // Set an open class on the tab wrapper.
+      .toggleClass('open', open)
+      .find('.responsive-preview-options');
+    // The list of options might render outside the window.
+    correctEdgeCollisions.call($list);
+  }
+
+  /**
+   * Toggles the layout preview component on or off.
+   *
+   * When first toggled on, the layout preview component is built. All
+   * subsequent toggles hide or show the built component.
+   *
+   * @param Object event
+   *   jQuery Event object.
+   *
+   * @param Boolean activate
+   *   A boolean that forces the preview to show (true) or to hide (false).
+   */
+  function toggleLayoutPreview (event, activate) {
+    event.preventDefault();
+    // Build the preview if it doesn't exist.
+    if (!$container) {
+      buildpreview();
+    }
+    $toolbarTab
+      .find('> button')
+      .toggleClass('active', activate);
+    $container
+      .toggleClass('active', activate);
+    $('body')
+      .toggleClass('responsive-preview-active', activate);
+  }
+
+  /**
+   * Assembles a layout preview.
+   */
+  function buildpreview () {
+    $(parentWindow.document.body).once('responsive-preview-container', function (index, element) {
+      $container = $(Drupal.theme('layoutContainer'));
+
+      // Add a close button.
+      $container
+        .append(Drupal.theme('layoutClose'));
+
+      // Attach the iframe that will hold the preview.
+      $frame = $(Drupal.theme('layoutFrame'))
+        .css({ width: size })
+        .appendTo($container);
+
+      // Append the container to the window.
+      $container.appendTo(parentWindow.document.body);
+      // Displace the top of the container.
+      $container
+        .css({ top: getDisplacement('top') })
+        .attr('data-offset-top', getDisplacement('top'));
+
+      // The contentDocument property is not supported in IE until IE8.
+      iframeDocument = $frame[0].contentDocument || $frame[0].contentWindow.document;
+
+      $container
+        .on('click.responsivePreview', '#responsive-preview-close', {activate: false}, toggleLayoutPreview)
+        .on('sizeUpdate.responsivePreview', refreshPreviewSizing);
+
+      // Trigger a resize to kick off some initial placements.
+      $(parentWindow)
+        .on('resize.responsivePreview', updateDimensions)
+        .trigger('resize.responsivePreview');
+
+      // Load the current page URI into the preview iframe.
+      iframeDocument.location.href = Drupal.encodePath(Drupal.settings.currentPath);
+    });
+  }
+
+  /**
+   * Updates the dimension variables of the preview components.
+   *
+   * @param Object dimensions
+   *   An object with the following properties:
+   *    - Number width: The width the preview should be set to.
+   *    - Number height (optional): The height the preview should be set to.
+   *
+   * @todo dimensions.height is not yet being used.
+   */
+  function updateDimensions () {
+    var width = device.width || NaN;
+    var height = device.height || NaN;
+    var max = document.documentElement.clientWidth;
+    var gutterPercent = (1 - (width / max)) / 2;
+    var left = gutterPercent * max;
+    // Set the left offset of the frame.
+    // The gutters must be at least the width of the edgeTolerance
+    left = (left < edgeTolerance) ? edgeTolerance : left;
+    // The frame width must fit within the difference of the gutters and the
+    // page width.
+    width = (max - (left * 2) < width) ? max - (left * 2) : width;
+    // Set the dimension variables in the closure.
+    offset = left;
+    size = width;
+    // Trigger a dimension change.
+    $container.trigger('sizeUpdate.responsivePreview');
+  }
+
+  /**
+   * Handles refreshing the layout toolbar tab positioning.
+   *
+   * @param Object event
+   *   jQuery Event object.
+   */
+  function handleWindowToolbarResize (event) {
+    var $list = $toolbarTab.find('.responsive-preview-options');
+    // Move the list back onto the screen.
+    correctEdgeCollisions.call($list);
+    // Update the display of the option items.
+    var $options = $list.find('.responsive-preview-device');
+    // Hide layout options that are wider than the current screen
+    prunePreviewChoices.call($options, edgeTolerance);
+    // Hide the tab if no options are visible. Show the tab if at least one is.
+    $toolbarTab.toggle($options.parent('li').not('.element-hidden').length > 0);
+  }
+
+  /**
+   * Resizes the preview iframe to the configured dimensions of a device.
+   *
+   * @param Object event
+   *   A jQuery event object.
+   */
+  function loadDevicePreview (event) {
+    event.preventDefault();
+    var $link = $(event.target);
+    device.width = $link.data('responsive-preview-width');
+    device.height = $link.data('responsive-preview-height');
+    // Toggle the preview on.
+    toggleLayoutPreview(event, true);
+    updateDimensions();
+  }
+
+  /**
+   * Redraws the layout preview component based on the stored dimensions.
+   *
+   * @param Object event
+   *   A jQuery event object.
+   */
+  function refreshPreviewSizing (event) {
+    var edge = (dir === 'rtl') ? 'right' : 'left';
+    var options = {
+      width: size
+    };
+    options[edge] = offset;
+    $frame
+      .stop(true, true)
+      .animate(options, 'fast');
+    // Reposition the close button.
+    $('#responsive-preview-close')
+      .css(edge, offset + size);
+  }
+
+  /**
+   * Get the total displacement of given region.
+   *
+   * @param String region
+   *   Region name. Either "top" or "bottom".
+   *
+   * @return Number
+   *   The total displacement of given region in pixels.
+   */
+  function getDisplacement (region) {
+    var displacement = 0;
+    var lastDisplaced = $('[data-offset-' + region + ']');
+    if (lastDisplaced.length) {
+      displacement = parseInt(lastDisplaced.attr('data-offset-' + region), 10);
+    }
+    return displacement;
+  }
+
+  /**
+   * Corrects element window edge collisions.
+   *
+   * Elements are moved back into the window if part of the element is
+   * rendered outside the visible window.
+   *
+   * This should be invoked against a jQuery set like this:
+   * correctEdgeCollisions.call($('div'));
+   */
+  function correctEdgeCollisions () {
+    // The position of the dropdown depends on the language direction.
+    var edge = (dir === 'rtl') ? 'left' : 'right';
+    // Go through each element and correct edge collisions.
+    return this.each(function (index, element) {
+      $(this)
+        // Invoke jQuery UI position on the device options.
+        .position({
+          'my': edge +' top',
+          'at': edge + ' bottom',
+          'of': $toolbarTab,
+          'collision': 'flip fit'
+        });
+    });
+  }
+
+  /**
+   * Hides device preview options that are too wide for the current window.
+   *
+   * This should be invoked against a jQuery set like this:
+   * prunePreviewChoices.call($('div'), 20);
+   *
+   * @param Number tolerance
+   *   The distance from the edge of the window that a device cannot exceed
+   *   or it will be pruned from the list.
+   */
+  function prunePreviewChoices (tolerance) {
+    var docWidth = document.documentElement.clientWidth;
+    tolerance = (typeof tolerance === 'number' && tolerance > 0) ? tolerance : 0;
+    return this.each(function () {
+      var $this = $(this);
+      var width = parseInt($this.data('responsive-preview-width'), 10);
+      var fits = ((width + (tolerance * 2)) < docWidth);
+      $this.parent('li').toggleClass('element-hidden', !fits);
+    });
+  }
+
+  /**
+   * Registers theme templates with Drupal.theme().
+   */
+  $.extend(Drupal.theme, {
+    /**
+     * Theme function for the preview container element.
+     *
+     * @return
+     *   The corresponding HTML.
+     */
+    layoutContainer: function () {
+      return '<div id="responsive-preview-container"><div class="responsive-preview-modal-background"></div></div>';
+    },
+
+    /**
+     * Theme function for the close button for the preview container.
+     *
+     * @return
+     *   The corresponding HTML.
+     */
+    layoutClose: function () {
+      return '<button id="responsive-preview-close" role="button" aria-pressed="false"><span class="element-invisible">' + Drupal.t('Close') + '</span></button>';
+    },
+
+    /**
+     * Theme function for a responsive preview iframe element.
+     *
+     * @return
+     *   The corresponding HTML.
+     */
+    layoutFrame: function (url) {
+      return '<iframe id="responsive-preview" frameborder="0" scrolling="auto" allowtransparency="true"></iframe>';
+    }
+  });
+
+}(jQuery, Drupal));
diff --git a/core/modules/responsive_preview/responsive_preview.info b/core/modules/responsive_preview/responsive_preview.info
new file mode 100644
index 0000000..06e86eb
--- /dev/null
+++ b/core/modules/responsive_preview/responsive_preview.info
@@ -0,0 +1,5 @@
+name = Responsive Preview
+description = Provides a component that previews the a page in various device dimensions.
+package = Core
+version = VERSION
+core = 8.x
diff --git a/core/modules/responsive_preview/responsive_preview.module b/core/modules/responsive_preview/responsive_preview.module
new file mode 100644
index 0000000..43abbeb
--- /dev/null
+++ b/core/modules/responsive_preview/responsive_preview.module
@@ -0,0 +1,133 @@
+<?php
+
+/**
+ * @file
+ * Provides a component that previews the a page in various device dimensions.
+ */
+
+/**
+ * Implements hook_help().
+ */
+function responsive_preview_help($path, $arg) {
+
+  switch ($path) {
+    case 'admin/help#responsive_preview':
+      $output = '<h3>' . t('About') . '</h3>';
+      $output .= '<p>' . t('The Responsive Preview module provides a quick way to preview a page on your site within the dimensions of many popular device and screen sizes.') . '</p>';
+      $output .= '<h3>' . t('Uses') . '</h3>';
+      $output .= '<p>' . t('To launch a preview, first click the toolbar tab with the small device icon. The tab has the title "@title". A list of devices will appear. Selecting a device name will launch a preview of the current page within the dimensions of that device.', array('@title' => t('Preview page layout'))) . '</p>';
+      $output .= '<p>' . t('To close the preview, click the close button signified visually by an x.') . '</p>';
+      return $output;
+  }
+}
+
+/**
+ * Returns a list of devices and their properties from configuration.
+ */
+function responsive_preview_get_devices_list() {
+  $devices = config('responsive_preview.devices')->get('devices');
+
+  $links = array();
+
+  foreach($devices as $name => $info) {
+    $links[$name] = array(
+      'title' => $info['label'],
+      'href' => '',
+      'fragment' => '!',
+      'exteranl' => TRUE,
+      'options' => array(
+        'fragment' => '!',
+        'exteranl' => TRUE,
+      ),
+      'attributes' => array(
+        'class' => array('responsive-preview-device'),
+        'data-responsive-preview-width' => ($info['dimensions']['width']) ? $info['dimensions']['width'] : '',
+        'data-responsive-preview-height' => ($info['dimensions']['height']) ? $info['dimensions']['height'] : '',
+      ),
+    );
+  }
+
+  return $links;
+}
+
+/**
+ * Prevents the preview tab from rendering on administration pages.
+ */
+function responsive_preview_access() {
+  return !path_is_admin(current_path());
+}
+
+/**
+ * Implements hook_toolbar().
+ */
+function responsive_preview_toolbar() {
+
+  $items['responsive_preview'] = array(
+    '#type' => 'toolbar_item',
+    'tab' => array(
+      'trigger' => array(
+        '#theme' => 'html_tag',
+        '#tag' => 'button',
+        '#value' => t('Layout preview'),
+        '#value_prefix' => '<span class="element-invisible">',
+        '#value_suffix' => '</span>',
+        '#attributes' => array(
+          'id' => 'responsive-preview',
+          'title' => "Preview page layout",
+          'class' => array('icon', 'icon-responsive-preview', 'trigger'),
+        ),
+      ),
+      'device_options' => array(
+        '#theme' => 'links',
+        '#links' => responsive_preview_get_devices_list(),
+        '#attributes' => array(
+          'class' => array('responsive-preview-options'),
+        ),
+      ),
+    ),
+    '#wrapper_attributes' => array(
+      'class' => array('responsive-preview-toolbar-tab'),
+    ),
+    '#attached' => array(
+      'library' => array(
+        array('responsive_preview', 'responsive-preview'),
+      ),
+    ),
+    '#weight' => 200,
+    '#access' => responsive_preview_access(),
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_library().
+ */
+function responsive_preview_library_info() {
+  $libraries = array();
+  $path = drupal_get_path('module', 'responsive_preview');
+  $options = array(
+    'scope' => 'footer',
+    'attributes' => array('defer' => TRUE),
+  );
+
+  $libraries['responsive-preview'] = array(
+    'title' => 'Preview layouts',
+    'website' => 'http://drupal.org/project/responsive_preview',
+    'version' => VERSION,
+    'css' => array(
+      $path . '/css/responsive-preview.base.css',
+      $path . '/css/responsive-preview.theme.css',
+    ),
+    'js' => array(
+      $path . '/js/responsive-preview.js' => $options,
+    ),
+    'dependencies' => array(
+      array('system', 'jquery'),
+      array('system', 'drupal'),
+      array('system', 'jquery.ui.position'),
+    ),
+  );
+
+  return $libraries;
+}
-- 
1.7.10.4

