• Jump To … +
    server.coffee src/actionknob.coffee src/autosem.coffee src/bitbucket_kba.coffee src/browserlog.coffee src/datareduction.coffee src/dci.coffee src/dciknob.coffee src/deeseeeye.coffee src/dnd.coffee src/doof.coffee src/formurla-mngr.coffee src/fractalpanel.coffee src/fractalpanel_test.coffee src/front.coffee src/ingestor.coffee src/kbabitbucket.coffee src/knobctrl.coffee src/lib_test.coffee src/nanoclock.coffee src/noodb.coffee src/noodbabstract.coffee src/noodbbrowser.coffee src/noodbbrowser_test.coffee src/noodbsec.coffee src/noorauth.coffee src/noorplugin.coffee src/noorquery.coffee src/noorvm.coffee src/noorwrite.coffee src/quadparser.coffee src/quadparsern3.coffee src/rbac.coffee src/reactor.coffee src/rebase.coffee src/rsrcidx.coffee src/sandboxactions.coffee src/screen_ctx.coffee src/spogi.coffee src/tabular_widget.coffee src/visctrl.coffee src/voicesknob.coffee src/whowhen.coffee src/xsd2native.coffee
  • visctrl.coffee

  • ¶
    VIZ = {} # Classes placed in this object are available to FormURLaManager to run as commands
    
    colorlog = (msg, color, size) ->
        color ?= "red"
        size ?= "1.2em"
        console.log("%c#{msg}", "color:#{color};font-size:#{size};")
    
    noop = () ->
    
    strip_quotes = (s) ->
      s.replace(/\"$/,'').replace(/^\"/,'')
    
    localPartOfCurie = (curie) ->
      curie.split(':')[1]
    
    unquoteLiteral = (v) ->
      depth = 3
      m = true
      while m and depth  # remove up to three sets of nested outer double quotes
        m = v.match(/^"(.*)"$/)
        if m
          v = m[1]
        depth = depth - 1
      v = v.replace(/\\n/g,"\n") # unescape \n
      v = v.replace(/\\\x22/g, "\x22") # unescape baskslash double-quote
      return v
    
    class Description
    
    class Controller
  • ¶

    @loadCSSDependenciesStarted = “fred” # give loadDependenciesIfNeeded() somewhere to stop

      run_formurla: (formurla) ->
  • ¶

    Purpose: Provide a single point of entry for going from screen to screen. At this point Nooron is not yet acting as a full Single Page Application, because it is hitting the server for each new url change, but by funnelling those transitions through this function we can make the change to full SPA gracefully.

        window.location = @make_formurla_into_url_path(formurla)
    
      make_formurla_into_url_path: (formurla) ->
        if not formurla.startsWith('/__/')
          formurla = "/__/" + formurla
        if formurla.includes(',,')
          console.debug("there should not be ',,' in", formurla)
          formurla = formurla.replace(',,', ',')
        return formurla
    
    class VisualizationController extends Controller
  • ¶

    Subclasses are the controllers for particular visualization styles

    For example: AllegationsTable portrays a set of allegations using the table_widget with columns for s,p,o,g,i and a row per spogi.

    SubjectTable uses the table_widget with columns per predicate, rows per subject and “evaluations” being aggregations of objects.

      loadDependenciesStart = (depList, selector, template) ->
  • ¶

    This sucker does not use ‘this’ and works right on the document so can be a function

        injected = ""
        if depList
          prevElem = document.querySelector(selector)
          lines = []
          for depUri in depList
            line = template.replace('REPLACE_URI', depUri)
            lines.push(line)
  • ¶

    $(prevElem).after(line)

          injected = lines.join("\n")
          $(prevElem).after(injected)
        return injected
    
      loadScripts = (scriptList, callback) ->
  • ¶

    WORK IN PROGRESS

        if scriptList
          for script in scriptList
            $.getScript(script).done(callback)
    
      loadDependenciesIfNeeded = (aClass) ->
  • ¶

    We want every class up the hierarchy to run this function and since it is not the function which is overridden at each level (and hence present) it is the CSSDependencies and/or the ScriptDependencies, the normal use of super does not work. The order of operations is: 0) bail if the loading has already happened for this class 1) climb down toward the root of the class hierarchy 2) stop climbing down until the next level down has already been loaded 3) ensure that known CSS and Script resources are loaded, from the most rootward up

    So we must take responsibility for crawling up the prototype tree recursively ourselves:

        if (aClass.hasOwnProperty('loadCSSDependenciesStarted') and aClass.loadCSSDependenciesStarted)
  • ¶

    loading has already happened for aClass and hence for all its parents so bail

          return
        if not aClass.__proto__.__proto__?
  • ¶

    we have gone too far so bail

          return
  • ¶

    We have not been here before and there is deeper to go so load the parent first

        loadDependenciesIfNeeded(aClass.__proto__)
  • ¶

    Then we take care of any dependencies THIS level in the class hierarchy has

        theClass = aClass.constructor
        theClass = aClass
        if not aClass.hasOwnProperty('loadCSSDependenciesStarted')
          theClass.loadCSSDependenciesStarted = true
          if theClass.hasOwnProperty('CSSDependencies')
            injected = loadDependenciesStart(theClass.CSSDependencies,
              "head > link:last-of-type",
              """<link href="REPLACE_URI" rel="stylesheet" type="text/css"/>""") # """
            msg = theClass.constructor.name + " " + injected
            console.info(msg)
  • ¶

    alert(msg) if injected

        if not theClass.hasOwnProperty('loadScriptDependenciesStarted')
          theClass.loadScriptDependenciesStarted = true
          if theClass.hasOwnProperty('ScriptDependencies')
            injected = loadDependenciesStart(theClass.ScriptDependencies,
              "head > script:last-of-type",
              """<script src="REPLACE_URI" type="text/javascript"></script>""") # """
            msg = theClass.constructor.name + " " + injected
            console.info(msg)
  • ¶

    alert(msg) if injected

        return
  • ¶

    CSSDependencies: [‘VisualizationController.css’] ScriptDependencies: [‘VisualizationController.js’]

      constructor: (@formurlaManager, @fracpanel, @args, @kwargs, @expression_ast) ->
  • ¶

    cache a couple of things for local access

        loadDependenciesIfNeeded(this)
        @noodb = @formurlaManager.noodb
        @rootPanel = @formurlaManager.rootPanel
        @expose_to_console()
        @creation_time = (new Date()).toISOString()
        @id = @noodb.synthetic_key_factory_next()
        @content_id = "CID" + @noodb.synthetic_key_factory_next()
        @spogi_count = 0
    
        @respect_discriminator_in_kwargs()
        @register_visualization_with_fracpanel()
        @register_actions()
        if @rootPanel.clientArgs.suppress_fracpanel_buttons_by_default
          @fracpanel.hide_all_buttons()
    
      attach_myNavigator: ->
        @myNavigatorsPanel = @fracpanel.split("south")
        @myNavigator = @formurlaManager.run_formurla_src_in_panel("navigator()", @myNavigatorsPanel)
        @myNavigator.navigatingFor = this
        @myNavigator.begin_navigating()
        return
    
      expose_to_console: ->
        if window.location.origin.includes('localhost')
          window.VIZes ?= []
          window.VIZes.push(this)
    
      localSel: (selector) -> # This is a nicer name than myQrySel which it is a replacement for
        return @myQrySel(selector)
    
      localSelAll: (selector) -> # This is a nicer name than myQrySelAll which it is a replacement for
        return @myQrySelAll(selector)
    
      localize: (selector) ->
  • ¶

    localize selector so it only matches DOM elems within this visualization

        return "#" + @content_id + ' ' + selector
    
      myQrySel: (selector) ->
        return document.querySelector(@localize(selector))
    
      myQrySelAll: (selector) ->
        return document.querySelectorAll(@localize(selector))
    
      contentAppend: (html) ->
        $(@localize('')).append(html)
    
      make_unique_id: (prefix) ->
        return (prefix or "") + @noodb.synthetic_key_factory_next()
    
      submitTransaction: (quads, defaultQuad) ->
  • ¶

    Apply the defaults.

        for q in quads
          q[0] ?= defaultQuad[0]
          q[1] ?= defaultQuad[1]
          q[2] ?= defaultQuad[2]
          q[3] ?= defaultQuad[3]
        @noodb.allege_transaction(quads)
    
      get_writable_graph: ->
        return @graph_uri or @kwargs.g or @formurlaManager.global_args.write_kb
    
      make_checkbox_fieldset: (params) ->
        i = -1
        checkboxes = []
        base_id = @make_unique_id('M__')
        for [value, label] in params.entries
          i++
          id = "#{base_id}_#{i}"
          checkboxes.push("""
            <label for="#{id}">
              <input type="checkbox" id="#{id}" value="#{value}"
                     name="#{params.field_name}">
              #{label}
            </label>
          """) # """
        blob = checkboxes.join("\n")
    
        if legend = params.legend # yes, assign and test in one line
          blob = "<fieldset><legend>#{legend}</legend>#{blob}</fieldset>"
        return blob
    
      respect_discriminator_in_kwargs: ->
        discr_uri_or_qname = @kwargs.discr
        if discr_uri_or_qname
  • ¶

    “not not” forces the value to be a boolean

          discr_uri_or_qname.is_qname = not discr_uri_or_qname.match(/^http\:/)
          @set_discriminator(@kwargs.discr, null, true)
    
      prefixes: # TODO this should GO AWAY see issue #36
        naa: 'http://nooron.com/_/NooronAppArchitecture.ttl#'
        rdfs: 'http://www.w3.org/2000/01/rdf-schema#'
        nrndscr: 'http://nooron.com/_/NooronDiscriminators.ttl#'
    
      expand_prefix: (qname) -> # TODO this should GO AWAY see issue #36
        if (not qname.is_qname? or not qname.is_qname) and (not qname.match(/^http/))
          console.warn("NOT A QNAME", qname)
          return qname
        [prefix, key] = qname.split(':')
        prefrag = @prefixes[prefix]
        if prefrag
          return prefrag + key
        throw new Error "not able to expand_prefix: #{prefix}"
    
      uri_to_qname: (uri) ->
        if not uri?
          return uri
        for prefix, prefrag of @prefixes
          if uri.startsWith(prefrag)
            retval =  "#{prefix}:#{uri.substr(prefrag.length)}"
            retval.is_qname = true
            return retval
        return uri
    
      register_actions: ->
        return
        action_ctlr = @fracpanel.action_button__ctlr
        if action_ctlr?
          say_hello = () -> alert('"Hello!"')
  • ¶

    action_ctlr.make_button_for(‘say “hello”‘, say_hello)

          action_ctlr.make_button_for 'Add New...', @make_add_new_cursors,
            icon: 'plus-circle'
            color: 'green'
    
      get_title: ->
        title = "#{@constructor.name}"
        if @succinctTopic
          return title
        topic = @graph_uri or @kwargs.s or @kwargs.i or @kwargs.g or @kwargs.p or "..."
        return title + " for #{topic}"
    
      default_discriminator_src: "Latest(Latest(Latest(everyone)))"
      default_discriminator_src: "Latest(everyone)"
  • ¶

    default_discriminator_src: “Latest(@A)” Default_discriminator_src: “↻(↻(↻(everyone)))” default_discriminator_src: “Earliest(Earliest(Earliest(everyone)))” default_discriminator_src: “Latest(everyone)” default_discriminator_src: “Latest(@A)” default_discriminator_src: “Latest(Earliest(@A,@Bob))”

      set_discriminator: (discriminator_uri, discriminator_src, suppress_reload) ->
  • ¶

    TODO figure out how to inject criterion_reducer_mapping if specified TODO figure out how to minimally but sufficiently alter the criterion_reducer_mapping

        if discriminator_uri and not discriminator_src
          uri = @expand_prefix(discriminator_uri)
          l = 'rdfs:label'
          l.is_qname = true
          label = @expand_prefix(l)
          console.info("set_discr...()", uri)
          terms = [{s: uri}, {p: label}]
          console.log("  terms:", JSON.stringify(terms))
          labels = @noodb.query(terms)
          if not labels.length
            @noodb.log.error("Discriminator not found: '#{uri}'")
            return
          disc_label_spogi = labels[0]
          discriminator_src = disc_label_spogi.o.getNativeValue()
    
        @discriminator_old = @discriminator
        @discriminator = new Discriminator(discriminator_src, @noodb.get_server_session().user_symbol)
        @discriminator.uri = @uri_to_qname(discriminator_uri)
        @fracpanel.set_knob_label(@fracpanel.voices_button, discriminator_src)
  • ¶

    fracpanel = @visualization_picker_for.fracpanel

        @formurlaManager.replace_discriminator(@, suppress_reload)
    
      default_discriminator_qname: 'nrndscr:latest_everyone'
    
      discriminate: (spogi) ->
        if not @discriminator?
          @set_discriminator(@default_discriminator_qname, @default_discriminator_src, true) # true means suppress_reload
        @discriminator.discriminate(spogi, @noodb)
    
      make_add_new_cursors: =>
        if not TextCursor?
          TextCursor = require('textcursor').TextCursor
  • ¶

    txtcrsr = new TextCursor(‘div’, “click elsewhere”)

        cand = new TextCursor(@localize('.candidateCell'), 'new candidate')
        crit = new TextCursor(@localize('.criterionControl'), 'new criterion')
        evl  = new TextCursor(@localize('td.eval'), 'new evaluation')
    
      get_id: () ->  # this is a unique id for this content
        @id
      receive: ->
        @spogi_count++
      permitKeysDuringQueryFromKWARGS: 'spogi'.split('')
      getQueryFromKWARGS: ->
        qry = []
        for k,v of @kwargs
          if not (k in @permitKeysDuringQueryFromKWARGS)
            continue
          term = {}
          term[k] = v
          qry.push(term)
        return qry
      showDocs: ->
        if @docs
          $("#"+"#{@content_id}").append(@docs)
      perform_subscriptions: () ->
        qry = [g: @args[0]]
  • ¶

    TODO: if kwargs then make qry from them else use args else do not subscribe

        if @kwargs
          qry = @getQueryFromKWARGS()
        queries = [qry]
        subscribe_kb_qrys = @formurlaManager.global_args.subscribe_kb_qrys
        if subscribe_kb_qrys?
          for qry in subscribe_kb_qrys
  • ¶

    alert(“perform_subscriptions() #{JSON.stringify(qry)}”)

            queries.push(qry)
        if not @subscriptions_performed? # ensure perform_subscriptions only runs once
  • ¶

    TODO: ensure that subscriptions are shared among active visualizations

          @subscriptions_performed = []
          for qry in queries
  • ¶

    alert(JSON.stringify(qry))

            @subscriptions_performed.push(qry)
            @noodb.subscribe_visualization(@, qry)
    
      describeDraggable: (draggableElem) ->
  • ¶

    Our purpose is to figure out what kind of thing this draggable represents presumably so a selection of what kind of visualization ought to be performed on it.

    It should probably what type of thing it is: s,p,o,g,i,a,w (a=Author, w=When) Is it an object or a literal? What is it’s literal datatype? Does it have an id? a nid? Is it an aggregated amount?

  • ¶

    All subclasses of VisualizationController should call super (ie, this method) then add their own insight into what was clicked, for only they can know.

        desc = new Description()
        desc.viz = @
        desc.frac_id = @fracpanel.frac_id
        return desc
  • ¶

    To protect visualizations from using html ids which collide with other visualizations showing the same information we need ways to strengthen the ids by localizing them to the visualization. We do this by prepending the local, probably semantically significant id with the id of Sometimes its necessary to recover the semantic content of the

      strong_id_delimiter: "=-="
      strengthen_id: (weak_id) ->
        return "#{@fracpanel.frac_id}#{@strong_id_delimiter}#{weak_id}"
      unstrengthen_id: (strong_id) ->
        return strong_id.replace("#{@fracpanel.frac_id}#{@strong_id_delimiter}","")
    
      make_fracpanel_css_classname: ->
        return 'vis-func-' + this.constructor.func_name
    
      register_visualization_with_fracpanel: () ->
  • ¶

    Introduce this visualization instance to the fracpanel containing it Also pass an identifier to fracpanel to use as a CSS classname on the panels outer div

        @fracpanel.register_visualization(@, @make_fracpanel_css_classname())
    
      cachedScript: (url, options) ->
        options ?= {}
        $.extend(options, {datatype: 'script', cache: true, url: url})
        @setInnerHTML("cachedScript('#{url}', #{JSON.stringify(options)})")
        return $.ajax(options)
    
      setInnerHTML: (html) ->
        @fracpanel.content_area[0].innerHTML = html
    
      format_post_date: (creationDate) ->
        now = new Date()
        if creationDate
          posted_vid_yr = creationDate.getUTCFullYear()
          posted_vid_month = creationDate.getUTCMonth()
          posted_vid_day = creationDate.getUTCDate()
          previous_post_day = new Date(creationDate)
          now_yr = now.getUTCFullYear()
          now_month = now.getUTCMonth()
          now_day = now.getUTCDate()
    
          day_before = now.setDate(now.getDate() - 1)
          day_before = new Date(day_before)
    
          day_before_yr = day_before.getUTCFullYear()
          day_before_month = day_before.getUTCMonth()
          day_before_day = day_before.getUTCDate()
  • ¶

    console.log “The posted date: #{posted_vid_yr} #{posted_vid_month} #{posted_vid_day}” console.log “Today’s date: #{now_yr} #{now_month} #{now_day}” console.log “Yesterday is #{day_before_yr} #{day_before_month} #{day_before_day}”

          if now_yr is posted_vid_yr and now_month is posted_vid_month and now_day is posted_vid_day
            formatted_hours = ('0' + creationDate.getUTCHours()).slice(-2)
            formatted_minutes = ('0' + creationDate.getUTCMinutes()).slice(-2)
            creationDate = "#{formatted_hours}:#{formatted_minutes}"
          else if day_before_yr is posted_vid_yr and day_before_month is posted_vid_month and day_before_day is posted_vid_day
            creationDate = "Yesterday"
          else #display the date only
            formatted_months = ('0' + (creationDate.getUTCMonth()+1)).slice(-2)
            formatted_days = ('0' + creationDate.getUTCDate()).slice(-2)
            creationDate = "#{creationDate.getUTCFullYear()}-#{formatted_months}-#{formatted_days}"
  • ¶

    creationDate = creationDate.toISOString().replace(/T|Z/g, ‘ ‘)

        else
          creationDate = 'unknown'
        return creationDate
    
    class TabularVisualizationController extends VisualizationController
  • ¶

    This is where generic visualization methods specialized for the table_widget live

  • ¶

    CSSDependencies: [‘TabularVisualizationController.css’] ScriptDependencies: [‘TabularVisualizationController.js’]

      constructor: ->
        super
        @graph_uri = @args[0] # TODO remove this hackery once all uses of graph_uri are gone
        tw_args = @build_tw_args()
        @widget = tabular_widget(tw_args)
        @widget.make_widget_here(tw_args)
        @widget.fill_widget_from_ponderspec(tw_args)
        @perform_subscriptions()
    
      build_tw_args: () ->
        if @fracpanel.first_frac # TODO uh, why is this needed? HMM we should have @fracComponent here not @fracpanel
          frame_id = @fracpanel.first_frac.content_area.attr('id')
        else
          frame_id = @fracpanel.content_area.attr('id')
  • ¶

    table_id = @noodb.synthetic_key_factory_next()

        drag_and_dropper = new dnd.DragAndDropOrClickToVisualize(@)
    
        candidate_click_callback = @get_candidate_click_callback() or drag_and_dropper.click
    
        tw_args =
          hide_footer: true
          candidate_click_callback: candidate_click_callback
          frame_id: frame_id
          table_id: @content_id
  • ¶

    pndr_id: frame_id + “_unclear_what_value_is_appropriate” # TODO issue #121

          simple_list: []
          title: @get_title()
          display_candidate_delete_control: false
          display_candidate_link_edit_control: true
          show_edit_button: true
          show_footer: true
          candidate_label_editable: false
  • ¶

    candidate__drop_handler: drag_and_dropper.drop

          candidate__drag_start_handler: drag_and_dropper.drag
          candidate__target: ""
          ponderspec:
            candidates: @get_candidates()
            criteria: @get_criteria()
            data: @get_data()
        return tw_args
    
      get_candidate_click_callback: ->
    
      get_criteria: ->
        example_criteria = [
          label: 'who'
          id: 'who'
          updateable_by: 'system'
          valuetype_id: 'plain' # plain, integer, etc
        ,
          label: 'when'
          id: 'whn'
          updateable_by: 'system'
          valuetype_id: 'datetime' # plain, integer, etc
        ]
        return []
    
      get_candidates: ->
        example_candidates = [
          id: 'google'
          url: 'http://google.com'
          label: "Google"
        ,
          id: 'apple'
          url: 'http://apple.com'
          label: "Apple"
        ]
        return []
    
      get_data: ->
        data_example = [
          ['google', 'who', 'Larry and Sergei']
          ['apple', 'who', 'Steve and Steve']
        ]
        return []
    
      findTRid: (draggableElem) ->
  • ¶

    draggableElem is actually the table row

        thing = draggableElem
        id = thing.getAttribute('id')
  • ¶

    if not id? id = $(thing).parent(“[id]”).attr(‘id’)

        while not id?
          thing = thing.parentElement
          id = thing.getAttribute('id')
        return id
    
      get_qname_from_elem: (elem) ->
  • ¶

    https://www.w3.org/TR/2009/CR-curie-20090116/

        cand_id = @findTRid(elem)
        return @unstrengthen_id(cand_id)
    
      getTableColumnNum0: (draggableElem) ->
  • ¶

    Return the 0-based column index of this cell AKA the criterion idx

        return draggableElem.cellIndex
    
      getOrCreatePredicate: (pred_id, status) ->
        criterion = @widget.get_criterion_by_id(pred_id)
        if not criterion?
          label = @popQueuedLabel(pred_id) or pred_id
          valuetype_id = @popQueuedType(pred_id) or 'plain'
          args =
            label: label
            id: pred_id
            updateable_by: 'system'
            valuetype_id: valuetype_id
          args.cells_visualizable = true
          criterion = @widget.add_criterion(args)
          if status # if the caller wants to know, tell them whether it is newly created
            status.created = true
        return criterion
    
      getOrCreateSubject: (subj_url, subj_label_fallback, status) ->
        candidate = @widget.find_candidate_by_url(subj_url)
        if not candidate?
          weak_id = @unstrengthen_id(subj_url)
          label = @popQueuedLabel(weak_id) or subj_label_fallback or subj_url
          candidate = @widget.add_candidate
            id: subj_url
  • ¶

    url: “allegations(s=#{subj_url})”

            label: label
          if status # if the caller wants to know, tell them whether it is newly created
            status.created = true
        return candidate
    
      popQueuedLabel: (obj_id) ->
        @label_queue ?= {} # initialize if need be
        retval = @label_queue[obj_id]
        if retval?
          delete @label_queue[obj_id]
        return retval
    
      popQueuedType: (obj_id) ->
        @type_queue ?= {} # initialize if need be
        retval = @type_queue[obj_id]
        if retval?
          delete @type_queue[obj_id]
        return retval
    
      tryToLabelCandidateOrCriterion: (label, obj_id) ->
        @label_queue ?= {} # initialize if need be
        candidate = @widget.find_candidate_by_url(obj_id) or
          @widget.find_candidate_by_url(@strengthen_id(obj_id))
        if candidate?
          candidate.label(label)
          candidate.update_cand_visible()
          delete @label_queue[obj_id]
          return
        criterion = @widget.get_criterion_by_id(obj_id)
        if criterion?
          criterion.label(label)
          criterion.update_crit_visible()
          delete @label_queue[obj_id]
          return
  • ¶

    If an already existing candidate or criterion is not found, queue the label for later.

        @label_queue[obj_id] = label # TODO properly handle situation with muliple names
    
      setLabelOnCandidateOrCriterion: (spogi) ->
  • ¶

    We might receive the names for things which haven’t been created yet. So name them or queue them for later naming when they do get created.

    The assumption is that the predicate is ‘rdfs:label’ or ‘rdfs:name’ or such

        label = spogi.o.key()
        label = strip_quotes(label) # strip leading and trailing quotes HACK!!
        obj_id = spogi.s.key()
        @tryToLabelCandidateOrCriterion(label, obj_id)
    
      tryToTypeCandidateOrCriterion: (xsd_type, obj_id) ->  # just Criteria at this point
        @type_queue ?= {}
        valuetype_id = @widget.xsd_to_valuetype(xsd_type)
        msg = "#{obj_id} #{xsd_type} #{valuetype_id}"
        criterion = @widget.get_criterion_by_id(obj_id)
        @noodb.log.info(msg)
        if criterion?
          criterion.set_valuetype(valuetype_id)
          delete @type_queue[obj_id]
          return
  • ¶

    If an already existing candidate or criterion is not found, queue the label for later.

        @type_queue[obj_id] = valuetype_id # TODO properly handle situation with muliple names
    
      setCriterionType: (spogi, xsd_type) ->
        obj_id = spogi.s.key()
        @tryToTypeCandidateOrCriterion(xsd_type, obj_id)
    
    class VIZ.AllegationsTable extends TabularVisualizationController
      @func_name = 'allegations'
      @pretty_name = "Allegation Table"
      id = "allegations_table"
    
      discriminate: (spogi) ->
  • ¶

    this performs NO discrimination at all, perfect when you want to see all the allegations

        return spogi
    
      get_criteria: ->
        return [
          label: 'subject'
          id: 's'
          updateable_by: 'system'
          valuetype_id: 'plain'
          cells_visualizable: true
        ,
          label: 'predicate'
          id: 'p'
          updateable_by: 'system'
          valuetype_id: 'plain'
          cells_visualizable: true
        ,
          label: 'object'
          id: 'o'
          updateable_by: 'system'
          valuetype_id: 'plain'
        ,
          label: 'graph'
          id: 'g'
          updateable_by: 'system'
          valuetype_id: 'plain'
          cells_visualizable: true
        ,
          label: 'when'
          id: 'whn'
          updateable_by: 'system'
          valuetype_id: 'datetime'
        ]
    
      receive: (spogi) ->
        i = spogi.i.key()
    
        candidate_id = @strengthen_id(i)
        candidate = @widget.add_candidate
          id: candidate_id
  • ¶

    url: “allegations(i=#{i})”

          label: i
    
        if candidate and candidate.tr?
  • ¶

    console.log “candidate.tr()”, candidate.tr()

          candidate.tr().setAttribute('id',candidate_id)
    
        @widget.add_datum [candidate_id, 's', spogi.s.key()]
        @widget.add_datum [candidate_id, 'p', spogi.p.key()]
        @widget.add_datum [candidate_id, 'o', spogi.o.getNativeValue()]
        @widget.add_datum [candidate_id, 'g', spogi.g.key()]
        @widget.add_datum [candidate_id, 'whn', spogi.ww().getISO()]
    
      describeDraggable: (draggableElem) ->
  • ¶

    Our purpose in this method is to return a description of cell which was clicked or dragged so it can be determined how to visualize it. These descriptions should be equivalent to english sentences such as: “‘rdfs:label’ is the URI of a predicate.” .thing_value: “rdfs:label” .thing_valuetype: xsd:anyUri OR xsd:qname OR ???? .thing_isa: ‘p’ # a predicate “‘nrn:primordialKB’ is the qname of a graph.” .thing_value: “nrn:primordialKB” .thing_valuetype: xsd:anyUri OR xsd:qname OR ???? .thing_isa: ‘g’ # a graph Weirdly, for the allegations() visualization anyway, the .i of the row the cell is from doesn’t really matter. It seems hard to assert, but the graph doesn’t really matter either.

        desc = super(draggableElem)
        cand_id = @findTRid(draggableElem)
        crit_idx = @getTableColumnNum0(draggableElem)
        weak_cand_id = @unstrengthen_id(cand_id)
        if crit_idx?
          criterion = @widget.get_criterion_by_idx(crit_idx)
          if criterion?
            crit_id = criterion.id()
            if crit_id?
              desc.criterion_id = crit_id
              datum = @widget.get_datum_by_cand_id_and_crit_id(cand_id, crit_id)
              desc.thing_isa = crit_id # eg: s,p,o,g,whn,who,i
              desc.thing_valuetype = datum.get_valuetype()
              desc.thing_value = datum.get_saveable_value()
  • ¶

    desc.id = weak_cand_id # does not matter!!!

        return desc
    
    class AbstractD3VisualizationController extends VisualizationController
  • ¶

    ScriptDependencies: [‘/bower_components/d3/d3.min.js’]

    class GraphVisualizationController extends AbstractD3VisualizationController
      CSSDependencies: [
        '/css/huviz/huvis.css',
        '/css/huviz/huvis_controls.css',
        '/css/huviz/CRRT.css',
        '/css/huviz/gclui.css']
      ScriptDependencies: ['/huviz/huviz.js']
    
      constructor: ->
        super
        @script = decodeURIComponent(@args[0] or @kwargs.script or '')
        div_id = @noodb.synthetic_key_factory_next()
    
        @huviz = require("huviz")
    
        main_panel = @fracpanel
        contentSel = "#" + @fracpanel.content_area.attr("id")
  • ¶

    gclui_id = @make_unique_id(‘gclui_’) @fracpanel.content_area.append(“””

    “””) gclui_id = @make_unique_id(‘gclui_’) @fracpanel.content_area.append(“””
    “””)

  • ¶

    picker_panel = main_panel.split(“east”) controls_panel = picker_panel.split(“south”) controls_panel.TLBR_handles().hide() controls_panel.visualization_button.hide() picker_panel.TLBR_handles().hide() drag_and_dropper = new dnd.DragAndDropOrClickToVisualize(@)

        args =
          hide_fullscreen_button: true
          show_tabs: false
          skip_discover_names: true
          skip_log_tick: true
          make_pickers: false
          huviz_top_sel: contentSel
  • ¶

    drag_start_handler: drag_and_dropper.drag

        @widget = @make_widget(args)
        @widget.updateWindow()
        @noodb.log.info("graph widget:",@widget)
        if @script
  • ¶

    alert “script:\n #{@script}”

          callback = () =>
            @noodb.log.info("running script:",@script)
            @widget.gclui.run_script(@script)
            @noodb.log.info("ran script:",@script)
        @perform_subscriptions(callback)
    
      make_widget: (args) ->
        return new @huviz.Huviz(args)
    
    class VIZ.Graph extends GraphVisualizationController
      @func_name = 'graph'
      @pretty_name: 'Network Diagram'
      constructor: ->
        super
        @make_bare_quint = require("quadparser").make_bare_quint
    
      receive: (spogi) ->
        super
        meth = () =>
          document.dispatchEvent(new CustomEvent("dataset-loaded", {detail: @graph_uri}))
        if @dataset_loaded_timebomb?
          clearTimeout(@dataset_loaded_timebomb)
        @dataset_loaded_timebomb = setTimeout(meth,300)
  • ¶

    @noodb.log.alert “receive()”,spogi

        if @spogi_count % 100 is 0
          @widget.show_state_msg("parsed relation " + @spogi_count)
        o = spogi.o
        o_type =  "http://www.w3.org/1999/02/22-rdf-syntax-ns#object" # rdf:object
        o_value = o.getValue()
    
        if not o.isUri
          o_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#PlainLiteral" # rdf:literal
          o_value = o.getValue()
  • ¶

    o_value = JSON.parse(o_value)

        q =
          s: spogi.s.key()
          p: spogi.p.key()
          o:
            type: o_type
            value: o_value
          g: spogi.i.key()#.replace("nrn:",'')
  • ¶

    @noodb.log.alert(o.isUri, o_type, o_value)

        @widget.add_quad(q)
    
    class VIZ.OntoGraph extends VIZ.Graph
      @func_name = "ontograph"
      @pretty_name: "Ontology Graph"
    
      make_widget: (args) ->
        return new @huviz.OntoViz(args)
    
    class VIZ.SubjectsTable extends TabularVisualizationController
      @func_name = 'subjects'
      @pretty_name = "Subjects Table" # TODO convert " = " to ": "
      id = "subjectss_table" # TODO fix, document or remove this
    
      build_tw_args: () ->
        tw_args = super()
        tw_args.interaction_mode = 'spreadsheet'
        tw_args.cell_value_onchange_handler = @cell_value_onchange_handler
        return tw_args
    
      cell_value_onchange_handler: (evt, cell) =>
        localized_id = cell.cand_obj().id()
        s = @unstrengthen_id(localized_id)
        p = cell.crit_id()
        o = evt.target.value
        g = @kwargs.g
        @noodb.allege(s,p,o,g)
    
      receive: (spogi) ->
  • ¶

    Every unique subj becomes a row. Every unique predicate becomes a column. Every spogi is accumulated in an ‘evaluation’ ie a cell value. For the moment the candidate labels and criterion labels are just the ids.

        s_key = spogi.s.key()
        p_key = spogi.p.key()
        o_key = spogi.o.key()
    
        APPLY_LABELS_AND_TYPES = true
    
        if APPLY_LABELS_AND_TYPES # TODO break this stanza out like...
  • ¶
                          return null if @capture_labels_and_types(spogi)
    
          if p_key.match(/rdfs:type/) # is 'rdfs:type'
            xsd_type = o_key.match(/xsd\:(.*)/)
            if xsd_type
  • ¶

    alert “ignoring #{xsd_type[0]} likely criterion type: “ + spogi.toString()

              @setCriterionType(spogi, xsd_type[1])
            return
  • ¶

    TODO make p_key able to be compared with the prefixed version, eg: ‘foaf:name’

          if p_key in ['foaf:name',
              'http://xmlns.com/foaf/0.1/name',
              'rdfs:label',
              'http://www.w3.org/2000/01/rdf-schema#label']
            @setLabelOnCandidateOrCriterion(spogi)
            return
    
        pred_id = p_key
        criterion_status = {}
        criterion = @getOrCreatePredicate(pred_id, status) # ie the criterion, the column
    
        subj_url = @strengthen_id(s_key)
        subj_label_fallback = s_key
  • ¶

    now make the candidate (AKA the row, the subject)

        candidate_status = {}
        candidate = @getOrCreateSubject(subj_url, subj_label_fallback, candidate_status)
        if candidate
          candidate.tr().setAttribute('id', subj_url)
  • ¶

    populate the evaluation (the cell value)

          @widget.add_datum [subj_url, pred_id, spogi.o.getNativeValue()] # TODO implement aggregation
        else
          @noodb.log.debug "no candidate found for '#{subj_url}': \nSPOGI: #{spogi.toString()}"
    
      describeDraggable: (draggableElem) ->
  • ¶

    Our purpose in this method is to return a description of cell which was clicked or dragged so it can be determined how to visualize it. These descriptions should be equivalent to english sentences such as: “‘rdfs:label’ is the URI of a predicate.” .thing_value: “rdfs:label” .thing_valuetype: xsd:anyUri OR xsd:qname OR ???? .thing_isa: ‘p’ # a predicate “‘nrn:primordialKB’ is the qname of a graph.” .thing_value: “nrn:primordialKB” .thing_valuetype: xsd:anyUri OR xsd:qname OR ???? .thing_isa: ‘g’ # a graph Weirdly, for the allegations() visualization anyway, the .i of the row the cell is from doesn’t really matter. It seems hard to assert, but the graph doesn’t really matter either.

        desc = super(draggableElem)
        cand_id = @findTRid(draggableElem)
        crit_idx = @getTableColumnNum0(draggableElem)
        weak_cand_id = @unstrengthen_id(cand_id)
        if crit_idx?
          criterion = @widget.get_criterion_by_idx(crit_idx)
          if criterion?
            crit_id = criterion.id()
            if crit_id?
              datum = @widget.get_datum_by_cand_id_and_crit_id(cand_id, crit_id)
              desc.thing_isa = 'nrn:evaluationAggregation'
              desc.thing_crit_id = crit_id # eg: s,p,o,g,whn,who,i
              desc.thing_cand_id = weak_cand_id
              desc.thing_graph_id = @kwargs.g
              desc.thing_valuetype = datum.get_valuetype()
              desc.thing_value = datum.get_saveable_value()
        else # it was the row (ie candidate) label itself being dragged
          desc.thing_isa = "TBD:Q_WhatIsTheTypeOfCandidatesInThisTable"
          desc.thing_cand_id = weak_cand_id
          desc.thing_graph_id = @kwargs.g
  • ¶

    desc.id = weak_cand_id # does not matter!!!

        return desc
    
    
    class VIZ.ClassTable extends VIZ.SubjectsTable
      @func_name = 'class' # TODO rename this for safety vs Javascript
      @pretty_name: "Class Instances"
      @docs: "Like subjects() but all rows are instances (or shown as) of the class. WIP"
      id = "class_table"
  • ¶

    For this to work there will need to be a new type of query: any Spogi where the subject of that spogi has a triple

    
    class VIZ.KBsTable extends VIZ.ClassTable
      @func_name = 'kbs'
      @pretty_name: "Knowledge Bases"
      @docs: "All rows are KBs. WIP"
      id = "kbs_table"
    
      make_add_new_cursors: =>
        if not TextCursor?
          TextCursor = require('textcursor').TextCursor
  • ¶

    txtcrsr = new TextCursor(‘div’, “click where you’d like to add”)

        cand = new TextCursor(@localize('.candidateCell'), 'new Knowledge Base')
        crit = new TextCursor(@localize('.criterionControl'), 'new criterion')
        evl  = new TextCursor(@localize('td.eval'), 'new evaluation')
    
    
    class VIZ.ActionTable extends VisualizationController
      @docs : """
      The ActionTable shows Actions (Intransitive) and (Transitive) Verbs.
    
      Columns:
        FontAwesome icon name
        FontAwesome icon recipe (ie layered and colored)
        Unicode character
        Action name (Verb name)
        Verb name template (ala Smalltalk or ObjectC method signature)
          eg: "AddNew:Class:"
          eg: "AddNew:Spogi:"
          eg: "AddNew()owl:Criterion;"
          eg: "AddNew()owl:KB;"
      """
    
    class VIZ.EvaluationsTable extends TabularVisualizationController
      @func_name = 'evaluations'
      @pretty_name = "Evaluations"
      id = "evaulations_table"
    
      get_criteria: ->
        retval = [
          label: 'when'
          id: 'whn'
          updateable_by: 'system'
          valuetype_id: 'datetime'
        ,
          label: 'author'
          id: 'author'
          updateable_by: 'system'
          valuetype_id: 'plain'
        ,
          label: @get_predicate_id() # TODO calculate this like: getLabelForPredicate(p:) where p: is a kwarg
          id: 'predicate'            # this could be the predicate id, but maybe that does not matter
          updateable_by: 'system'
          valuetype_id: 'plain'      # TODO calculate this like: getDataTypeForPredicate(p:)
        ]
    
        if @include_subject_column() # if no subject was specified then show a column for the subjects of the evaluations
          retval.splice 2,0,{
            label: 'subject'
            id: 'subject'
            updateable_by: 'system'
            cells_visualizable: true
            valuetype_id: 'plain'}
    
        return retval
    
      get_title: ->
        topic = ""
        for k in "s,p,o,g,i".split(",")
          if @kwargs[k]?
            if topic
              topic += ", "
            topic += "#{k}=#{@kwargs[k]} "
  • ¶

    topic = @graph_uri or @kwargs.s or @kwargs.i or @kwargs.g or “improve get_title()”

        topic = topic or "improve get_title()"
        return "#{@constructor.name} for (#{topic})"
    
      include_subject_column: ->
        not @kwargs.s?
    
      get_predicate_id: ->
        @kwargs.p
    
      receive: (spogi) ->
        i = spogi.i.key()
    
        @widget.add_candidate
          id: i
  • ¶

    url: “allegations(i=#{i})”

          label: i
        whn = spogi.ww().getISO()
        @widget.add_datum [i, 'whn', whn]
        author = i.split('_')[0]
        @widget.add_datum [i, 'author', author]
        the_eval = spogi.o.getNativeValue()
        @widget.add_datum [i, 'predicate', the_eval]
        if @include_subject_column()
          subj = spogi.s.key()
          @widget.add_datum [i, 'subject', subj]
    
      describeDraggable: (draggableElem) ->
  • ¶

    Our purpose in this method is to return a description of cell which was clicked or dragged so it can be determined how to visualize it. These descriptions should be equivalent to english sentences such as: “‘rdfs:label’ is the URI of a predicate.” .thing_value: “rdfs:label” .thing_valuetype: xsd:anyUri OR xsd:qname OR ???? .thing_isa: ‘p’ # a predicate “‘nrn:primordialKB’ is the qname of a graph.” .thing_value: “nrn:primordialKB” .thing_valuetype: xsd:anyUri OR xsd:qname OR ???? .thing_isa: ‘g’ # a graph Weirdly, for the allegations() visualization anyway, the .i of the row the cell is from doesn’t really matter. It seems hard to assert, but the graph doesn’t really matter either.

    WARNING this was copied from above. MUST BE CHANGED DRAMATICALLY

        desc = super(draggableElem)
        cand_id = @findTRid(draggableElem)
        crit_idx = @getTableColumnNum0(draggableElem)
        weak_cand_id = @unstrengthen_id(cand_id)
        if crit_idx?
          criterion = @widget.get_criterion_by_idx(crit_idx)
          if criterion?
            crit_id = criterion.id()
            if crit_id?
              desc.criterion_id = crit_id
              datum = @widget.get_datum_by_cand_id_and_crit_id(cand_id, crit_id)
              desc.thing_isa = crit_id # eg: s,p,o,g,whn,who,i
              desc.thing_valuetype = datum.get_valuetype()
              desc.thing_value = datum.get_saveable_value()
  • ¶

    desc.id = weak_cand_id # does not matter!!!

        return desc
    
    class VIZ.DiscriminatorMenu extends VIZ.SubjectsTable
      @func_name = 'discriminators'
      @pretty_name = 'Discriminator Menu'
      @docs = """This visualization shows the menu of Discriminators in the
      """
    
      constructor: ->
        super
    
      default_discriminator_src: "Latest(everyone)"
    
      get_title: ->
        return "Pick the voices to hear"
    
      get_candidate_click_callback: ->
        return @replace_discriminator_with_clicked
    
      replace_discriminator_with_clicked: (evt) =>
        @knob_which_controls_me.hide_popup()
        discriminator_uri = @get_qname_from_elem(evt.target)
        @replace_discriminator_by_uri(discriminator_uri)
    
      replace_discriminator_by_uri: (discriminator_uri) ->
        the_viz = @knob_which_controls_me.get_visualization_I_accessorize()
        the_viz.set_discriminator(discriminator_uri)
    
    class VIZ.VisualizationMenu extends VIZ.SubjectsTable
      @func_name = 'visualizations'
      @pretty_name = 'Visualization Menu'
      @docs = """This visualization shows the menu of visualizations in the
      'visualization_menu' in the upper left of every FractalPanel.
    
      It should be distinguished by what it returns.
      What should the criteria be?
      How do purposes, defaults and individual evaluations (preferences?) relate?
      """
    
      constructor: ->
        super
    
      get_title: ->
        return "Pick a Visualization..."
    
      replace_visualization_function_with_clicked: (evt) =>
        @knob_which_controls_me.hide_popup()
        viz_class_name = @get_qname_from_elem(evt.target)
        func_name = 'print'
        if viz_class_name
          viz_class_name = viz_class_name.replace("nrn:","")
          func_name = VIZ[viz_class_name].func_name
        function_src = "print(func_name_is_#{func_name})"
        msg = "clicked #{func_name}"
        fracpanel = @visualization_picker_for.fracpanel # really, both are FractalComponents
        @formurlaManager.replace_function(fracpanel, func_name)
    
      get_candidate_click_callback: ->
        return @replace_visualization_function_with_clicked
    
    class SpecialColumnsTable extends VIZ.SubjectsTable
      @func_name = null # TODO null could be used to signal that it is not callable as a function
      @docs = """Subclasses of SpecialColumnsTable can introduce special processing
      for various columns in the table.
    
      The first motivation was so DCIMenu could cause the naa:font_awesome_icon predicate
      to be excluded as a column but rendered as a font-awesome icon in the label column."""
    
      constructor: ->
        super
        @expand_special_column_handlers()
        console.log @special_column_handlers
    
      receive: (spogi) ->
        p_key = spogi.p.key()
    
        col_handler = @get_special_column_handler(p_key)
  • ¶

    alert(“#{p_key} is #{col_handler}”)

        if col_handler? # if col_handler is defined (ie if there is one)
          if col_handler is false # and it is false, that means SKIP spogis with the p_key
            return
          else
            if typeof(col_handler) is 'function'
              spogi = col_handler(spogi)
              if spogi is false # A col_handler returning false is suppressing the spogi
                return          # or maybe fully handling the spogi itself.
  • ¶

    If we get here the col_handler might have modified the spogi it returns or done something extra with the spogi and returned it for normal handling.

            else
              @log.warning("The col_handler for #{p_key} should be false or a function returning false or a spogi")
        return super(spogi)
    
      get_special_column_handler: (p_key) ->
        retval = @special_column_handlers[p_key]
        return retval or VIZ.SubjectsTable.receive
    
      expand_special_column_handlers: ->
  • ¶

    TODO replace this by handling issue #36 (system for dealing with prefixes)

        @noodb.log.warning("expand_special_column_handlers() should go away with issue #36")
        for k,v of @special_column_handlers
          [prefix, key] = k.split(':')
          prefrag = @prefixes[prefix]
          if prefrag
            xpsn = prefrag + key
            @special_column_handlers[xpsn] = v
  • ¶

    alert(“prefix: #{prefix} key: #{key} –> #{xpsn}”)

    class VIZ.Spreadsheet extends VIZ.SubjectsTable # SpecialColumnsTable
      @func_name = 'spreadsheet'
      @docs = """Spreadsheet is a generic ontology-driven spreadsheet with some inspiration from Improv."""
    
    class VIZ.Timesheet extends VIZ.Spreadsheet
      @resources = """
        https://www.npmjs.com/package/durational
          fromSeconds(seconds), fromString(string), toString(object|integer)
        https://www.npmjs.com/package/repeating-interval
          eg new Interval('R/2016-08-23T04:00:00Z/P1D');
          ie                 start time           period
        https://www.npmjs.com/package/humanize-duration
          humanizeDuration(97320000) // '1 day, 3 hours, 2 minutes'
    
        http://www.datypic.com/sc/xsd/t-xsd_duration.html
        http://books.xmlschemata.org/relaxng/ch19-77073.html
        https://www.npmjs.com/package/parse-xsd-duration
          pxd('P2Y6M5DT12H35M30S') // ==> 79317330
        https://github.com/revington/xsdurationjs
          add or subtract xsd:durations to Date()
        https://www.npmjs.com/package/iso8601-duration
          obj = parse("PT1H30M") obj = {hours: 1, minutes: 30}
          toSeconds(obj) "PT1H30M"
        https://www.npmjs.com/package/date-duration
          Duration .addTo(Date), .subtractFrom(Date)
      """
      @func_name = 'timesheet'
      @docs = """Timesheet is a spreadsheet which initially has hardcoded computed columns and rows."""
    
      constructor: ->
        super
  • ¶

    @vm = @noodb.make_vm(@vm_settings)

        TabularInteractor = require('reactor').TabularInteractor
        @interactor = new TabularInteractor()
        @make_columns()
    
      vm_settings:
        prefix: 'action' # should this be @kwargs.g ???
        caching: true
    
      column_specs: [
          id: 'nrn:workSessionStart'
          datatype: 'dateTime'
          formula: "Date.now() - 10000"
        ,
          id: 'nrn:workSessionEnd'
          datatype: 'dateTime'
          formula: "Date.now()"
        , # ☾ ☽
          id: 'nrn:workSessionDuration'
          formula: "☾nrn:workSessionEnd☽ - ☾nrn:workSessionStart☽"
          datatype: "duration"
      ]
    
      make_columns: ->
        for col in @column_specs
          col.id = col.id.replace(/^nrn\:/, 'http://nooron.com/__/')
          if col.datatype
            @tryToTypeCandidateOrCriterion(col.datatype, col.id)
          criterion = @getOrCreatePredicate(col.id)
          if col.formula
            criterion.interactor(@interactor)
            criterion.formula(col.formula)
    
    class VIZ.DCIMenu extends SpecialColumnsTable
      @func_name = 'dcimenu'
      @pretty_name = 'Action Menu'
      @docs = """This visualization shows the menu of Actions in the 'action_menu'
      in the lower left corner of every FractalPanel.
    
      Test with http://localhost:9998/__/dcimenu(g=nrn:NooronActions)
      """
    
      constructor: ->
        super
        @vm = @noodb.make_vm(@vm_settings)
    
      vm_settings:
        prefix: 'action:'
        caching: true
    
      get_title: ->
        return "Pick an Action or Verb..."
    
      special_column_handlers:
        'naa:font_awesome_icon': false # @prepend_font_awesome_icon
        'naa:css_color_name': false    # @color_icon_with
        'naa:FNC': @register_to_run_the_FNC
    
      register_to_run_the_FNC: (spogi) =>
        fnc_body = spogi.o.key()
        return spogi
    
      run_FNC: (evt) =>
        func_name = @get_qname_from_elem(evt.target)
        try
          if func_name
            func_name = func_name.replace(@vm_settings.prefix, "")
            retval = @noodb.apply_by_name(func_name, @vm)
            if retval?
              alert("#{func_name} returns: #{retval}")
            return retval
        catch e
          alert(e.toString())
          throw e
    
      get_candidate_click_callback: ->
        return @run_FNC
    
    class VegaVisualizationController extends VisualizationController
      CSSDependencies: []
      ScriptDependencies: [
        '/bower_components/vega/vega.js',
        '/bower_components/vega-lite/vega-lite.js']
  • ¶

    This is an Abstract class https://vega.github.io/vega-lite/tutorials/getting_started.html

      constructor: ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
    
        @availablePredicates = {}
        @showDocs()
        @perform_subscriptions()
        @initialize_plottedPredicates()
        @initialize_vlSpec()
        @addResizeListener()
  • ¶

    create select options

        @insertVlControls()
    
    
      addResizeListener: () ->
        $(@fracpanel.content_area).bind("_splitpaneparentresize", @resize_handler)
    
      resize_handler: () =>
        @updateVegaSize()
        @render()
    
      initialize_plottedPredicates: () ->
        console.log ("initialize_plottedPredicates called")
        @plottedPredicates = []
        if @kwargs.x
          console.log ("set kwargs x")
          @plottedPredicates.push(@kwargs.x)
        if @kwargs.y
          @plottedPredicates.push(@kwargs.y)
        if @kwargs.size
          @plottedPredicates.push(@kwargs.size)
        if @kwargs.color
          @plottedPredicates.push(@kwargs.color)
        if @kwargs.text
          @plottedPredicates.push(@kwargs.text)
    
      updateVegaSize: () ->
        @vlSpec.config.cell = @get_panelSize()
        if @vlSpec.plot == 'bar' then @set_barScale()
    
      get_panelSize: () ->
        width: $(@visContent).width() - 200
        height: $(@visContent).height() - 100
    
      set_barScale: () ->
        newBarWidth_x = @get_panelSize().width / @vlSpec.data.values.length - 5
        newBarWidth_y = @get_panelSize().height / @vlSpec.data.values.length - 5
        @vlSpec.encoding.x.scale.bandSize = Math.round(newBarWidth_x)
        @vlSpec.encoding.y.scale.bandSize = Math.round(newBarWidth_y)
    
      initialize_vlSpec: () ->
        console.log("initialize_vlSpec")
        @vlSpec =
          description: "Vega-lite visualization"
          mark: 'point' # value that determines the type of plotting; default 'point'
          encoding:
            x:
              field:  @plottedPredicates[0] or "stub" # URL - or - selected control Selection
              type: "quantitative"
            y:
              field:  @plottedPredicates[1] or "stub" # second numerical option
              type: "quantitative"
            size:
              field:  @plottedPredicates[2] or "stub" # third numerical option
              type: "quantitative"
          config:
            cell: @get_panelSize()
          data:
            values: []
    
        @vlSpec.mark = @plotSet().mark
        @vlSpec.plot = @plotSet().plot
        if @plotSet().encoding.x
          jQuery.extend(@vlSpec.encoding.x, @plotSet().encoding.x)
        if @plotSet().encoding.y
          jQuery.extend(@vlSpec.encoding.y, @plotSet().encoding.y)
        if @plotSet().encoding.size
          @vlSpec.encoding.size = @plotSet().encoding.size
    
      insertVlControls: () ->
        $("#"+"#{@content_id}").append('X: <select name="x" style=""></select> ').change(@changeDimension)
        $("#"+"#{@content_id}").append('Y: <select name="y" style=""></select> ').change(@changeDimension)
        if @vlSpec.plot == 'bubble'
          $("#"+"#{@content_id}").append('Size: <select name="size" style=""></select> ').change(@changeDimension)
    
      render: () ->
        @vgSpec = vl.compile(@getVlSpec()).spec
        if @vlSpec.plot == 'bar' then @set_barScale()
    
        visctlr = @
        vg.parse.spec @vgSpec, (chart) =>
          @view = chart({el: "#"+@content_id}).update()
          @view_data = @view.data()
          return
    
      getEmbedSpec: () ->
        return {
          mode: "vega-lite"
          spec: @getVlSpec()
        }
    
      receivePredicate: (p_key) ->
        @sels = {}
        console.log("init receivePredicate ")
        length_to_name = {1:'x', 2:'y', 3:'size'}
        if not @availablePredicates[p_key]
          @availablePredicates[p_key] = true
          $(@localize("[name='x']")).append("<option value='#{p_key}'>#{p_key}</option>")
          $(@localize("[name='y']")).append("<option value='#{p_key}'>#{p_key}</option>")
          if @vlSpec.plot == 'bubble'
            $(@localize("[name='size']")).append("<option value='#{p_key}'>#{p_key}</option>")
          if @plottedPredicates.length < 3
            @plottedPredicates.push(p_key)
            name = length_to_name[@plottedPredicates.length]
            @changeDimension
              target:
                name: name
                value: p_key
            @sels[name] = p_key
          if @kwargs.x
            @sels.x = @kwargs.x
          if @kwargs.y
            @sels.y = @kwargs.y
          if @kwargs.size
            @sels.size = @kwargs.size
    
          @updateSelectors()
    
      updateSelectors: () ->
          sel = @localize("select[name='x'] option[value='#{@sels.x}']")
          $(sel).prop('selected', true)
          sel = @localize("select[name='y'] option[value='#{@sels.y}']")
          $(sel).prop('selected', true)
          sel = @localize("select[name='size'] option[value='#{@sels.size}']")
          $(sel).prop('selected', true)
    
      changeDimension: (evt) =>
        evt.target.value
        @vlSpec.encoding[evt.target.name].field = evt.target.value
        if @plottedPredicates.length > 1
          @render()
    
      receive: (spogi) ->
        super
  • ¶

    console.log(“receive initiated”)

        s_key = spogi.s.key()
        o_key = spogi.o.key()
        p_key = spogi.p.key()
        if p_key.includes('#')
          p_key = p_key.split('#')[1]
        else if p_key.includes(':')
          p_key = p_key.split(':')[1]
        @receivePredicate(p_key)
    
        found = false
        for record in @vlSpec.data.values
          if record._id is s_key
            found = record
            break
        if not found
          found =
            _id: s_key
          @vlSpec.data.values.push(found)
        found[p_key] = o_key
        @render()
    
       getVlSpec: () ->
         return @vlSpec
    
    class VIZ.ScatterPlot extends VegaVisualizationController
       @example_formurla = "scatter(g=nrn:70sCars)"
       @func_name = 'scatter'
       @pretty_name = "Scatter Plot"
       @docs = "A simple scatter plot with Vega-Lite"
  • ¶

    scatter defaults

       plotSet: () ->
         plot: 'scatter'
         mark: 'circle' # 'circle','square', 'point'
         encoding:
           x:
             type: "quantitative"
           y:
             type: "quantitative"
    
    class VIZ.BubblePlot extends VegaVisualizationController
      @func_name = 'bubble'
      @pretty_name = "Bubble Plot"
      @docs = "A bubble scatter plot (3 dimensions) use of Vega-Lite"
    
      plotSet: () ->
        plot: 'bubble'
        mark: 'point' # 'circle','square', 'point'
        encoding:
          x:
            type: "quantitative"
          y:
            type: "quantitative"
          size: #z dimension - only valid for scatter? (Bubble)
            field:  @plottedPredicates[2] or "stub"
            type: "quantitative"
    
    class VIZ.BarPlot extends VegaVisualizationController
      @func_name = 'bar'
      @pretty_name = "Bar Graph"
      @docs = "Plot of Bar Graph with Vega-lite"
    
      plotSet: () ->
        plot: 'bar'
        mark: 'bar'
        encoding:
          x:
            type: "ordinal"
            scale:
              bandSize: 30
          y:
  • ¶

    type: “ordinal”

            scale:
              bandSize: 30
  • ¶

    console.log (“BarPlot initiated”)

    class VIZ.LinePlot extends VegaVisualizationController
      @func_name = 'line'
      @pretty_name = "Simple Line Plot"
      @docs = "A line plot using Vega-Lite"
    
      plotSet: () ->
        plot: 'line'
        mark: 'line' # 'circle','square', 'point'
        encoding:
          x:
            type: "quantitative"
          y:
            type: "quantitative"
    
    class VIZ.AreaPlot extends VegaVisualizationController
      @func_name = 'area'
      @pretty_name = "Simple Area Plot"
      @docs = "A area plot using Vega-Lite"
    
      plotSet: () ->
        plot: 'area'
        mark: 'area' # 'circle','square', 'point'
        encoding:
          x:
            type: "quantitative"
          y:
            type: "quantitative"
    
    class VIZ.LeafletMap extends VisualizationController
      CSSDependencies: ['/leaflet/dist/leaflet.css']
      ScriptDependencies: ['/leaflet/dist/leaflet.js']
      @func_name = 'map'
      @pretty_name = "Leaflet Map"
      @docs = "Map visualization"
      constructor: ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        $("#"+"#{@content_id}").append('<div id="mapid' + @content_id + '" style="height: ' + @get_panelSize().height + 'px; width: ' + @get_panelSize().width + 'px;overflow:hidden"></div>')
        @showDocs()
        @markerArray = []
        @leafSpec =
          data:
            values: []
        @mapViz = L.map('mapid'+@content_id).setView([0,0], 3)
        L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
        	maxZoom: 19,
        	attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
        ).addTo(@mapViz)
        @defaultMessage()
        @perform_subscriptions()
        @addResizeListener()
        @render_kwargs_points()
    
      render_kwargs_points: ->
        if @kwargs.points?
          point_strings = @kwargs.points.split(';')
          i = 0
          for point_str in point_strings
            i++
            point_args = point_str.split(',')
            lat = point_args.shift()
            long = point_args.shift()
            point_args.unshift(["#{lat},#{long}"])
            point_args[1] ?= false # let automatic stuff happen about the color
            point_args[2] ?= "point-#{i}" # label defaults to 'point-N'
            @render.apply(@, point_args)
        return
    
      defaultMessage: () ->
        L.popup(
          className: 'nodata'
          )
          .setLatLng([0, 0])
          .setContent('<h1>No Geodata found in dataset.</h1>')
          .openOn(@mapViz)
    
      receive: (spogi) ->
  • ¶

    FIXME: indent by standard 2

          s_key = spogi.s.key()
          o_key = spogi.o.key()
          p_key = spogi.p.key()
          p_key = p_key.split(':')[1]
          found = false
          for record in @leafSpec.data.values
            if record._id is s_key
              found = record
              break
          if not found
            found =
              _id: s_key
            @leafSpec.data.values.push(found)
          found[p_key] = o_key
          geoCheck = o_key.split(':') #split off geo coordinantes
          label = found._id.split(':')[1]
          if geoCheck[0] == "geo"
            geoInfo = geoCheck[1].split(';')
            return @render(geoInfo, 'blue', label, found)
    
      render: (geo, color, label, spogi) ->
  • ¶

    FIXME: indent by standard 2 http://leafletjs.com/reference-1.3.0.html

          location = geo[0].split(",")
          uncertainty = if geo[1] then geo[1].split('=')[1] else false
          @mapViz.closePopup()
          if uncertainty
            color = color or 'blue'
            L.circle([location[0],location[1]],
              color: color,
              fillColor: color,
              fillOpacity: 0.1,
              radius: uncertainty
            ).bindPopup(label).addTo(@mapViz)
          else
            marker_options = {title:label, riseOnHover:true, draggable: true}
            if color # is specified then draw a circleMarker
              marker_options.color = color
              marker_options.fillColor = color
              marker_options.fillOpacity = .6
              L.circleMarker([location[0],location[1]],marker_options).bindPopup(label).addTo(@mapViz)
            else
              L.marker([location[0],location[1]],marker_options).bindPopup(label).addTo(@mapViz)
          @markerArray.push([location[0],location[1]]) #create array of markers for map zoom
          @renderMapZoom()
    
      renderMapZoom: () ->
        if @markerArray.length > 0 then @mapViz.fitBounds(@markerArray)
    
      addResizeListener: () ->
        $(@fracpanel.content_area).bind("_splitpaneparentresize", @resize_handler)
    
      resize_handler: () =>
        @bheight = @get_panelSize().height
        @bwidth = @get_panelSize().width
        $("#mapid"+"#{@content_id}").width(@bwidth).height(@bheight)
        @renderMapZoom()
    
      get_panelSize: () ->
        width: $(@visContent).width()
        height: $(@visContent).height()
  • ¶

    Abstract VisualizationControllers are not in VIZ because they are not exported

    class AbstractVideoProvider extends VisualizationController
      default_vidsrc = 'upload'
      form_extension_from_concrete_video_recorder = ""
      DEFUNCT_video_provider_form = """
        <form class="video_provider" method="POST" style="clear:both">
          <fieldset>
            <legend>Provide a Video</legend>
            <div class="recording_status"></div>
            <label for="video_title">Video Title</label>
            <input type="text" name="rdfs:label" id="video_title" size=40
                   value="working title" required/><br/>
    
            <label for="video_description">Description</label>
            <textarea name="schema:description"
                      id="video_description"></textarea>
            <fieldset>
              <legend>Choose where to get video from</legend>
    
              <input type="radio" id="upload" name="vidsrc">
              <label for="upload">Upload a Video</label>
    
              <input type="radio" id="record" name="vidsrc">
              <label for="record">Record a Video</label>
    
              <input type="radio" id="hosted" name="vidsrc">
              <label for="hosted">Hosted Video</label>
    
              <fieldset class="upload_fields" style="display:none">
                <input type="file" name="file_to_upload"/>
              </fieldset>
    
              <fieldset class="hosted_fields" style="display:none">
                <label for="hosted_video_uri">URL of hosted video</label>
                <input type="url" id="hosted_video_uri" size=30
                       name="oa:hasBody"
                       value="https://youtu.be/uImk2RgCq_U"
                       placeholder="eg https://youtu.be/wh4t3v3r"/>
                <details>
                  <summary><i class="fas fa-question-circle"></i></summary>
                  <p>
                    <i class="fab fa-youtube"></i> YouTube
                    urls should be formatted like <code>https://youtu.be/wh4t3v3r</code>
                    as provided by the <button><i class="fas fa-share"></i> Share</button>
                    button rather than like
                    <code>https://youtube.com/watch?v=wh4t3v3r</code>
                    as shown in the location bar.
                  </p>
                  <p>
                    Currently, support for no other hosted video service is provided,
                    though it is planned.
                  </p>
                </details>
              </fieldset>
    
              <fieldset class="record_fields" style="display:none">
                <video class="video_recorder" autoplay muted style="" disabled="disabled"></video>
                <div><i class="fas fa-circle" style="color:red"><input
                    type="button" function="rec-stop"
                    value="Record" style="border-left-color: red;"/></i>
                <div>
              </fieldset>
            </fieldset>
    
            <div class="additions"></div>
            <button type="submit">Submit Video</button>
          </fieldset>
        </form>
      """
      video_provider_form = """
      	<form class="video_provider" method="POST" style="clear:both">
    
      		<div class="mobile_page record_video">
            <div class="local_action_bar"><h3>Record New Video</h3> </div>
      			<fieldset class="record_fields">
      				<video class="video_recorder" autoplay muted disabled="disabled"></video>
      				<div class="rec_controls">
      					<input class="big_rec_btn" type="button" function="rec-stop" value="Record"/>
      					<div class="change_camera_icon" style="display:none">
      						<i class="fas fa-camera"></i><i class="fas fa-sync-alt"></i>
      					</div>
      				</div>
            			<div class="recording_status"></div>
                	</fieldset>
      		</div>
    
          <!-- this is the idea: VIDSRC_video eg 'upload_video' so we can target them with JS -->
      		<div class="mobile_page upload_video">
      			<div class="local_action_bar"><h3>Upload Video</h3> </div>
      			<fieldset class="upload_fields">
                <div class="file_upload_wrap">
                    <label for="file_to_upload" class="custom_file_upload"><span><i class="fas fa-cloud-upload-alt"></i> Upload Video</span><input type="file" name="file_to_upload"/></label>
                </div>
                </fieldset>
      		</div>
      		<div class="mobile_page hosted_video">
            <div class="local_action_bar"><h3>Add Link</h3></div>
      			<fieldset class="hosted_fields">
                  <label for="hosted_video_uri">Add link to Youtube</label>
                  <input type="url" id="hosted_video_uri" size="30"
                         name="oa:hasBody"
                         placeholder="example: https://youtu.be/wh4t3v3r"/>
                  <p>
                    <i class="fab fa-youtube"></i> YouTube urls should be links
                    as provided by the <button><i class="fas fa-share"></i> Share</button> button.
                  </p>
                </fieldset>
      		</div>
      		<div class="mobile_page add_video_details">
      			<div class="select_vid_type">
      				<div class="video_type_selection">
      					<select name="oa:motivatedBy" class="custom_select">
                  <option value="dvrsont:neutral">Neutral</option>
                  <option value="dvrsont:support">Support</option>
      						<option value="dvrsont:checker">Fact Checker</option>
      						<option value="dvrsont:contradiction">Contradiction</option>
      						<option value="dvrsont:custom">Custom</option>
      					</select>
      				</div>
      				<div class="subject_video_thumb tiny_thumb source"></div>
              <div class="subject_video_thumb tiny_thumb comment"></div>
      			</div>
      				<fieldset>
      					<label for="video_title">title</label>
      					<input type="text" name="rdfs:label" id="video_title" size="40"
                    placeholder="please enter your title" required/>
      					<label for="video_description">description</label>
      					<textarea name="schema:description" class="video_description_text"
                    placeholder="please enter your description"></textarea>
      					<!-- <label for="video_keyword_tags">tags <span class="small_text">(Optional)</span></label>
                <textarea name="schema:tags" id="video_keyword_tags"></textarea> -->
      				</fieldset>
      				<fieldset style="display:none">
                <label for="thumbnail">thumbnail</label>
      					default <input type="radio" name="gender" value="default">
                random <input type="radio" name="gender" value="random">
                select <input type="radio" name="gender" value="select">
                upload <input type="radio" name="gender" value="upload">
      				</fieldset>
      				<fieldset style="display:none">
                <label for="privacy">privacy</label>
      					Choose Privacy
      				</fieldset>
    
            	</div>
          <div class="additions"></div>
      		<button type="submit">submit</button>
          </form>
        """
    
      available_vidsrc: ['record', 'upload', 'hosted']
      unavailable_vidsrc: []
    
      ensure_videoProvider: (args) ->
  • ¶

    The videoProvider offers the different ways to provide a video (currently three)

    • upload
    • record
    • hosted
        args ?= {}
        args.hidden ?= false
        if not @videoProvider?
          @build_videoProvider()
          if args.hidden
            $(@videoProvider).hide()
    
      build_videoProvider: ->
        $("#"+"#{@content_id}").append(video_provider_form)
        $(".video_type_selection .custom_select").selectmenu({
          appendTo: ".select_vid_type .video_type_selection"
        })
        @videoProvider = @myQrySel("form.video_provider")
        @videoProviderSubmitButton = @myQrySel("[type='submit']")
        @expose_only_available_vidsrc()
        $("input[name='vidsrc']").click(@on_click_vidsrc) # make them switchable
        $("##{default_vidsrc}").trigger('click') # pick the default vidsrc
        @videoProvider.onsubmit = @on_submit_video_provider
    
      expose_only_available_vidsrc: ->
        for vidsrc in @unavailable_vidsrc
          $(@myQrySel("[for='#{vidsrc}']")).hide()
          $(@myQrySel("##{vidsrc}")).hide()
        for vidsrc in @available_vidsrc
          $(@myQrySel("[for='#{vidsrc}']")).show()
          $(@myQrySel("##{vidsrc}")).show()
        return
    
      on_click_vidsrc: (evt) =>
        priorVidsrc = @currentVidsrc
        nextVidsrc = evt.target.id
        for fieldset in @available_vidsrc
  • ¶

    alert(“#{fieldset} #{nextVidsrc}”)

          if fieldset is nextVidsrc
            $("##{@content_id} fieldset.#{fieldset}_fields").show('slow')
          else
            $("##{@content_id} fieldset.#{fieldset}_fields").hide('slow')
  • ¶

    call the incoming enabler, eg @enable_vidsrc_record()

        @enable_vidsrc(nextVidsrc)
        return
    
      enable_vidsrc: (nextVidsrc) ->
        priorVidsrc = @currentVidsrc
        enabler = @["enable_vidsrc_#{nextVidsrc}"]
        enabler.call(this) if enabler?
        if priorVidsrc
  • ¶

    call the outgoing disabler eg @disable_vidsrc_record()

          disabler = @["disable_vidsrc_#{priorVidsrc}"]
          disabler.call(this) if disabler?
        @currentVidsrc = nextVidsrc
        return
    
      enable_vidsrc_record: ->
        @ensure_videoRecorder()
        $(".video_provider video.video_recorder").attr('disabled', null)
        $(".video_provider :submit").attr('disabled', true)
    
      enable_vidsrc_upload: ->
        $("##{@content_id} .upload_fields [type='file']").attr("required", "required")
      disable_vidsrc_upload: ->
        $("##{@content_id} .upload_fields [type='file']").attr("required", null)
    
      enable_vidsrc_hosted: ->
        $("##{@content_id} .hosted_fields [type='url']").attr("required", "required")
      disable_vidsrc_hosted: ->
        $("##{@content_id} .hosted_fields [type='url']").attr("required", null)
    
      on_submit_video_provider: (evt) =>
  • ¶

    Called at beginning of submission process to figure out which vidsrc to deal with This is the right place for operations which should happen before executing any type of submission.

        evt.preventDefault()
        @show_submitting()
        submitter = @["submit_vidsrc_#{@currentVidsrc}"]
        submitter.call(this, evt) if submitter?
        return
    
      canonicalize_hosted_uri: (uri) ->
  • ¶

    Returns [canonical_url, error_msg] with only one of them having a value

        canonicalYT = 'https://youtu.be/'
        url = new URL(uri)
        if url.hostname is 'youtu.be'
          if (vidId = url.pathname)
            return [canonicalYT + vidId]
        else if url.hostname is 'www.youtube.com'
          if (vidId = url.searchParams.get('v'))
            return [canonicalYT + vidId]
        return [false, 'Unrecognized URL type: ' + uri]
    
      submit_vidsrc_hosted: (event) =>
        form = @myQrySel(".video_provider")
        hosted_uri = form.hosted_video_uri.value
        [canonical_hosted_uri, msg] = @canonicalize_hosted_uri(hosted_uri)
        if msg
          @show_progress_info(msg, true)
          return false
        @on_done_submitting_hosted(canonical_hosted_uri)
        return
    
      on_done_submitting_hosted: (hosted_url) ->
        [full_uri, triples, curie] = @on_done_submitting_hosted_before(hosted_url)
        @on_done_submitting_common(full_uri, triples, curie)
        return
    
      submit_vidsrc_upload: (event) =>
        event.preventDefault()
        form = @myQrySel(".video_provider")
        fd = new FormData()
        fd.append('file', form.file_to_upload.files[0])
        jaxArgs =
          type: 'POST'
          url: '/m/v/save'
          data: fd
          processData: false
          contentType: false
        $.ajax(jaxArgs)
            .done(@on_done_submitting_upload)
            .error(@on_file_upload_error)
    
      on_file_upload_error: (jqxhr, textStatus, err) =>
        @set_recording_status(textStatus)
    
      ensure_videoRecorder: ->
        if not @videoRecorder?
          @build_videoRecorder()
    
      build_videoRecorder: ->
  • ¶

    $(“#”+”#{@content_id}”).append(video_rec_controls)

        @videoRecorder = document.querySelector(@localize('.video_recorder'))
  • ¶

    Do not use ids to select these guys, nooron might have many panes! @record_button = document.querySelector(@localize(“input[value=’record’]”)) - DEPRECATED @stop_recording_button = @myQrySel(“input[value=’stop’]”) - DEPRECATED @stop_recording_button.setAttribute(“disabled”,”disabled”) - DEPRECATED @record_slash_stop_button = @myQrySel(“input[function =’rec-stop’]”)

        @record_slash_stop_button = @myQrySel("input[function='rec-stop']")
    
        if not navigator.mediaDevices
          alert('getUserMedia support required to use this page')
    
        @recordingChunks = []
  • ¶

    Not showing vendor prefixes.

        constraints =
          audio: true
          video:
            width: 640
            height: 480
        streamPromise = navigator.mediaDevices.getUserMedia(constraints)
        streamPromise.then(@resolve_getMediaRecorderStream,
                           @reject_getMediaRecorderStream)
    
      set_recording_status: (str) ->
        @myQrySel(".recording_status").innerHTML = str
    
      resolve_getMediaRecorderStream: (mediaStream) =>
        @mediaRecorder = new MediaRecorder(mediaStream)
        @mediaRecorder.ondataavailable = @onDataAvailable
        @videoRecorder.srcObject = mediaStream
  • ¶

    url = window.URL.createObjectURL(mediaStream) video.src = url

        @record_slash_stop_button.onclick = () =>
          mode = @record_slash_stop_button.getAttribute('value')
          if mode is 'Record'
            @record_slash_stop_button.setAttribute('value', "Stop")
            @mediaRecorder.start()
            @set_recording_status('recorder started')
          else
            @record_slash_stop_button.setAttribute('value', "Record")
            @mediaRecorder.stop()
            $(".big_rec_btn").blur()
            @set_recording_status('recorder stopped')
  • ¶

    aThumbNail = @grabThumbNail()

            @videoRecorder_thumbnail = @grab_thumbnail_from_video('image/jpeg', @videoRecorder)
  • ¶

    @record_button.setAttribute(“value”,”stop”) @stop_recording_button.setAttribute(“value”,”record”)

  • ¶

    @record_button.onclick = () => - DEPRECATED @record_button.setAttribute(“disabled”,”disabled”) @stop_recording_button.removeAttribute(“disabled”) @mediaRecorder.start() @set_recording_status(‘recorder started’) aThumbNail = @grabThumbNail() - DEPRECATED @stop_recording_button.onclick = () => - DEPRECATED @stop_recording_button.setAttribute(“disabled”,”disabled”) @record_button.removeAttribute(“disabled”) @mediaRecorder.stop() @set_recording_status(‘recorder stopped’)

        @videoRecorder.onloadedmetadata = (e) =>
          console.log('onloadedmetadata', e)
        @mediaRecorder.onstop = (e) =>
          console.log('e', e)
          console.log('chunks', @recordingChunks)
          @enable_form_submission()
          @bigVideoBlob = new Blob(@recordingChunks,
                                   {'type' : 'video/webm; codecs=webm' })
          return
    
      enable_form_submission: ->
        $(@videoProviderSubmitButton).prop('disabled',false)
    
      disable_form_submission: ->
        $(@videoProviderSubmitButton).prop('disabled',true)
    
      grab_thumbnail_from_video: (contentType, videoElem) ->
        contentType ?= 'image/jpeg'
        videoElem ?= @videoRecorder
        canvas = document.createElement('canvas')
        canvas.width = videoElem.videoWidth
        canvas.height = videoElem.videoHeight
        canvas.getContext('2d').drawImage(videoElem, 0, 0)
        return canvas.toDataURL(contentType)
    
      make_thumb_uri_from_curie: (videoCurie) ->
        localPart = localPartOfCurie(videoCurie)
        if videoCurie.startsWith('youtu')
          return "https://i.ytimg.com/vi/#{localPart}/sddefault.jpg"
        if videoCurie.startsWith('dvrsvid')
          return "/m/v/#{localPart}.jpg"
        if videoCurie.startsWith('dvrsdata')
          return "/m/v/#{localPart}.jpg"
        console.log("make_thumb_uri_from_curie(#{videoCurie}) returning default")
        return "/vhost/graphics/video_default_thumb.png"
    
      reject_getMediaRecorderStream: (err) =>
        console.log "error: " + err
    
      onDataAvailable: (e) =>
        @recordingChunks.push(e.data)
    
      show_submitting: ->
        submittingProgress = """
          <div class="submit_progress"></div><div class="progress_info">Submitting Video.... <i class="fas fa-spinner fa-spin"></i></div>
          """
        $(".video_provider").after(submittingProgress)
        return
    
      submit_vidsrc_record: (event) =>
        fd = new FormData()
        fd.append('fname', 'test.webm')
        fd.append('data', @bigVideoBlob)
        $.ajax(
          type: 'POST'
          url: '/m/v/save'
          data: fd
          processData: false
          contentType: false
        ).done(@on_done_submitting_record)
         .error(@on_file_upload_error)
    
      reset_video_recording: ->
        @recordingChunks = []
  • ¶

    TODO turn off camera because when we are on PlayMedia the recorder needs to be torn down more after use

        return
    
      on_done_submitting_record: (fullname_w_ext) =>
        [full_uri, triples, curie] = @on_done_submitting_RecUpl_before(fullname_w_ext)
  • ¶

    now we can over-ride any RecUpl triples or set Rec specific ones

  • ¶
    • length
    • width
    • height
    • gps location
    • mimetype
        @reset_video_recording()
        @on_done_submitting_common(full_uri, triples, curie)
        return
    
      on_done_submitting_upload: (fullname_w_ext) =>
        [full_uri, triples, curie] = @on_done_submitting_RecUpl_before(fullname_w_ext)
        @on_done_submitting_common(full_uri, triples, curie)
        return
    
      get_hosted_preds: ->
        return @get_RecUpl_preds()
    
      get_RecUpl_preds: ->
        return ['rdfs:label', 'schema:description']
    
      ttl_value_from_input: (value, input) ->
        type = input.type
        if type in ['text']
          return "\"#{value}\""
        if type in ['textarea']
          return "\"\"\"#{value}\"\"\""
        return value
    
      get_pred_val_entries_for: (predicates) ->
        entries = []
        for curie in predicates
          field_name = curie
          inputs = @myQrySelAll("[name='#{field_name}']")
          if inputs.length
            for input in inputs
              if input.type is 'checkbox' and not input.checked
                continue
              value = input.value
              if value? and value isnt ""
                ttlValue = @ttl_value_from_input(value, input)
                entries.push([curie, ttlValue])
  • ¶

    $(“##{@content_id}”).append(“

    #{curie}
    #{value}
    “)

        return entries
    
      make_hosted_curie_from_fullUri: (fullUri) ->
        youtubeId = fullUri.split('/')[-1]
        return 'youtu:' + youtubeId
    
      on_done_submitting_hosted_before: (hosted_uri, triples) =>
        fullUri = hosted_uri
  • ¶

    #curie = @make_vid_hosted_curie_from_fullUri(fullUri)

        curie = @make_vid_curie_from_fullUri(fullUri)
        triples ?= []
        pred_val_entries = @get_pred_val_entries_for(@get_hosted_preds())
        triples.push([curie, 'rdf:type', 'video:Video']) # REVIEW what should this be for youtube, vimeo etc????
        for [subj, obj] in pred_val_entries
          triples.push([curie, subj, obj])
        return [fullUri, triples, curie]
    
      on_done_submitting_RecUpl_before: (fullname_w_ext, triples) =>
        fullUri = window.location.origin + "/m/v/#{fullname_w_ext}"
        curie = @make_vid_curie_from_fullUri(fullUri)
        triples ?= []
        pred_val_entries = @get_pred_val_entries_for(@get_RecUpl_preds())
        triples.push([curie, 'rdf:type', 'video:Video'])
        for [subj, obj] in pred_val_entries
          triples.push([curie, subj, obj])
        return [fullUri, triples, curie]
    
      make_vid_curie_from_fullUri: (fullUri) ->
  • ¶

    FIXME oh god this is so unprincipled!!!!

        path = fullUri.split('/')
        fname = path.pop()
        fid = fname.split('.')[0]
        if fullUri.includes('youtube') or fullUri.includes('youtu.be')
          prefix = 'youtu'
        else
          prefix = 'dvrsvid'
        return "#{prefix}:#{fid}"
    
      receive_transaction_response: (tranxRes) ->
  • ¶

    After the server has processed an allege_transaction() call it sends a tranxRes which contains at least

        alert(tranxRes.placeHolder2Curie) # what the placeholders became
    
      makePlaceholder: (term) -> # Make Resource Placeholder
  • ¶

    Purpose: Create a placeholder for a resource which will be created on the server side. The purpose of the ‘term’ being provided is so that all the matching uses of a particular term can be replaced on the server side with the same new resource id.

        return "?#{term}?"
    
      XXX_add_form_triples: (triples) ->
        THE_ANNOT = @makePlaceholder('dvrsdata:anno')
        THE_VIDEO = @makePlaceholder('dvrsvid:video')
        THE_ACTUAL_VIDEO = null
        for elem in @videoProvider
          if elem.type is 'fieldset'
            continue
          if elem.name is 'oa:Motivation' and elem.checked
            triples.push([THE_ANNOT, 'oa:Motivation', elem.value])
          if elem.value?
            if elem.name in ['rdf:label', 'schema:description']
              if elem.name in ['oa:hasBody']
                THE_ACTUAL_VIDEO = elem.value
              triples.push([THE_VIDEO, elem.name, "\"\"\"#{elem.value}\"\"\""])
            if elem.name in ['oa:hasBody'] # FIXME only for the HOSTED video source
              triples.push([THE_ANNOT, elem.name, elem.value])
        if THE_ACTUAL_VIDEO?
          for triple in triples
            if triple[0] is THE_VIDEO
              triple[0] = THE_ACTUAL_VIDEO
        return
    
      on_done_submitting_common: (fullUri, triples, curie) ->
        @create_triples_for_transaction(fullUri, triples)
  • ¶

    @add_form_triples(triples) # REVIEW is add_form_triples now redundant????

        defaultQuad = [null, null, null, @get_writable_graph()]
        @submitTransaction(triples, defaultQuad)
        $(".progress_info").html("Video Posted")
        @set_recording_status('Your Video Was Posted!')
  • ¶

    subclasses might go somewhere or close provider pane from here

        return
    
      make_playmedia_formurla: (vidCurie, moreTerms) ->
  • ¶

    moreTerms is an optional list of Formurla terms such as “g=nrn:dvrsvid” without separating commas

        moreTerms = moreTerms or []
        terms = [vidCurie]
        for term in moreTerms
          if term.startsWith(',')
            throw new Error("term '#{term}' should not start with ,")
          terms.push(term)
        return "playmedia(#{terms.join(",")})"
    
      run_playmedia_formurla: (vidCurie) ->
        moreTerms = []
        if (graph = @get_writable_graph())
          moreTerms.push("g=#{graph}")
        formurla = @make_playmedia_formurla(vidCurie, moreTerms)
        @run_formurla(formurla)
        return
    
      create_triples_for_transaction: (fullUri, triples) ->
  • ¶

    Purpose: Marshal the triples which are common to all vidsrc, ie record, upload and hosted. Legend: ?dvrsvid:whatever? # Placeholder for server-side expansion into an actualCurie ?dvrsdata:annot? # Placeholder for server-side expansion into actualCure for the Annotation

                            # Not a blank node because
    

    dvrsvid:BODY_VID # this is the client-side CURIE for the video which IS the reponse

                            # examples:
                                youtu:5pl3XwTSfek    the response is a youtube video
                                dvrsvid:Jusa98H      the response has been uploaded or recorded by a user
    

    dvrsvid:TARGET_VID # this is the client-side CURIE for the video being responded to

                            # examples:
                                youtu:5pl3XwTSfek    the response is a youtube video
                                dvrsvid:Jusa98H      the response has been uploaded or recorded by a user
    

    The Triples: dvrsvid:BODY_VID rdf:type video:Video dvrsvid:BODY_VID rdfs:label “working title” dvrsvid:BODY_VID schema:description “title says it all”

    ?dvrsdata:annot? rdf:type oa:Annotation ?dvrsdata:annot? oa:motivatedBy oa:linking ?dvrsdata:annot? oa:hasBody ?dvrsvid:bodyVideo? # actualCurie not a BNode

    ?dvrsdata:annot? oa:hasTarget ?dvrsvid_:targetBNode? # BNode for Annotation TARGET ?dvrsvid_:targetBNode? oa:hasSource dvrsvid:TARGET_VID # URL of video being responded to ?dvrsvid_:targetBNode? oa:hasSelector ?dvrsvid_:targetSelector? # BLANK NODE for TARGET Selector

    ?dvrsvid_:targetSelector? rdf:type oa:FragmentSelector ?dvrsvid_:targetSelector? dcterms:conformsTo http://www.w3.org/TR/media-frags/ ?dvrsvid_:targetSelector? rdf:value “t=12.0,22.9” # start and stop secs in TARGET

    
        return
  • ¶

    ALL OF THESE ENTRIES ARE REDUNDANT because the equivalents have been provided in recSrc-sensitive way by on_done_submitting_RecUpl_before and friends

  • ¶

    Let’s keep these here for reference on how to grab values this way

  • ¶

    Triples about the video being uploaded, so needed for response and root videos

        targetVidCurie = @video_curie
        bodyVidCurie = @make_vid_curie_from_fullUri(fullUri)
        triples.push([bodyVidCurie, 'rdf:type', 'video:Video'])
        if (val = @videoProvider.querySelector("[name='rdfs:label']")?value)
          triples.push([bodyVidCurie, 'rdfs:label', @n3Quote(val)])
        if (val = @videoProvider.querySelector("[name='schema:description']")?value)
          triples.push([bodyVidCurie, 'schema:description', @n3Quote(val)])
    
      n3Quote: (val) ->
        return "\"#{val}\""
    
      build_triples_for_upload: (videoUrl, triples) ->
        triples ?= []
        return triples
    
      build_triples_for_recording: (videoUrl, triples) ->
        triples ?= []
        return triples
    
      choose_vidsrc_if_indicated: ->
        if (vidsrc = @kwargs.vidsrc) # ie upload, record, hosted
          $('#'+vidsrc).click()
          @choose_provid(vidsrc)
    
      choose_provid: (vidsrc) ->
        $(@videoProvider).show()
        @hide_vidsrc_other_than(vidsrc)
        @show_vidsrc(vidsrc)
        @enable_vidsrc(vidsrc)
        return
    
      hide_vidsrc_other_than: (vidsrc) ->
        for aVidsrc in @available_vidsrc
          if aVidsrc isnt vidsrc
            for elem in [@localSel(".#{aVidsrc}_video")]
              if elem
                elem.setAttribute('style','display:none')
        return
    
      show_vidsrc: (vidsrc) ->
        if (elem = @localSel(".#{vidsrc}_video"))
          elem.setAttribute('style',null)
        return
    
    class VIZ.ProvideVideo extends AbstractVideoProvider
      @func_name = 'provid'
      @pretty_name = "Provide Video"
      @docs = "Record, upload or point at hosted video on the web."
      succinctTopic: true
    
      constructor: ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        @ensure_videoProvider()
        @choose_vidsrc_if_indicated()
        $(@localize('.select_vid_type')).hide() # select_vid_type not relevant when creating a root video
        @attach_myNavigator()
    
      on_done_submitting_common: (fullUri, triples, curie) ->
        super(fullUri, triples, curie)
        @run_playmedia_formurla(curie)
        return
  • ¶

    TODO AutoSem into its own module

    class AutoSem
  • ¶

    ## Purpose: Provide an intermediary between OWL/RDF classes and JavaScript classes so it is easy to deal with semantically driven class structures programmatically. What? A graph of interconnected JS objects, keyed by their @id amasses in dbStore.

    ##

      seqNo = 0
      @_getOrCreateResource: (subjCurie, dbStore, whichClass) ->
  • ¶

    Return the annotation after creating it if need be

        resource = dbStore[subjCurie]
        if not resource?
          resource = new whichClass(subjCurie)
          dbStore[subjCurie] = resource
          @_replaceUses(resource, dbStore)
        return resource
      @_replaceUses: (theObj, dbStore) ->
  • ¶

    Find all the existing _uses and replace theObj.id with theObj

        uses = dbStore._uses[theObj.id] or []
        for [objId, propName] in uses # FIXME ensure uses is deduped
          dbStore[objId]._replaceAnyKV(propName, theObj)
        return
      constructor: (@id) ->
        this["@context"] = @_context
        @_seqNo = seqNo++
  • ¶

    @type = @constructor.name

      _curieToJsonld: {}
      _curie_to_parts: (curie) ->
  • ¶

    Usage: [prefix, local] = thing._curie_to_parts(curie)

        return curie.split(':')
      _curie_without_colons: (curie) ->
  • ¶

    Purpose: @_curie_without_colons(curie) ==> ‘prefix__local’

        prefix_local_pair = @_curie_to_parts(curie)
        return prefix_local_pair.join('__')
      _getProp: (curie) ->
  • ¶

    the values in this list could be obtained by querying noodb for subjects matching: p=rdfs:domain,o=oa:Annotation

        return @_curieToJsonld[curie]
  • ¶

    return curie in [‘oa:hasBody’, ‘oa:hasTarget’, ‘oa:motivatedBy’]

      _replaceAnyKV: (propName, theObj) ->
  • ¶

    Purpose: this[propName] has theObj.id as a value, put theObj in its place

        arrayOrValue = this[propName]
        if arrayOrValue?
          id = theObj.id
          if Array.isArray(arrayOrValue)
            arrayOrValue.forEach (elem, i, array) ->
              if elem is id
                array[i] = theObj
          else
            if this[propName] is id
              this[propName] = theObj
        return
      _setCurieKV: (p_curie, o_val, dbStore) ->
        theKey = @_curieToJsonld[p_curie]
        if not theKey? # no mapping to jsonld is available, so just use the curie
          [prefix, local] = @_curie_to_parts(p_curie)
          if prefix is @_context_prefix
            theKey = local  # eg bodyValue
          else
            theKey = @_curie_without_colons(p_curie) # eg rdf__type
        theObj = dbStore[o_val] or o_val
        if not this[theKey]?
          this[theKey] = theObj
        else
          if this[theKey].constructor.name isnt 'Array'
            this[theKey] = [this[theKey]]
          this[theKey].push(theObj)
        if true # TODO make this if o_val has type 'rdf:object'
  • ¶

    keep a mapping of curies like o_val to the object.id and

          dbStore._uses ?= {}
          dbStore._uses[o_val] ?= []
          dbStore._uses[o_val].push([@id, theKey])
          @_dirtyKeys ?= [] # list of keys which have change since last cleaning of dirt
          @_dirtyKeys.push(theKey)
      _dump: ->
        return JSON.stringify(Object.entries(this))
    
    class VideoSemClass extends AutoSem
      __context: "http://videoontology.org/BOGUS_PATH"
    
    class Video extends VideoSemClass
    
    class AnnotationSemClass extends AutoSem
      _context: "http://www.w3.org/ns/anno.jsonld"
    
    class Annotation extends AnnotationSemClass
      _curieToJsonld:
        'oa:hasBody': 'body'
        'oa:bodyValue': 'bodyValue'
        'oa:hasTarget': 'target'
        'oa:motivatedBy': 'motivation'
        'oa:styledBy': 'stylesheet'
    
      isDisplayable: ->
        return @isBodyDisplayable() and @isTargetDisplayable()
    
      isBodyDisplayable: ->
        if @body?
  • ¶

    if @getBodyCurie().split(‘:’)[0] in [‘youtu’, ‘dvrsvid’] return true

          if typeof @body is 'string'
            if @body.startsWith('dvrsvid:') or @body.startsWith('youtu:')
              return true
          else
            if typeof @body.id is 'string'
              if @body.id.startsWith('dvrsvid:') or @body.id.startsWith('youtu:')
                return true
        return false
    
      isTargetMatch: (curie) ->
        if @target?
          if @target is curie
            return true
          else
            if @target.oa__hasSource?
              if @target.oa__hasSource.id is curie
                return true
        return false
    
      getBodyCurie: ->
        return (typeof @body is 'string' and @body) or (typeof @body.id is 'string' and @body.id)
    
      getTargetLabel: ->
        if (target = @target) and (src = target.oa__hasSource) and (label = src.rdfs__label)
          return (Array.isArray(label) and label[0]) or label # might be array or string
        return ''
    
      getBodyLabel: ->
        retval = ''
        if (body = @body) and
            ((label = body.rdfs__label) or
             (src = body.oa__hasSource) and (label = src.rdfs__label))
          retval = unquoteLiteral((Array.isArray(label) and label[0]) or label) # might be array or string
        return retval
    
      getBodyDescription: ->
        retval = ''
        if (body = @body) and
            ((desc = body.schema__description) or
             (src = body.oa__hasSource) and (desc = src.schema__description))
          retval = unquoteLiteral((Array.isArray(desc) and desc[0]) or desc) # might be array or string
        return retval
    
      getMotivation: ->
        retval = ''
        if (motive = @motivation)
          retval = motive
        return retval
    
    class AbstractAnnotationRecorder extends AbstractVideoProvider
  • ¶

    should provide triples for motivatedBy, hasBody, startBody, stopBody, hasTarget, stopTarget, startTarget, MORE????

  • ¶

    Wonderfully simple demo of video recording and form upload https://gist.github.com/rissem/d51b1997457a7f6dc4cf05064f5fe984

    Multimedia Ontologies for Video Semantics https://pdfs.semanticscholar.org/presentation/d34a/12c811124c347811ade26a4a1f1630101b5b.pdf

    Video Ontology (VidOnt) http://videoontology.org/video.ttl

    wget https://www.sample-videos.com/video/mp4/720/big_buck_bunny_720p_5mb.mp4 /__/playmedia(big_buck_bunny_720p_5mb)

      constructor: ->
        super
        @annotations = {}
    
      receive: (spogi) ->
        super
  • ¶

    TODO: @replace get_motivation_entries with @accumulateLookups(spogi)

        @accumulate(spogi)
    
      dbStore:
        _uses: {}
      accumulate: (spogi) ->
        anObj = null
        p = spogi.p
        p_curie = p.key()
        s_curie = spogi.s.key()
        o_key = spogi.o.key()
        if p_curie is 'rdf:type'
          if o_key is 'oa:Annotation'
            anObj = Annotation._getOrCreateResource(s_curie, @dbStore, Annotation)
          else
            anObj = AnnotationSemClass._getOrCreateResource(s_curie, @dbStore, AnnotationSemClass)
  • ¶

    anObj._setCurieKV(p_curie, o_key, @dbStore)

        if p_curie.startsWith('oa:') # this is going to be interesting!
          annot_pred = Annotation::_getProp(p_curie)
          if annot_pred
  • ¶

    ie the subject is an Annotation

            anObj = AnnotationSemClass._getOrCreateResource(s_curie, @dbStore, AnnotationSemClass)
  • ¶

    anObj._setCurieKV(p_curie, o_key, @dbStore)

        if not anObj
  • ¶

    the subject is NOT an Annotation

          anObj = AnnotationSemClass._getOrCreateResource(s_curie, @dbStore, AnnotationSemClass)
        if anObj
  • ¶

    if p_curie isnt ‘rdf:type’

          anObj._setCurieKV(p_curie, o_key, @dbStore)
          @revisitParents(anObj)
        return anObj
    
      revisitParents: (anObj, onlyReceiveOnce) ->
        onlyReceiveOnce ?= {} # set it at beginning of recursion
        if onlyReceiveOnce[anObj.id]
          return # bail at this point if we have already run revisitParents() on anObj with id
        if @kwargs.dump
          $("##{@content_id}").append("<li><code style='color:red'>#{anObj._dump()}</code></li>")
          window.scrollTo(0,document.body.scrollHeight)
        onlyReceiveOnce[anObj.id] = true
        @receiveAnnotation(anObj)
        for [parentObjId, keyOnParent] in (@dbStore._uses[anObj.id] or [])
          @revisitParents(@dbStore[parentObjId], onlyReceiveOnce)
        return
    
      get_motivation_entries: ->
        terms = [{p: 'rdf:type'},{o: 'oa:Motivation'}] # {g: 'nrn:oa'} or nrn:dvrsont
        entries = []
        resultSet = @noodb.query(terms)
        for quad in resultSet
          val = quad.s.key()
          label = val.replace(/^.*\:/,'')
          entries.push([val,label])
        return entries
    
      create_triples_for_transaction: (fullUri, triples) ->
        super
  • ¶

    This is where triples are created which are only needed in the event that this is a response which is being created.

        targetVidCurie = @video_curie
        bodyVidCurie = @make_vid_curie_from_fullUri(fullUri)
  • ¶

    Triples about the Annotation and hence… Only needed for a response

        annotPlcHldr = @makePlaceholder('dvrsdata:annot')
        triples.push([annotPlcHldr, 'rdf:type', 'oa:Annotation'])
        if (val = @videoProvider.querySelector("[name='rdfs:label']")?value)
          triples.push([annotPlcHldr, 'rdfs:label', @n3Quote(val)])
        if (select = @videoProvider.querySelector("[name='oa:motivatedBy']"))
            if (val = select.options[select.selectedIndex].value)
              triples.push([annotPlcHldr, 'oa:motivatedBy',  val])
        triples.push([annotPlcHldr, 'oa:hasBody', bodyVidCurie])
  • ¶

    Triples which connect the annotation to the response and main videos. Only needed for a response targetBNodePlcHldr = @makePlaceholder(‘dvrsdata_:targetBNode’) # note _: makes it a BNode

        targetBNodePlcHldr = @makePlaceholder('_:targetBNode') # note _: makes it a BNode
        triples.push([annotPlcHldr, 'oa:hasTarget', targetBNodePlcHldr])
        triples.push([targetBNodePlcHldr, 'oa:hasSource', targetVidCurie])
  • ¶

    Triples capturing the fragment selector (ie the region of the target video). Only needed for a response targetSelectorBNodePlcHldr = @makePlaceholder(‘dvrsvid_:targetSelector’) # note _: makes it a BNode

        targetSelectorBNodePlcHldr = @makePlaceholder('_:targetSelector') # note _: makes it a BNode
        triples.push([targetBNodePlcHldr, 'oa:hasSelector', targetSelectorBNodePlcHldr])
        triples.push([targetSelectorBNodePlcHldr, 'rdf:type', 'oa:FragmentSelector'])
        triples.push([targetSelectorBNodePlcHldr, 'dcterms:conformsTo', 'medifrag:'])
        timeRange = "t=#{@rangeBegin_input.val()},#{@rangeEnd_input.val()}"
        triples.push([targetSelectorBNodePlcHldr, 'rdf:value', @n3Quote(timeRange)])
    
      show_progress_info: (msg, fade) ->
        $(".progress_info").html(msg)
        if fade
          $("div.submit_progress").delay( 1000 ).fadeOut( 1400 )#.hide()
          $("div.progress_info").delay( 1000 ).fadeOut( 1400 )#.hide()
    
      hide_provide_widget: ->
        $(".video_provider.playmedia").hide()
        $("div.submit_progress").delay( 1000 ).fadeOut( 1400 )#.hide()
        $("div.progress_info").delay( 1000 ).fadeOut( 1400 )#.hide()
        return
    
    class VIZ.PlayMediaAndAnnotations extends AbstractAnnotationRecorder
  • ¶

    CSSDependencies: [‘/‘] see views/front.html.eco for these. they were loading as text and what about crossorigin?

      DISABLED_ScriptDependencies: [
        "https://unpkg.com/prop-types@15.6/prop-types.min.js" #crossorigin
        "https://unpkg.com/react@16/umd/react.development.js" #crossorigin
        "https://unpkg.com/react-dom@16/umd/react-dom.development.js" #crossorigin
        "/bower_components/d3/d3.min.js"
        '/diversus-flower/index.js'
        ]
    
      @func_name = 'playmedia'
      @pretty_name = "Play Video"
      @docs = "Show a video and its annotations"
  • ¶

    Alternatives to native HTML5 player https://plyr.io/

      video_defaults = {
        comment_video_width:
          max: 250
          min: 150
      }
    
      main_file_player = """
        <video class="main_video" controls curie="CURIE">
          <source src="SRC" type="video/TYP" />
          Your browser does not support the video tag.
        </video>
      """
    
      video_player_controls  = """
      <div class="playvid_flexbox">
        <div class="row main_vid_wrapper">
        </div>
        <div class="row video_details_wrapper">
          <div class="feedback__likes"> Views: 234, Links: 234</div>
          <div class="oa__motivation"></div>
          <div class="main_video_creation_date"></div>
          <h3 class="main_video_title"></h3>
          <p class="main_video_description"></p>
          <div class="fade_handle"><i class="fas fa-sort-down"></i><i class="fas fa-sort-up"></i></div>
        </div>
      </div>"""
  • ¶

    Those wacky little triangles to use as video range setter thumbs https://www.fileformat.info/info/unicode/char/25e2/index.htm https://www.fileformat.info/info/unicode/char/25e3/index.htm ◢ ◣

      video_types = [
        {
        name: 'supporting'
        color: 'green'
        }
        {
        name: 'contradicting'
        color: 'black'
        }
        {
        name: 'assessing'
        color: 'darkblue'
        }
        {
        name: 'questioning'
        color: 'darkgoldenrod'
        }
      ]
      con_video_dispay = ''
      num_vids = 0
    
      available_vidsrc: ['record', 'upload', 'hosted']
  • ¶

    unavailable_vidsrc: [‘hosted’]

      constructor: -> # playmedia(dvrsvid:big_buck_bunny__5mb) # VIZ.PlayMediaAndAnnotations
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        @video_curie = @args[0]
        @displayedAnnotations = {}
        @queuedAnnotations = new Set()
        @perform_subscriptions()
        @subscribe_to_annotations()
        @setInnerHTML(video_player_controls) # TODO this should be made more generic when other than videos desired
        @displayersByCurie = {}
  • ¶

    If the focusedCurie is the root then that is the Web Annotation “Target” ie the thing that all the other petals are about. If the focusedCurie is a petal then the curie represents an Annotation https://www.w3.org/TR/annotation-model/#web-annotation-principles

        @focusedCurie = @video_curie
        @main_displayer = @get_or_create_displayer_for_curie(@video_curie)
        @create_response_controls(@video_curie)
        @create_video_responses_box()
        @show_related_media()
        @ensure_videoProvider({hidden: true})
        @attach_myNavigator()
        window.addEventListener("orientationchange", @orientchange_adjust)
        $("form.video_provider").addClass('playmedia')
        $(".video_details_wrapper").click(@toggle_description_wrapper_size)
        return
    
      toggle_description_wrapper_size: () ->
        $(".video_details_wrapper").toggleClass('full_height')
    
      orientchange_adjust: () ->
        $(".oa__motivation").text(window.orientation)
    
      choose_provid: (vidsrc) ->
        super(vidsrc)
        console.info("PlayMedia.choose_provid(#{vidsrc}) hide flower or scroll down to bottom?")
  • ¶

    hide flower? scroll down to bottom?

      receiveAnnotation: (annot) ->
  • ¶

    Purpose: Every Annotation which arrives (or changes) comes here to be considered for possible display or other processing.

        dump = (msg) =>
          if @kwargs.dump
            $("##{@content_id}").append("<li style='color:orange'>#{msg}</li>")
            window.scrollTo(0,document.body.scrollHeight)
        if annot.rdf__type is "oa:Annotation"
          if not @displayedAnnotations[annot.id]
            if annot.isBodyDisplayable()
              if annot.isTargetMatch(@video_curie)
                @queueAnnotation(annot)
              else dump("not isTargetMatch(#{@video_curie})")
            else dump('not isBodyDisplayable()')
          else dump('not already displayed, WTF????')
        else dump('not Annotation')
        return
    
      queueAnnotation: (annot) ->
        @queuedAnnotations.add(annot)
        @drainQueuedAnnotationsIfReady()
    
      readyToDrainQueuedAnnotations: ->
        main_displayer = @getMainDisplayer()
        return not not main_displayer.video_duration
    
      drainQueuedAnnotationsIfReady: ->
        if not @readyToDrainQueuedAnnotations()
          return
        iterator = @queuedAnnotations.values()
        while (annot = iterator.next().value)
          @displayAnnotation(annot)
          @queuedAnnotations.delete(annot)
        return
    
      displayAnnotation: (annot) ->
        @displayedAnnotations[annot.id] = annot
        @addAnnotationToVisualization(annot)
  • ¶

    @display_response_video_player(annot)

        return
    
      DEFUNCT_display_response_video_player: (annot) ->
        player = @create_response_video_player(annot, "100%")
        vid_box = "<div class='vid_group'>#{player}</div>"
        $(@response_box).append(player)
        @video_play_pause_cntrl = $(@localize(".play-cntrl"))
        @video_play_pause_cntrl.off('click').click(@svg_play_video)
        @video_play_pause_cntrl.dblclick("dblclick", @open_as_featured_video)
        @slide_cntrl = $(@localize(".slide-cntrl"))
        @slide_cntrl.draggable().
            on("mousedown", @start_cntrl_drag).
            on("drag", @update_progress_knob).
            on("mouseup", @end_cntrl_drag)
        $(@localize(".vid-wrap")).
            on("mouseleave", @leave_drag_area) #stop working controls when outside container
        return
    
      on_done_submitting_common: (fullUri, triples, curie) ->
        super(fullUri, triples, curie)
        @hide_provide_widget()
  • ¶

    focus on our contribution?

        return
    
      motivationToColor: (motivation_curie) ->
  • ¶

    [prefix, local] = motivation_curie.split(‘:’) mapping = {‘neutral’: ‘grey’, ‘support’: ‘green’, ‘checker’: ‘blue’, ‘contradiction’: ‘red’, ‘custom’: “yellow”} return mapping[local or ‘neutral’] or ‘orange’

        return 'grey'
    
      addAnnotationToVisualization: (annot) ->
        petalArgs =
          key: annot.id,
          title: annot.body.rdfs__label
          thumbUrl: @make_thumb_uri_from_curie(annot.getBodyCurie())
          stroke: @motivationToColor(annot.getMotivation())
          relPos: .3
        relPos = @getAnnotationStartMomentAsRelPos(annot)
        if not relPos or relPos > 1
          colorlog("#{annot.body.id} has bad relPos #{relPos}")
        else
          petalArgs.relPos = relPos
        @zeFlower.addPetal(petalArgs)
    
      getVideoDurationPromise: (videoElem, thenable) ->
  • ¶

    TASK: convert this so it is based on onloadedmetadata

        if videoElem.duration is Infinity
          console.log(video,"has .duration of ", videoElem.duration)
          video.currentTime = 99999999
          delay = () =>
  • ¶

    HACK delay the restart of the video once the proper duration is discovered

            @play_or_pause_video(videoElem.srcElement)
          setTimeout(delay, 500)
    
      getMainDisplayer: ->
        return @displayersByCurie[@video_curie]
    
      getMainVideoDuration: ->
        displayer = @getMainDisplayer()
        retval = displayer and displayer.video_duration
        if not retval
          colorlog("getMainVideoDuration() failed", 'red', '3em')
        return retval
    
      getAnnotationStartMomentAsRelPos: (annot) ->
        durationOfRoot = @getMainVideoDuration()
        if not durationOfRoot
          console.log("durationOfRoot unavailable")
          return
        if not ((target = annot.target) and
                (selector = target.oa__hasSelector) and
                (selector.dcterms__conformsTo is 'medifrag:') and
                (mediafrag = selector.rdf__value)
                )
          console.log("can not get annot.target.oa__hasSelector.rdf__value for",annot.body.id)
          return
        mediafrag = mediafrag.replace(/\"/g, '') # FIXME these surrounding quotes should already be gone
        mediafrag = mediafrag.replace(/^t\=/, '') # strip leading "t="
        [startSecStr, stopSecStr] = mediafrag.split(',')
        try
          startSec = parseFloat(startSecStr)
        catch e
          console.log("Annotation", annot.body.id, "parseFloat(${startSecStr})", e)
        debugger if not startSec?
        return (startSec / durationOfRoot)
    
      DEFUNCT_start_cntrl_drag: (e) =>
        vid_wrap = e.currentTarget.parentNode.parentNode
        video = vid_wrap.getElementsByTagName('video')
        video = video[0]
        video.pause()
    
      DEFUNCT_end_cntrl_drag: (e) =>
        console.log "mouse in, start dragging"
        vid_wrap = e.currentTarget.parentNode.parentNode
        video = vid_wrap.getElementsByTagName('video')
        video = video[0]
    
      DEFUNCT_leave_drag_area: (e) =>
        console.log "exit and now ignore dragging"
  • ¶

    drop the control handle when outside of vid container

        cntrl = e.currentTarget
        $(cntrl).trigger("mouseup")
    
      update_progress_knob: (e) =>
        x = e.offsetX
        y = e.offsetY
        vid_wrap = e.currentTarget.parentNode.parentNode
  • ¶

    console.log e

        video = vid_wrap.getElementsByTagName('video')
        video = video[0]
  • ¶

    console.log video console.log video.duration + “ “ + video.currentTime

        width = e.currentTarget.parentNode.clientWidth
        vid_offset = width * 0.05
        vid_width = width * 0.9 / 2
        cntrl_radius = vid_width-vid_offset
        centerX = vid_width + vid_offset
        centerY = vid_width + vid_offset
  • ¶

    Cartesian coordinates to polar arc coordinates console.log x + “, “ + y + “ “ + “width: “ + vid_width

        adj_x = x - width/2
        adj_y = width/2 - y
        radians = Math.atan2(adj_y,adj_x )
        arc_pos = radians * (-180/Math.PI) + 90
  • ¶

    console.log adj_x + “, “ + adj_y + “ “ + “rad: “ + radians# + “arc_pos: “ + arc_pos

        cntrl_pos = @polar_to_cartesian(centerX, centerY, cntrl_radius, arc_pos)
        slide_cntrl = e.currentTarget
        slide_cntrl.setAttribute("cy", cntrl_pos.y)
        slide_cntrl.setAttribute("cx", cntrl_pos.x)
        slide_cntrl.setAttribute("r", "7px")
  • ¶

    Calculate the new time

        new_radians = radians + Math.PI
  • ¶

    console.log “radidans: “ + new_radians

        new_arc_pos = radians * (-180/Math.PI) + 90
        if new_arc_pos < 0
          new_arc_pos = new_arc_pos + 360
        new_time = (new_arc_pos / 359.9) * video.duration
  • ¶

    console.log “arc: “ + new_arc_pos + “ -> new time: “ + new_time

        video.currentTime = new_time
    
      DEFUNCT_open_as_featured_video: (e) =>
  • ¶

    TODO - This is pretty ugly. Would be better if video info was from meta-data, I think.

        target = e.target
        video_div = $(target).parent().parent().find("source").attr('src')
        vid_name = video_div.split('.')[0]
        vid_name = vid_name.split('/').pop(-1)
        vidCurie = "dvrsvid:#{vid_name}"
        @run_playmedia_formurla(vidCurie)
        return
  • ¶

    window.location.href = “playmedia(dvrsvid:#{vid_name},g=nrn:dvrsdata)”

      DEFUNCT_create_response_video_player: (annot) ->
        [body_namespace, body_id] = annot.body.id.split(':')
        if annot.motivation?
          motivation = annot.motivation.split(':').pop()
        else
          motivation = ''
        video_info =
          src: body_id
          type: 'webm'
          comm_type: motivation
        @create_video_player_package(video_info)
    
      create_video_displayer: (curie) ->
  • ¶

    Build the right kind of video_player for the type of video this is: video, youtube, vimeo, dailymotion, etc

        displayerRec =
          curie: curie # TODO rename this to annot_or_root_curie
          isRoot: curie is @video_curie
        console.info(curie,"create_video_displayer()")
  • ¶

    TODO clean up this creation date capture

        try
          formatted_creation_date = @format_post_date(curie)
        catch e
          console.log("cannot format_post_date(#{curie})")
        displayerRec.formatted_creation_date = formatted_creation_date or ''
    
        if displayerRec.isRoot # ie curie is the ROOT PETAL or MAIN VIDEO
          displayerRec.video_curie = curie
        else # curie is an Annotation with a body video associated with it
          if (annot = @displayedAnnotations[curie])
            displayerRec.annot = annot
            displayerRec.video_curie = annot.getBodyCurie()
            displayerRec.video_title = annot.getBodyLabel()
            displayerRec.video_description = annot.getBodyDescription()
          else
            throw new Error("failed to find video_curie (aka body.id) for #{curie}")
        is_youtube = displayerRec.video_curie.startsWith('youtu:')
        if is_youtube
  • ¶

    FIXME this should be fancier: @main_youtube_player should instead be in an assoc keyed by curie

          ytPlayer = @create_youtube_player(displayerRec.video_curie, displayerRec)
          displayerRec.elem = ytPlayer.a
          displayerRec.ytPlayer = ytPlayer
          displayerRec.type = 'youtube'
        else
  • ¶

    FIXME this should be fancier: @main_youtube_player should instead be in an assoc keyed by curie

          video = @create_video_player(displayerRec.video_curie, displayerRec)
          video.ondurationchange = (evt) =>
            @change_displayer_duration(evt, displayerRec)
          displayerRec.elem = video
          displayerRec.type = 'video'
        return displayerRec
    
      create_response_controls: (curie) ->
        @main_video_title = @myQrySel(".main_video_title")
        @main_video_description = @myQrySel(".main_video_description")
        @main_video_creation_date = @myQrySel(".main_video_creation_date")
        @main_video_motivation = @myQrySel(".oa__motivation")
        window.addEventListener("resize", @reset_layout)
        return
    
      create_youtube_player: (curie, displayer) ->
  • ¶

    https://developers.google.com/youtube/iframe_api_reference#Loading_a_Video_Player Note that documented technique is to await the onYouTubeIframeAPIReady() call The following could fail as a result of possibly being called before YT.Player exists. If so the use of onYouTubeIframeAPIReady() would make sense.

        video_id = localPartOfCurie(curie)
        html = """<div class="main_youtube_video" curie="#{curie}"></div>"""
        youtube_video = @insert_displayer_html_for_curie_and_class(html, curie, 'main_youtube_video')
  • ¶

    https://developers.google.com/youtube/player_parameters#Parameters consider using params: fs: show fullscreen button? (0/1) controls: suppress youtube controls? (0/1)

        ytpArgs =
          height: 320
          width: 480
          rel: 0 # youtube will still show related vids, but 0 means at least from same channel
  • ¶

    startSeconds: 300 endSeconds: .04 # about one frame

          modestbranding: 1 # simplify youtube branding
          iv_load_policy: 3 # suppress video annotations
          disablekb: 1
          videoId: video_id
          playsinline: 1 # cause iOS devices to NOT go fullscreen when playing
  • ¶

    playerVars: autoPlay: 1

          events:
            onReady: (evt) =>
              @YTPlayer_onReady(evt, displayer)
            onStateChange: (evt) =>
              @YTPlayer_onStateChange(evt, displayer)
        youtube_player = new YT.Player(youtube_video, ytpArgs)
        youtube_player.a.setAttribute('curie', curie)
        return youtube_player
    
      YTPlayer_onStateChange: (evt, displayer) =>
  • ¶

    https://developers.google.com/youtube/iframe_api_reference#Events

        console.log('onStateChange', evt)
        ytp = YT.PlayerState
        toName =
          '-1': 'UNSTARTED'
          '0': 'ENDED'
          '1': 'PLAYING'
          '2': 'PAUSED'
          '3': 'BUFFERING'
          '5': 'CUED'
        ytp = YT.PlayerState
        if evt.data is ytp.PLAYING
          @start_youtube_currentTime_monitor(displayer)
        else if evt.data in [ ytp.PAUSED, ytp.ENDED ]
          @stop_youtube_currentTime_monitor(displayer)
        else
          console.log("YP.PlayerState", evt.data, toName[""+evt.data], 'ignored')
        return
    
      start_youtube_currentTime_monitor: (displayer) ->
        if displayer.currentTime_monitor?
          @stop_youtube_currentTime_monitor(displayer)
        displayer.currentTime_monitor = setInterval(@make_youtube_currentTime_monitor(displayer), 30)
    
      stop_youtube_currentTime_monitor: (displayer) ->
        if displayer.currentTime_monitor?
          clearInterval(displayer.currentTime_monitor)
          delete displayer.currentTime_monitor
    
      make_youtube_currentTime_monitor: (displayer) =>
        return () =>
          currentTime = displayer.ytPlayer.getCurrentTime()
          @update_currentTime_listeners(currentTime or 0, displayer)
          return
    
      play_or_pause_youtube: (ytPlayer, which) ->
  • ¶

    https://developers.google.com/youtube/iframe_api_reference#Playback_status ytPlayer is passed in so this might be either the main video or a response vid

        ytp = YT.PlayerState
        UNSTARTED = -1
        pState = ytPlayer.getPlayerState()
  • ¶

    pState in [ytp.PLAYING]

        paused = pState in [ UNSTARTED, ytp.BUFFERING, ytp.ENDED , ytp.PAUSED, ytp.CUED ]
        which ?= paused and 'play' or 'pause'
        if which is 'play'
          ytPlayer.playVideo()
        if which is 'pause'
          ytPlayer.pauseVideo()
        return
    
      getVideoId: (curie) ->
        curie ?= @video_curie
        return localPartOfCurie(curie)
    
      get_or_create_displayer_for_curie: (curie) ->
        displayerRec = @displayersByCurie[curie]
        if not displayerRec
          content_type = 'video'
          if content_type is 'video'
            displayerRec = @create_video_displayer(curie)
            @displayersByCurie[curie] = displayerRec
        if not displayerRec
          throw new Error("get_or_create_displayer_for_curie() does not know how to deal with #{curie}")
        return displayerRec
    
      insert_displayer_html_for_curie_and_class: (html, curie, css_class) ->
        @myQrySel('.main_vid_wrapper').insertAdjacentHTML('beforeend', html)
        return @myQrySel('[curie="' + curie + '"].' + css_class) # get DOM not jquery obj
    
      create_video_player: (curie, displayer) ->
        subj = curie
        video_id_und_ext = subj.split('.') # FIXME this is sooo weak!
        video_id = @getVideoId(video_id_und_ext[0])
        video_ext = video_id_und_ext[1] or "webm"
        video_fname = "#{video_id}.#{video_ext}"
        video_fullpath = "/m/v/#{video_fname}"
        video_html = main_file_player
          .replace(/CURIE/g, curie)
          .replace(/SRC/g, video_fullpath)
          .replace(/TYP/g, video_ext)
        video_player = @insert_displayer_html_for_curie_and_class(video_html, curie, 'main_video')
        $(video_player).append(
          '<source src="'+video_fullpath.replace('.webm','.mp4')+'"type="video/mp4"/>')
        video_player.ontimeupdate = (evt) =>
  • ¶

    @on_video_time_update(evt, displayer)

          @update_currentTime_listeners(video_player.currentTime or 0, displayer)
        return video_player
    
      change_displayer_duration: (evt, displayer) =>
        displayer.video_duration = evt.target.duration
        @drainQueuedAnnotationsIfReady()
        return
    
      create_video_responses_box: ->
        $("##{@content_id} .playvid_flexbox").
            append("<div id='level2_vids' class='row flower_wrap'>" + con_video_dispay + "</div>")
        @response_box = @localize("#level2_vids")
  • ¶

    @record_video_response_button.click(@start_video_response)

        return
    
      show_related_media: (elem) ->
        elem ?= $('#level2_vids').append('<div class="a_flower"></div>')[0].lastElementChild
        if elem
          @zeFlower = putDiversusFlowerInElemOrId(elem,
            demoModeAfterNoDataSec: -1
            log: noop
            warn: noop
            showThumbnails: true
          )
  • ¶

    see props at: https://github.com/DIVERSUSandTIM/diversus-flower/blob/master/src/index.js#L506

          @zeFlower.setRootPetal
              title: @main_video_title_title
              key: @video_curie
              thumbUrl: @make_thumb_uri_from_curie(@video_curie)
          @zeFlower.setRootClickHandler(@flowerRootClickHandler)
          @zeFlower.setPetalClickHandler(@flowerPetalClickHandler)
          @zeFlower.setPetalDblClickHandler(@flowerPetalDblClickHandler)
        else
          console.error('no elem for flower')
        return
    
      getCurieFromPetal: (petal) ->
        return petal.props.myKey
    
      petalIsFocused: (petal) ->
  • ¶

    The petal represents an Annotation, not a video! The body of the annotation is displayed as the content of a non-root petal. The target of an annotation is the root of flower. The annoations are equivalent to the edges in an arbitrary graph structure. It is important that the distinction between a petal representing an annotation and the content which is to be displayed within the associated petal be maintained. One reason is that the same content (eg the same video) might appear many many times as the body (ie the displayable content) associated with multiple annotations, ie petals. In truth the region of the body (ie the response) https://www.w3.org/TR/annotation-model/#web-annotation-principles

        return @focusedCurie is @getCurieFromPetal(petal)
    
      getFocusedDisplayer: ->
        return @displayersByCurie[@focusedCurie]
    
      focusOnDisplayer: (displayer) ->
        focusedDisplayer = @getFocusedDisplayer()
        @hide_displayer(focusedDisplayer)
        @play_or_pause_displayer(focusedDisplayer, 'pause')
        @focusedCurie = displayer.curie
        @show_displayer(displayer)
        return
    
      flowerRootClickHandler: (evt, rootPetal) =>
        if @petalIsFocused(rootPetal)
          focusedDisplayer = @getFocusedDisplayer()
          @play_or_pause_displayer(focusedDisplayer)
        else
          @focusOnDisplayer(@displayersByCurie[@getCurieFromPetal(rootPetal)])
        return
    
      YTPlayer_onReady: (evt, displayer) =>
        colorlog('YTPlayer_onReady')
  • ¶

    @main_video_duration = evt.target.getDuration()

        displayer.video_duration = evt.target.getDuration()
        @drainQueuedAnnotationsIfReady()
        return
    
      flowerPetalClickHandler: (evt, petal) =>
        console.log("flowerPetalClickHandler() called", evt, petal)
        if @petalIsFocused(petal)
          @play_or_pause_displayer(@getFocusedDisplayer())
        else
          displayer = @get_or_create_displayer_for_curie(@getCurieFromPetal(petal))
          @focusOnDisplayer(displayer)
        return
    
      flowerPetalDblClickHandler: (evt, petal) =>
        curie = @getCurieFromPetal(petal)
        displayer = @get_or_create_displayer_for_curie(curie)
        video_curie = displayer.video_curie
  • ¶

    TODO convert to a transformation of the Flower to make this Petal the Root

        @run_playmedia_formurla(video_curie)
        return
    
      main_video_is_focused: ->
        return @focusedCurie is @video_curie
    
      get_root_video_elem: ->
        return @get_video_elem_by_curie(@video_curie)
    
      get_video_elem_by_curie: (curie) ->
        displayerRec = @get_or_create_displayer_for_curie(curie)
        return displayerRec.elem
    
      hide_displayer: (displayer) ->
        displayer.elem.setAttribute('style', 'display:none')
        return
    
      show_displayer: (displayer) ->
        displayer.elem.setAttribute('style','display:inline')
        @display_metadata(displayer)
        return
    
      display_metadata: (displayer) ->
        $(@main_video_creation_date).text(displayer.formatted_creation_date or '')
        $(@main_video_title).text(displayer.video_title or '')
        $(@main_video_description).text(displayer.video_description or '')
        $(@main_video_motivation).text(displayer.motivation or '')
        return
    
      DEFUNCT_hide_main_video: ->
        if (elem = @get_root_video_elem())
          elem.setAttribute('style','display:none')
        return
    
      DEFUNCT_show_main_video: ->
        if (elem = @get_root_video_elem())
          elem.setAttribute('style','display:inline')
        return
    
      DEFUNCT_hide_secondary_video: (petal) ->
        curie = petal.props.myKey
        if (elem = @get_video_elem_by_curie(curie))
          elem.setAttribute('style','display:none')
        else
          console.log('hide_secondary_video() could not find', curie)
        return
    
      DEFUNCT_show_secondary_video: (petal) ->
        curie = petal.props.myKey
        if (elem = @get_video_elem_by_curie(curie))
          elem.setAttribute('style','display:none')
        else
          console.log('show_secondary_video() could not find', curie)
          @get_or_create_displayer_for_curie(curie)
        return
    
      reset_layout: () => # Resizes comment videos & columns when screen is resized
        width_col = @calculate_cols_width()
        vid_package_width = width_col.px
        width = "#{width_col.pct}%"
        $(".vid-wrap").css("width": width, "padding-top": width)
        startAngle = 0
        endAngle = 359.9
        slideBarArc = @video_slide_bar(vid_package_width, startAngle, endAngle)
        $("path.slide-arc").attr("d",slideBarArc)
    
      DEFUNCT_create_video_player_package: (video_info) -> # Comment videos with formatting and controls
        video_display_id = @make_unique_id("NOOVID_")
        video_html = @build_video_player(video_info)
        vid_package_width =  @calculate_cols_width() #percentage and px width
  • ¶

    console.log video_info.comm_type

        vid_color = @vid_type_color(video_info.comm_type)
        video_svg = @build_svg_controls(vid_package_width.px, vid_color)
        video_player_package =
          "<div id='#{video_display_id}' class='vid-wrap'
          style='width: #{vid_package_width.pct}%; padding-top: #{vid_package_width.pct}%;'>
          #{video_html} #{video_svg} </div>"
    
      calculate_cols_width: () ->
  • ¶

    return column widths

        min = video_defaults.comment_video_width.min
        max = video_defaults.comment_video_width.max
        window_width = window.innerWidth
  • ¶

    console.log window_width

        size_check = window_width / num_vids
        if size_check > max
          cols = window_width / max  + 1
        else if size_check < min
          cols = window_width / min
        else
          cols = window_width / size_check
        col_width_px = window_width / Math.floor(cols)
        col_width_pct = 100 / Math.floor(cols)
        width =
          "px": col_width_px
          "pct": col_width_pct
    
      build_comment_video_grid: (comment_video_items, num_vids) ->
  • ¶

    Based on the width of the window and setting min-size

        video_grid = "<div class='vid-row'>" + comment_video_items + "</div>"
    
      build_video_player: (video_info) ->
  • ¶

    Get the file parameters for the video file name, format type (e.g. video/mp4), height, width, thumbnail file name TODO Test if thumbnail exists, otherwise create image? // File or URL?

        thumbnail = video_info.thumbnail or "video_default_thumb.png"
        thumbnail = "/m/v/" + thumbnail
        vid_url = "/m/v/" + video_info.src
  • ¶

    If the vid_url is missing an extension give it the one from the type TODO clearly this is a violent hack

        guess_ext = not vid_url.match(/\.(webm|mp4)$/)
        mksrc = (url, ext) ->
          return """<source src="#{url}.#{ext}" type="video/#{ext}">"""
        sources = []
        if guess_ext
          sources.push(mksrc(vid_url, 'webm'))
          sources.push(mksrc(vid_url, 'mp4'))
        type = "video/" + video_info.type
        resize = ''
  • ¶

    Set height and width depending on orientation

        if video_info.size? and video_info.size.height > video_info.size.width
          resize = "width: 100%"
        else
          resize = "height: 100%"
  • ¶

    TODO Should set the video type from data

        return """<div class="vid-display vid-level2">
      			<video class="comment-video" preload="metadata" xxposter="#{thumbnail}" style='#{resize}'>
      				#{sources.join("\n")}
                Your browser does not support the video tag.
      			</video>
      	  </div>""" # """ # for emacs
    
      video_playing: (video) =>
        if video.target.duration is Infinity
          console.log "Video has duration set to Infinity"
  • ¶

    set current time to a very large number to force video duration to be reset

          video.target.currentTime = 99999999
          delay = () =>
  • ¶

    HACK delay the restart of the video once the proper duration is discovered

            @play_or_pause_video(video.srcElement)
          setTimeout(delay, 500)
        value = (100 / video.target.duration) * video.target.currentTime
        arc_pos = 359.9 * value/100
        width = video.target.offsetParent.offsetParent.clientWidth
        vid_offset = width * 0.05
        vid_width = width * 0.9 / 2
        cntrl_radius = vid_width-vid_offset
        x = vid_width + vid_offset
        y = vid_width + vid_offset
        slide_cntrl = video.target.parentNode.nextElementSibling.getElementsByClassName("slide-cntrl")[0]
        cntrl_pos = @polar_to_cartesian(x, y, cntrl_radius, arc_pos)
        radius = cntrl_pos.y
        slide_cntrl.setAttribute("cy", cntrl_pos.y)
        slide_cntrl.setAttribute("cx", cntrl_pos.x)
        slide_cntrl.setAttribute("r", "7px")
        cx = cntrl_pos.x
        cy = cntrl_pos.y
        inside = "false"
    
      build_svg_controls: (vid_package_width, vid_color) ->
        startAngle = 0
        endAngle = 359.9
        stroke = "5px"
        slideBarArc = @video_slide_bar(vid_package_width, startAngle, endAngle)
        svg =
          "<svg class='vid-type-circle'>
            <circle class='play-cntrl' cx='50%' cy='50%' r='35%' stroke='none' fill='none'
                pointer-events='visible'/>
            <path class='slide-arc' d='#{slideBarArc}'style='stroke-width: #{stroke};
                stroke: #{vid_color}; fill: none;></path>
      		  <path class='comment-arc'/>
      		  <circle class='slide-cntrl draggable ui-widget-content' cx='0' cy='0' r='0'
                stroke='#{vid_color}' stroke-width= '2px'; fill='#eee'/>
    		</svg>"
    
      vid_type_color: (comm_type) ->
        console.log comm_type
        if not comm_type
          return 'grey'
        color = (record for record in video_types when record.name is comm_type)
        if color
          vid_color = color[0].color
        else
          vid_color = 'pink'
    
      get_motivation_entries: ->
        terms = [{g: 'nrn:oa'},{p: 'rdf:type'},{o: 'oa:Motivation'}]
        entries = [
          ['dvrsont:assessing','assessing'],
          ['dvrsont:contradicting','contradicting'],
          ['dvrsont:questioning','questioning'],
          ['dvrsont:supporting','supporting'],
          ]
        resultSet = @noodb.query(terms)
        for quad in resultSet
          val = quad.s.key()
          label = val.replace(/^.*\:/,'')
          entries.push([val,label])
        return entries
    
      insert_form_additions: ->
  • ¶

    FIXME: This should be refactored so the (rdf:type oa:Motivation) are lazily injected into the form from knowledge, dynamically thanks to the ol hivemind. @insert_motivations_inputs()

        @insert_time_inputs()
        return
    
      insert_motivations_inputs: ->
        additions = $("##{@content_id} .video_provider .additions")
        if not additions.length
          console.warn(".additions not found")
          return
        motivationsFieldset = @make_checkbox_fieldset(
          legend: 'Nature of Response',
          field_name: 'oa:motivatedBy',
          entries: @get_motivation_entries())
        additions.append(motivationsFieldset)
        return
    
      time_inputs: """
          <fieldset class="time_range_set">
            <legend>time range main video</legend>
            <div class="record_time_in">
              <label for="UNIQUE_begin">begin</label>
              <input id="UNIQUE_begin" type="number" step="0.01" min="0" name="rangeBegin" value="0.00"/>
              <button for="UNIQUE_begin" class="begin_lock fas fa-lock-open" type="button"></button>
            </div>
            <div class="record_time_out">
              <label for="UNIQUE_end">end</label>
              <input id="UNIQUE_end" type="number" step="0.01" min="0" name="rangeEnd" value="0.00"/>
              <button for="UNIQUE_end" class="end_lock fas fa-lock-open" type="button"></button>
            </div>
          </fieldset>
          <fieldset class="time_range_set">
            <legend>time range response video</legend>
            <div class="record_time_in">
              <label for="UNIQUE_begin">begin</label>
              <input id="UNIQUE_begin" type="number" step="0.01" min="0" name="rangeBegin" value="0.00"/>
              <button for="UNIQUE_begin" class="begin_lock fas fa-lock-open" type="button"></button>
            </div>
            <div class="record_time_out">
              <label for="UNIQUE_end">end</label>
              <input id="UNIQUE_end" type="number" step="0.01" min="0" name="rangeEnd" value="0.00"/>
              <button for="UNIQUE_end" class="end_lock fas fa-lock-open" type="button"></button>
            </div>
          </fieldset>
    
      """
    
      insert_time_inputs: ->
        additions = $("##{@content_id} .video_provider .additions")
        if not additions.length
          console.warn(".additions not found")
          return
        id = @make_unique_id()
        additions.append(@time_inputs.replace(/UNIQUE/g, id))
        @timeRangeLock2input = {}
    
        @rangeBegin_input = $(@localize("[name='rangeBegin']"))
        @timeRangeLock2input["#{id}_begin"] = @rangeBegin_input
        @rangeBeginLock =  $(@localize(".begin_lock"))
        @rangeBeginLock.on('click', @toggleRangeLock)
    
        @rangeEnd_input  = $(@localize("[name='rangeEnd']"))
        @timeRangeLock2input["#{id}_end"] = @rangeEnd_input
        @rangeEndLock =  $(@localize(".end_lock"))
        @rangeEndLock.on('click', @toggleRangeLock)
        return
    
      toggleRangeLock: (evt) =>
        target = $(evt.target)
        window.thingy = target
        for_id = $(target).attr('for')
        slave = @timeRangeLock2input[for_id]
        newState = not $(slave).prop('disabled')
        $(slave).prop('disabled', newState)
        if newState
          target.removeClass('fa-lock-open')
          target.addClass('fa-lock')
        else
          target.removeClass('fa-lock')
          target.addClass('fa-lock-open')
    
      subscribe_to_annotations: ->
  • ¶

    REVIEW: This is redundant (and maybe not working). See server.coffee setUpVHostOrRedir() read_kb_qrys

        qry = []
        qry.push({g: 'nrn:dvrsdata'}) # FIXME how do you spec a KB?
        qry.push({g: 'nrn:oa'}) # FIXME how do you spec a KB?
        @noodb.subscribe_visualization(@, qry)
    
      receive: (spogi) ->
  • ¶

    console.log spogi.p.key()

        if @kwargs.dump > 1
          $("##{@content_id}").append("<h3 style=\"color:blue\">#{spogi.asTTL().join('  ')} .</h3><br>")
        super(spogi)
        main_video_curie = @args[0]
        s_curie = spogi.s.key()
  • ¶

    TODO move all of this to create_video_displayer

        if s_curie is main_video_curie
          displayer = @get_or_create_displayer_for_curie(s_curie)
          p_curie = spogi.p.key()
          val = unquoteLiteral(spogi.o.literal_value())
          if p_curie is 'rdfs:label'
            displayer.video_title = val
          else if p_curie is 'schema:description'
            displayer.video_description = val
          else if p_curie is 'oa:motivation'
            displayer.motivation = val
          else
            return # so we skip display_metadata()
          if @focusedCurie is s_curie # we do not want to change the display if a petal is focused
            @display_metadata(displayer)
        return
    
      create_video_player_controls: (video) ->
        player_controls = """
        <div id="vc-#{video}">
        </div>""" # """
    
      svg_play_video: (e) =>
        vid_wrap = e.currentTarget.parentNode.parentNode
        video = vid_wrap.getElementsByTagName('video')
        video = video[0]
  • ¶

    console.log “duration:” + video.duration console.log “currentTime:” + video.currentTime

        video.addEventListener("timeupdate", @video_playing)
        @play_or_pause_video(video)
        return
    
      play_or_pause_secondary_video: (video, which) ->
        console.warn("play_or_pause_secondary_video() called, but not implemented")
    
      play_or_pause_displayer: (displayer, which) ->
  • ¶

    Toggle between play and pause or do as indicated by the optional which argument

        if displayer.type is 'video'
          @play_or_pause_video(displayer.elem, which)
        if displayer.type is 'youtube'
          @play_or_pause_youtube(displayer.ytPlayer, which)
        return
    
      play_or_pause_video: (video, which) =>
        which ?= video.paused and 'play' or 'pause'
        if which is 'play'
          video.play()
        if which is 'pause'
          video.pause()
    
      video_slide_bar: (vid_package_width, startAngle, endAngle) ->
  • ¶

    vid_offset = document.getElementsByClassName(‘vid_display’)[0].offsetLeft

        vid_offset = vid_package_width * 0.05
  • ¶

    vid_width = (document.getElementsByClassName(‘vid_display’)[0].offsetWidth)/2

        vid_width = vid_package_width * 0.9 / 2
        cntrl_radius = vid_width-vid_offset
        x = vid_width + vid_offset
        y = vid_width + vid_offset
        @describe_arc(x, y, cntrl_radius, startAngle, endAngle)
    
      polar_to_cartesian: (centerX, centerY, radius, angleInDegrees) =>
      	angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180.0
      	return {
      		x: centerX + (radius * Math.cos(angleInRadians)),
      		y: centerY + (radius * Math.sin(angleInRadians))
        }
    
      describe_arc: (x, y, radius, startAngle, endAngle) ->
  • ¶

    console.log “x: #{x}, y: #{y}, r: #{radius}”

        start = @polar_to_cartesian(x, y, radius, endAngle)
        end = @polar_to_cartesian(x, y, radius, startAngle)
        arcSweep = if endAngle - startAngle <= 180 then "0" else "1"
  • ¶

    console.log “M #{start.x} #{start.y} A #{radius} #{radius} 0 #{arcSweep} 0 #{end.x} #{end.y}”

        arc = "M #{start.x} #{start.y} A #{radius} #{radius} 0 #{arcSweep} 0 #{end.x} #{end.y}"
    
      make_responses_draggable: ->
        DNDer = new dnd.DragAndDropOrClickToVisualize(@)
        thumb_croppers = $(@localize('.thumb-cropper'))
        thumb_croppers.draggable({start: DNDer.drag})
        thumb_croppers.dblclick(DNDer.click) # TODO should make this video the main one
    
      describeDraggable: (sourceElem) ->
        desc = new Description()
        desc.viz = @
        desc.frac_id = @fracpanel.frac_id
        desc.thing_isa = 's'
        desc.thing_valuetype = 'string'
        desc.thing_value = sourceElem.id
        return desc
    
      on_video_time_update: (e, displayer) => # moved body of this method into create_video_player
  • ¶

    TODO If the user has manually adjusted the start or stop time, we should not automatically change it. But for now…

        @update_currentTime_listeners(@main_video.currentTime or 0, displayer)
        return
    
      update_currentTime_listeners: (currentTime, displayer) ->
        @update_response_range_inputs(currentTime)
        @zeFlower.update_currentTime_and_relPos(currentTime, currentTime/displayer.video_duration)
        return
    
      update_response_range_inputs: (currentTime) ->
        if not @rangeBegin_input
          return
        val = (currentTime or 0).toFixed(2)
        if not @rangeBegin_input.prop('disabled')
          @rangeBegin_input.val(val)
        if not @rangeEnd_input.prop('disabled')
          @rangeEnd_input.val(val)
        return
    
      make_response_button_draggable: () ->
        @record_video_response_button.draggable
          axis: 'x'
          XXstart: =>
  • ¶

    alert(‘oink’)

            @main_video.pause()
          XXend: =>
            @main_video.play()
    
        ignore =
          refreshPositions: true
          scroll: false
  • ¶

    snapTolerance: 500

          cursorAt:
            top: 5
            left: 5
          XXXhelper: (e, ui) ->
            elem_to_drag = e.target
            $(elem_to_drag).css('left', e.clientX).css('top', e.clientY)
            return true
    
      build_videoProvider: ->
        super
        @insert_form_additions()
        return
    
      start_video_response: () =>
        @main_video.pause()
        @ensure_videoProvider()
        return true
    
      get_video_played_fraction: (currentTime) ->
        currentTime ?= @main_video.currentTime
        return currentTime / @main_video.duration
    
      set_video_played_fraction: (new_fraction) ->
        newTime = newFraction * @main_video.duration
        @main_video.currentTime = newTime
    
      submit_video_response: () ->
        time_1 = @main_video.currentTime
  • ¶

    https://www.w3.org/TR/annotation-model/

        jsonld_annot =
          body: "http://uri.of.response"
          target:
            source: @get_main_video_uri()
            selector:
              range: "t=npt:10,20" # from sec 10, to sec 20
    
    class VIZ.GraphMedia extends VIZ.PlayMediaAndAnnotations
      @func_name = 'graphmedia'
      @pretty_name = "Graph Media"
      @docs = "like PlayMedia but leveraging HuViz"
      succintTopic: true
    
      CSSDependencies: [
        '/css/huviz/huvis.css',
        '/css/huviz/huvis_controls.css',
        '/css/huviz/CRRT.css',
        '/css/huviz/gclui.css']
      ScriptDependencies: ['/huviz/huviz.js']
    
      show_related_media: (elem) ->
        Huviz = require('huviz').Orlando
        huviz_top_sel = @localize('.a_flower')
        elem ?= $('#level2_vids').append('<div class="a_flower" style="position:relative"></div>')[0].lastElementChild
        if not elem
          throw new Error('can not make a_flower')
        @huviz = new Huviz
          default_node_url: '/huviz/docs/cwrc_logo.png'
          hide_fullscreen_button: true
          huviz_top_sel: huviz_top_sel
          settings:
            fisheye_radius: 0
            fisheye_zoom: 1
            make_nodes_for_literals: false
            show_edges: true
            single_chosen: true
            show_thumbs_dont_graph: true
          show_tabs: false
          stay_square: true
    
        @add_root_node()
    
      add_root_node: ->
  • ¶

    Rightly, when quad.o.value is a url then quad.o.type should equal: http://www.w3.org/1999/02/22-rdf-syntax-ns#object

        @huviz.add_quad
          s: @video_curie
          p: 'rdf:type'
          o: {value: 'video:Video'}
        @huviz.add_quad
          s: @video_curie
          p: 'rdfs:label'
          o: {value: @main_video_title_title}
        @huviz.add_quad
          s: @video_curie
          p: 'foaf:thumbnail'
          o: {value: @make_thumb_uri_from_curie(@video_curie)}
    
      addAnnotationToVisualization: (annot) ->
        label = annot.getBodyLabel()
        edge_label = @huviz.add_quad
          s: annot.id
          p: 'rdfs:label'
          o: {value: label or 'no label'}
        edge_a = @huviz.add_quad
          s: annot.id
          p: 'rdf:type'
          o: {value: 'oa:Annotation'}
        @huviz.add_quad
          s: annot.id
          p: @xxx_get_random_motivation() # TODO make this the actual motivation for this annotation
          o: {value: @video_curie}
        @huviz.add_quad
          s: @video_curie
          p: 'foaf:thumbnail'
          o: {value: @make_thumb_uri_from_curie(@video_curie)}
    
        console.log(label)
  • ¶

    edge_a.subj relPos = @getAnnotationStartMomentAsRelPos(annot) if not relPos or relPos > 1 colorlog(“#{annot.body.id} has bad relPos #{relPos}”) @huviz.add_quad

      xxx_get_random_motivation: ->
        items = ['oa:questioning', 'oa:linking', 'oa:commenting']
        return items[Math.floor(Math.random()*items.length)];
    
    class VIZ.ResearchMedia extends VIZ.PlayMediaAndAnnotations
      @func_name = 'researchmedia'
      @pretty_name = "Research Media"
      @docs = "like PlayMedia but using the Blossom"
      succintTopic: true
  • ¶

    CSSDependencies: [ ‘/css/huviz/huvis.css’, ‘/css/huviz/huvis_controls.css’, ‘/css/huviz/CRRT.css’, ‘/css/huviz/gclui.css’] ScriptDependencies: [‘/blossom/src/index.js’]

      show_related_media: (elem) ->
  • ¶

    This is where we should instantiate the blossom widget See VIZ.GraphMedia for an example of instantiating Huviz and saving that instance as @huviz

        blossom_selector = @localize('.a_flower')
        $('#level2_vids').append('<div class="a_flower" style="position:relative"><H1>blossom goes here</H1></div>')[0].lastElementChild
        @blossom = {}  # Put blossom instance here
        @add_root_node()
    
      add_root_node: ->
  • ¶

    This method just shows how to get at the information about the root petal

        return
  • ¶

    these are the things about the video that the visualization likely needs

        @video_curie
        @main_video_title_title
        @make_thumb_uri_from_curie(@video_curie)
    
      addAnnotationToVisualization: (annot) ->
  • ¶

    as each new Annotation flows into the visualization, possibly real-time, it is received here

        console.info("addAnnotationToVisualization() should do something with incoming Annotations")
        petalArgs =
          key: annot.id,
          title: annot.getBodyLabel()
          thumbUrl: @make_thumb_uri_from_curie(annot.getBodyCurie())
          stroke: @motivationToColor(annot.getMotivation())
          relPos: .3
        relPos = @getAnnotationStartMomentAsRelPos(annot)
        if not relPos or relPos > 1
          colorlog("#{annot.body.id} has bad relPos #{relPos}")
        else
          petalArgs.relPos = relPos
  • ¶

    At this point the petalArgs should feed the blossom as appropriate. This works on initial load or realtime as a new response arrives. I get the sense that the blossom should have all this queued and then triggered just once.

        return
    
    class VIZ.ListMedia extends AbstractVideoProvider
      @func_name = 'listmedia'
      @pretty_name = "List media"
      @docs = "List available media"
      succinctTopic: true
    
      widget_html = """
      <div class="video_list" border=0>
      </div>
      <div class="video_list_background"><h1>Please Login</h1><p>Access through main menu below</p></div>
      """
    
      available_vidsrc: ['record', 'upload', 'hosted']
  • ¶

    unavailable_vidsrc: [‘hosted’]

      constructor: -> # VIZ.ListMedia extends AbstractVideoProvider
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        @renderedVideos = {}
  • ¶

    @ensure_videoProvider()

        @visContent.append(widget_html)
        @video_list_JQ_elem = $(@myQrySel('.video_list'))
        @attach_myNavigator()
        @perform_subscriptions()
    
      receive: (spogi) ->
        super
        @accumulateVideos(spogi)
        return
    
      dbStore:
        _uses: {}
      accumulateVideos: (spogi) ->
  • ¶

    Purpose: We collect the spogi which arrive and use them to reconstitute JS objects with .id which comes from the subj of the triples and property names which come from predicates and property values which come from the objects.

    We then render()

        anObj = null
        p = spogi.p
        p_curie = p.key()
        s_curie = spogi.s.key()
        o_key = spogi.o.key()
        a_Video = false
        a_Video = p_curie is 'rdf:type' and o_key is 'video:Video'
        if a_Video
          anObj = Video._getOrCreateResource(s_curie, @dbStore, Video)
        if not anObj
          anObj = AutoSem._getOrCreateResource(s_curie, @dbStore, VideoSemClass)
        if anObj
          if @kwargs.debug
            @add_to_video_list("<tr><td>#{anObj.constructor.name}</td><td>#{spogi.asTTL().join(' ')}</td></tr>")
          if not a_Video
  • ¶

    skip if “rdf:type video:Video” because that has already been handled

            anObj._setCurieKV(p_curie, o_key, @dbStore) # PREFIX:LOCAL becomes PREFIX__LOCAL
          @renderOrUpdate(anObj)
    
      getListItemId: (vidCurie) ->
        domSafeId = vidCurie.replace(':','__')
        return "listitem__#{domSafeId}"
    
      renderOrUpdate: (vidObj) ->
        if vidObj instanceof Video
          [elem, created] = @getOrCreateVideoListItem(vidObj)
          shouldUpdate = not created
  • ¶

    If a ListItem for vidObj already exists it needs updating. Otherwise it just got created so is already up-to-date.

          if shouldUpdate
            @updateVideoElem(vidObj, elem)
        return
    
      getOrCreateVideoListItem: (vidObj) ->
        elemId = "#" + @getListItemId(vidObj.id)
        elem = @myQrySel(elemId) # TODO cache these in a dict for performance, right?
        created = not elem? # must create if not found
        if created
          html = @render_video(vidObj, @kwargs.g)
          @add_to_video_list(html)
          elem = @myQrySel(elemId)
        return [elem, created]
    
      add_to_video_list: (html) ->
        @video_list_JQ_elem.prepend(html)
        return
    
      updateVideoElem: (vidObj, elem) ->
        for k in vidObj._dirtyKeys or []
          v = vidObj[k]
          if k in ['schema__description', 'rdfs__label']
            $(elem).find("."+k).text(unquoteLiteral(v))
        vidObj._dirtyKeys = [] # we have just cleared them
    
      render_video: (vidObj, graf) ->
        localPart = localPartOfCurie(vidObj.id)
        playmedia_uri = (vidObj) =>
          moreTerms = []
          if graf
            moreTerms.push("g=#{graf}")
          return @make_formurla_into_url_path(@make_playmedia_formurla(vidObj.id, moreTerms))
        try
          creationDate = @noodb.extractDateFromCurie(vidObj.id)
          prettyCreationDate = @format_post_date(creationDate)
        catch e
          console.log('unable to make prettyCreationDate for',vidObj.id)
          prettyCreationDate = ''
    
        thumb = """<img src="#{@make_thumb_uri_from_curie(vidObj.id)}"/>"""
        return """
            <div id="#{@getListItemId(vidObj.id)}" class="video_item">
              <div class="video_image">
                <a href="#{playmedia_uri(vidObj)}">
              #{thumb}
                </a>
              </div>
              <a class="description_link_box" href="#{playmedia_uri(vidObj)}"></a>
              <div class="video_description">
                <div class="feedback__likes">Links: 0, Views: 0</div>
                <div class="creation__date">#{prettyCreationDate}</div>
                <h4><a class="rdfs__label" href="#{playmedia_uri(vidObj)}">#{vidObj.rdfs__label or ''}</a></h4>
                <div class="schema__description">#{vidObj.schema__description or ''}</div>
                <div class="uploader__profile">Anonymous</div>
                <div class="uploader_icon">A</div>
              </div>
            </div>
        """ # emacs coffee-mode needs this -> "
    
      display_new_video: (subj, graf) ->
        return @add_to_video_list(@render_video(subj, graf))
    
      on_done_submitting_common: (fullUri, triples, curie) ->
        super(fullUri, triples, curie)
        alert('now close provider pane')
        @run_playmedia_formurla(curie)
        return
    
    class VIZ.AppNavigator extends VisualizationController
  • ¶

    FIXME move this out into a template (or react component?) in the VHost

      @func_name = "navigator"
      @pretty_name = "App Navigator"
      @docs = "Navigation visualizaton for websites and applications (incl. mobile)"
      widget_html = """
      <div class="navigation_wrap" border=0>
        <div class="app_logo">
          <a href="/"><img src="/vhost/graphics/diversus_logo.svg" alt="Diversus Logo"></a>
        </div>
        <div class="app_action_menu"><i class="fas fa-chevron-left"  style="visibility:hidden"></i><i class="fas fa-plus add_new_video"></i><i class="fas fa-chevron-right" style="visibility:hidden"></i></div>
        <div class="nav_menu">
          <div class="mobile_nav_icon"><i class="fas fa-bars"></i></div>
        </div>
    
        <div class="mobile_add_video_wrap">
          <ul class="main_nav_options">
            <li class="choose-provid-record">
              <div class="nav_icon"><i class="fas fa-video"></i></div>Camera</li>
            <li class="choose-provid-hosted">
              <div class="nav_icon"><i class="fab fa-youtube"></i></div>YouTube</li>
            <li class="choose-provid-upload">
              <div class="nav_icon"><!--i class="fas fa-grip-horizontal"></i--><i class="fas fa-cloud-upload-alt"></i></div>Upload</li>
            <!--
            <li><div class="nav_icon"><i class="far fa-images"></i></div>Media Library</li>
              -->
            <li><div class="nav_icon cancel_button">Cancel</li>
          </ul>
        </div>
    
        <div class="mobile_nav_menu_wrap">
          <div class="user_nav_wrap">
            <div class="user_image"><img src=""></div>
            <div class="user_info">
              <h2>Name of User</h2>
              <h3>Name of Channel</h3>
            </div>
          </div>
          <ul class="main_nav_options">
    
            <li class="disabled"><div class="nav_icon"><i class="fas fa-user-plus"></i></div>Invite Friends</li>
            <li class="disabled"><div class="nav_icon"><i class="fas fa-grip-horizontal"></i></div>My Content</li>
            <li class="disabled"><div class="nav_icon"><i class="fas fa-sliders-h"></i></div>Filters</li>
            <li class="disabled"><div class="nav_icon"><i class="fas fa-cogs"></div></i>Settings</li>
            <li>
               <a href="http://diversus.me" style="color:inherit; text-decoration: none;">
                  <div class="nav_icon"><i class="fas fa-info"></i></div>About Diversus</a>
            </li>
            <li class="logout-button"><div class="nav_icon"><i class="fas fa-sign-out-alt"></i></div>Sign out</li>
            <li class="login-button"><div class="nav_icon"><i class="fas fa-sign-in-alt"></i></div>Sign in</li>
          </ul>
        </div>
      </div>
      """ # emacs coffee-mode needs this -> "
      constructor: () ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
  • ¶

    Normally the constructor builds the content for a visualization, but there is a race condition such that the navigator has already been constructed before the .navigatingFor property on the Navigator has been assigned. So the @attach_myNavigator() method calls our begin_navigating() method once all the connections are made. @visContent.html(“””

    AppNavigator should be called by other visualizations using @attach_myNavigator()“””)

      begin_navigating: ->
        navList = @createNavigationList
        @visContent.html(widget_html)
        $(".mobile_nav_icon").click(@mobileNavToggle)
        $(".add_new_video").click(@addNewVideoToggle)
        @addLoginHandlers()
    
        navForFunc = @get_navigatingFor_func_name()
        if navForFunc is 'listmedia'
          $(".choose-provid-record").click(() => @choose_provid('record'))
          $(".choose-provid-hosted").click(() => @choose_provid('hosted'))
          $(".choose-provid-upload").click(() => @choose_provid('upload'))
          $(".mobile_add_video_wrap .cancel_button").click(() ->
            $(".mobile_add_video_wrap").hide()
            $("form.video_provider").hide()
            console.log "Loookning for back"
            if $(".video_list").html() # Must be on list page so stay there
              $("form.video_provider").hide()
              console.log "on list page so just hide"
            else  # else must be on provid page so back button to list view
              window.history.back()
              console.log "back up to previous page"
            )
        else if navForFunc in ['playmedia', 'provid']
          $(".choose-provid-record").click(() => @navigatingFor_choose_provid('record'))
          $(".choose-provid-hosted").click(() => @navigatingFor_choose_provid('hosted'))
          $(".choose-provid-upload").click(() => @navigatingFor_choose_provid('upload'))
          $(".mobile_add_video_wrap .cancel_button").click(() ->
            $(".mobile_add_video_wrap").hide()
            $("form.playmedia.video_provider").hide()
            )
        return
    
      get_navigatingFor_func_name: () ->
        if @navigatingFor?
          return @navigatingFor.constructor.func_name
        return
    
      navigatingFor_choose_provid: (vidsrc) ->
        @addNewVideoToggle()
        @navigatingFor.choose_provid(vidsrc)
        return
    
      choose_provid: (vidsrc) =>
        if vidsrc
          kwargs = (@navigatingFor?) and @navigatingFor.kwargs or @kwargs
          kb_arg = kwargs.g and ("g=" + kwargs.g + ',') or ''
          @run_formurla("provid(#{kb_arg}vidsrc=#{vidsrc})")
    
      mobileNavToggle: () ->
        if $(".mobile_nav_menu_wrap").is(":hidden") then $(".mobile_add_video_wrap").hide()
        $(".mobile_nav_menu_wrap").toggle()
    
      addNewVideoToggle: () ->
        if $(".mobile_add_video_wrap").is(":hidden") then $(".mobile_nav_menu_wrap").hide()
        $(".mobile_add_video_wrap").toggle()
    
      addLoginHandlers: () ->
        $(".login-button").click(@goToLogin)
        $(".logout-button").click(@logUserOut)
        @updateLoginButtons()
    
      updateLoginButtons: () ->
        if @noodb.email_login.logged_in
          $('.login-button').hide()
          $('.logout-button').show()
          $('body').addClass("logged_in")
        else
          $('.logout-button').hide()
          $('.login-button').show()
          $('body').removeClass("logged_in")
    
      goToLogin: () =>
        window.location = "/tmpl/login.html"
    
      logUserOut: () =>
        @noodb.email_login.log_out()
        @updateLoginButtons()
    
    class VIZ.Spangler extends VisualizationController
      @func_name = "spangler"
      @pretty_name = "Spangler"
      @docs = "the fractal string art toy: Spangler"
      init_spangler: () ->
        @prep_html()
        scr = 'http://spangler.gig.gl/spangler.js'  # FIXME why does this not work?
        scr = "https://raw.githubusercontent.com/smurp/spangler_js/master/spangler.js"
  • ¶

    $.getScript(scr, @on_spangler_script_loaded).fail(()->alert(‘failed’))

        @init_via_cachedScript(scr)
        return
      init_via_cachedScript: (script_uri) ->
        @cachedScript(script_uri)
          .done((script_src, textStatus) =>
            $.globalEval(script_src)
            @on_spangler_script_loaded()
          ).fail((jqXHR, textStatus) =>
            @setInnerHTML("<h1>#{script_uri}</h1><h2>#{textStatus}</h2>"+"<pre>#{JSON.stringify(jqXHR,null,4)}</pre>"))
    
      prep_html: ->
        @setInnerHTML("""
          <div>
            <canvas id="spangler" width="300" height="300"/>
            <code id="show_spec_here">2,5</code>
          </div>""")
    
      on_spangler_script_loaded: () =>
  • ¶

    @widget = Object.create(SpanglePanel) @widget.init()

        @setInnerHTML('<h1>script loaded</h1>')
    
      constructor: ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        @init_spangler()
    
    class AbstractBlocklyController extends VisualizationController
      DeferredCSSDependencies: []
      DeferredScriptDependencies: [
        '/blockly/blockly_uncompressed.js',
        '/blockly/blocks_compressed.js',
        '/blockly/javascript_compressed.js',
        '/blockly/msg/js/en.js',
        '/closure-library/closure/goog/base.js']
    
    class VIZ.BlocklyControllerTest extends AbstractBlocklyController
      @func_name = 'blockly'
      @pretty_name = "Blockly Panel"
      @docs = "Programming panel using Blockly"
      constructor: ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        $("#"+"#{@content_id}").append('<div id="blocklyDiv-' + @content_id + '" style="height: ' + @get_panelSize().height + 'px; width: ' + @get_panelSize().width + 'px;overflow:hidden"></div>')
  • ¶

    add custom blocks (test)

        Blockly.Blocks['formurla_viz_picker'] = init:() ->
          @jsonInit(
            message0: "%1 %2"
            args0: [
                {
                  type: "field_dropdown"
                  name: "vis_type"
                  options: [
                    [
                      "Subjects Table",
                      "subjects"
                    ],
                    [
                      "Scatter Plot",
                      "scatter"
                    ],
                    [
                      "Node-Link Layout",
                      "graph"
                    ],
                    [
                      "Bar Graph",
                      "bar"
                    ]
                  ]
                },
                {
                  type: "input_value"
                  name: "value_vis_type"
                }
              ]
            output: "String"
            colour: 330,
            tooltip: "Returns number of letters in the provided text."
            helpUrl: "http://www.w3schools.com/jsref/jsref_length_string.asp"
            )
    
        Blockly.Blocks['data_set'] = init:() ->
          @jsonInit(
            message0: "Data Set: %1"
            args0: [
                {
                  type: "field_input"
                  name: "data_set"
                  text: "test"
                }
              ]
            output: "string"
            colour: 160
            tooltip: "Returns number of letters in the provided text."
            helpUrl: "http://www.w3schools.com/jsref/jsref_length_string.asp"
            )
  • ¶

    Code generator for custom Blocks

        Blockly.JavaScript['formurla_viz_picker'] = (block) ->
          @dropdown_vis_type = block.getFieldValue('vis_type')
          @value_value_vis_type = Blockly.JavaScript.valueToCode(block, 'value_vis_type', Blockly.JavaScript.ORDER_ATOMIC)
          code = @dropdown_vis_type + '(' + @value_value_vis_type + ')'
          [code, Blockly.JavaScript.ORDER_ATOMIC ]
    
        Blockly.JavaScript['data_set'] = (block) ->
          @text_data_set = block.getFieldValue('data_set');
          code = "g=nrn:" + @text_data_set
          [code, Blockly.JavaScript.ORDER_ATOMIC ]
  • ¶

    Code version of Blockly injection

        @toolbox = '<xml>'
  • ¶

    @toolbox += ‘ ‘ @toolbox += ‘ ‘

        @toolbox += '  <block type="formurla_viz_picker"></block>'
        @toolbox += '  <block type="data_set"></block>'
  • ¶

    @toolbox += ‘ ‘; @toolbox += ‘ ‘;

        @toolbox += '</xml>'
        @workspace = Blockly.inject('blocklyDiv-' + @content_id, {toolbox: @toolbox, scrollbars: false});
  • ¶

    move toolbox into proper div

        console.log($(".blocklyToolboxDiv").html())
  • ¶

    Show code area

        $("#blocklyDiv-" + @content_id).append('<div class="codeDisplay" style="position:absolute;top:0;right:0;width:400px;background-color:#eee;padding:10px;">Code Window</div>')
    
        @workspace.addChangeListener(@showCodeScript)
        @addResizeListener()
        @initBlockly()
    
    
      initBlockly: () ->
  • ¶

    Currently pulls XML info from the page itself. For our purposes it would be better if we more directly created the initial workspace from code

        @workspaceDefault = jQuery.parseXML('<xml><block type="controls_if" inline="false" x="20" y="20">
          <mutation else="1"></mutation>
          <value name="IF0">
            <block type="logic_compare" inline="true">
              <field name="OP">EQ</field>
              <value name="A">
                <block type="math_arithmetic" inline="true">
                  <field name="OP">MULTIPLY</field>
                  <value name="A">
                    <block type="math_number">
                      <field name="NUM">6</field>
                    </block>
                  </value>
                  <value name="B">
                    <block type="math_number">
                      <field name="NUM">7</field>
                    </block>
                  </value>
                </block>
              </value>
              <value name="B">
                <block type="math_number">
                  <field name="NUM">42</field>
                </block>
              </value>
            </block>
          </value>
          <statement name="DO0">
            <block type="text_print" inline="false">
              <value name="TEXT">
                <block type="text">
                  <field name="TEXT">Don\'t panic</field>
                </block>
              </value>
            </block>
          </statement>
          <statement name="ELSE">
            <block type="text_print" inline="false">
              <value name="TEXT">
                <block type="text">
                  <field name="TEXT">Panic</field>
                </block>
              </value>
            </block>
          </statement>
        </block></xml>')
        Blockly.Xml.domToWorkspace( @workspace, $(@workspaceDefault).find('xml')[0]) #initialize the workspace to a default
    
    
      showCodeScript: () =>
        $(@localize(".codeDisplay")).text(Blockly.JavaScript.workspaceToCode(@workspace))
    
      addResizeListener: () ->
        $(@fracpanel.content_area).bind("_splitpaneparentresize", @resize_handler)
    
      resize_handler: () =>
        @bheight = @get_panelSize().height-20 #panel -20px to avoid scroll bars appearing on resize
        @bwidth = @get_panelSize().width-20
        $("#"+"#{@content_id}" + " #blocklyDiv").width(@bwidth).height(@bheight)
    
      get_panelSize: () ->
        width: $(@visContent).width()
        height: $(@visContent).height()
    
    class BlocklyController extends AbstractBlocklyController
      constructor: ->
        super
        @visContent = $(@fracpanel.content_area).attr('id', @content_id)
        $("#"+"#{@content_id}").append('<div id="blocklyDiv-' + @content_id + '" style="height: ' + @get_panelSize().height + 'px; width: ' + @get_panelSize().width + 'px;overflow:hidden"></div>')
  • ¶

    console.log(@toolbox())

        @initBlocks()
        @toolbox()
        @initializeWorkspace()
  • ¶

    Show code area

        $("#blocklyDiv-" + @content_id).append('<div class="codeDisplay" style="z-index:99;position:absolute;top:0;right:0;width:400px;background-color:#eee;padding:10px;">Code Window</div>')
        $("#blocklyDiv-" + @content_id).append('<pre class="discriminatorAST" style="z-index:99;position:absolute;bottom:0;right:0;width:600px;background-color:#eee;padding:10px;">Discriminator AST Woot</pre>')
        @workspace = Blockly.inject('blocklyDiv-' + @content_id, {toolbox: @toolbox, scrollbars: false});
    
        Blockly.Xml.domToWorkspace(Blockly.Xml.textToDom(@initXML), @workspace)
  • ¶

    @create_api_workspace(@workspace)

        @initializeDiscriminator()
        @workspace.render()
    
        @workspace.addChangeListener(Blockly.Events.disableOrphans)
  • ¶

    console.log(@workspace)

        @addResizeListener()
        @workspace.addChangeListener(@showCodeScript)
        @workspace.addChangeListener(@showDiscriminatorAST)
  • ¶

    @workspace.addChangeListener(@showCodeScript, @showDiscriminatorAST)

        if not @kwargs.showAST
          $(@localize(".discriminatorAST")).hide()
        if not @kwargs.showCode
          $(@localize(".codeDisplay")).hide()
    
      initBlocks: ->
  • ¶

    to be overridden by subclasses

      showCodeScript: () =>
        @display_code = Blockly.JavaScript.workspaceToCode(@workspace).replace(/\s+/g, '')
        @display_code += Blockly.Xml.domToPrettyText(Blockly.Xml.workspaceToDom(@workspace))
        $(@localize(".codeDisplay")).text(@display_code)
    
      showDiscriminatorAST: () =>
        output = ''
        try
          src = Blockly.JavaScript.workspaceToCode(@workspace).replace(/\s+/g, '')
  • ¶

    src = “Latest(Min(me|everyone)).re(Truth,Beauty);Latest(everyone);” src = “AVG(MAX(EARLIEST(@clinton,@obama~@trump|everyone~@trump2))).re(Criteria,Criteria2);”

          discriminator = new Discriminator(src)
          output = JSON.stringify(discriminator.discr_ast, null, 4)
        catch e
          output = e.toString()
  • ¶

    console.log output

        $(@localize(".discriminatorAST")).text(output)
    
      addResizeListener: () ->
        $(@fracpanel.content_area).bind("_splitpaneparentresize", @resize_handler)
    
      resize_handler: () =>
        @bheight = @get_panelSize().height-20 #panel -20px to avoid scroll bars appearing on resize
        @bwidth = @get_panelSize().width-20
        $("#"+"#{@content_id}" + " #blocklyDiv").width(@bwidth).height(@bheight)
    
      get_panelSize: () ->
        width: $(@visContent).width()
        height: $(@visContent).height()
    
    
    class VIZ.MakeDiscriminator extends AbstractBlocklyController
      @func_name = 'makeDiscriminator'
      @pretty_name = "Discriminator Panel"
      @docs = "Creates a Discriminator which constrains data by provenance"
  • ¶

    add custom blocks (test)

      initBlocks: ->
        Blockly.Blocks['discriminator'] = init:() ->
              @jsonInit(
                type: "discriminator"
                message0: "Discriminator %1 %2"
                args0: [
                  {
                    "type": "input_dummy"
                  },
                  {
                    type: "input_statement"
                    name: "new_level"
                    check: "aggregator_core"
                  }
                ]
                tooltip: ""
                helpUrl: ""
                )
    
        Blockly.Blocks['aggregator_core'] = init:() ->
              @jsonInit(
                type: "aggregator_core"
                message0: "Aggregator %1 %2 Who to Heed or Aggregator %3 %5 %4"
                args0: [
                  {
                    type: "field_dropdown"
                    name: "agg_function"
                    options: [
                      [
                        "Minimum",
                        "MIN"
                      ],
                      [
                        "Maximum",
                        "MAX"
                      ],
                      [
                        "Average",
                        "AVG"
                      ],
                      [
                        "Earliest",
                        "EARLIEST"
                      ],
                      [
                        "Latest",
                        "LATEST"
                      ]
                    ]
                  }
                  {
                    type: "input_dummy"
                    class: "test-blockly"
                  }
                  {
                    type: "input_statement"
                    name: "new_level"
                    check: [
                      "who_to_heed"
                      "aggregator"
                      ]
                  }
                  {
                    type: "input_statement"
                    name: "criteria"
                    check: "criteria"
                    align: "RIGHT"
                  }
                  {
                    type: "field_label"
                    text: "for Criteria"
                    class: "block-criteria"
                  }
                ]
                previousStatement: "aggregator_core"
                nextStatement: "aggregator_core"
                colour: 230,
                tooltip: ""
                helpUrl: ""
                )
    
        Blockly.Blocks['aggregators'] = init:() ->
              @jsonInit(
                type: "aggregator"
                message0: "Aggregator %1 %2 %3"
                args0: [
                  {
                    type: "field_dropdown"
                    name: "agg_function"
                    options: [
                      [
                        "Minimum",
                        "MIN"
                      ],
                      [
                        "Maximum",
                        "MAX"
                      ],
                      [
                        "Average",
                        "AVG"
                      ],
                      [
                        "Earliest",
                        "EARLIEST"
                      ],
                      [
                        "Latest",
                        "LATEST"
                      ]
                    ]
                  }
                  {
                    type: "input_dummy"
                  }
                  {
                    type: "input_statement"
                    name: "new_level"
                    check: [
                      "who_to_heed"
                      "aggregator"
                      ]
                  }
                ]
                previousStatement: "aggregator"
                colour: 210,
                tooltip: ""
                helpUrl: ""
              )
    
        Blockly.Blocks['who_to_heed'] = init:() ->
            @jsonInit(
              message0: "Who To Heed %1 Heed %2 NOT %3 OR %4"
              type: "who_to_heed"
              args0: [
                {
                  type: "input_dummy"
                },
                {
                  type: "input_statement",
                  name: "include",
                  check: "a_user",
                  align: "RIGHT"
                },
                {
                  type: "input_statement",
                  name: "exclude",
                  check: "a_user",
                  align: "RIGHT"
                },
                {
                  type: "input_statement",
                  name: "otherwise",
                  check: "who_to_heed",
                  align: "RIGHT"
                }
              ]
              previousStatement: "who_to_heed"
              colour: 65,
              tooltip: "Returns filter function"
              helpUrl: "http://www.w3schools.com/jsref/jsref_length_string.asp"
              )
    
        Blockly.Blocks['who_to_heed_or'] = init:() ->
            @jsonInit(
              message0: "OR Who To Heed %1 Heed %2 NOT %3 OR %4"
              type: "who_to_heed_or"
              args0: [
                {
                  type: "input_dummy"
                },
                {
                  type: "input_statement"
                  name: "include"
                  check: "a_user"
                  align: "RIGHT"
                },
                {
                  type: "input_statement"
                  name: "exclude"
                  check: "a_user"
                  align: "RIGHT"
                },
                {
                  type: "input_value"
                  name: "otherwise"
                  check: "who_to_heed_or"
                  align: "RIGHT"
                }
              ]
              output: "who_to_heed_or"
              colour: 45,
              tooltip: ""
              helpUrl: ""
              )
    
        Blockly.Blocks['predicate_reducer'] = init:() ->
            @jsonInit(
              type: "predicatereducer"
              message0: "%1 %2 %3 %4"
              args0: [
                {
                  type: "field_input",
                  name: "NAME",
                  text: "usesReducer"
                },
                {
                  type: "input_value",
                  name: "dataReducer",
                  check: "DataReducer",
                  align: "RIGHT"
                },
                {
                  type: "field_input",
                  name: "forCriterion",
                  text: "forCriterion"
                },
                {
                  type: "input_value",
                  name: "criteria",
                  check: "Criterion",
                  align: "RIGHT"
                }
              ]
              inputsInline: false,
              output: "PredicateReducer"
              colour: 315,
              tooltip: ""
              helpUrl: ""
              )
    
        Blockly.Blocks['a_user'] = init:() ->
              @jsonInit(
                type: "a_user"
                message0: "@ %1"
                args0: [
                  {
                    type: "field_input",
                    name: "userName",
                    text: "default"
                  }
                ]
                inputsInline: true,
                previousStatement: "a_user"
                nextStatement: "a_user"
                colour: 120,
                tooltip: ""
                helpUrl: ""
                )
    
        Blockly.Blocks['user_sel'] = init:() ->
            @jsonInit(
              type: "user_sel"
              message0: "%1"
              args0: [
                {
                  type: "field_dropdown",
                  name: "userName",
                  options: [
                    [
                      "me",
                      "me"
                    ],
                    [
                      "others",
                      "others"
                    ],
                    [
                      "everyone",
                      "everyone"
                    ]
                  ]
                }
              ]
              inputsInline: true,
              previousStatement: "a_user"
              nextStatement: "a_user"
              colour: 120,
              tooltip: ""
              helpUrl: ""
              )
    
        Blockly.Blocks['predicate_reducer_2'] = init:() ->
            @jsonInit(
              type: "predicatereducer"
              message0: "PredicateReducer %1 %2 %3 %4 %5"
              args0: [
                  {
                    type: "input_dummy"
                  },
                  {
                    type: "field_input",
                    name: "NAME",
                    text: "usesDataReducer"
                  },
                  {
                    type: "input_value",
                    name: "dataReducer",
                    check: "DataReducer",
                    align: "RIGHT"
                  },
                  {
                    type: "field_input",
                    name: "forCriteria",
                    text: "forCriteria"
                  },
                  {
                    type: "input_statement",
                    name: "NAME",
                    check: "Criterion",
                    align: "RIGHT"
                  }
                ]
              inputsInline: false
              output: "PredicateReducer"
              colour: 315
              tooltip: ""
              helpUrl: ""
              )
    
        Blockly.Blocks['data_set'] = init:() ->
              @jsonInit(
                message0: "Data Set: %1"
                args0: [
                    {
                      type: "field_input"
                      name: "data_set"
                      text: "test"
                    }
                  ]
                output: "string"
                colour: 160
                tooltip: "Returns number of letters in the provided text."
                helpUrl: "http://www.w3schools.com/jsref/jsref_length_string.asp"
                )
    
        Blockly.Blocks['dscrm_picker'] = init:() ->
              @jsonInit(
                message0: "%1"
                type: "dscrm_picker"
                args0: [
                    {
                      type: "field_dropdown"
                      name: "dscrm_type"
                      options: [
                        [
                          "Minimum",
                          "MIN"
                        ],
                        [
                          "Maximum",
                          "MAX"
                        ],
                        [
                          "Average",
                          "AVG"
                        ],
                        [
                          "Earliest",
                          "EARLIEST"
                        ],
                        [
                          "Latest",
                          "LATEST"
                        ]
                      ]
                    }
                  ]
                output: "filter"
                colour: 280
                tooltip: "Returns filter function"
                helpUrl: "http://www.w3schools.com/jsref/jsref_length_string.asp"
                )
    
        Blockly.Blocks['criteria'] = init:() ->
              @jsonInit(
                type: "criteria"
                message0: "%1"
                args0: [
                  {
                    type: "field_input",
                    name: "CriteriaName",
                    text: "Criteria"
                  }
                ]
                inputsInline: true,
                previousStatement: "criteria"
                nextStatement: "criteria"
                colour: 20,
                tooltip: ""
                helpUrl: ""
                )
  • ¶

    Code generator for custom Blocks

        Blockly.JavaScript['discriminator'] = (block) ->
              @statements_new_level = Blockly.JavaScript.statementToCode(block, 'new_level', Blockly.JavaScript.ORDER_ADDITION);
              code = @statements_new_level
    
        Blockly.JavaScript['aggregator_core'] = (block) ->
              @value_agg_function = block.getFieldValue('agg_function')
              @statements_new_level = Blockly.JavaScript.statementToCode(block, 'new_level', Blockly.JavaScript.ORDER_ADDITION) || '-';
              @statements_criteria = Blockly.JavaScript.statementToCode(block, 'criteria', Blockly.JavaScript.ORDER_COMMA);
              @add_criteria="";
              if(@statements_criteria)
                @add_criteria=".re(" + @statements_criteria.slice(0,-1) + ")";
              code = @value_agg_function + "(" + @statements_new_level + ")" + @add_criteria + ";";
    
        Blockly.JavaScript['aggregators'] = (block) ->
              @value_agg_function = block.getFieldValue('agg_function')
              @statements_new_level = Blockly.JavaScript.statementToCode(block, 'new_level', Blockly.JavaScript.ORDER_ADDITION) || '-';
              code = @value_agg_function + "(" + @statements_new_level + ")";
    
        Blockly.JavaScript['who_to_heed'] = (block) ->
              @statements_include = Blockly.JavaScript.statementToCode(block, 'include', Blockly.JavaScript.ORDER_COMMA) || '-';
              @statements_exclude = Blockly.JavaScript.statementToCode(block, 'exclude', Blockly.JavaScript.ORDER_ADDITION);
              @value_otherwise = Blockly.JavaScript.statementToCode(block, 'otherwise', Blockly.JavaScript.ORDER_ATOMIC);
              @incl = ""
              @excl = ""
              if(@statements_exclude)
                @incl = "~" + @statements_exclude.slice(0,-1);
              if(@value_otherwise)
                @excl = "|" + @value_otherwise;
              code = @statements_include.slice(0,-1) + @incl + @excl;
    
        Blockly.JavaScript['who_to_heed_or'] = (block) ->
              @statements_include = Blockly.JavaScript.statementToCode(block, 'include', Blockly.JavaScript.ORDER_ADDITION) || '-';
              @statements_exclude = Blockly.JavaScript.statementToCode(block, 'exclude', Blockly.JavaScript.ORDER_ADDITION)
              @value_otherwise = Blockly.JavaScript.statementToCode(block, 'otherwise', Blockly.JavaScript.ORDER_ATOMIC)
              @incl = ""
              @excl = ""
              if(@statements_exclude)
                @incl = "~" + @statements_exclude.slice(0,-1);
              if(@value_otherwise)
                @excl = "|" + @value_otherwise;
              code = @statements_include.slice(0,-1) + @incl + @excl;
    
        Blockly.JavaScript['a_user'] = (block) ->
              @text_username = block.getFieldValue('userName');
              code = "@" + @text_username + ",";
    
        Blockly.JavaScript['user_sel'] = (block) ->
              @dropdown_user_name = block.getFieldValue('userName')
              code = @dropdown_user_name + ","
    
        Blockly.JavaScript['filter_picker'] = (block) ->
              @dropdown_filter_type = block.getFieldValue('filter_type')
              @value_value_filter_type = Blockly.JavaScript.valueToCode(block, 'value_filter_type', Blockly.JavaScript.ORDER_ATOMIC)
              code = @dropdown_filter_type + '(' + @value_value_filter_type + ')'
    
        Blockly.JavaScript['dscrm_picker'] = (block) ->
              @dropdown_dscrm_type = block.getFieldValue('dscrm_type')
              code = @dropdown_dscrm_type
    
        Blockly.JavaScript['data_set'] = (block) ->
              @text_data_set = block.getFieldValue('data_set');
              code = "g=nrn:" + @text_data_set
    
        Blockly.JavaScript['criteria'] = (block) ->
              @text_criteria = block.getFieldValue('CriteriaName');
              code = @text_criteria + ",";
    
      toolbox: () ->
        @toolbox = '<xml>'
        @toolbox += '  <block type="aggregator_core"></block>'
        @toolbox += '  <block type="aggregators"></block>'
        @toolbox += '  <block type="who_to_heed"></block>'
        @toolbox += '  <block type="a_user"></block>'
        @toolbox += '  <block type="user_sel"></block>'
        @toolbox += '  <block type="criteria"></block>'
        @toolbox += '</xml>'
    
      initializeWorkspace: () ->
        @initXML = '<xml>'
        @initXML += '<block type="discriminator" deletable="false" movable="false" x="40" y="40"></block>'
        @initXML += '</xml>'
    
      initializeDiscriminator: () ->
        @jsonDiscrm =
          prog: [
            {
              type: "call"
              method:
                type: "method"
                name: "re"
                args: [
                  "Criteria1",
                  "Criteria2"
                ]
              func:
                type: "var"
                value: "AVG"
              args: [
                type: "call"
                func:
                  type: "var"
                  value: "MAX"
                args: [
                  {
                    type: "atuser"
                    value: "Clinton"
                  }
                  {
                    type: "atuser"
                    value: "Obama"
                  }
                  {
                    type: "punc"
                    value: "~"
                  }
                  {
                    type: "genuser"
                    value: "everyone"
                  }
                  {
                    type: "punc"
                    value: "|"
                  }
                  {
                    type: "atuser"
                    value: "ReadySet"
                  }
                  {
                    type: "punc"
                    value: "|"
                  }
                  {
                    type: "atuser"
                    value: "SpecialPerson"
                  }
                ]
              ]
            }
            {
              type: "call"
              method:
                type: "method"
                name: "re"
                args: [
                  "myCriterion"
                ]
              func:
                type: "var"
                value: "LATEST"
              args: [
                {
                  type: "atuser"
                  value: "Me"
                }
                {
                  type: "punc"
                  value: "~"
                }
                {
                  type: "atuser"
                  value: "God"
                }
              ]
            }
            {
              type: "call"
              func:
                type: "var"
                value: "MAX"
              args: [
                {
                  type: "call"
                  func:
                    type: "var"
                    value: "AVG"
                  args: [
                    {
                      type: "call"
                      func:
                        type: "var"
                        value: "EARLIEST"
                      args: [
                          {
                            type: "genuser"
                            value: "everyone"
                          }
  • ¶
     {
       type: "punc"
       value: "|"
     }
     {
       type: "atuser"
       value: "craxy"
     }
    
                      ]
                    }
                  ]
                }
              ]
            }
          ]
  • ¶

    Go through each Aggregator in the prog list

        for aggCntrl, i in @jsonDiscrm.prog
  • ¶

    check args if there func then look to see if there are more args

          baseAgg = @create_aggregator_core(aggCntrl.func.value) #create base Agg
          if aggCntrl.args[0].args
            if aggCntrl.args[0].args[0].args
              newAgg = @create_aggregator(aggCntrl.args[0].func.value, baseAgg)
              newAgg = @create_aggregator(aggCntrl.args[0].args[0].func.value, newAgg)
  • ¶

    now work on the WhoToHeed Block Set

              filterList = aggCntrl.args[0].args[0].args
              @create_whoToHeed_set(filterList,newAgg)
    
            else
              newAgg = @create_aggregator(aggCntrl.args[0].func.value, baseAgg)
  • ¶

    WhoToHeed Block Set

              filterList = aggCntrl.args[0].args
              @create_whoToHeed_set(filterList,newAgg)
          else
            console.log("one level only")
            parent = baseAgg
  • ¶

    WhoToHeed Block Set

            filterList = aggCntrl.args
            @create_whoToHeed_set(filterList,baseAgg)
  • ¶

    check for criteria filters (methods)

          if aggCntrl.method
            criteriaArray = aggCntrl.method.args
            @create_criteria_set(criteriaArray, baseAgg)
    
      create_aggregator_core: (param) ->
        discriminatorBlock = @workspace.getTopBlocks()[0]
        blockAttributes =
          type: "aggregator_core"
          parent: discriminatorBlock
          input: "new_level"
          value:
            field: "agg_function"
            value: param
        block = @create_block(blockAttributes)
    
      create_aggregator: (param, parent) ->
        blockAttributes =
          type: "aggregators"
          parent: parent
          input: "new_level"
          value:
            field: "agg_function"
            value: param
        block = @create_block(blockAttributes)
    
      create_criteria_set: (criteria, parent) ->
  • ¶

    console.log(block)

        for cntrl, i in criteria
  • ¶

    console.log(cntrl)

          blockAttributes =
            type: "criteria"
            parent: parent
            input: "criteria"
            value:
              field: "CriteriaName"
              value: cntrl
          @create_block(blockAttributes)
    
      create_whoToHeed_set: (params, parent) ->
  • ¶

    console.log(blockAtt) blockAtt = args: []

        blockAttributes =
          type: "who_to_heed"
          parent: parent
          input: "new_level"
        parent = @create_block(blockAttributes)
  • ¶

    console.log(parent)

        usrGrpSet = []
        includeVals = true
        for crtr, i in params
  • ¶

    console.log(crtr)

          if crtr.type == 'punc' && crtr.value == '|'
  • ¶

    console.log(‘need to create next block of who to heed’)

            for user, j in usrGrpSet
              if includeVals then blkInput = "include" else blkInput = "exclude"
              @create_userFilter_block(user, blkInput, parent)
            blockAttributes =
              type: "who_to_heed"
              parent: parent
              input: "otherwise"
            parent = @create_block(blockAttributes)
            usrGrpSet = []
            includeVals = true
          else if crtr.type == 'punc' && crtr.value == '~'
  • ¶

    console.log(‘what follows should be user blocks in NOT on current block’) @create_userFilter_block(usrGrpSet, parent) console.log(@usrGrpSet)

            includeVals = false
            blkInput = "include"
            for user, j in usrGrpSet
              @create_userFilter_block(user, blkInput, parent)
            usrGrpSet = []
          else if crtr.type == 'genuser' || crtr.type == 'atuser'
  • ¶

    console.log(‘Create genuser block for currentCreate atuser block for current ‘)

            usrGrpSet.push(crtr)
          else
            console.log('error here')
        if usrGrpSet
          for user, j in usrGrpSet
            if includeVals then blkInput = "include" else blkInput = "exclude"
            @create_userFilter_block(user, blkInput, parent)
    
      create_userFilter_block: (param, input, parent) ->
        console.log(input)
        if param.type =='genuser'
          type = "user_sel"
        else
          type = "a_user"
  • ¶

    type = param.type==’genuser’ then “user_sel” else “a_user”

        blockAttributes =
          type: type
          parent: parent
          input: input
          value:
            field: "userName"
            value: param.value
        @create_block(blockAttributes)
    
      create_api_workspace: () ->
        @initializeDiscriminator()
    
        discriminatorBlock = @workspace.getTopBlocks()[0]
  • ¶

    new block created to fit into the Discrimnator block = workspace.newBlock(‘aggregator_core’) block.setParent(descriminatorBlock) block.initSvg() block.render()

  • ¶

    Aggregator base

        blockAttributes =
          type: "aggregator_core"
          parent: discriminatorBlock
          input: "new_level"
          value:
            field: "agg_function"
            value: "MAX"
        block = @create_block(blockAttributes)
    
        blockAttributes =
          type: "criteria"
          parent: block
          input: "criteria"
          value:
            field: "CriteriaName"
            value: "Criteria 1"
        block2 = @create_block(blockAttributes)
    
        blockAttributes =
          type: "criteria"
          parent: block
          input: "criteria"
          value:
            field: "CriteriaName"
            value: "Criteria 2"
        block2 = @create_block(blockAttributes)
    
        blockAttributes =
          type: "who_to_heed"
          parent: block
          input: "new_level"
        block = @create_block(blockAttributes)
  • ¶

    console.log(block)

        blockAttributes =
          type: "user_sel"
          parent: block
          input: "include"
          value:
            field: "userName"
            value: "everyone"
        block = @create_block(blockAttributes)
  • ¶

    parentConnection = descriminatorBlock.getInput(blockAttributes.input).connection; childConnection = block.previousConnection; parentConnection.connect(childConnection);

        inputValue = blockAttributes.input
  • ¶

    console.log(blockAttributes)

  • ¶

    parentConnection = descriminatorBlock.getInput(blockAttributes.input).connection childConnection = block.previousConnection parentConnection.connect(childConnection)

      create_block: (blockAttributes) ->
        block = @workspace.newBlock(blockAttributes.type)
        block.setParent(blockAttributes.parent)
        if blockAttributes.value
          block.setFieldValue(blockAttributes.value.value, blockAttributes.value.field)
        block.initSvg()
        block.render()
    
        parentBlock = blockAttributes.parent
  • ¶

    console.log(parentBlock)

        parentConnection = parentBlock.getInput(blockAttributes.input).connection
        childConnection = block.previousConnection
        parentConnection.connect(childConnection)
    
        return block
    
    
      create_xml_workspace_DEPRECIATED: (jsonDiscrm) ->
  • ¶

    Create a discrinator block to ititialize workspace @xml_workspace = ‘‘ @xml_workspace += ‘‘

  • ¶

    Create a aggregator_core block (ALWAYS NEED AT LEAST ONE) @xml_workspace += ‘‘ @xml_workspace += @create_agg_block(jsonDiscrm.prog[0].func.value)

  • ¶

    if there is another aggrecator then add basic aggregator level (OPTIONAL)

    ‘‘ + json_descrim_code.filter + ‘‘ ‘‘

    ‘‘

  • ¶

    Else just add Who to Heed whoToHeed() -> user() -> build if for NOT then add user() -> build @whoToHeed() loop inside] @xml_workspace += ‘‘ + @create_whoToHeed_block(jsonDiscrm.prog[0].args.args[0].value) + ‘‘ @xml_workspace += ‘‘ + @create_whoToHeed_block(jsonDiscrm.prog[0].args[0].args[0].value) + ‘‘ @xml_workspace += ‘‘ + @create_criteria_block(jsonDiscrm.prog[0].method.args) + ‘‘ console.log @xml_workspace @xml_workspace += ‘‘ @xml_workspace += ‘‘

      create_criteria_block_DEPRECIATED: (criteria) ->
        for crtr, i in criteria
          if i == 0
            @crit_block = '<block type="criteria">' + @create_criteria_field(crtr) + '</block>'
          else
            @newEntry = '<block type="criteria">' + @create_criteria_field(crtr) + '<next>' + @crit_block + '</next></block>'
            @crit_block += @newEntry
    
      create_criteria_field_DEPRECIATED: (value) ->
        @crit_field_block = '<field name="CriteriaName">' + value + '</field>'
    
      create_agg_block_DEPRECIATED: (agg) ->
        @agg_block = '<field name="agg_function">' + agg + '</field>'
    
      create_whoToHeed_block_DEPRECIATED: (heed) ->
  • ¶

    console.log(heed.include)

        @heed_block = '<block type="who_to_heed">'
    
        @heed_block += '<statement name="include"><block type="a_user">'
        @heed_block += '<field name="UserName">' + heed + '</field>'
  • ¶

    for user, i in heed.include if i == 0 @heed_block += @create_user_block(user) console.log(“yes: “ + i + “: “ + user) else @heed_block += ‘‘ + @create_user_block(user) + ‘‘ console.log(“no: “ + i + “: “ + user)

        @heed_block += '</block></statement>'
  • ¶

    @heed_block += ‘‘ + @create_user_block(heed.exclude) + ‘‘

        @heed_block += '</block>'
  • ¶

    console.log(@heed_block)

      create_user_block_DEPRECIATED: (users) ->
        for crtr, i in users
          if i == 0
            @user_block = '<block type="a_user">' + @create_user_field(crtr) + '</block>'
          else
            @newEntry = '<block type="a_user">' + @create_user_field(crtr) + '<next>' + @crit_block + '</next></block>'
            @user_block += @newEntry
    
      create_user_field_DEPRECIATED: (user) ->
  • ¶

    console.log(“just user: “ + user) if this is a single user (i.e. by name) then add this kind of block

        @user_block = '<field name="UserName">' + user + '</field>'
  • ¶

    if this ‘me’ or a group (e.g. ‘other’ or workgroup) then add this kind of block @user_block = ‘‘ + user + ‘‘

    
    
    class KnowledgeHostedVisualizationController extends VisualizationController
      @func_name = null # this is Abstract ie only subclasses can be instantiated
      @docs = """This abstract visualization is a foundation and bridge from
      conventionally-hosted code to knowledge-hosted visualizations.
      """
      @constructor: ->
        super
  • ¶

    ############################################################################ The BUILTINs

    ############################################################################

    
    use_viz_by_class = (formurlaManager, vizclass, fracpanel, args, kwargs, expression_ast) ->
      viz = new vizclass(formurlaManager, fracpanel, args, kwargs, expression_ast)
      window.nooron_views ?= []
      window.nooron_views.push(viz)
      return viz
    
    class KWARGS
    
    get_args_and_kwargs = (formurla_args) ->
      retval =
        args: []
        kwargs: new KWARGS()
      for arg in formurla_args
        if arg.type in ['var','num','str']
          retval.args.push(arg.value)
        if arg.type is 'assign' and arg.operator is '='
          retval.kwargs[arg.left.value] = arg.right.value
      return retval
    
    build_builtin = (a_class) ->
      return () ->
        all_args = Array.prototype.slice.call(arguments) # make an Array from arguments
        frac = all_args.shift()
        expression_ast = all_args[0]
        {args, kwargs} = get_args_and_kwargs(expression_ast.args)
        viz_inst = use_viz_by_class(this, a_class, frac, args, kwargs, expression_ast)
        return viz_inst
    
    register_visualizations = (formurlaManager) ->
      for a_name,a_class of VIZ # "evaluations,subjects,allegations,graph".split(",")
        func_name = a_class.func_name
        formurlaManager.prototype["BUILTIN_#{func_name}"] = build_builtin(a_class)
    
    
    (exports ? this).register_visualizations = register_visualizations