diff --git a/core/modules/responsive_preview/config/responsive_preview.device.desktop.yml b/core/modules/responsive_preview/config/responsive_preview.device.desktop.yml deleted file mode 100644 index 14e5ba9..0000000 --- a/core/modules/responsive_preview/config/responsive_preview.device.desktop.yml +++ /dev/null @@ -1,10 +0,0 @@ -id: desktop -label: Typical desktop -dimensions: - width: 1366 - height: 768 - dppx: 1 -orientation: landscape -weight: 5 -status: 1 -langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.ipad.yml b/core/modules/responsive_preview/config/responsive_preview.device.ipad.yml index c14ecac..6795ad8 100644 --- a/core/modules/responsive_preview/config/responsive_preview.device.ipad.yml +++ b/core/modules/responsive_preview/config/responsive_preview.device.ipad.yml @@ -5,6 +5,6 @@ dimensions: height: 2048 dppx: 2 orientation: portrait -weight: 2 -status: 1 +weight: 5 +status: 0 langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.iphone4.yml b/core/modules/responsive_preview/config/responsive_preview.device.iphone4.yml index c57d380..07ca7b7 100644 --- a/core/modules/responsive_preview/config/responsive_preview.device.iphone4.yml +++ b/core/modules/responsive_preview/config/responsive_preview.device.iphone4.yml @@ -5,6 +5,6 @@ dimensions: height: 960 dppx: 2 orientation: portrait -weight: 1 -status: 1 +weight: 4 +status: 0 langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.iphone5.yml b/core/modules/responsive_preview/config/responsive_preview.device.iphone5.yml index d076c8d..e38d0f0 100644 --- a/core/modules/responsive_preview/config/responsive_preview.device.iphone5.yml +++ b/core/modules/responsive_preview/config/responsive_preview.device.iphone5.yml @@ -5,6 +5,6 @@ dimensions: height: 1136 dppx: 2 orientation: portrait -weight: 0 -status: 1 +weight: 3 +status: 0 langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.large.yml b/core/modules/responsive_preview/config/responsive_preview.device.large.yml new file mode 100644 index 0000000..97bab7f --- /dev/null +++ b/core/modules/responsive_preview/config/responsive_preview.device.large.yml @@ -0,0 +1,10 @@ +id: large +label: Typical desktop +dimensions: + width: 1366 + height: 768 + dppx: 1 +orientation: landscape +weight: 2 +status: 0 +langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.medium.yml b/core/modules/responsive_preview/config/responsive_preview.device.medium.yml new file mode 100644 index 0000000..467ba82 --- /dev/null +++ b/core/modules/responsive_preview/config/responsive_preview.device.medium.yml @@ -0,0 +1,10 @@ +id: medium +label: Tablet +dimensions: + width: 800 + height: 1280 + dppx: 1.325 +orientation: portrait +weight: 1 +status: 1 +langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.nexus4.yml b/core/modules/responsive_preview/config/responsive_preview.device.nexus4.yml index d43ffea..2f90b24 100644 --- a/core/modules/responsive_preview/config/responsive_preview.device.nexus4.yml +++ b/core/modules/responsive_preview/config/responsive_preview.device.nexus4.yml @@ -5,6 +5,6 @@ dimensions: height: 1280 dppx: 2 orientation: portrait -weight: 3 -status: 1 +weight: 6 +status: 0 langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.nexus7.yml b/core/modules/responsive_preview/config/responsive_preview.device.nexus7.yml index bf17615..2affe9b 100644 --- a/core/modules/responsive_preview/config/responsive_preview.device.nexus7.yml +++ b/core/modules/responsive_preview/config/responsive_preview.device.nexus7.yml @@ -5,6 +5,6 @@ dimensions: height: 1280 dppx: 1.325 orientation: portrait -weight: 4 -status: 1 +weight: 7 +status: 0 langcode: en diff --git a/core/modules/responsive_preview/config/responsive_preview.device.small.yml b/core/modules/responsive_preview/config/responsive_preview.device.small.yml new file mode 100644 index 0000000..3e63537 --- /dev/null +++ b/core/modules/responsive_preview/config/responsive_preview.device.small.yml @@ -0,0 +1,10 @@ +id: small +label: Smart phone +dimensions: + width: 768 + height: 1280 + dppx: 2 +orientation: portrait +weight: 0 +status: 1 +langcode: en diff --git a/core/modules/responsive_preview/css/responsive-preview.base-rtl.css b/core/modules/responsive_preview/css/responsive-preview.base-rtl.css deleted file mode 100644 index 790cc0a..0000000 --- a/core/modules/responsive_preview/css/responsive-preview.base-rtl.css +++ /dev/null @@ -1,32 +0,0 @@ -/** - * @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 .toolbar-tab-responsive-preview.tab { - float: left; -} -.toolbar-tab-responsive-preview .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 { - left: auto; - right: -200%; -} -.responsive-preview.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 deleted file mode 100644 index cd82f1b..0000000 --- a/core/modules/responsive_preview/css/responsive-preview.base.css +++ /dev/null @@ -1,105 +0,0 @@ -/** - * @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. - */ -.toolbar-tab-responsive-preview { - 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 .toolbar-tab-responsive-preview.tab { - display: block; - float: right; /* LTR */ - position: relative; -} -.toolbar-tab-responsive-preview .trigger { - display: block; -} -/* Device preview options. */ -.toolbar-tab-responsive-preview .item-list { - display: none; - position: absolute; - white-space: nowrap; - z-index: 1; -} -.toolbar-tab-responsive-preview.open .item-list { - display: block; -} -.js .toolbar-tab-responsive-preview.tab .options li { - float: none; -} - -/** - * Preview container. - * - * The container is kept offscreen after it is built and has been disabled. - */ -.responsive-preview { - bottom: 0; - height: 100%; - left: -200%; /* LTR */ - position: relative; - top: 0; - width: 100%; - z-index: 1050; -} -.responsive-preview.active { - left: 0; /* LTR */ - position: fixed; -} -.responsive-preview .control { - position: absolute; -} -.responsive-preview .modal-background { - bottom: 0; - height: 100%; - left: 0; - position: static; - right: 0; - top: 0; - width: 100%; - z-index: 1; -} -.responsive-preview.active .modal-background { - position: fixed; -} - -/** - * Preview iframe. - */ -.responsive-preview .frame-container { - position: absolute; - z-index: 100; -} -.responsive-preview .frame-container iframe { - position: relative; -} - -/** - * 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.icons-rtl.css b/core/modules/responsive_preview/css/responsive-preview.icons-rtl.css deleted file mode 100644 index 0c8d2b6..0000000 --- a/core/modules/responsive_preview/css/responsive-preview.icons-rtl.css +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @file - * RTL icon styling for responsive preview. - */ - -.toolbar .bar .toolbar-tab-responsive-preview .icon-responsive-preview:before { - left: auto; /* LTR */ - right: 1em; -} - -/** - * Responsive preview controls icons. - */ -.responsive-preview .icon-close:before { - left: 9px; - right: auto; -} -.responsive-preview .icon-orientation:before { - left: auto; - right: 9px; -} diff --git a/core/modules/responsive_preview/css/responsive-preview.icons.css b/core/modules/responsive_preview/css/responsive-preview.icons.css index 6ed041e..5060d1d 100644 --- a/core/modules/responsive_preview/css/responsive-preview.icons.css +++ b/core/modules/responsive_preview/css/responsive-preview.icons.css @@ -43,18 +43,35 @@ height: 22px; top: 0.6667em; } +[dir="rtl"] .toolbar .bar .toolbar-tab-responsive-preview .icon-responsive-preview:before { + left: auto; /* LTR */ + right: 1em; +} .toolbar .toolbar-tab-responsive-preview.tab .options .device.icon-active { padding-left: 2.25em; } .toolbar .toolbar-tab-responsive-preview.tab .options .device.icon-active:before { background-position: -999px -999px; + height: 14px; + left: 3px; /* LTR */ + top: 0.5em; + width: 13px; +} +[dir="rtl"] .toolbar .toolbar-tab-responsive-preview.tab .options .device.icon-active:before { + left: auto; + right: 6px; } .toolbar .toolbar-tab-responsive-preview.tab .options .device.icon-active.active:before { - background-position: center -110px; + background-position: center -116px; } @media only screen and (min-width: 16.5em) { .toolbar .toolbar-tab-responsive-preview.tab .options .device.icon-active { - padding: 0.5em 1.3333em 0.5em 2.25em; + padding: 0.5em 1.3333em 0.5em 2.25em; /* LTR */ + text-indent: 0; + width: auto; + } + [dir="rtl"] .toolbar .toolbar-tab-responsive-preview.tab .options .device.icon-active { + padding: 0.5em 2.25em 0.5em 1.3333em; text-indent: 0; width: auto; } @@ -75,21 +92,39 @@ background-position: left -44px; right: 9px; /* LTR */ } +[dir="rtl"] .responsive-preview .icon-close:before { + left: 9px; + right: auto; +} .responsive-preview .icon-close:active:before, .responsive-preview .icon-close.active:before, .responsive-preview .icon-close:hover:before { background-position: left -56px; } .responsive-preview .icon-orientation:before { - background-position: left -92px; + background-position: left -92px; /* LTR */ left: 9px; /* LTR */ } +[dir="rtl"] .responsive-preview .icon-orientation:before { + background-position: left -155px; + left: auto; + right: 9px; +} .responsive-preview .icon-orientation:hover:before { - background-position: left -104px; + background-position: left -104px; /* LTR */ +} +[dir="rtl"] .responsive-preview .icon-orientation:hover:before { + background-position: left -167px; } .responsive-preview .icon-orientation.rotated:before { - background-position: left -68px; + background-position: left -68px; /* LTR */ +} +[dir="rtl"] .responsive-preview .icon-orientation.rotated:before { + background-position: left -131px; } .responsive-preview .icon-orientation.rotated:hover:before { - background-position: left -80px; + background-position: left -80px; /* LTR */ +} +[dir="rtl"] .responsive-preview .icon-orientation.rotated:hover:before { + background-position: left -143px; } diff --git a/core/modules/responsive_preview/css/responsive-preview.module.css b/core/modules/responsive_preview/css/responsive-preview.module.css new file mode 100644 index 0000000..9b20d70 --- /dev/null +++ b/core/modules/responsive_preview/css/responsive-preview.module.css @@ -0,0 +1,116 @@ +/** + * @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. + */ +.toolbar-tab-responsive-preview { + 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 .toolbar-tab-responsive-preview.tab { + display: block; + float: right; /* LTR */ + position: relative; +} +[dir="rtl"].js .toolbar .bar .toolbar-tab-responsive-preview.tab { + float: left; +} +.toolbar-tab-responsive-preview .trigger { + display: block; +} +/* Device preview options. */ +.toolbar-tab-responsive-preview .item-list { + display: none; + position: absolute; + white-space: nowrap; + z-index: 1; +} +.toolbar-tab-responsive-preview.open .item-list { + display: block; +} +.js .toolbar-tab-responsive-preview.tab .options li { + float: none; +} + +/** + * Preview container. + * + * The container is kept offscreen after it is built and has been disabled. + */ +.responsive-preview { + bottom: 0; + height: 100%; + left: -200%; /* LTR */ + position: relative; + top: 0; + width: 100%; + z-index: 1050; +} +[dir="rtl"] .responsive-preview { + left: auto; + right: -200%; +} +.responsive-preview.active { + left: 0; /* LTR */ + position: fixed; +} +[dir="rtl"] .responsive-preview.active { + left: auto; + right: 0; +} +.responsive-preview .control { + position: absolute; +} +.responsive-preview .modal-background { + bottom: 0; + height: 100%; + left: 0; + position: static; + right: 0; + top: 0; + width: 100%; + z-index: 1; +} +.responsive-preview.active .modal-background { + position: fixed; +} + +/** + * Preview iframe. + */ +.responsive-preview .frame-container { + position: absolute; + z-index: 100; +} +.responsive-preview .frame-container iframe { + position: relative; +} + +/** + * 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 deleted file mode 100644 index b8892ab..0000000 --- a/core/modules/responsive_preview/css/responsive-preview.theme-rtl.css +++ /dev/null @@ -1,30 +0,0 @@ -/** - * @file - * RTL styling for responsive preview. - */ - -/** - * Toolbar tab. - */ - -/* Toolbar tab triangle toggle. */ -.toolbar-tab-responsive-preview .trigger:after { - left: 1em; - right: auto; -} -.toolbar-tab-responsive-preview.open:before { - left: 0; - right: auto; -} -.toolbar-tab-responsive-preview.open .trigger:after { - left: 0.7em; - right: auto; -} -.responsive-preview .control.close { - left: 0; - right: auto; -} -.responsive-preview .control.orientation { - left: auto; - right: 0; -} diff --git a/core/modules/responsive_preview/css/responsive-preview.theme.css b/core/modules/responsive_preview/css/responsive-preview.theme.css index d276aa4..637e4bd 100644 --- a/core/modules/responsive_preview/css/responsive-preview.theme.css +++ b/core/modules/responsive_preview/css/responsive-preview.theme.css @@ -7,14 +7,15 @@ * Toolbar tab. */ .toolbar-tab-responsive-preview .options { - background-color: #0f0f0f; + background-color: white; } /* Device preview options. */ .toolbar-tab-responsive-preview .options { box-shadow: 0 0.8em 2.5em -0.8em rgba(0, 0, 0, 0.75); } -.toolbar-tab-responsive-preview .options li { - background-color: white; +/* [dir] is needed to override Bartik's .item-list li padding */ +[dir] .toolbar-tab-responsive-preview .options li { + margin: 0; padding: 0; } .toolbar-tab-responsive-preview .trigger { @@ -55,6 +56,12 @@ color: #ccc; cursor: default; } +/* Configuration link. */ +.toolbar-tab-responsive-preview.tab .configure { + border-top: 1px solid #000; + color: #000; + margin-top: 0.5em; +} /* Toolbar tab triangle toggle. */ .toolbar-tab-responsive-preview .trigger:after { @@ -76,6 +83,10 @@ width: 0; z-index: 1 } +[dir="rtl"] .toolbar-tab-responsive-preview .trigger:after { + left: 1em; + right: auto; +} .toolbar-tab-responsive-preview.open:before { background-color: white; bottom: 0; @@ -87,6 +98,10 @@ width: 2em; z-index: 1; } +[dir="rtl"] .toolbar-tab-responsive-preview.open:before { + left: 0; + right: auto; +} .toolbar-tab-responsive-preview.open .trigger:after { border-bottom: 0.4545em solid; border-top-color: transparent; @@ -94,6 +109,10 @@ right: 0.7em; /* LTR */ top: 1.25em; } +[dir="rtl"] .toolbar-tab-responsive-preview.open .trigger:after { + left: 0.7em; + right: auto; +} .toolbar-tab-responsive-preview:hover .trigger:after, .toolbar-tab-responsive-preview .trigger.active:after, .toolbar-tab-responsive-preview:hover .trigger.active:after { @@ -121,8 +140,8 @@ .responsive-preview .modal-background { background-color: black; background-color: rgba(0,0,0,0.92); - background-image: -webkit-linear-gradient(left, black,rgb(20,20,20) 15%, rgb(45,45,45) 40%, rgb(45,45,45) 60%, rgb(20,20,20) 85%, black 100%); - background-image: linear-gradient(left, black, rgb(20,20,20) 15%, rgb(45,45,45) 40%, rgb(45,45,45) 60%, rgb(20,20,20) 85%, black 100%); + background-image: -webkit-linear-gradient(left, rgb(20,20,20),rgb(50,50,50) 25%, rgb(100,100,100) 40%, rgb(100,100,100) 60%, rgb(50,50,50) 85%, rgb(20,20,20)); + background-image: linear-gradient(left, rgb(20,20,20),rgb(50,50,50) 25%, rgb(100,100,100) 40%, rgb(100,100,100) 60%, rgb(50,50,50) 85%, rgb(20,20,20)); } /** @@ -138,11 +157,19 @@ .responsive-preview .control.close { right: 0; /* LTR */ } +[dir="rtl"] .responsive-preview .control.close { + left: 0; + right: auto; +} .responsive-preview .control.orientation { left: 0; /* LTR */ } +[dir="rtl"] .responsive-preview .control.orientation { + left: auto; + right: 0; +} .responsive-preview .device-label { - color: #e0e0e0; + color: #bbbbbb; font-family: sans-serif; font-size: 0.75em; font-weight: normal; @@ -163,11 +190,11 @@ * Responsive preview frame. */ .responsive-preview .frame-container { - background-color: #343434; + background-color: #212121; border-radius: 20px; box-shadow: - 0 0 0px 1px #404040, - 2px 2px 0 0px black; + 0 0 0px 1px #777, + 1px 1px 60px 0px #000; -webkit-transition: all 150ms ease-out; -moz-transition: all 150ms ease-out; -o-transition: all 150ms ease-out; @@ -175,9 +202,7 @@ margin-top: 2em; } .responsive-preview .frame-container iframe { - box-shadow: - 0 0 0 2px black, - 0 0 0 3px #404040; + box-shadow: 0 0 0 1px #808080; -webkit-transition: all 150ms ease-out; -moz-transition: all 150ms ease-out; -o-transition: all 150ms ease-out; diff --git a/core/modules/responsive_preview/images/responsive-preview-icons.png b/core/modules/responsive_preview/images/responsive-preview-icons.png index 0ce06e7..b30de7e 100644 --- a/core/modules/responsive_preview/images/responsive-preview-icons.png +++ b/core/modules/responsive_preview/images/responsive-preview-icons.png @@ -1,11 +1,13 @@ PNG  - IHDR  BqtEXtSoftwareAdobe ImageReadyqe<IDATxYMv0$uWn'{ -v!KO}9A e;~ -ȝeI<^7,FDߏ<ϻۃdjD`0Huri'I "YzMȷۭ3t6W$F@>57_=j! L"+aKIYS -V!R]lNV+XiDbZBrvNNN -A{@lMb6>&6rחa#1NKӉ3if`<}{{;̬ T*"⼧6@7rkE*Ud ׯ8<c#"M')H0u& -yK @0Wl2c)z$\l޳*];wr?~edr n&(ɷ=_phkNa4H([S2PY6zcneK(>]IENDB` \ No newline at end of file + IHDR R+ynIDATh͘[J+A{ ,a%d YB|(DDD1,!K 5h.驮=~B&Mj9;;YA )ԕ999Q\{|| ( DGGG@{xx( D@( D{{{@ ( DPbwvv(v{{Pb(vssPb766(v}}Pb(vuu}`TXgw +nGVPC#SwM'ԕf)v>JQ;4žJQ+4žJQ34>==JQ#4><<JQl6{@i [@i k@i 2hns0[gws~~>*u ,=>1O ȁ(TFcR^k֩S>!3JiLVF{p"uJ{ξ>P5ϴhL+b-:u8(>M28؊.Q`2%3DT1&Ĵ0vC)11: X=TjA1&hTЯJ؉Uso/s1bU.'Js,[bZ!Ɣ:0#;{cmFW ˽FW܋]+P̕Ju%m7M]ɇԮ0O$օX*|[AR%l,{+!x(JQHr*p%RSJ1H='qBVܰU{BYCvMO d1P $Ӊuk c +U,N)VX$bAr +IXr +QPn g1Od屍=k/Un +n)옼=kcP.nS +ql^n(vWThNXXi, ݤ+ +n*Ie;Z +A,S +15Qpo(Q@RU +MR @J.Db %!hJ.$H#,cǣrǣJSئ]H.@\2QѨ ), pxQ4㑘{oΏ*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 index 2948f81..fe6083c 100644 --- a/core/modules/responsive_preview/js/responsive-preview.js +++ b/core/modules/responsive_preview/js/responsive-preview.js @@ -3,11 +3,23 @@ * Provides a component that previews the page in various device dimensions. */ -(function ($, Backbone, Drupal, drupalSettings) { +(function ($, Backbone, Drupal, drupalSettings, undefined) { "use strict"; -var previewModel, tabModel, appView, tabView, blockView, keyboardView; +var options = $.extend({ + gutter: 60, + // The width of the device border around the iframe. This value is critical + // to determine the size and placement of the preview iframe container, + // therefore it must be defined here instead of in the CSS file. + bleed: 30, + strings: { + close: Drupal.t('close'), + orientation: Drupal.t('Change orientation'), + portrait: Drupal.t('portrait'), + landscape: Drupal.t('landscape') + } +}, drupalSettings.responsivePreview || {}); var currentPath; @@ -16,7 +28,7 @@ var currentPath; */ Drupal.behaviors.responsivePreview = { attach: function (context) { - // once() returns a jQuery set. It will be empty if no unprocessed + // jQuery.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'); @@ -25,7 +37,6 @@ Drupal.behaviors.responsivePreview = { currentPath = currentPath || drupalSettings.currentPath; if ($body.length) { - var options = $.extend(this.defaults, drupalSettings.responsivePreview || {}); // 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 @@ -33,14 +44,14 @@ Drupal.behaviors.responsivePreview = { // following code will have no effect. $(window.document.body).once('responsive-preview'); - var envModel = new Drupal.responsivePreview.models.EnvironmentModel({ + var envModel = Drupal.responsivePreview.models.envModel = new Drupal.responsivePreview.EnvironmentModel({ dir: document.getElementsByTagName('html')[0].getAttribute('dir') }); - tabModel = new Drupal.responsivePreview.models.TabStateModel(); - previewModel = new Drupal.responsivePreview.models.PreviewStateModel(); + var tabModel = Drupal.responsivePreview.models.tabModel = new Drupal.responsivePreview.TabStateModel(); + var previewModel = Drupal.responsivePreview.models.previewModel = new Drupal.responsivePreview.PreviewStateModel(); // Manages the PreviewView. - appView = new Drupal.responsivePreview.views.AppView({ + Drupal.responsivePreview.views.appView = new Drupal.responsivePreview.AppView({ // The previewView model. model: previewModel, envModel: envModel, @@ -54,7 +65,7 @@ Drupal.behaviors.responsivePreview = { // The toolbar tab view. var $tab = $(context).find('#responsive-preview-toolbar-tab'); if ($tab.length > 0) { - tabView = new Drupal.responsivePreview.views.TabView({ + Drupal.responsivePreview.views.tabView = new Drupal.responsivePreview.TabView({ el: $tab.get(), model: previewModel, tabModel: tabModel, @@ -68,7 +79,7 @@ Drupal.behaviors.responsivePreview = { // The control block view. var $block = $(context).find('#block-responsive-preview-controls'); if ($block.length > 0) { - blockView = new Drupal.responsivePreview.views.BlockView({ + Drupal.responsivePreview.views.blockView = new Drupal.responsivePreview.BlockView({ el: $block.get(), model: previewModel, envModel: envModel, @@ -80,7 +91,7 @@ Drupal.behaviors.responsivePreview = { } // Keyboard controls view. - keyboardView = new Drupal.responsivePreview.views.KeyboardView({ + Drupal.responsivePreview.views.keyboardView = new Drupal.responsivePreview.KeyboardView({ el: $block.get(), model: previewModel }); @@ -103,6 +114,10 @@ Drupal.behaviors.responsivePreview = { if (event.keyCode === 27) { previewModel.set('isActive', false); } + }) + // Close the preview if the overlay is opened. + .on('drupalOverlayOpen.responsivepreview', function () { + previewModel.set('isActive', false); }); // Allow other scripts to respond to responsive preview mode changes. @@ -133,664 +148,662 @@ Drupal.behaviors.responsivePreview = { } }, detach: function (context) { + /** + * Loops through object properties; applies a callback function. + */ + function looper (obj, iterator) { + for (var prop in obj) { + if (obj.hasOwnProperty(prop)) { + iterator.call(null, prop, obj[prop]); + } + } + } // Remove listeners on the window and document. $(window).add(document).off('.responsivepreview'); // Set the preview to an inactive state. - previewModel.set('isActive', false); - // Remove listener on the tabModel. - tabModel.off(); - // Remove any views - (appView && appView.remove()); - (blockView && blockView.remove()); - (tabView && tabView.remove()); - (keyboardView && keyboardView.remove()); - // Set the scope variables to an undefined value to remove references to - // the views. - previewModel = tabModel = appView = blockView = tabView = keyboardView = this.undef; - }, - defaults: { - gutter: 60, - // The width of the device border around the iframe. This value is critical - // to determine the size and placement of the preview iframe container, - // therefore it must be defined here instead of in the CSS file. - bleed: 30, - strings: { - close: Drupal.t('close'), - orientation: Drupal.t('Change orientation'), - portrait: Drupal.t('portrait'), - landscape: Drupal.t('landscape') - } + Drupal.responsivePreview.models.previewModel.set('isActive', false); + // Remove and delete the views. + looper(Drupal.responsivePreview.views, function (label, view) { + Drupal.responsivePreview.views[label].remove(); + Drupal.responsivePreview.views[label] = undefined; + }); + // Remove listeners and delete the models. + looper(Drupal.responsivePreview.models, function (label, model) { + Drupal.responsivePreview.models[label].off(); + Drupal.responsivePreview.models[label] = undefined; + }); } }; Drupal.responsivePreview = Drupal.responsivePreview || { - models: { + + // Storage for view instances. + views: {}, + + // Storage for model instances. + models: {}, + + /** + * Backbone Model for the environment in which the Responsive Preview operates. + */ + EnvironmentModel: Backbone.Model.extend({ + defaults: { + // The viewport width, within which the preview will have to fit. + viewportWidth: null, + // Text direction of the document, affects some positioning. + dir: 'ltr', + // Viewport offset values. + offsets: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + } + }), + + /** + * Backbone Model for the Responsive Preview toolbar tab state. + */ + TabStateModel: Backbone.Model.extend({ + defaults: { + // The state of toolbar list of available device previews. + isDeviceListOpen: false + } + }), + + /** + * Backbone Model for the Responsive Preview preview state. + */ + PreviewStateModel: Backbone.Model.extend({ + defaults: { + // The state of the preview. + isActive: false, + // Indicates whether the preview iframe has been built. + isBuilt: false, + // Indicates whether the device is portrait (false) or landscape (true). + isRotated: false, + // The number of devices that fit the current viewport (i.e. previewable). + fittingDeviceCount: 0, + // Currently selected device link. + activeDevice: null, + // Dimensions of the currently selected device to preview. + dimensions: { + // The width of the device to preview. + width: null, + // The height of the device to preview. + height: null, + // The dots per pixel of the device to preview. + dppx: null + } + } + }), + + /** + * Manages the PreviewView. + */ + AppView: Backbone.View.extend({ + /** - * Backbone Model for the environment in which the Responsive Preview operates. + * {@inheritdoc} */ - EnvironmentModel: Backbone.Model.extend({ - defaults: { - // The viewport width, within which the preview will have to fit. - viewportWidth: null, - // Text direction of the document, affects some positioning. - dir: 'ltr', - // Viewport offset values. - offsets: { - top: 0, - right: 0, - bottom: 0, - left: 0 - } - } - }), + initialize: function () { + this.envModel = this.options.envModel; + // Listen to changes on the previewModel. + this.model.on('change:isActive', this.render, this); + }, /** - * Backbone Model for the Responsive Preview toolbar tab state. + * {@inheritdoc} */ - TabStateModel: Backbone.Model.extend({ - defaults: { - // The state of toolbar list of available device previews. - isDeviceListOpen: false + render: function (previewModel, isActive, options) { + // The preview container view. + if (isActive && !this.previewView) { + // Holds the Backbone View of the preview. This view is created and destroyed + // when the preview is enabled or disabled respectively. + this.previewView = new Drupal.responsivePreview.PreviewView({ + el: Drupal.theme('responsivePreviewContainer'), + // The previewView model. + model: this.model, + envModel: this.envModel, + // Gutter size around preview frame. + gutter: this.options.gutter, + // Preview device frame width. + bleed: this.options.bleed, + strings: this.options.strings + }); + } + else if (!isActive && this.previewView) { + this.previewView.remove(); + delete this.previewView; } - }), + }, /** - * Backbone Model for the Responsive Preview preview state. + * {@inheritdoc} */ - PreviewStateModel: Backbone.Model.extend({ - defaults: { - // The state of the preview. - isActive: false, - // Indicates whether the preview iframe has been built. - isBuilt: false, - // Indicates whether the device is portrait (false) or landscape (true). - isRotated: false, - // The number of devices that fit the current viewport (i.e. previewable). - fittingDeviceCount: 0, - // Currently selected device link. - activeDevice: null, - // Dimensions of the currently selected device to preview. - dimensions: { - // The width of the device to preview. - width: null, - // The height of the device to preview. - height: null, - // The dots per pixel of the device to preview. - dppx: null - } + remove: function () { + // Remove the previewView if it exists. + (this.previewView && this.previewView.remove()); + // Call the parent remove method on this view. + Backbone.View.prototype.remove.call(this); + } + }), + + /** + * Handles responsive preview toolbar tab interactions. + */ + TabView: Backbone.View.extend({ + + events: { + 'click .trigger': 'toggleDeviceList', + 'mouseleave': 'toggleDeviceList', + }, + + /** + * {@inheritdoc} + */ + initialize: function () { + this.gutter = this.options.gutter; + this.bleed = this.options.bleed; + this.tabModel = this.options.tabModel; + this.envModel = this.options.envModel; + + // The selectDevice function is declared outside of the view because it is + // shared among views. It must be bound to this for the correct context + // to obtain. + this.$el.on('click.responsivepreview', '.device', $.proxy(selectDevice, this)); + + this.model.on('change:isActive change:dimensions change:activeDevice change:fittingDeviceCount', this.render, this); + + this.tabModel.on('change:isDeviceListOpen', this.render, this); + + this.envModel.on('change:viewportWidth', updateDeviceList, this); + this.envModel.on('change:viewportWidth', this.correctDeviceListEdgeCollision, this); + }, + + /** + * {@inheritdoc} + */ + render: function () { + var $deviceLink = $(this.model.get('activeDevice')); + var name = $deviceLink.data('responsive-preview-name'); + var isActive = this.model.get('isActive'); + var isDeviceListOpen = this.tabModel.get('isDeviceListOpen'); + this.$el + // Render the visibility of the toolbar tab. + .toggle(this.model.get('fittingDeviceCount') > 0) + // Toggle the display of the device list. + .toggleClass('open', isDeviceListOpen); + + // Render the state of the toolbar tab button. + this.$el + .find('> button') + .toggleClass('active', isActive) + .attr('aria-pressed', isActive); + + // Clean the active class from the device list. + this.$el + .find('.device.active') + .removeClass('active'); + + this.$el + .find('[data-responsive-preview-name="' + name + '"]') + .toggleClass('active', isActive); + // When the preview is active, a class on the body is necessary to impose + // styling to aid in the display of the preview element. + $('body').toggleClass('responsive-preview-active', isActive); + // The list of devices might render outside the window. + if (isDeviceListOpen) { + this.correctDeviceListEdgeCollision(); } - }) - }, - views: { + return this; + }, + /** - * Manages the PreviewView. + * Toggles the list of devices available to preview from the toolbar tab. + * + * @param jQuery.Event event */ - AppView: Backbone.View.extend({ - - /** - * {@inheritdoc} - */ - initialize: function () { - this.envModel = this.options.envModel; - // Listen to changes on the previewModel. - this.model.on('change:isActive', this.render, this); - }, - - /** - * {@inheritdoc} - */ - render: function (previewModel, isActive, options) { - // The preview container view. - if (isActive && !this.previewView) { - // Holds the Backbone View of the preview. This view is created and destroyed - // when the preview is enabled or disabled respectively. - this.previewView = new Drupal.responsivePreview.views.PreviewView({ - el: Drupal.theme('responsivePreviewContainer'), - // The previewView model. - model: this.model, - envModel: this.envModel, - // Gutter size around preview frame. - gutter: this.options.gutter, - // Preview device frame width. - bleed: this.options.bleed, - strings: this.options.strings - }); - } - else if (!isActive && this.previewView) { - this.previewView.remove(); - delete this.previewView; - } - }, - - /** - * {@inheritdoc} - */ - remove: function () { - // Remove the previewView if it exists. - (this.previewView && this.previewView.remove()); - // Call the parent remove method on this view. - Backbone.View.prototype.remove.call(this); + toggleDeviceList: function (event) { + // Force the options list closed on mouseleave. + if (event.type === 'mouseleave') { + this.tabModel.set('isDeviceListOpen', false); } - }), + else { + this.tabModel.set('isDeviceListOpen', !this.tabModel.get('isDeviceListOpen')); + } + + event.preventDefault(); + event.stopPropagation(); + }, /** - * Handles responsive preview toolbar tab interactions. + * Model change handler; corrects possible device list window edge collision. */ - TabView: Backbone.View.extend({ - - events: { - 'click': 'toggleDeviceList', - 'mouseleave': 'toggleDeviceList', - }, - - /** - * {@inheritdoc} - */ - initialize: function () { - this.gutter = this.options.gutter; - this.bleed = this.options.bleed; - this.tabModel = this.options.tabModel; - this.envModel = this.options.envModel; - - // The selectDevice function is declared outside of the view because it is - // shared among views. It must be bound to this for the correct context - // to obtain. - this.$el.on('click.responsivepreview', '.device', $.proxy(selectDevice, this)); - - this.model.on('change:isActive change:dimensions change:activeDevice change:fittingDeviceCount', this.render, this); - - this.tabModel.on('change:isDeviceListOpen', this.render, this); - - this.envModel.on('change:viewportWidth', updateDeviceList, this); - this.envModel.on('change:viewportWidth', this.correctDeviceListEdgeCollision, this); - }, - - /** - * {@inheritdoc} - */ - render: function () { - var $deviceLink = $(this.model.get('activeDevice')); - var name = $deviceLink.data('responsive-preview-name'); - var isActive = this.model.get('isActive'); - var isDeviceListOpen = this.tabModel.get('isDeviceListOpen'); - this.$el - // Render the visibility of the toolbar tab. - .toggle(this.model.get('fittingDeviceCount') > 0) - // Toggle the display of the device list. - .toggleClass('open', isDeviceListOpen); - - // Render the state of the toolbar tab button. - this.$el - .find('> button') - .toggleClass('active', isActive) - .attr('aria-pressed', isActive); - - // Clean the active class from the device list. - this.$el - .find('.device.active') - .removeClass('active'); - - this.$el - .find('[data-responsive-preview-name="' + name + '"]') - .toggleClass('active', isActive); - // When the preview is active, a class on the body is necessary to impose - // styling to aid in the display of the preview element. - $('body').toggleClass('responsive-preview-active', isActive); - // The list of devices might render outside the window. - if (isDeviceListOpen) { - this.correctDeviceListEdgeCollision(); - } - return this; - }, - - /** - * Toggles the list of devices available to preview from the toolbar tab. - * - * @param Object event - * jQuery Event object. - */ - toggleDeviceList: function (event) { - // Force the options list closed on mouseleave. - if (event.type === 'mouseleave') { - this.tabModel.set('isDeviceListOpen', false); - } - else { - this.tabModel.set('isDeviceListOpen', !this.tabModel.get('isDeviceListOpen')); - } + correctDeviceListEdgeCollision: function () { + // The position of the dropdown depends on the language direction. + var dir = this.envModel.get('dir'); + var edge = (dir === 'rtl') ? 'left' : 'right'; + this.$el + .find('.item-list') + .position({ + 'my': edge +' top', + 'at': edge + ' bottom', + 'of': this.$el, + 'collision': 'flip fit' + }); + } + }), - event.preventDefault(); - event.stopPropagation(); - }, - - /** - * Model change handler; corrects possible device list window edge collision. - */ - correctDeviceListEdgeCollision: function () { - // The position of the dropdown depends on the language direction. - var dir = this.envModel.get('dir'); - var edge = (dir === 'rtl') ? 'left' : 'right'; - this.$el - .find('.item-list') - .position({ - 'my': edge +' top', - 'at': edge + ' bottom', - 'of': this.$el, - 'collision': 'flip fit' - }); - } - }), + /** + * Handles responsive preview control block interactions. + */ + BlockView: Backbone.View.extend({ /** - * Handles responsive preview control block interactions. + * {@inheritdoc} */ - BlockView: Backbone.View.extend({ - - /** - * {@inheritdoc} - */ - initialize: function () { - this.gutter = this.options.gutter; - this.bleed = this.options.bleed; - this.envModel = this.options.envModel; - - // The selectDevice function is declared outside of the view because it is - // shared among views. It must be bound to this for the correct context - // to obtain. - this.$el.on('click.responsivepreview', '.device', $.proxy(selectDevice, this)); - - this.model.on('change:isActive change:dimensions change:activeDevice change:fittingDeviceCount', this.render, this); - - this.envModel.on('change:viewportWidth', updateDeviceList, this); - }, - - /** - * {@inheritdoc} - */ - render: function () { - var $deviceLink = $(this.model.get('activeDevice')); - var name = $deviceLink.data('responsive-preview-name'); - var isActive = this.model.get('isActive'); - this.$el - // Render the visibility of the toolbar block. - .toggle(this.model.get('fittingDeviceCount') > 0) - .find('.device.active') - .removeClass('active'); - - this.$el - .find('[data-responsive-preview-name="' + name + '"]') - .addClass('active'); - // When the preview is active, a class on the body is necessary to impose - // styling to aid in the display of the preview element. - $('body').toggleClass('responsive-preview-active', isActive); - return this; - } - }), + initialize: function () { + this.gutter = this.options.gutter; + this.bleed = this.options.bleed; + this.envModel = this.options.envModel; + + // The selectDevice function is declared outside of the view because it is + // shared among views. It must be bound to this for the correct context + // to obtain. + this.$el.on('click.responsivepreview', '.device', $.proxy(selectDevice, this)); + + this.model.on('change:isActive change:dimensions change:activeDevice change:fittingDeviceCount', this.render, this); + + this.envModel.on('change:viewportWidth', updateDeviceList, this); + }, /** - * Handles keyboard input. + * {@inheritdoc} */ - KeyboardView: Backbone.View.extend({ - - /* - * {@inheritdoc} - */ - initialize: function () { - $(document).on('keyup.responsivepreview', _.bind(this.onKeypress, this)); - }, - - /** - * Responds to esc key press events. - * - * @param jQuery.Event event - */ - onKeypress: function (event) { - if (event.keyCode === 27) { - this.model.set('isActive', false); - } - }, - - /** - * Removes a listener on the document; calls the standard Backbone remove. - */ - remove: function () { - // Unbind the keyup listener. - $(document).off('keyup.responsivepreview'); - // Call the standard remove method on this. - Backbone.View.prototype.remove.call(this); + render: function () { + var $deviceLink = $(this.model.get('activeDevice')); + var name = $deviceLink.data('responsive-preview-name'); + var isActive = this.model.get('isActive'); + this.$el + // Render the visibility of the toolbar block. + .toggle(this.model.get('fittingDeviceCount') > 0) + .find('.device.active') + .removeClass('active'); + + this.$el + .find('[data-responsive-preview-name="' + name + '"]') + .addClass('active'); + // When the preview is active, a class on the body is necessary to impose + // styling to aid in the display of the preview element. + $('body').toggleClass('responsive-preview-active', isActive); + return this; + } + }), + + /** + * Handles keyboard input. + */ + KeyboardView: Backbone.View.extend({ + + /* + * {@inheritdoc} + */ + initialize: function () { + $(document).on('keyup.responsivepreview', _.bind(this.onKeypress, this)); + }, + + /** + * Responds to esc key press events. + * + * @param jQuery.Event event + */ + onKeypress: function (event) { + if (event.keyCode === 27) { + this.model.set('isActive', false); } - }), + }, /** - * Handles the responsive preview element interactions. + * Removes a listener on the document; calls the standard Backbone remove. */ - PreviewView: Backbone.View.extend({ - - events: { - 'click #responsive-preview-close': 'onClose', - 'click #responsive-preview-orientation': 'onRotate' - }, - - /** - * {@inheritdoc} - */ - initialize: function () { - this.gutter = this.options.gutter; - this.bleed = this.options.bleed; - this.strings = this.options.strings; - this.envModel = this.options.envModel; - - this.model.on('change:isRotated change:dimensions change:activeDevice', this.render, this); - - // Recalculate the size of the preview container when the window resizes. - this.envModel.on('change:viewportWidth change:offsets', this.render, this); - - // Build the preview. - this._build(); - - // Call an initial render. - this.render(); - }, - - /** - * {@inheritdoc} - */ - render: function () { - // Refresh the preview. - this._refresh(); - Drupal.displace(); - - // Render the state of the preview. - var that = this; - // Wrap the call in a setTimeout so that it invokes in the next compute - // cycle, causing the CSS animations to render in the first pass. - window.setTimeout(function () { - that.$el.toggleClass('active', that.model.get('isActive')); - }, 0); + remove: function () { + // Unbind the keyup listener. + $(document).off('keyup.responsivepreview'); + // Call the standard remove method on this. + Backbone.View.prototype.remove.call(this); + } + }), - return this; - }, + /** + * Handles the responsive preview element interactions. + */ + PreviewView: Backbone.View.extend({ - /** - * Closes the preview. - * - * @param Object event - * A jQuery event object. - */ - onClose: function (event) { - this.model.set('isActive', false); - }, - - /** - * Responds to rotation button presses. - * - * @param Object event - * A jQuery event object. - */ - onRotate: function (event) { - this.model.set('isRotated', !this.model.get('isRotated')); - }, - - /** - * Builds the preview iframe. - */ - _build: function () { - var offsets = this.envModel.get('offsets'); - var $frameContainer = $(Drupal.theme('responsivePreviewFrameContainer')) - .find('#responsive-preview-close span') - .text(this.strings.close) - .end() - .find('#responsive-preview-orientation span') - .text(this.strings.orientation) - .end() - // The padding around the frame must be known in order to position it - // correctly, so the style property is defined in JavaScript rather than - // CSS. - .css('padding', this.bleed); - // Attach the iframe that will hold the preview. - var $frame = $(Drupal.theme('responsivePreviewFrame')) - .attr({ - 'data-loading': true, - src: drupalSettings.basePath + Drupal.encodePath(currentPath), - width: '100%', - height: '100%' - }) - // Load the current page URI into the preview iframe. - .on('load.responsivepreview', $.proxy(this._refresh, this)) - // Add the frame to the preview container. - .appendTo($frameContainer); - // Insert the container into the DOM. - this.$el - .css({ - 'top': offsets.top, - 'right': offsets.right, - 'left': offsets.left - }) - // Apend the frame container. - .append($frameContainer) - // Append the container to the body to initialize the iframe document. - .appendTo('body'); - // Mark the preview element processed. - this.model.set('isBuilt', true); - }, - - /** - * Refreshes the preview based on the current state (device & viewport width). - */ - _refresh: function () { - var isRotated = this.model.get('isRotated'); - var $deviceLink = $(this.model.get('activeDevice')); - var $container = this.$el.find('#responsive-preview-frame-container'); - var $frame = $container.find('> iframe'); - var offsets = this.envModel.get('offsets'); - - // Get the static state. - var edge = (this.envModel.get('dir') === 'rtl') ? 'right' : 'left'; - var minGutter = this.gutter; - - // Get current (dynamic) state. - var dimensions = this.model.get('dimensions'); - var isRotated = this.model.get('isRotated'); - var viewportWidth = this.envModel.get('viewportWidth') - (offsets.left + offsets.right); - - // Calculate preview width & height. If the preview is rotated, swap width - // and height. - var displayWidth = dimensions[(isRotated) ? 'height' : 'width']; - var displayHeight = dimensions[(isRotated) ? 'width' : 'height']; - var width = displayWidth / dimensions.dppx; - var height = displayHeight / dimensions.dppx; - - // Get the container padding and border width for the left and right. - var bleed = this.bleed; - var spread = width + (bleed * 2); - - // Calculate gutter. - var gutterPercent = (1 - (spread / viewportWidth)) / 2; - var gutter = gutterPercent * viewportWidth; - gutter = (gutter < minGutter) ? minGutter : gutter; - - // The preview width plus gutters must fit within the viewport width. - width = (viewportWidth - (gutter * 2) < spread) ? viewportWidth - (gutter * 2) - (bleed * 2) : width; - - // Updated the state of the rotated icon. - this.$el.find('.control.orientation').toggleClass('rotated', isRotated); - - // Resize & reposition the iframe. - this.$el.css({ + events: { + 'click #responsive-preview-close': 'onClose', + 'click #responsive-preview-orientation': 'onRotate' + }, + + /** + * {@inheritdoc} + */ + initialize: function () { + this.gutter = this.options.gutter; + this.bleed = this.options.bleed; + this.strings = this.options.strings; + this.envModel = this.options.envModel; + + this.model.on('change:isRotated change:dimensions change:activeDevice', this.render, this); + + // Recalculate the size of the preview container when the window resizes. + this.envModel.on('change:viewportWidth change:offsets', this.render, this); + + // Build the preview. + this._build(); + + // Call an initial render. + this.render(); + }, + + /** + * {@inheritdoc} + */ + render: function () { + // Refresh the preview. + this._refresh(); + Drupal.displace(); + + // Render the state of the preview. + var that = this; + // Wrap the call in a setTimeout so that it invokes in the next compute + // cycle, causing the CSS animations to render in the first pass. + window.setTimeout(function () { + that.$el.toggleClass('active', that.model.get('isActive')); + }, 0); + + return this; + }, + + /** + * Closes the preview. + * + * @param jQuery.Event event + */ + onClose: function (event) { + this.model.set('isActive', false); + }, + + /** + * Responds to rotation button presses. + * + * @param jQuery.Event event + */ + onRotate: function (event) { + this.model.set('isRotated', !this.model.get('isRotated')); + }, + + /** + * Builds the preview iframe. + */ + _build: function () { + var offsets = this.envModel.get('offsets'); + var $frameContainer = $(Drupal.theme('responsivePreviewFrameContainer')) + .find('#responsive-preview-close span') + .text(this.strings.close) + .end() + .find('#responsive-preview-orientation span') + .text(this.strings.orientation) + .end() + // The padding around the frame must be known in order to position it + // correctly, so the style property is defined in JavaScript rather than + // CSS. + .css('padding', this.bleed); + // Attach the iframe that will hold the preview. + var $frame = $(Drupal.theme('responsivePreviewFrame')) + .attr({ + 'data-loading': true, + src: drupalSettings.basePath + Drupal.encodePath(currentPath), + width: '100%', + height: '100%' + }) + // Load the current page URI into the preview iframe. + .on('load.responsivepreview', $.proxy(this._refresh, this)) + // Add the frame to the preview container. + .appendTo($frameContainer); + // Insert the container into the DOM. + this.$el + .css({ 'top': offsets.top, 'right': offsets.right, 'left': offsets.left + }) + // Apend the frame container. + .append($frameContainer) + // Append the container to the body to initialize the iframe document. + .appendTo('body'); + // Mark the preview element processed. + this.model.set('isBuilt', true); + }, + + /** + * Refreshes the preview based on the current state (device & viewport width). + */ + _refresh: function () { + var isRotated = this.model.get('isRotated'); + var $deviceLink = $(this.model.get('activeDevice')); + var $container = this.$el.find('#responsive-preview-frame-container'); + var $frame = $container.find('> iframe'); + var offsets = this.envModel.get('offsets'); + + // Get the static state. + var edge = (this.envModel.get('dir') === 'rtl') ? 'right' : 'left'; + var minGutter = this.gutter; + + // Get current (dynamic) state. + var dimensions = this.model.get('dimensions'); + var isRotated = this.model.get('isRotated'); + var viewportWidth = this.envModel.get('viewportWidth') - (offsets.left + offsets.right); + + // Calculate preview width & height. If the preview is rotated, swap width + // and height. + var displayWidth = dimensions[(isRotated) ? 'height' : 'width']; + var displayHeight = dimensions[(isRotated) ? 'width' : 'height']; + var width = displayWidth / dimensions.dppx; + var height = displayHeight / dimensions.dppx; + + // Get the container padding and border width for the left and right. + var bleed = this.bleed; + var spread = width + (bleed * 2); + + // Calculate gutter. + var gutterPercent = (1 - (spread / viewportWidth)) / 2; + var gutter = gutterPercent * viewportWidth; + gutter = (gutter < minGutter) ? minGutter : gutter; + + // The preview width plus gutters must fit within the viewport width. + width = (viewportWidth - (gutter * 2) < spread) ? viewportWidth - (gutter * 2) - (bleed * 2) : width; + + // Updated the state of the rotated icon. + this.$el.find('.control.orientation').toggleClass('rotated', isRotated); + + // Resize & reposition the iframe. + this.$el.css({ + 'top': offsets.top, + 'right': offsets.right, + 'left': offsets.left + }); + var position = {}; + position[edge] = (gutter > minGutter) ? gutter : minGutter; // Depends on text direction. + $frame + .css({ + width: width, + height: height }); - var position = {}; - position[edge] = (gutter > minGutter) ? gutter : minGutter; // Depends on text direction. - $frame - .css({ - width: width, - height: height - }); - $container - .css(position); - - // Scale if not responsive. - this._scaleIfNotResponsive(); - - // Update the device label. - $container.find('.device-label').text(Drupal.t('@label (@widthpx by @heightpx, @dpidppx, @orientation)', { - '@label': $deviceLink.text(), - '@width': Math.ceil(displayWidth), - '@height': Math.ceil(displayHeight), - '@dpi': dimensions.dppx, - '@orientation': (isRotated) ? this.strings.landscape : this.strings.portrait - })); - - // Update the positioning of the modal background. - this.$el.find('.modal-background').css(offsets); - }, - - /** - * Applies scaling in order to better approximate content display on a device. - */ - _scaleIfNotResponsive: function () { - var scalingCSS = this._calculateScalingCSS(); - if (scalingCSS === false) { - return; - } + $container + .css(position); - // Step 0: find DOM nodes we'll need to modify. - var $frame = this.$el.find('#responsive-preview-frame'); - var $html = $($frame[0].contentDocument || $frame[0].contentWindow.document).find('html'); - - // Step 1: When scaling (as we're about to do), the background (color and - // image) doesn't scale along. Fortunately, we can fix things in case of - // background color. - // @todo: figure out a work-around for background images, or somehow - // document this explicitly. - function isTransparent (color) { - // TRICKY: edge case for Firefox' "transparent" here; this is a - // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724 - return (color === 'rgba(0, 0, 0, 0)' || color === 'transparent'); - } - var htmlBgColor = $html.css('background-color'); - var bodyBgColor = $html.find('body').css('background-color'); - if (!isTransparent(htmlBgColor) || !isTransparent(bodyBgColor)) { - var bgColor = isTransparent(htmlBgColor) ? bodyBgColor : htmlBgColor; - $frame.css('background-color', bgColor); - } + // Scale if not responsive. + this._scaleIfNotResponsive(); - // Step 2: apply scaling. - $html.css(scalingCSS); - }, - - /** - * Calculates scaling based on device dimensions and . - * - * Websites that don't indicate via that their width - * is identical to the device width will be rendered at a larger size: at the - * layout viewport's default width. This width exceeds the visual viewport on - * the device, and causes it to scale it down. - * - * This function checks whether the underlying web page is responsive, and if - * it's not, then it will calculate a CSS scaling transformation, to closely - * approximate how an actual mobile device would render the web page. - * - * We assume all mobile devices' layout viewport's default width is 980px. It - * is the value used on all iOS and Android >=4.0 devices. - * - * Related reading: - * - http://www.quirksmode.org/mobile/viewports.html - * - http://www.quirksmode.org/mobile/viewports2.html - * - https://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html - * - http://tripleodeon.com/2011/12/first-understand-your-screen/ - * - http://tripleodeon.com/wp-content/uploads/2011/12/table.html?r=android40window.innerw&c=980 - */ - _calculateScalingCSS: function () { - var isRotated = this.model.get('isRotated'); - var settings = this._parseViewportMetaTag(); - var defaultLayoutWidth = 980, initialScale = 1; - var layoutViewportWidth, layoutViewportHeight; - var visualViewPortWidth; // The visual viewport width === the preview width. - - if (settings.width) { - if (settings.width === 'device-width') { - // Don't scale if the page is marked to be as wide as the device. - return false; - } - else { - layoutViewportWidth = parseInt(settings.width, 10); - } + // Update the device label. + $container.find('.device-label').text(Drupal.t('@label (@widthpx by @heightpx, @dpidppx, @orientation)', { + '@label': $deviceLink.text(), + '@width': Math.ceil(displayWidth), + '@height': Math.ceil(displayHeight), + '@dpi': dimensions.dppx, + '@orientation': (isRotated) ? this.strings.landscape : this.strings.portrait + })); + + // Update the positioning of the modal background. + this.$el.find('.modal-background').css(offsets); + }, + + /** + * Applies scaling in order to better approximate content display on a device. + */ + _scaleIfNotResponsive: function () { + var scalingCSS = this._calculateScalingCSS(); + if (scalingCSS === false) { + return; + } + + // Step 0: find DOM nodes we'll need to modify. + var $frame = this.$el.find('#responsive-preview-frame'); + var $html = $($frame[0].contentDocument || $frame[0].contentWindow.document).find('html'); + + // Step 1: When scaling (as we're about to do), the background (color and + // image) doesn't scale along. Fortunately, we can fix things in case of + // background color. + // @todo: figure out a work-around for background images, or somehow + // document this explicitly. + function isTransparent (color) { + // TRICKY: edge case for Firefox' "transparent" here; this is a + // browser bug: https://bugzilla.mozilla.org/show_bug.cgi?id=635724 + return (color === 'rgba(0, 0, 0, 0)' || color === 'transparent'); + } + var htmlBgColor = $html.css('background-color'); + var bodyBgColor = $html.find('body').css('background-color'); + if (!isTransparent(htmlBgColor) || !isTransparent(bodyBgColor)) { + var bgColor = isTransparent(htmlBgColor) ? bodyBgColor : htmlBgColor; + $frame.css('background-color', bgColor); + } + + // Step 2: apply scaling. + $html.css(scalingCSS); + }, + + /** + * Calculates scaling based on device dimensions and . + * + * Websites that don't indicate via that their width + * is identical to the device width will be rendered at a larger size: at the + * layout viewport's default width. This width exceeds the visual viewport on + * the device, and causes it to scale it down. + * + * This function checks whether the underlying web page is responsive, and if + * it's not, then it will calculate a CSS scaling transformation, to closely + * approximate how an actual mobile device would render the web page. + * + * We assume all mobile devices' layout viewport's default width is 980px. It + * is the value used on all iOS and Android >=4.0 devices. + * + * Related reading: + * - http://www.quirksmode.org/mobile/viewports.html + * - http://www.quirksmode.org/mobile/viewports2.html + * - https://developer.apple.com/library/safari/#documentation/AppleApplications/Reference/SafariWebContent/UsingtheViewport/UsingtheViewport.html + * - http://tripleodeon.com/2011/12/first-understand-your-screen/ + * - http://tripleodeon.com/wp-content/uploads/2011/12/table.html?r=android40window.innerw&c=980 + */ + _calculateScalingCSS: function () { + var isRotated = this.model.get('isRotated'); + var settings = this._parseViewportMetaTag(); + var defaultLayoutWidth = 980, initialScale = 1; + var layoutViewportWidth, layoutViewportHeight; + var visualViewPortWidth; // The visual viewport width === the preview width. + + if (settings.width) { + if (settings.width === 'device-width') { + // Don't scale if the page is marked to be as wide as the device. + return false; } else { - layoutViewportWidth = defaultLayoutWidth; + layoutViewportWidth = parseInt(settings.width, 10); } + } + else { + layoutViewportWidth = defaultLayoutWidth; + } - if (settings.height && settings.height !== 'device-height') { - layoutViewportHeight = parseInt(settings.height, 10); - } + if (settings.height && settings.height !== 'device-height') { + layoutViewportHeight = parseInt(settings.height, 10); + } - if (settings['initial-scale']) { - initialScale = parseFloat(settings['initial-scale'], 10); - if (initialScale < 1) { - layoutViewportWidth = defaultLayoutWidth; - } + if (settings['initial-scale']) { + initialScale = parseFloat(settings['initial-scale'], 10); + if (initialScale < 1) { + layoutViewportWidth = defaultLayoutWidth; } + } - // Calculate the scale, prevent excesses (ensure the (0.25, 1) range). - var dimensions = this.model.get('dimensions'); - // If the preview is rotated, width and height are swapped. - visualViewPortWidth = dimensions[(isRotated) ? 'height' : 'width'] / dimensions.dppx; - var scale = initialScale * (100 / layoutViewportWidth) * (visualViewPortWidth / 100); - scale = Math.min(scale, 1); - scale = Math.max(scale, 0.25); - - var transform = "scale(" + scale + ")"; - var origin = "0 0"; - return { - 'min-width': layoutViewportWidth + 'px', - 'min-height': layoutViewportHeight + 'px', - '-webkit-transform': transform, - '-ms-transform': transform, - 'transform': transform, - '-webkit-transform-origin': origin, - '-ms-transform-origin': origin, - 'transform-origin': origin - }; - }, - - /** - * Parses tag's "content" attribute, if any. - * - * Parses something like this: - * - * into this: - * { - * width: 'device-width', - * initial-scale: '1', - * maximum-scale: '5', - * minimum-scale: '1', - * user-scalable: 'yes' - * } - * - * @return Object - * Parsed viewport settings, or {}. - */ - _parseViewportMetaTag: function () { - var settings = {}; - var $viewportMeta = $(document).find('meta[name=viewport][content]'); - if ($viewportMeta.length > 0) { - $viewportMeta - .attr('content') - // Reduce multiple parts of whitespace to a single space. - .replace(/\s+/g, '') - // Split on comma (which separates the different settings). - .split(',') - .map(function (setting) { - setting = setting.split('='); - settings[setting[0]] = setting[1]; - }); - } - return settings; + // Calculate the scale, prevent excesses (ensure the (0.25, 1) range). + var dimensions = this.model.get('dimensions'); + // If the preview is rotated, width and height are swapped. + visualViewPortWidth = dimensions[(isRotated) ? 'height' : 'width'] / dimensions.dppx; + var scale = initialScale * (100 / layoutViewportWidth) * (visualViewPortWidth / 100); + scale = Math.min(scale, 1); + scale = Math.max(scale, 0.25); + + var transform = "scale(" + scale + ")"; + var origin = "0 0"; + return { + 'min-width': layoutViewportWidth + 'px', + 'min-height': layoutViewportHeight + 'px', + '-webkit-transform': transform, + '-ms-transform': transform, + 'transform': transform, + '-webkit-transform-origin': origin, + '-ms-transform-origin': origin, + 'transform-origin': origin + }; + }, + + /** + * Parses tag's "content" attribute, if any. + * + * Parses something like this: + * + * into this: + * { + * width: 'device-width', + * initial-scale: '1', + * maximum-scale: '5', + * minimum-scale: '1', + * user-scalable: 'yes' + * } + * + * @return Object + * Parsed viewport settings, or {}. + */ + _parseViewportMetaTag: function () { + var settings = {}; + var $viewportMeta = $(document).find('meta[name=viewport][content]'); + if ($viewportMeta.length > 0) { + $viewportMeta + .attr('content') + // Reduce multiple parts of whitespace to a single space. + .replace(/\s+/g, '') + // Split on comma (which separates the different settings). + .split(',') + .map(function (setting) { + setting = setting.split('='); + settings[setting[0]] = setting[1]; + }); } - }) - } + return settings; + } + }) }; /** @@ -830,8 +843,7 @@ function updateDeviceList () { /** * Updates the model to reflect the properties of the chosen device. * - * @param Object event - * A jQuery event object. + * @param jQuery.Event event */ function selectDevice (event) { var $link = $(event.target); @@ -873,8 +885,8 @@ $.extend(Drupal.theme, { responsivePreviewFrameContainer: function () { return '
' + '' - + '' - + '' + + '' + + '' + '
'; }, diff --git a/core/modules/responsive_preview/lib/Drupal/responsive_preview/DeviceAccessController.php b/core/modules/responsive_preview/lib/Drupal/responsive_preview/DeviceAccessController.php new file mode 100644 index 0000000..eacbc49 --- /dev/null +++ b/core/modules/responsive_preview/lib/Drupal/responsive_preview/DeviceAccessController.php @@ -0,0 +1,35 @@ + 'link', + '#title' => t('Configure devices'), + '#href' => url('admin/config/content/responsive-preview'), + '#options' => array( + 'attributes' => array( + 'class' => array('configure'), + ), + ), + ); return $links; } @@ -116,7 +127,7 @@ function responsive_preview_toolbar() { '#type' => 'html_tag', '#tag' => 'button', '#value' => t('Layout preview'), - '#value_prefix' => '', + '#value_prefix' => '', '#value_suffix' => '', '#attributes' => array( 'title' => t('Preview page layout'), @@ -161,7 +172,7 @@ function responsive_preview_library_info() { 'title' => 'Preview layouts', 'version' => VERSION, 'css' => array( - $path . '/css/responsive-preview.base.css', + $path . '/css/responsive-preview.module.css', $path . '/css/responsive-preview.theme.css', $path . '/css/responsive-preview.icons.css', ),