signageos-2.3.x-dev/templates/signageos-applet.html.twig

templates/signageos-applet.html.twig
<!doctype html>
<html lang="en" style="height:100%;">
<head>
  <title>{{ sitename }}</title>
</head>
<body style="height:100%;margin:0;padding:0;">
<div id="loadScreen">
  <style>
    #loadScreen {
      height:100%;
      padding:10%;
      text-align:center;
      background-color:{{ bgcolor }};
      color:{{ fgcolor }};
    }
    #loadScreen svg {
      width:100%;
      max-height:60%;
      height:auto;
    }
  </style>
  <h1>{{ welcome }}</h1>
  {{ logo|raw }}
  <p id="index">... loading ...</p>
</div>
<div id="underlays"></div>
<img id="image" src="#" alt="image" style="display:none;"/>
<div id="video" style="display:none;">&nbsp;</div>
<div id="html" style="display:none;">&nbsp;</div>
<div id="overlays"></div>
<iframe id="browser"></iframe>
<div id="browserwidgets"><div class="close"></div></div>
<script type="application/ecmascript">
  window.drupalSettings = {};
  async function startApplet() {

    String.prototype.hashCode = function(){
      let hash = 0, i, char;
      if (this.length === 0) return hash;
      for (i = 0; i < this.length; i++) {
        char = this.charCodeAt(i);
        hash = ((hash<<5)-hash)+char;
        // Convert to 32bit integer.
        hash = hash & hash;
      }
      return hash;
    };

    const myLog = function(info) {
      if (sos.config.debug) {
        console.log(info);
        sos.command.dispatch({
          type: 'DeviceLog.Debug',
          payload: JSON.stringify(info)
        });
      }
    };

    const myError = function(error) {
      myLog('ERROR ====================================================');
      myLog(error);
      sos.command.dispatch({
        type: 'DeviceLog.Error',
        payload: JSON.stringify(error)
      });
    };

    const myReport = function(slide) {
      sos.command.dispatch({
        type: 'DeviceReport.Slide',
        payload: slide
      });
    };

    const updateDynamicContent = function(contentItems, item, url, timeout) {
      setTimeout(async function () {
        if (!paused) {
          myLog('Downloading dynamic content ' + item + ' from ' + url);
          let fileName = 'dynamic-content-' + item;
          const content = await readLocalFile(fileName, url, false);
          myLog('Received ' + content.length + ' bytes');
          await prepareContent(contentItems, item, content);

          sos.offline.cache.deleteFile(fileName)
            .catch(function (error) {
              myError(error);
            });
        }
        updateDynamicContent(contentItems, item, url, sos.config.refreshInterval * 1000);
      }, timeout);
    };

    const updateDynamicBlock = function(item, blockid, url, timeout) {
      setTimeout(async function () {
        if (!paused) {
          myLog('Downloading dynamic block ' + blockid);
          let fileName = 'dynamic-block-' + blockid;
          const content = await readLocalFile(fileName, url, false);
          myLog('Received ' + content.length + ' bytes');
          let parser = new DOMParser();
          let dom = parser.parseFromString(content, 'text/html');
          let newContent = dom.querySelector('div[data-drupal-digitalsignage-dynamic="true"]');
          item.innerHTML = newContent.innerHTML;

          sos.offline.cache.deleteFile(fileName)
            .catch(function (error) {
              myError(error);
            });
        }
        updateDynamicBlock(item, blockid, url, sos.config.refreshInterval * 1000);
      }, timeout);
    };

    const loadSchedule = async function(forceReload) {
      myLog('Loading schedule');
      let fileName = 'schedule.json';
      if ((forceReload || sos.config.reloadcontent) && existingFileUids.includes(fileName)) {
        myLog('Deleting existing schedule');
        await sos.offline.cache.deleteFile(fileName)
          .catch(function (error) {
            myError(error);
          });
      }
      const content = await readLocalFile(fileName, sos.config.api + '?mode=schedule&deviceId=' + sos.config.deviceId, true);
      myLog('Received:');
      myLog(content);
      contentAssets = content.assets;
      schedule = content.schedule;
      emergencyentities = content.emergencyentities;
      underlays = content.underlays;
      overlays = content.overlays;
    };

    const readLocalFile = async function(fileName, url, json) {
      myLog('Loading ' + fileName + ' from ' + url);
      await sos.offline.cache.loadOrSaveFile(fileName, url, sos.config.httpHeader)
        .catch(function (error) {
          myError(error);
        });
      let {filePath} = await sos.offline.cache.loadFile(fileName);
      return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest();
        xhr.open('GET', filePath);
        xhr.onload = function() {
          if (xhr.readyState === 4) {
            if (xhr.status === 200 || xhr.status === 0) {
              let data;
              if (json) {
                try {
                  data = JSON.parse(xhr.responseText);
                }
                catch (e) {
                  myError("Invalid json: " + xhr.responseText);
                  data = [];
                }
              }
              else {
                data = xhr.responseText;
              }
              resolve(data);
            } else {
              myError("Error loading file " + filePath);
              myReport('error');
              // reject("Error loading file " + filePath);
              if (json) {
                resolve([]);
              }
              else {
                resolve('');
              }
            }
          }
        };
        xhr.send();
      });
    };

    const prepareAssetFile = async function(uri, uid, type) {
      if (sos.config.reloadassets && existingFileUids.includes(uid)) {
        await sos.offline.cache.deleteFile(uid)
          .catch(function (error) {
            myError(error);
          });
      }
      return {
        "uri": uri,
        "uid": uid,
        "type": type,
        "headers": sos.config.httpHeader,
        "flags": [sos.offline.flags.append(document.head)]
      };
    };

    const loadAssets = async function() {
      myLog('Loading assets');
      let scripts = [];
      scripts.push(await prepareAssetFile(sos.config.api + '?mode=load&type=css&deviceId=' + sos.config.deviceId, 'styles.css', sos.offline.types.css));
      for (const script in sos.config.scripts) {
        if (sos.config.scripts.hasOwnProperty(script)) {
          scripts.push(await prepareAssetFile(sos.config.scripts[script]['uri'], sos.config.scripts[script]['uid'], sos.offline.types.javascript));
        }
      }
      let parser = new DOMParser();
      let dom = parser.parseFromString(contentAssets, 'text/html');
      let cssFiles = await extractFileLinks(dom, 'link', 'href');
      let jsFiles = await extractFileLinks(dom, 'script', 'src');
      let i;
      for (i = 0; i < cssFiles.length; i++) {
        let file = cssFiles[i];
        scripts.push(await prepareAssetFile(file, file.hashCode() + '.css', sos.offline.types.css));
      }
      for (i = 0; i < jsFiles.length; i++) {
        let file = jsFiles[i];
        scripts.push(await prepareAssetFile(file, file.hashCode() + '.js', sos.offline.types.javascript));
      }

      let contentSettings = {};
      let settingsElement = dom.querySelector('script[type="application/json"][data-drupal-selector="drupal-settings-json"]');
      if (settingsElement !== null) {
        contentSettings = JSON.parse(settingsElement.textContent);
      }
      else {
        settingsElement = dom.createElement('script');
        settingsElement.setAttribute('type', 'application/json');
        settingsElement.setAttribute('data-drupal-selector', 'drupal-settings-json');
      }
      window.drupalSettings = Object.assign(sos.config.drupalSettings, contentSettings);
      settingsElement.textContent = JSON.stringify(window.drupalSettings);
      document.body.appendChild(settingsElement);

      myLog(scripts);
      await sos.offline.addFilesSync(scripts)
        .catch(function (error) {
          myError(error);
        });

      for (const font in sos.config.fonts) {
        if (sos.config.fonts.hasOwnProperty(font)) {
          sos.config.fonts[font]['append'] = document.head;
          myLog("Loading font");
          myLog(sos.config.fonts[font]);
          await sos.offline.addFont(sos.config.fonts[font])
            .catch(function (error) {
              myError(error);
            });
          myLog("Received font " + sos.config.fonts[font]['uid']);
        }
      }
    };

    const extractFileLinks = async function(dom, tag, attr) {
      let files = [];
      let elements = dom.getElementsByTagName(tag);
      let i;
      for (i = 0; i < elements.length; i++) {
        let element = elements[i];
        if (element.getAttribute('data-sos-loaded') !== 'ok') {
          element.setAttribute('data-sos-loaded', 'ok');
          if (element.hasAttribute(attr)) {
            files.push(sos.config.baseUrl + element.getAttribute(attr));
          }
        }
      }
      return files;
    }

    const loadImages = async function(html, tag) {
      let parser = new DOMParser();
      let dom = parser.parseFromString(html, 'text/html');
      let elements = dom.getElementsByTagName(tag);
      let i;
      for (i = 0; i < elements.length; i++) {
        let element = elements[i];
        if (element.getAttribute('data-sos-loaded') !== 'ok') {
          element.setAttribute('data-sos-loaded', 'ok');
          if (element.hasAttribute('src')) {
            let path = element.getAttribute('src').replace(" 1x", "");
            let uid = "content-" + path.hashCode();
            if (sos.config.reloadcontent && existingFileUids.includes(uid)) {
              myLog("Deleting " + uid);
              await sos.offline.cache.deleteFile(uid)
                .catch(function (error) {
                  myError(error);
                });
            }
            myLog("Loading " + path);
            let uri = sos.config.api + '?mode=load&type=content&deviceId=' + sos.config.deviceId + '&contentPath=' + btoa(path.replace("&amp;", "&"));
            myLog("Uri " + uri);
            const {filePath} = await sos.offline.cache.loadOrSaveFile(uid, uri, sos.config.httpHeader)
              .catch(function (error) {
                myError(error);
              });
            element.setAttribute('src', filePath);
            myLog("Received " + filePath);
          }
        }
      }
      return dom.body.innerHTML;
    };

    const prepareContent = async function(contentItems, item, content) {
      content = await loadImages(content, 'img');
      content = await loadImages(content, 'picture');
      content = await loadImages(content, 'source');
      if (contentItems[item].hasOwnProperty('content')) {
        contentItems[item].content = content;
      }
      else {
        contentItems[item] = content;
      }
    };

    const loadContent = async function(contentItems) {
      myLog('Loading content');
      myLog(contentItems);
      if (contentItems === undefined || contentItems[0] === undefined) {
        return;
      }

      let ve = jQuery('#video');
      for (const item in contentItems) {
        if (contentItems.hasOwnProperty(item)) {
          contentItems[item].uid = contentItems[item].entity.type + '_' + contentItems[item].entity.id;
          if (contentItems[item].type !== 'html') {
            if (sos.config.reloadcontent && existingFileUids.includes(contentItems[item].uid)) {
              await sos.offline.cache.deleteFile(contentItems[item].uid)
                .catch(function (error) {
                  myError(error);
                });
            }
            contentItems[item].uri = sos.config.api + '?mode=load&type=' + contentItems[item].type + '&deviceId=' + sos.config.deviceId + '&entityType=' + contentItems[item].entity.type + '&entityId=' + contentItems[item].entity.id;
            myLog("Loading item");
            myLog("Uri " + contentItems[item].uri);
            const {filePath} = await sos.offline.cache.loadOrSaveFile(contentItems[item].uid, contentItems[item].uri, sos.config.httpHeader)
              .catch(function (error) {
                myError(error);
              });
            if (filePath) {
              myLog("Received " + filePath);
              contentItems[item].filePath = filePath;
              if (contentItems[item].type === 'video') {
                contentItems[item].arguments = [contentItems[item].filePath, 0, 0, ve.width(), ve.height()];
              }
            }
            else {
              contentItems[item].filePath = false;
              myError("Unable to receive " + contentItems[item].uri);
            }
          }
          else {
            await prepareContent(contentItems, item, contentItems[item].content);
          }
          if (contentItems[item].dynamic) {
            // TODO: Cancel updates when refreshing schedule.
            updateDynamicContent(contentItems, item, sos.config.api + '?mode=load&type=' + contentItems[item].type + '&deviceId=' + sos.config.deviceId + '&entityType=' + contentItems[item].entity.type + '&entityId=' + contentItems[item].entity.id, 0);
          }
        }
      }
    };

    const playSchedule = async function(playRound) {
      introElement.style.display = 'none';
      contentElement.innerHTML = '';
      let previousIndex;
      let currentIndex = 0;
      let singleSlide = (schedule.length === 1);
      let video = false;
      let videoArguments = [];

      if (singleSlide && schedule[0].dynamic) {
        schedule[0].duration = sos.config.refreshInterval * 1.5;
        singleSlide = false;
      }

      myReport('Starting play');
      do {
        if (paused) {
          myLog('Pausing');
          let timer = Drupal.digital_signage_timer.setInitialTimeout(10);
          await timer.promise;
          continue;
        }
        let previous = typeof previousIndex === 'undefined' ? undefined : schedule[previousIndex];
        let current = schedule[currentIndex];
        if (singleSlide && video) {
          myLog('Restarting video');
          await sos.video.play(...videoArguments);
        }
        else {
          myLog('Displaying slide ' + currentIndex);
          myReport(currentIndex);
          if (video) {
            await sos.video.stop(...videoArguments);
            video = false;
            videoArguments = []
          }

          // play current
          if (current.type === 'video') {
            video = true;
            videoArguments = current.arguments
            videoElement.style.display = 'block';
            await sos.video.play(...current.arguments);
          }
          else if (current.type === 'image' && current.filePath) {
            imageElement.src = current.filePath;
            imageElement.style.display = 'block';
          }
          else if (current.type === 'html') {
            htmlElement.innerHTML = current.content;
            htmlElement.style.display = 'block';
            Drupal.attachBehaviors();
            let videos = jQuery('video');
            if (videos.length > 0) {
              video = true;
              let position = videos.first().offset();
              myLog(jQuery('source', videos[0]).attr('src'));
              videoArguments = [jQuery('source', videos[0]).attr('src'), position.left, position.top, videos[0].offsetWidth, videos[0].offsetHeight];
              await sos.video.play(...videoArguments);
              videos.first().hide();
            }
          }
          else {
            myError('Unrecognized item type: ' + current.type);
          }
          // stop previous
          if (previous) {
            if (previous.type === 'video') {
              if (current.type !== 'video') {
                videoElement.style.display = 'none';
              }
              await sos.video.stop(...previous.arguments);
            }
            else if (previous.type === 'image' && current.type !== 'image') {
              imageElement.style.display = 'none';
            }
            else if (previous.type === 'html' && current.type !== 'html') {
              htmlElement.style.display = 'none';
            }
          }
          if (singleSlide && !video) {
            myLog('Finishing play, only one slide');
            return;
          }
        }

        // prepare next
        let timer = Drupal.digital_signage_timer.setInitialTimeout(current.duration);
        if (singleSlide && video) {
          timer.promise = sos.video.onceEnded(...videoArguments);
        }
        else {
          const nextIndex = (currentIndex + 1) % schedule.length;
          if (current.type === 'video') {
            timer.promise = sos.video.onceEnded(...current.arguments);
            const next = schedule[nextIndex];
            if (next.type === 'video') {
              await sos.video.prepare(...next.arguments);
            }
          }
          else if (current.type === 'image') {
            // Nothing to do, keep the predefined promise.
          }
          else if (current.type === 'html') {
            if (video) {
              timer.promise = sos.video.onceEnded(...videoArguments);
            }
            else {
              // Nothing to do, keep the predefined promise.
            }
          }
          else {
            myError('Unrecognized item type: ' + current.type);
            timer.promise = Promise.resolve();
          }
          previousIndex = currentIndex;
          currentIndex = nextIndex;
        }
        await timer.promise;
      } while (appletPlayRound === playRound);
      if (video) {
        await sos.video.stop(...videoArguments);
      }
    };

    const readLoadedFiles = async function() {
      myLog("Read loaded files");
      existingFileUids = await sos.offline.cache.listFiles()
        .catch(function (error) {
          myError(error);
        });
      myLog(existingFileUids);
    };

    const checkEmergencyMode = async function() {
      myLog('Checking emergency mode');
      if (sos.config.emergencyEntity.hasOwnProperty('type') && sos.config.emergencyEntity.hasOwnProperty('id')) {
        myLog('Emergency mode gets enabled');
        myLog(sos.config.emergencyEntity);
        for (const item in emergencyentities) {
          if (emergencyentities.hasOwnProperty(item)) {
            if (emergencyentities[item].entity.type === sos.config.emergencyEntity.type && emergencyentities[item].entity.id === sos.config.emergencyEntity.id) {
              if (originalSchedule.length === 0) {
                originalSchedule = schedule;
              }
              schedule = [];
              schedule[0] = emergencyentities[item];
              return;
            }
          }
        }
        myError('Emergency entity is not available.');
      }
    };

    const runSchedule = async function(forceReload) {
      appletPlayRound++;
      videoElement.style.display = 'none';
      imageElement.style.display = 'none';
      htmlElement.style.display = 'none';
      introElement.style.display = 'block';
      contentElement.innerHTML = 'Loading content...';
      underlaysElement.innerHTML = '';
      overlaysElement.innerHTML = '';
      await readLoadedFiles();
      await loadSchedule(forceReload);
      myLog(contentAssets);
      myLog(schedule);
      myLog(emergencyentities);
      myLog(underlays);
      myLog(overlays);
      await checkEmergencyMode();
      await loadAssets();
      if (schedule === undefined || schedule[0] === undefined) {
        // We haven't got any schedules yet, so let's quit.
        contentElement.innerHTML = 'No content available at this time.';
        myError('No content in the schedule yet.');
      }
      await loadContent(emergencyentities);
      await loadContent(schedule);
      await loadContent(originalSchedule);
      myLog('Handle underlays');
      let i = 0;
      for (i = 0; i < underlays.length; i++) {
        await prepareContent(underlays, i, underlays[i]);
        underlaysElement.innerHTML = underlaysElement.innerHTML + underlays[i];
      }
      myLog('Handle overlays');
      for (i = 0; i < overlays.length; i++) {
        await prepareContent(overlays, i, overlays[i]);
        overlaysElement.innerHTML = overlaysElement.innerHTML + overlays[i];
      }
      myLog('Handle dynamic content');
      let dynamicBlocks = document.querySelectorAll('div[data-drupal-digitalsignage-dynamic="true"]');
      for (i = 0; i < dynamicBlocks.length; i++) {
        dynamicBlocks[i].innerHTML = '';
        let blockid = dynamicBlocks[i].getAttribute('data-drupal-digitalsignage-blockid');
        updateDynamicBlock(dynamicBlocks[i], blockid, sos.config.api + '/block/' + blockid, 0);
      }
      myLog('Handle Links');
      let links = document.getElementsByTagName('a');
      myLog(links);
      for (i = 0; i < links.length; i++) {
        myLog('Link ' + i);
        links.item(i).addEventListener('click', async function (event) {
          myLog('Received click on link ' + i);
          event.preventDefault();
          paused = true;
          browserElement.src = this.getAttribute('href');
          browserElement.setAttribute('class', 'show');
          browserWidgets.setAttribute('class', 'show');
          Drupal.digital_signage_timer.popupTimer = Drupal.digital_signage_timer.setInitialTimeout(30);
          await Drupal.digital_signage_timer.popupTimer.promise;
          myLog('Timeout');
          browserWidgets.getElementsByClassName('close')[0].click();
        });
      }
      myLog('Restore');
      sos.restore();
      myLog('Play schedule');
      playSchedule(appletPlayRound)
        .then(response => myLog('Play schedule finished'));
      await readLoadedFiles();
    };

    const introElement = document.getElementById('loadScreen');
    const contentElement = document.getElementById('index');
    const imageElement = document.getElementById('image');
    const videoElement = document.getElementById('video');
    const htmlElement = document.getElementById('html');
    const underlaysElement = document.getElementById('underlays');
    const overlaysElement = document.getElementById('overlays');
    const browserElement = document.getElementById('browser');
    const browserWidgets = document.getElementById('browserwidgets');
    browserWidgets.getElementsByClassName('close')[0].addEventListener('click', function (event) {
      myLog('Received click on close browser');
      browserElement.removeAttribute('class');
      browserWidgets.removeAttribute('class');
      paused = false;
    });
    contentElement.innerHTML = 'sOS is loaded';
    await sos.onReady();
    contentElement.innerHTML = 'sOS is ready';
    myLog('sOS is ready');
    myLog(sos.config);
    let paused = false;
    let existingFileUids = [];
    let contentAssets = '';
    let schedule = {};
    let originalSchedule = {};
    let emergencyentities = {};
    let appletPlayRound = 0;
    let underlays = {};
    let overlays = {};
    runSchedule(false)
      .then(response => myLog('Schedule loaded and play started'));

    sos.command.onCommand((commandEvent) => {
      myLog('Received command');
      myLog(commandEvent.command.payload);
      switch (commandEvent.command.type) {
        case 'UpdateConfig':
          myLog('Command: Update Config');
          sos.config = commandEvent.command.payload.config;
          runSchedule(commandEvent.command.payload.reload);
          break;
        case 'EmergencyModeSet':
          myLog('Command: Set emergency mode');
          sos.config.emergencyEntity = {
            'type': commandEvent.command.payload.type,
            'id': commandEvent.command.payload.id,
          };
          runSchedule(false);
          break;
        case 'EmergencyModeDisable':
          myLog('Command: Disable emergency mode');
          sos.config.emergencyEntity = {};
          runSchedule(false);
          break;
        default:
          myError('Unknown command');
          myError(commandEvent);
      }
    });
  }
  typeof sos !== 'undefined' ?
    startApplet() :
    window.addEventListener('sos.loaded', startApplet);
</script>
</body>
</html>

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc