src/js/widgets-container/widget.js
author ymh <ymh.work@gmail.com>
Wed, 04 Sep 2024 17:32:50 +0200
changeset 1072 ac1eacb3aa33
parent 1068 7623f9af9272
child 1073 687133dc13cf
permissions -rw-r--r--
Migrate source and build to vite.js

/* widgetsDefinition of an ancestor for the Widget classes */
import Mustache from "mustache";
import jQuery from "jquery";

/**
 * @class IriSP.Widget is an "abstract" class. It's mostly used to define some properties common to every widget.
 *
 *  Note that widget constructors are never called directly by the user. Instead, the widgets are instantiated by functions
 *  defined in init.js
 *
 * @constructor
 * @param player - a reference to the player widget
 * @param config - configuration options for the widget
 *
 */

import _ from "lodash";

export default function (IriSP) {
  const Widgets = {};

  Widgets.Widget = class {

    static defaults = {};

    static template = "";

    static messages = { en: {} };

    constructor(player, config) {
      if (typeof player === "undefined") {
        /* Probably an abstract call of the class when
         * individual widgets set their prototype */
        return;
      }

      this.__subwidgets = [];

      /* Setting all the configuration options */
      var _type = config.type || "(unknown)",
        _config = _.defaults(
          {},
          config,
          player && player.config ? player.config.default_options : {},
          this.constructor.defaults
        ),
        _this = this;

      _(_config).forEach(function (_value, _key) {
        _this[_key] = _value;
      });

      this.$ = jQuery("#" + this.container);

      if (typeof this.width === "undefined") {
        this.width = this.$.width();
      } else {
        this.$.css("width", this.width);
      }

      if (typeof this.height !== "undefined") {
        this.$.css("height", this.height);
      }

      /* Setting this.player at the end in case it's been overriden
       * by a configuration option of the same name :-(
       */
      this.player =
        player ||
        new IriSP.FakeClass([
          "on",
          "trigger",
          "off",
          "loadWidget",
          "loadMetadata",
        ]);

      /* Adding classes and html attributes */
      this.$.addClass("Ldt-TraceMe Ldt-Widget").attr("widget-type", _type);

      this.l10n =
        typeof this.constructor.messages[IriSP.language] !== "undefined"
          ? this.constructor.messages[IriSP.language]
          : IriSP.language.length > 2 &&
            typeof this.constructor.messages[IriSP.language.substr(0, 2)] !== "undefined"
          ? this.constructor.messages[IriSP.language.substr(0, 2)]
          : this.constructor.messages["en"];

      /* Loading Metadata if required */

      function onsourceloaded() {
        if (_this.localannotations) {
          _this.localsource = player.loadLocalAnnotations(
            _this.localannotations
          );
          _this.source.merge(_this.localsource);
        }
        if (_this.media_id) {
          _this.media = this.getElement(_this.media_id);
        } else {
          var _mediaopts = {
            is_mashup: _this.is_mashup || false,
          };
          _this.media = _this.source.getCurrentMedia(_mediaopts);
        }
        if (_this.pre_draw_callback) {
          jQuery.when(_this.pre_draw_callback()).done(_this.draw());
        } else {
          _this.draw();
        }
        _this.player.trigger("widget-loaded");
      }

      if (this.metadata) {
        /* Getting metadata */
        this.source = player.loadMetadata(this.metadata);
        /* Call draw when loaded */
        this.source.onLoad(onsourceloaded);
      } else {
        if (this.source) {
          onsourceloaded();
        }
      }
    }

    toString() {
      return "Widget " + this.type;
    }

    templateToHtml(_template) {
      return Mustache.render(_template, this);
    }

    renderTemplate() {
      this.$.append(this.templateToHtml(this.constructor.template));
    }

    functionWrapper(_name) {
      var _this = this,
        _function = this[_name];
      if (typeof _function !== "undefined") {
        return function () {
          return _function.apply(
            _this,
            Array.prototype.slice.call(arguments, 0)
          );
        };
      } else {
        console.log(
          "Error, Unknown function IriSP.Widgets." + this.type + "." + _name
        );
      }
    }

    getFunctionOrName(_functionOrName) {
      switch (typeof _functionOrName) {
        case "function":
          return _functionOrName;
        case "string":
          return this.functionWrapper(_functionOrName);
        default:
          return undefined;
      }
    }

    onMdpEvent(_eventName, _functionOrName) {
      this.player.on(_eventName, this.getFunctionOrName(_functionOrName));
    }

    onMediaEvent(_eventName, _functionOrName) {
      this.media.on(_eventName, this.getFunctionOrName(_functionOrName));
    }

    getWidgetAnnotations() {
      var result = null;
      if (typeof this.annotation_type === "undefined") {
        result = this.media.getAnnotations();
      } else if (this.annotation_type.elementType === "annotationType") {
        result = this.annotation_type.getAnnotations();
      } else {
        result = this.media.getAnnotationsByTypeTitle(this.annotation_type);
      }
      if (typeof this.annotation_filter !== "undefined") {
        return this.annotation_filter(result);
      } else {
        return result;
      }
    }

    getWidgetAnnotationsAtTime() {
      var _time = this.media.getCurrentTime();
      return this.getWidgetAnnotations().filter(function (_annotation) {
        return _annotation.begin <= _time && _annotation.end > _time;
      });
    }

    isLoaded() {
      var isloaded = !_(this.__subwidgets).some(function (w) {
        return !(w && w.isLoaded());
      });
      return isloaded;
    }

    insertSubwidget(_selector, _widgetoptions, _propname) {
      var _id = _selector.attr("id"),
        _this = this,
        key = this.__subwidgets.length;
      this.__subwidgets.push(null);
      if (typeof _id == "undefined") {
        _id = _.uniqueId(this.container + "_sub_widget_" + _widgetoptions.type);
        _selector.attr("id", _id);
      }
      _widgetoptions.container = _id;
      _this.player.loadWidget(_widgetoptions, function (_widget) {
        if (_propname) {
          _this[_propname] = _widget;
        }
        _this.__subwidgets[key] = _widget;
      });
    }

    /*
     * Position the player to the next/previous annotations based on current player position
     *
     * Parameter: offset: -1 for previous annotation, +1 for next annotation
     */
    navigate(offset) {
      // offset is normally either -1 (previous slide) or +1 (next slide)
      var _this = this;
      var currentTime = _this.media.getCurrentTime();
      var annotations = _this
        .getWidgetAnnotations()
        .sortBy(function (_annotation) {
          return _annotation.begin;
        });
      for (var i = 0; i < annotations.length; i++) {
        if (
          annotations[i].begin <= currentTime &&
          currentTime < annotations[i].end
        ) {
          // Found a current annotation - clamp i+offset value to [0, length - 1]
          i = Math.min(annotations.length - 1, Math.max(0, i + offset));
          _this.media.setCurrentTime(annotations[i].begin);
          break;
        }
      }
    }

    /*
     * Propose an export of the widget's annotations
     *
     * Parameter: a list of annotations. If not specified, the widget's annotations will be exported.
     */
    exportAnnotations(annotations) {
      var widget = this;
      if (annotations === undefined) annotations = this.getWidgetAnnotations();
      var $ = jQuery;

      // FIXME: this should belong to a proper serialize/deserialize component?
      var content =
        Mustache.render("[video:{{url}}]\n", { url: widget.media.url }) +
        annotations
          .map(function (a) {
            return Mustache.render(
              "[{{ a.begin }}]{{ a.title }} {{ a.description }}[{{ a.end }}]",
              { a: a }
            );
          })
          .join("\n");

      var el = $("<pre>")
        .addClass("exportContainer")
        .text(content)
        .dialog({
          title: "Annotation export",
          open: function (event, ui) {
            // Select text
            var range;
            if (document.selection) {
              range = document.body.createTextRange();
              range.moveToElementText(this[0]);
              range.select();
            } else if (window.getSelection) {
              range = document.createRange();
              range.selectNode(this[0]);
              window.getSelection().addRange(range);
            }
          },
          autoOpen: true,
          width: "80%",
          minHeight: "400",
          height: 400,
          buttons: [
            {
              text: "Close",
              click: function () {
                $(this).dialog("close");
              },
            },
            {
              text: "Download",
              click: function () {
                a = document.createElement("a");
                a.setAttribute(
                  "href",
                  "data:text/plain;base64," + btoa(content)
                );
                a.setAttribute(
                  "download",
                  "Annotations - " +
                    widget.media.title.replace(/[^ \w]/g, "") +
                    ".txt"
                );
                a.click();
              },
            },
          ],
        });
    }

    /**
     * This method responsible of drawing a widget on screen.
     */
    draw() {
      /* implemented by "sub-classes" */
    }

    //Generates uid
    //source : http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
    generateUid() {
      return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
        /[xy]/g,
        function (c) {
          var r = (Math.random() * 16) | 0,
            v = c === "x" ? r : (r & 0x3) | 0x8;
          return v.toString(16);
        }
      );
    }
  };

  return Widgets;
}