Trying to insert a Panorama into an Essay

I am trying to incorporate output from Panorama Studio 3 Pro into my Backlight install using essays. ( PanoramaStudio 3 Pro | PanoramaStudio Panorama Software )

The original output from PS3P is here: https://www.lightsmithy.com/panoramas/test/high_front_pano.html

if I just insert the DIV code from the Panorama Builder’s html into the Page Content of my essay, thus:

##My Tudor High panorama
Panorama of my neighborhood

<a href="https://www.lightsmithy.com/panoramas/test/high_front_pano.html">Click here for full screen on phones</a>

<div align="center" style="height: 100%; overflow:hidden;">
<noscript><div style="color: red; width: 30%; border: 1px solid red; padding: 4px; font-family: sans-serif;">ERROR: Your web browser must have JavaScript enabled to show this panorama.</div></noscript>
<div id="panoStudioViewerID" style="position: absolute; left: 0px; top: 0px; width: 100% ; height: 100% ;"></div>
<script type="text/javascript" src="panoStudioViewer.js"></script>
<script type="text/javascript">panoStudioViewer.insert("panoStudioViewerID","high_front_pano.js",{html5:"viewport_fit_cover|disable_webgl_warning"});</script>
</div>

What I expect to get is this (mockup)

instead, I get this:

If I edit the position of the panoStudioViewerID to relative, I get this:

if I insert an image from the image builder first, like this:

##My Tudor High panorama
Panorama of my neighborhood

<a href="https://www.lightsmithy.com/panoramas/test/high_front_pano.html">Click here for full screen on phones</a>

<div data-presentation="single" data-images="708086_9054"></div>

<div align="center" style="height: 100%; overflow:hidden;">
<noscript><div style="color: red; width: 30%; border: 1px solid red; padding: 4px; font-family: sans-serif;">ERROR: Your web browser must have JavaScript enabled to show this panorama.</div></noscript>
<div id="panoStudioViewerID" style="position: absolute; left: 0px; top: 0px; width: 100% ; height: 100% ;"></div>
<script type="text/javascript" src="panoStudioViewer.js"></script>
<script type="text/javascript">panoStudioViewer.insert("panoStudioViewerID","high_front_pano.js",{html5:"viewport_fit_cover|disable_webgl_warning"});</script>
</div>

I get this:

The body content of the page is hidden (behind the absolutely positions view) and the pano is now too tall (controls are off the bottom). I can set the top position of the viewer and reveal the Body copy, but that is even worse

If I insert a smaller image, it works better (but page content is still hidden)

Clearly the display height of the panorama is being controlled by something else in the CSS and I probably need to either use some custom CSS or edit the display style of the panoStudioViewerID DIV to override it. I don’t know enough about CSS to even know where to start with either of those and I would appreciate some help from those who already know the code. (It doesn’t look from my reading that the Theater module can do this at all, but if that is the easy way I am happy to buy it)

I don’t think Essay was designed for something like this. @Matthew would know.

But I think the PYM embedded iframe might be a better choice for this kind of content

I hadn’t thought of doing this with iframes (not had good luck with that in the past)

How does theater handle thumbnails on an album set? as in, if I create an album set of panoramas, will it use an image for a thumbnail like it would for Essays?

You could just use a custom thumbnail. Very easy to do from Backlight Publisher

I think the problem you’re having is that the code you’re pulling from the Panorama Builder’s HTML is just not very good. Or, at the very least, it’s not at all portable.

Let’s walk through it.

<a href="https://www.lightsmithy.com/panoramas/test/high_front_pano.html">
  Click here for full screen on phones
</a>

^^^ This is a hyperlink for phones that opens panoramic on its own page. Neither problematic, nor exciting; we might derive from this that they’ve not bothered to make their presentation responsive, which is disappointing.

<div align="center" style="height: 100%; overflow:hidden;">
  ...
</div>

This is a DIV element that wraps everything below. The align attribute is our first red flag. According to the W3C:

HTML align attribute supports col, colgroup, tbody, td, tfoot, th, thead, tr elements.

Usage of align attribute for any other HTML elements is deprecated. You must use CSS for those.

So that’s rubbish.

It then sets the DIV height to 100% of its parent, which is pretty unreliable when dropping the code into the middle of a page that you didn’t write (which is exactly what’s happening here).

Let’s move on …

  <noscript>
    <div style="color: red; width: 30%; border: 1px solid red; padding: 4px; font-family: sans-serif;">
      ERROR: Your web browser must have JavaScript enabled to show this panorama.
    </div>
  </noscript>

^^^ This is a message that only displays when JavaScript is unsupported. IMO, an antiquated practice. In this day and age, everything supports JS, and if you’re using a browser or doesn’t, or you’ve got JS disabled for some reason, then you can probably expect 99% of the Internet not to work as intended. No harm here, but I don’t see the point.

  <div id="panoStudioViewerID" style="position: absolute; left: 0px; top: 0px; width: 100% ; height: 100% ;"></div>

^^^ Finally, we get to something interesting. This is the placeholder element into which the panoramic is inserted. It has an ID and that’s important; it tells us we can use this code only ONCE per page.

  <script type="text/javascript" src="panoStudioViewer.js"></script>

^^^ This includes the JavaScript file for the viewer.

  <script type="text/javascript">
    panoStudioViewer.insert("panoStudioViewerID", "high_front_pano.js", { 
      html5: "viewport_fit_cover|disable_webgl_warning",
    });
  </script>

^^^ And this executes the script, targeting the placeholder with the “high_front_pano.js”, which seems specific to that panoramic.

Now, what do with all of this information?

I’ll tell you in the followup post.

The first thing, you should create a new page template specifically to be used for your essays that embed these panoramic presentations. So either clone your existing page template, or create a new one.

In your page template, enable both Custom Stylesheets and PHPlugins.

Create a CSS file, and duplicate the provided PHPlugins .php file, then select those working files in your template.

Finally, create, clone or edit an essay template, and then select the new page template. This will allow it to use the custom CSS and PHPlugins that we’ll be introducing.


Now, I’m going to write a bunch of theoretical code. Theoretical, because I can’t test it, as I don’t have your panoramic to work worth locally. So I’ll make a few assumptions, and we may need to modify things a bit once you try to put it into practice.


Copy the panoStudioViewer.js into the backlight/custom/js folder, such that it is accessible using this path:

//backlight/custom/js/panoStudioViewer.js

While we’re here, also create a panos folder, giving us the path:

//backlight/custom/js/panos

^^^ All of your panoramic assets, you’ll copy here. Hopefully this will work. If things don’t work, then this is the first thing I’d revisit, as it might be necessary to move things around. Anyway …

And in our PHPlugins file, let’s include the presentation script:

function scripts() {
  echo '
    <script src="//backlight/custom/js/panoStudioViewer.js"></script>
  ';
  return false;
}

By putting this in PHPlugins, rather than blindly copy-and-pasting code into our content area, we ensure that the code is in the most appropriate place, and only included once.

We’ll return to this PHPlugin function to add more, but let’s pause on PHP, and pivot to HTML and CSS.


We need an HTML template that we can use when we want to embed a panoramic presentation; the original was a child DIV inside of a parent DIV, and we’re going to keep that pattern, while changing the CSS entirely. So, let’s use this:

<div class="pano-studio" data-id="NAME"></div>

Every time you want to embed a panoramic, you will copy-and-paste this code, then update the NAME to the name of the pano file; in our example, you would change to data-pano="high_front_pano", derived from the name of our JS file in the executable code.

In the custom CSS file, we can style our HTML container.

.pano-studio {
  border: 1px solid #000;
  margin: 1.5rem auto;
  overflow: hidden;
  padding-top: 56.25%;
  position: relative;
  width: 100%;
}

.pano-studio > div {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

Of note in this CSS, you can change or remove the border. The padding-top sets the aspect ratio of your presentation; this value is 16:9. Change the value if you’d prefer different dimensions.

And I believe that should be all we need here. Let’s return to the PHPlugins function.


Now that we have our HTML template, we can write a JavaScript function to pick up and execute our panoramic scripts. Expand the existing PHPlugins function:

function scripts() {
  echo '
    <script src="//backlight/custom/js/panoStudioViewer.js"></script>

    <script>
      document.querySelectorAll('.pano-studio').forEach((pano) => {
        // creates the child DIV
        const instance = document.createElement('div')
        // stores the pano ID in a variable
        const id = pano.getAttribute('data-id')
        // assigns it the pano ID
        instance.id = id
        // appends it to the parent DIV
        pano.appendChild(instance)

        // executes the panoramic
        panoStudioViewer.insert(id, `//backlight/custom/js/panos/${id}.js`, { 
          html5: "disable_webgl_warning",
        });
      });
    </script>
  ';
  return false;
}

And that’s it!

I’ve made a few assumptions that need testing, but if my assumptions prove correct, then you should only need to:

  1. copy pano assets into /backlight/custom/js/panos;
  2. then add this code into your essays when you want to embed however many panoramics:
<div class="pano-studio" data-id="high_front_pano"></div>
<div class="pano-studio" data-id="high_back_pano"></div>
<div class="pano-studio" data-id="low_pano"></div>
1 Like

@Matthew that was extremely helpful!

I shall have a go at working through and assimilating that and see what I can do. I will almost certainly have some followup questions, however;

in all fairness, I put in the “click here for phones” link, mostly as a troubleshooting aid and, since the viewer is responsive on it’s own, it does not display the fullscreen button on a phone, it just goes fullscreen. But not when embedded in a Backlight page (right now). Let’s let that rest for now.

You are spot on otherwise, the whole viewer codebase does not look to have been updated for several years (just the viewer, the actual panorama generator app has been)

1 Like

OK, first problem (for anyone following along)

According to phplugins-smaple

*************************************************************************************
 *  *                                                                                   *
 *  * Warning! When using echo or print special care must be qiven to using quotes.     *
 *  *                                                                                   *
 *  * Strings inside single quotes must contain only double quotes                      *
 *  * or all single quotes must be escaped (ie \') or vice-versa                        *
 *  *                                                                                   *
 *  *************************************************************************************

there are incorrect single quotes and backticks in the code sample for phplugin
@Matthew , if you fix that, please delete this reply

you can fix that by using the escape sequence: \' on single quotes inside the echo ' .....' statements

OK, it’s not working but I think I know why.
If I move the original high_front_pano.html file out of the directory that it’s assets are in (the pano tiles, the metadata file (high_front_pano.js,) the controls template). It displays nothing.

From this I am guessing PanostudioViewer.js always refers to its assets by relative path. Since we now load it from somewhere else it doesn’t work. I think I have confirmed this, see below.

If instead of:

<div class="pano-studio" data-id="high_front_pano"></div>

I use:

<div class="pano-studio">
<div align="center" style="height: 100%; overflow:hidden;">
<div id="panoStudioViewerID" style="position: absolute; left: 0px; top: 0px; width: 100% ; height: 100% ;"></div>
<script type="text/javascript" src="/panoramas/test/panoStudioViewer.js"></script>
<script type="text/javascript">panoStudioViewer.insert("panoStudioViewerID","/panoramas/test/high_front_pano.js",{html5:"viewport_fit_cover|disable_webgl_warning"});</script>
</div>
</div>

It works. (ugly, yes, but works)

I made a new test file Test2 - Lightsmithy in inserted the same code, now it doesn’t work.

if I put in the full paths (prefix /panoramas/test) it again works:

<div class="pano-studio">
<div align="center" style="height: 100%; overflow:hidden;">
<div id="panoStudioViewerID" style="position: absolute; left: 0px; top: 0px; width: 100% ; height: 100% ;"></div>
<script type="text/javascript" src="/panoramas/test/panoStudioViewer.js"></script>
<script type="text/javascript">panoStudioViewer.insert("panoStudioViewerID","/panoramas/test/high_front_pano.js",{html5:"viewport_fit_cover|disable_webgl_warning"});</script>
</div>
</div>

I did try moving the PanoramaViewer.js in to the same directory as the assets (//backlight/custom/js/panos/) and calling it from there, but that was also unsuccessful.

I would guess that the phplugin script needs to refer to files in the same directory the album is actually in, rather than /backlight/custom/js/*. I don’t know how to do that though. I did try changing it to (and panoStudioViewer.insert(id, “${id}.js”", { ) with no luck

However, this is much closer: https://www.lightsmithy.com/panoramas/test/

That’s annoying. I was hoping that wouldn’t be the case. Looks like you’re making progress, though.

hmmmm… actually…
if you look at the source code for: test 3 - Lightsmithy
where I am using Matthews plan of Page Source being:

##test 3
<div class="pano-studio" data-pano="high_front_pano"></div>

When I look at the actual source code the browser sees, that is all that is being written out, the panoViewer.js is not even being called (echo’d to page)

<div class="the__copy" id="the__copy">
  <div class="content clearfix">
  
<h2>test 3</h2>
<div class="pano-studio" data-pano="high_front_pano"></div>

  
  
  </div>
</div><!-- .the__copy -->

I’ve clearly missed something… Shouldn’t the Phplugin code be writing something in there?

Here is the complete panorama-phplugin.php

<?php
/*
 *  TTG Core Elements "PHPlugins" User Hooks v1.2 - initialization mainline
 *
 *  developed by john bishop images (http://johnbishopimages.com)
 *  for Matthew Campagna of The Turning Gate (http://theturninggate.net)
 *
 */

function user_load($style, $path) {
	$g_tsvrl = explode(' ', $style); // Extract gallery type
	define ('G_STYLE', strtoupper($g_tsvrl[1])); // and set global for later
	$g_path = str_ireplace('\\','/',$path); // change \ to /
	$chunks = explode('/',$g_path); // and put into array
	define ('G_PATH', strtoupper($chunks[count($chunks)-2])); // gallery folder name is second to last
	//define ( 'TTG_SITE', ''); // set new site root for navigation, resources, etc.
}

if (defined('BACKLIGHT_HOOK')) {
	require_once(realpath(BACKLIGHT_HOOK).'/modules/module-designer/application/helpers/APHPlugins.php');
}

class PHPlugins extends APHPlugins
{
/*
 *
 *  *************************************************************************************
 *  *                                                                                   *
 *  * Warning! When using echo or print special care must be qiven to using quotes.     *
 *  *                                                                                   *
 *  * Strings inside single quotes must contain only double quotes                      *
 *  * or all single quotes must be escaped (ie \') or vice-versa                        *
 *  *                                                                                   *
 *  *************************************************************************************
 *
 *  Fourteen user exits are defined in all web engines - all are optional
 *    (i.e. user_load.php may be the only processing)
 *
 *  Some web engines will have additional exits defined, specific to that gallery type
 *
 *  Each is called with the same parameters:
 *    %1  - TTG gallery-style gallery-release
 *        3 blank delimited values
 *        - %1.1  - 'TTG'
 *        - %1.2  - string describing gallery type; no embedded blanks
 *        - %1.3  - a series of two to three period delimited integers
 *              describing gallery release level; x.y or x.y.z
 *    %2  - server filesystem file name and path of calling file
 *
 *
 *  Defined exits:
 *
 *    user_load
 *      - return value ignored
 *      - called immediately after this file returns
 *      - called before any output is produced
 *      - all header and response variables are accessible
 *      - cookie and session processing can be initialized
 *      - globals to be used by later hook calls can be defined
 *
 *    head
 *      - return value ignored
 *      - called immediately before </head>
 *      - encompasses nothing; use to insert content into the <head>
 *
 *    header_top
 *      - if return=false, the contents of the normal Backlight header are skipped
 *      - called immediately within the header section
 *      - encompasses the full contents of the header section; can be used to replace those contents
 *
 *    header_bottom
 *      - return value ignored
 *      - called last in the header section
 *      - encompasses nothing; use to insert content at the end of the header section
 *
 *    masthead_primary_top
 *      - if return=false, normal Backlight masthead is skipped
 *      - called immediately before the masthead element
 *      - fully encompasses the masthead; can be used to replace it
 *
 *    masthead_primary_bottom
 *      - return value ignored
 *      - called immediately after the masthead
 *      - encompasses nothing; use to insert content after the masthead
 *
 *    masthead_secondary_top
 *      - if return=false, normal Backlight masthead is skipped
 *      - called immediately before the masthead element
 *      - fully encompasses the masthead; can be used to replace it
 *
 *    masthead_secondary_bottom
 *      - return value ignored
 *      - called immediately after the masthead
 *      - encompasses nothing; use to insert content after the masthead
 *
 *    navigation
 *      - if return=false, normal Backlight navigation is skipped
 *      - navigation is separate from the header section, so not affected by the above header... hooks.
 *      - encompasses the navigation <ul> element; can be used to replace it
 *
 *    main_top
 *      - return value ignored
 *      - called immediately within the main content column
 *      - encompasses nothing; use to insert content at the very top of the main content column
 *      - located outside the password protected area
 *
 *    main_bottom
 *      - return value ignored
 *      - called immediately before closing the main content column
 *      - encompasses nothing; use to insert content at the very bottom of the main content column
 *      - located outside the password protected area
 *
 *    footer_top
 *      - if return=false, the contents of the normal Backlight footer are skipped
 *      - called immediately within the footer section
 *      - encompasses the full contents of the footer section; can be used to replace those contents
 *
 *    footer_bottom
 *      - return value ignored
 *      - called last in the footer section
 *      - encompasses nothing; use to insert content at the end of the footer section
 *
 *    pallet_top_title
 *      - if return=false, the encompassed code is skipped
 *      - wraps the site title in the top pallet
 *      - use to replace the site title
 *
 *    toggle_T1
 *      - if return=false, the encompassed code is skipped
 *      - wraps the label element for "page__toggle__T1"
 *      - use to replace the toggle button with one of your own making
 *
 *    toggle_T2
 *      - if return=false, the encompassed code is skipped
 *      - wraps the label element for "page__toggle__T2"
 *      - use to replace the toggle button with one of your own making
 *
 *    social_top
 *      - if return=false, normal content is skipped
 *      - called immediately before social media icons in the top pallet
 *      - encompasses the social media icons; can be used to replace them
 *
 *    social_bottom
 *      - return value ignored
 *      - called immediately after social media icons in the top pallet
 *      - encompasses nothing; use to insert content following the social media icons
 *
 *    scripts
 *      - return value ignored
 *      - called immediately before </body>
 *      - encompasses nothing; use to insert content at the very bottom of the page
 *
 *    copy_top
 *      - if return=false, normal copy is skipped
 *      - called immediately within the page copy area
 *      - encompasses the page copy; can be used to replace it
 *
 *    copy_bottom
 *      - return value ignored
 *      - called immediately before closing the page copy area
 *      - encompasses nothing; use to insert content following the page copy
 *
 *    pallet01_top
 *      - if return=false, pallet content is skipped
 *      - called within pallet01, after masthead and navigation
 *      - encompasses the pallet01 content area
 *
 *    pallet01_bottom
 *      - return value ignored
 *      - called after the pallet01 content
 *      - encompasses nothing; use to insert content at the end of pallet01
 *
 *    pallet02_top
 *      - if return=false, pallet content is skipped
 *      - called within pallet02, after masthead and navigation
 *      - encompasses the pallet02 content area
 *
 *    pallet02_bottom
 *      - return value ignored
 *      - called after the pallet02 content
 *      - encompasses nothing; use to insert content at the end of pallet02
 *
 *    albumset_top
 *      - if return=false, normal copy is skipped
 *      - called immediately within the media area
 *      - encompasses the gallery grid / slideshow; can be used to replace it
 *
 *    albumset_bottom
 *      - return value ignored
 *      - called immediately before closing the media area
 *      - encompasses nothing; use to insert content following the gallery grid / slideshow
 *
 *    album_top
 *      - if return=false, the album content is skipped
 *      - called immediately within the media area
 *      - encompasses the album grid / slideshow; can be used to replace it
 *
 *    album_bottom
 *      - return value ignored
 *      - called immediately before closing the media area
 *      - encompasses nothing; use to insert content following the album grid / slideshow
 *
 *    single_top
 *      - if return=false, single image and content are skipped
 *      - called immediately above the single image display
 *      - encompasses the single image and related metadata; can be used to replace it
 *      - available only on single-image HTML pages for applicable album templates
 *
 *    single_bottom
 *      - return value ignored
 *      - called after the single image display
 *      - encompasses nothing; use to insert content following the single image display
 *      - available only on single-image HTML pages for applicable album templates
 *
 */

// SET USER FUNCTIONS BELOW
// Some example functions are included below. Feel free to delete or modify unwanted functions.
// ****************************************************************************************************


function scripts() {
  echo '
    <script src="panoStudioViewer.js"></script>

    <script>
      document.querySelectorAll(\'.pano-studio\').forEach((pano) => {
        // creates the child DIV
        const instance = document.createElement(\'div\')
        // stores the pano ID in a variable
        const id = pano.getAttribute(\'data-id\')
        // assigns it the pano ID
        instance.id = id
        // appends it to the parent DIV
        pano.appendChild(instance)

        // executes the panoramic
        panoStudioViewer.insert(id, \'//backlight/custom/js/panos/${id}.js\', { 
          html5: "disable_webgl_warning",
        });
      });
    </script>
  ';
  return false;
}


// ****************************************************************************************************
// END USER FUNCTIONS

} ?>

Did you add the html Matt suggested to the album copy area?

You should be seeing the script added via phplugins in the album’s source code. It will be toward the bottom.
(Make sure your album template is using the page template that has the phplugins file enabled)

The code from phplugins is there, but just not where you expect it to be. Look at line 1448.

In the javascript console, it shows an error:

Refused to execute script from 'https://www.lightsmithy.com/panoramas/test-3/panoStudioViewer.js' because its MIME type ('text/html') is not executable, and strict MIME type checking is enabled.

Is the panoStudioViewer.js file inside the test-3 album?

Whether or not JS-rendered markup is visible, also depends on how you’re looking. For example, you should be able to see it in the inspection console, but would not be able to see it when viewing the page source.

I’m definitely seeing an issue with the panoStudioViewer script being seemingly unreachable.

Do you want to zip up and share one of your panoramics?

I’m also seeing this on your page:

<div class="pano-studio" data-pano="high_front_pano">
  <div id="null"></div>
</div>

So the JS is appending the DIV, but is not correctly assigning the ID.

You’re using data-pano above, but you’re getting data-id here:

const id = pano.getAttribute('data-id')

Make sure these match; in my examples above, I’m using data-id in both places.

Do you want to zip up and share one of your panoramics?

Sure, here is one I don’t mind letting everyone have: http://gofile.me/6xIvJ/a7d6kQvsB

Download available until Dec. 15th

Missed that mismatch, but now corrected. Still not displaying