<commit_before>/*
Returns editableForm controller
*/
angular.module('xeditable').factory('editableFormController',
  ['$parse', 'editablePromiseCollection',
  function($parse, editablePromiseCollection) {

  var base = {
    $addEditable: function(editable) {
      //console.log('add editable', editable.elem, editable.elem.bind);
      this.$editables.push(editable);

      //'on' is not supported in angular 1.0.8
      editable.elem.bind('$destroy', angular.bind(this, this.$removeEditable, editable));

      //bind editable's local $form to self (if not bound yet, below form)
      if (!editable.scope.$form) {
        editable.scope.$form = this;
      }

      //if form already shown - call show() of new editable
      if (this.$visible) {
        editable.catchError(editable.show());
      }
    },

    $removeEditable: function(editable) {
      //arrayRemove
      for(var i=0; i < this.$editables.length; i++) {
        if(this.$editables[i] === editable) {
          this.$editables.splice(i, 1);
          return;
        }
      }
    },

    /**
     * Shows form with editable controls.
     *
     * @method $show()
     * @memberOf editable-form
     */
    $show: function() {
      if (this.$visible) {
        return;
      }

      this.$visible = true;

      var pc = editablePromiseCollection();

      //own show
      pc.when(this.$onshow());

      //clear errors
      this.$setError(null, '');

      //children show
      angular.forEach(this.$editables, function(editable) {
        pc.when(editable.show());
      });

      //wait promises and activate
      pc.then({
        onWait: angular.bind(this, this.$setWaiting),
        onTrue: angular.bind(this, this.$activate),
        onFalse: angular.bind(this, this.$activate),
        onString: angular.bind(this, this.$activate)
      });
    },

    $activate: function(name) {
      var i;
      if (this.$editables.length) {
        //activate by name
        if (angular.isString(name)) {
          for(i=0; i<this.$editables.length; i++) {
            if (this.$editables[i].name === name) {
              this.$editables[i].activate();
              return;
            }
          }
        }

        //try activate error field
        for(i=0; i<this.$editables.length; i++) {
          if (this.$editables[i].error) {
            this.$editables[i].activate();
            return;
          }
        }

        //by default activate first field
        this.$editables[0].activate();
      }
    },

    /**
     * Hides form with editable controls without saving.
     *
     * @method $hide()
     * @memberOf editable-form
     */
    $hide: function() {
      if (!this.$visible) {
        return;
      }
      this.$visible = false;
      // self hide
      this.$onhide();
      // children's hide
      angular.forEach(this.$editables, function(editable) {
        editable.hide();
      });
    },

    /**
     * Triggers oncancel event and calls $hide().
     *
     * @method $cancel()
     * @memberOf editable-form
     */
    $cancel: function() {
      if (!this.$visible) {
        return;
      }
      // self cancel
      this.$oncancel();
      // children's cancel
      angular.forEach(this.$editables, function(editable) {
        editable.cancel();
      });
      // self hide
      this.$hide();
    },

    $setWaiting: function(value) {
      this.$waiting = !!value;
      // we can't just set $waiting variable and use it via ng-disabled in children
      // because in editable-row form is not accessible
      angular.forEach(this.$editables, function(editable) {
        editable.setWaiting(!!value);
      });
    },

    /**
     * Shows error message for particular field.
     *
     * @method $setError(name, msg)
     * @param {string} name name of field
     * @param {string} msg error message
     * @memberOf editable-form
     */
    $setError: function(name, msg) {
      angular.forEach(this.$editables, function(editable) {
        if(!name || editable.name === name) {
          editable.setError(msg);
        }
      });
    },

    $submit: function() {
      if (this.$waiting) {
        return;
      }

      //clear errors
      this.$setError(null, '');

      //children onbeforesave
      var pc = editablePromiseCollection();
      angular.forEach(this.$editables, function(editable) {
        pc.when(editable.onbeforesave());
      });

      /*
      onbeforesave result:
      - true/undefined: save data and close form
      - false: close form without saving
      - string: keep form open and show error
      */
      pc.then({
        onWait: angular.bind(this, this.$setWaiting),
        onTrue: angular.bind(this, checkSelf, true),
        onFalse: angular.bind(this, checkSelf, false),
        onString: angular.bind(this, this.$activate)
      });

      //save
      function checkSelf(childrenTrue){
        var pc = editablePromiseCollection();
        pc.when(this.$onbeforesave());
        pc.then({
          onWait: angular.bind(this, this.$setWaiting),
          onTrue: childrenTrue ? angular.bind(this, this.$save) : angular.bind(this, this.$hide),
          onFalse: angular.bind(this, this.$hide),
          onString: angular.bind(this, this.$activate)
        });
      }
    },

    $save: function() {
      // write model for each editable
      angular.forEach(this.$editables, function(editable) {
        editable.save();
      });

      //call onaftersave of self and children
      var pc = editablePromiseCollection();
      pc.when(this.$onaftersave());
      angular.forEach(this.$editables, function(editable) {
        pc.when(editable.onaftersave());
      });

      /*
      onaftersave result:
      - true/undefined/false: just close form
      - string: keep form open and show error
      */
      pc.then({
        onWait: angular.bind(this, this.$setWaiting),
        onTrue: angular.bind(this, this.$hide),
        onFalse: angular.bind(this, this.$hide),
        onString: angular.bind(this, this.$activate)
      });
    },

    $onshow: angular.noop,
    $oncancel: angular.noop,
    $onhide: angular.noop,
    $onbeforesave: angular.noop,
    $onaftersave: angular.noop
  };

  return function() {
    return angular.extend({
      $editables: [],
      /**
       * Form visibility flag.
       *
       * @var {bool} $visible
       * @memberOf editable-form
       */
      $visible: false,
      /**
       * Form waiting flag. It becomes `true` when form is loading or saving data.
       *
       * @var {bool} $waiting
       * @memberOf editable-form
       */
      $waiting: false,
      $data: {}
    }, base);
  };
}]);
<commit_msg>docs: $activate<commit_after>/*
Returns editableForm controller
*/
angular.module('xeditable').factory('editableFormController',
  ['$parse', 'editablePromiseCollection',
  function($parse, editablePromiseCollection) {

  var base = {
    $addEditable: function(editable) {
      //console.log('add editable', editable.elem, editable.elem.bind);
      this.$editables.push(editable);

      //'on' is not supported in angular 1.0.8
      editable.elem.bind('$destroy', angular.bind(this, this.$removeEditable, editable));

      //bind editable's local $form to self (if not bound yet, below form)
      if (!editable.scope.$form) {
        editable.scope.$form = this;
      }

      //if form already shown - call show() of new editable
      if (this.$visible) {
        editable.catchError(editable.show());
      }
    },

    $removeEditable: function(editable) {
      //arrayRemove
      for(var i=0; i < this.$editables.length; i++) {
        if(this.$editables[i] === editable) {
          this.$editables.splice(i, 1);
          return;
        }
      }
    },

    /**
     * Shows form with editable controls.
     *
     * @method $show()
     * @memberOf editable-form
     */
    $show: function() {
      if (this.$visible) {
        return;
      }

      this.$visible = true;

      var pc = editablePromiseCollection();

      //own show
      pc.when(this.$onshow());

      //clear errors
      this.$setError(null, '');

      //children show
      angular.forEach(this.$editables, function(editable) {
        pc.when(editable.show());
      });

      //wait promises and activate
      pc.then({
        onWait: angular.bind(this, this.$setWaiting),
        onTrue: angular.bind(this, this.$activate),
        onFalse: angular.bind(this, this.$activate),
        onString: angular.bind(this, this.$activate)
      });
    },

    /**
     * Sets focus on form field specified by `name`.
     *
     * @method $activate(name)
     * @param {string} name name of field
     * @memberOf editable-form
     */
    $activate: function(name) {
      var i;
      if (this.$editables.length) {
        //activate by name
        if (angular.isString(name)) {
          for(i=0; i<this.$editables.length; i++) {
            if (this.$editables[i].name === name) {
              this.$editables[i].activate();
              return;
            }
          }
        }

        //try activate error field
        for(i=0; i<this.$editables.length; i++) {
          if (this.$editables[i].error) {
            this.$editables[i].activate();
            return;
          }
        }

        //by default activate first field
        this.$editables[0].activate();
      }
    },

    /**
     * Hides form with editable controls without saving.
     *
     * @method $hide()
     * @memberOf editable-form
     */
    $hide: function() {
      if (!this.$visible) {
        return;
      }
      this.$visible = false;
      // self hide
      this.$onhide();
      // children's hide
      angular.forEach(this.$editables, function(editable) {
        editable.hide();
      });
    },

    /**
     * Triggers `oncancel` event and calls `$hide()`.
     *
     * @method $cancel()
     * @memberOf editable-form
     */
    $cancel: function() {
      if (!this.$visible) {
        return;
      }
      // self cancel
      this.$oncancel();
      // children's cancel
      angular.forEach(this.$editables, function(editable) {
        editable.cancel();
      });
      // self hide
      this.$hide();
    },

    $setWaiting: function(value) {
      this.$waiting = !!value;
      // we can't just set $waiting variable and use it via ng-disabled in children
      // because in editable-row form is not accessible
      angular.forEach(this.$editables, function(editable) {
        editable.setWaiting(!!value);
      });
    },

    /**
     * Shows error message for particular field.
     *
     * @method $setError(name, msg)
     * @param {string} name name of field
     * @param {string} msg error message
     * @memberOf editable-form
     */
    $setError: function(name, msg) {
      angular.forEach(this.$editables, function(editable) {
        if(!name || editable.name === name) {
          editable.setError(msg);
        }
      });
    },

    $submit: function() {
      if (this.$waiting) {
        return;
      }

      //clear errors
      this.$setError(null, '');

      //children onbeforesave
      var pc = editablePromiseCollection();
      angular.forEach(this.$editables, function(editable) {
        pc.when(editable.onbeforesave());
      });

      /*
      onbeforesave result:
      - true/undefined: save data and close form
      - false: close form without saving
      - string: keep form open and show error
      */
      pc.then({
        onWait: angular.bind(this, this.$setWaiting),
        onTrue: angular.bind(this, checkSelf, true),
        onFalse: angular.bind(this, checkSelf, false),
        onString: angular.bind(this, this.$activate)
      });

      //save
      function checkSelf(childrenTrue){
        var pc = editablePromiseCollection();
        pc.when(this.$onbeforesave());
        pc.then({
          onWait: angular.bind(this, this.$setWaiting),
          onTrue: childrenTrue ? angular.bind(this, this.$save) : angular.bind(this, this.$hide),
          onFalse: angular.bind(this, this.$hide),
          onString: angular.bind(this, this.$activate)
        });
      }
    },

    $save: function() {
      // write model for each editable
      angular.forEach(this.$editables, function(editable) {
        editable.save();
      });

      //call onaftersave of self and children
      var pc = editablePromiseCollection();
      pc.when(this.$onaftersave());
      angular.forEach(this.$editables, function(editable) {
        pc.when(editable.onaftersave());
      });

      /*
      onaftersave result:
      - true/undefined/false: just close form
      - string: keep form open and show error
      */
      pc.then({
        onWait: angular.bind(this, this.$setWaiting),
        onTrue: angular.bind(this, this.$hide),
        onFalse: angular.bind(this, this.$hide),
        onString: angular.bind(this, this.$activate)
      });
    },

    $onshow: angular.noop,
    $oncancel: angular.noop,
    $onhide: angular.noop,
    $onbeforesave: angular.noop,
    $onaftersave: angular.noop
  };

  return function() {
    return angular.extend({
      $editables: [],
      /**
       * Form visibility flag.
       *
       * @var {bool} $visible
       * @memberOf editable-form
       */
      $visible: false,
      /**
       * Form waiting flag. It becomes `true` when form is loading or saving data.
       *
       * @var {bool} $waiting
       * @memberOf editable-form
       */
      $waiting: false,
      $data: {}
    }, base);
  };
}]);
<|endoftext|>


<commit_before>/* Handles messages that get sent from the menu bar in the main process */

var webviews = require('webviews.js')
var webviewGestures = require('webviewGestures.js')
var browserUI = require('browserUI.js')
var focusMode = require('focusMode.js')
var modalMode = require('modalMode.js')
var findinpage = require('findinpage.js')
var PDFViewer = require('pdfViewer.js')
var tabEditor = require('navbar/tabEditor.js')
var readerView = require('readerView.js')
var taskOverlay = require('taskOverlay/taskOverlay.js')

module.exports = {
  initialize: function () {
    ipc.on('zoomIn', function () {
      webviewGestures.zoomWebviewIn(tabs.getSelected())
    })

    ipc.on('zoomOut', function () {
      webviewGestures.zoomWebviewOut(tabs.getSelected())
    })

    ipc.on('zoomReset', function () {
      webviewGestures.resetWebviewZoom(tabs.getSelected())
    })

    ipc.on('print', function () {
      if (PDFViewer.isPDFViewer(tabs.getSelected())) {
        PDFViewer.printPDF(tabs.getSelected())
      } else if (readerView.isReader(tabs.getSelected())) {
        readerView.printArticle(tabs.getSelected())
      } else if (webviews.placeholderRequests.length === 0) {
        // work around #1281 - calling print() when the view is hidden crashes on Linux in Electron 12
        // TODO figure out why webContents.print() doesn't work in Electron 4
        webviews.callAsync(tabs.getSelected(), 'executeJavaScript', 'window.print()')
      }
    })

    ipc.on('findInPage', function () {
      /* Page search is not available in modal mode. */
      if (modalMode.enabled()) {
        return
      }

      findinpage.start()
    })

    ipc.on('inspectPage', function () {
      webviews.callAsync(tabs.getSelected(), 'toggleDevTools')
    })

    ipc.on('openEditor', function () {
      tabEditor.show(tabs.getSelected())
    })

    ipc.on('showBookmarks', function () {
      tabEditor.show(tabs.getSelected(), '!bookmarks ')
    })

    ipc.on('showHistory', function () {
      tabEditor.show(tabs.getSelected(), '!history ')
    })

    ipc.on('duplicateTab', function (e) {
      if (modalMode.enabled()) {
        return
      }

      if (focusMode.enabled()) {
        focusMode.warn()
        return
      }

      browserUI.duplicateTab(tabs.getSelected())
    })

    ipc.on('addTab', function (e, data) {
      /* new tabs can't be created in modal mode */
      if (modalMode.enabled()) {
        return
      }

      /* new tabs can't be created in focus mode */
      if (focusMode.enabled()) {
        focusMode.warn()
        return
      }

      var newTab = tabs.add({
        url: data.url || ''
      })

      browserUI.addTab(newTab, {
        enterEditMode: !data.url // only enter edit mode if the new tab is empty
      })
    })

    ipc.on('saveCurrentPage', async function () {
      var currentTab = tabs.get(tabs.getSelected())

      // new tabs cannot be saved
      if (!currentTab.url) {
        return
      }

      // if the current tab is a PDF, let the PDF viewer handle saving the document
      if (PDFViewer.isPDFViewer(tabs.getSelected())) {
        PDFViewer.savePDF(tabs.getSelected())
        return
      }

      // https://github.com/minbrowser/min/issues/1480
      // TODO include more extensions here, or come up with a better way of detecting this
      if (['jpg', 'jpeg', 'png', 'webp', 'webm', 'mp4', 'ogg', 'mp3'].some(ext => tabs.get(tabs.getSelected()).url.endsWith('.' + ext))) {
        webviews.callAsync(tabs.getSelected(), 'downloadURL', [tabs.get(tabs.getSelected()).url])
        return
      }

      var savePath = await ipc.invoke('showSaveDialog', {
        defaultPath: currentTab.title.replace(/[/\\]/g, '_')
      })

      // savePath will be undefined if the save dialog is canceled
      if (savePath) {
        if (!savePath.endsWith('.html')) {
          savePath = savePath + '.html'
        }
        webviews.callAsync(tabs.getSelected(), 'savePage', [savePath, 'HTMLComplete'])
      }
    })

    ipc.on('addPrivateTab', function () {
      /* new tabs can't be created in modal mode */
      if (modalMode.enabled()) {
        return
      }

      /* new tabs can't be created in focus mode */
      if (focusMode.enabled()) {
        focusMode.warn()
        return
      }

      browserUI.addTab(tabs.add({
        private: true
      }))
    })

    ipc.on('toggleTaskOverlay', function () {
      taskOverlay.toggle()
    })

    ipc.on('goBack', function () {
      webviews.callAsync(tabs.getSelected(), 'goBack')
    })

    ipc.on('goForward', function () {
      webviews.callAsync(tabs.getSelected(), 'goForward')
    })
  }
}
<commit_msg>download image rather than saving page for more file types<commit_after>/* Handles messages that get sent from the menu bar in the main process */

var webviews = require('webviews.js')
var webviewGestures = require('webviewGestures.js')
var browserUI = require('browserUI.js')
var focusMode = require('focusMode.js')
var modalMode = require('modalMode.js')
var findinpage = require('findinpage.js')
var PDFViewer = require('pdfViewer.js')
var tabEditor = require('navbar/tabEditor.js')
var readerView = require('readerView.js')
var taskOverlay = require('taskOverlay/taskOverlay.js')

module.exports = {
  initialize: function () {
    ipc.on('zoomIn', function () {
      webviewGestures.zoomWebviewIn(tabs.getSelected())
    })

    ipc.on('zoomOut', function () {
      webviewGestures.zoomWebviewOut(tabs.getSelected())
    })

    ipc.on('zoomReset', function () {
      webviewGestures.resetWebviewZoom(tabs.getSelected())
    })

    ipc.on('print', function () {
      if (PDFViewer.isPDFViewer(tabs.getSelected())) {
        PDFViewer.printPDF(tabs.getSelected())
      } else if (readerView.isReader(tabs.getSelected())) {
        readerView.printArticle(tabs.getSelected())
      } else if (webviews.placeholderRequests.length === 0) {
        // work around #1281 - calling print() when the view is hidden crashes on Linux in Electron 12
        // TODO figure out why webContents.print() doesn't work in Electron 4
        webviews.callAsync(tabs.getSelected(), 'executeJavaScript', 'window.print()')
      }
    })

    ipc.on('findInPage', function () {
      /* Page search is not available in modal mode. */
      if (modalMode.enabled()) {
        return
      }

      findinpage.start()
    })

    ipc.on('inspectPage', function () {
      webviews.callAsync(tabs.getSelected(), 'toggleDevTools')
    })

    ipc.on('openEditor', function () {
      tabEditor.show(tabs.getSelected())
    })

    ipc.on('showBookmarks', function () {
      tabEditor.show(tabs.getSelected(), '!bookmarks ')
    })

    ipc.on('showHistory', function () {
      tabEditor.show(tabs.getSelected(), '!history ')
    })

    ipc.on('duplicateTab', function (e) {
      if (modalMode.enabled()) {
        return
      }

      if (focusMode.enabled()) {
        focusMode.warn()
        return
      }

      browserUI.duplicateTab(tabs.getSelected())
    })

    ipc.on('addTab', function (e, data) {
      /* new tabs can't be created in modal mode */
      if (modalMode.enabled()) {
        return
      }

      /* new tabs can't be created in focus mode */
      if (focusMode.enabled()) {
        focusMode.warn()
        return
      }

      var newTab = tabs.add({
        url: data.url || ''
      })

      browserUI.addTab(newTab, {
        enterEditMode: !data.url // only enter edit mode if the new tab is empty
      })
    })

    ipc.on('saveCurrentPage', async function () {
      var currentTab = tabs.get(tabs.getSelected())

      // new tabs cannot be saved
      if (!currentTab.url) {
        return
      }

      // if the current tab is a PDF, let the PDF viewer handle saving the document
      if (PDFViewer.isPDFViewer(tabs.getSelected())) {
        PDFViewer.savePDF(tabs.getSelected())
        return
      }

      // https://github.com/minbrowser/min/issues/1480
      // TODO include more extensions here, or come up with a better way of detecting this
      if (['apng', 'avif', 'gif', 'jpg', 'jpeg', 'jfif', 'pjpeg', 'pjp', 'png', 'svg', 'webp', 'bmp', 'ico', 'tiff', 'webm', 'mp4', 'ogg', 'mp3'].some(ext => tabs.get(tabs.getSelected()).url.endsWith('.' + ext))) {
        webviews.callAsync(tabs.getSelected(), 'downloadURL', [tabs.get(tabs.getSelected()).url])
        return
      }

      var savePath = await ipc.invoke('showSaveDialog', {
        defaultPath: currentTab.title.replace(/[/\\]/g, '_')
      })

      // savePath will be undefined if the save dialog is canceled
      if (savePath) {
        if (!savePath.endsWith('.html')) {
          savePath = savePath + '.html'
        }
        webviews.callAsync(tabs.getSelected(), 'savePage', [savePath, 'HTMLComplete'])
      }
    })

    ipc.on('addPrivateTab', function () {
      /* new tabs can't be created in modal mode */
      if (modalMode.enabled()) {
        return
      }

      /* new tabs can't be created in focus mode */
      if (focusMode.enabled()) {
        focusMode.warn()
        return
      }

      browserUI.addTab(tabs.add({
        private: true
      }))
    })

    ipc.on('toggleTaskOverlay', function () {
      taskOverlay.toggle()
    })

    ipc.on('goBack', function () {
      webviews.callAsync(tabs.getSelected(), 'goBack')
    })

    ipc.on('goForward', function () {
      webviews.callAsync(tabs.getSelected(), 'goForward')
    })
  }
}
<|endoftext|>


<commit_before>import React from "react";

import {ProjectContext} from "./constants";

const renderImageryRow = (filteredImageryList, defaultImageryId, projectImageryList, setProjectDetails, type) => (

        {filteredImageryList.length > 0
            ? filteredImageryList.map((imagery, idx) => (

                    <input
                        checked={projectImageryList.includes(imagery.id) || imagery.id === defaultImageryId}
                        className="form-check-input"
                        disabled={imagery.id === defaultImageryId}
                        id={imagery.id}
                        onChange={e => setProjectDetails({
                            projectImageryList: e.target.checked
                                ? [...projectImageryList, imagery.id]
                                : projectImageryList.filter(img => img !== imagery.id)
                        })}
                        type="checkbox"
                    />
                    {imagery.title}

            )) : {`No ${type} imagery found.`}}

);

export function ImagerySelection() {
    return (
        <ProjectContext.Consumer>
            {({imageryId, institutionImagery, projectImageryList, setProjectDetails}) => (institutionImagery
                ? (

Default Imagery

                            <select
                                className="form-control form-control-sm"
                                onChange={e => setProjectDetails({imageryId: parseInt(e.target.value)})}
                                size="1"
                                value={imageryId || -1}
                            >
                                {institutionImagery.map((imagery, uid) =>
                                    <option key={uid} value={imagery.id}>{imagery.title}</option>)}
                            </select>

Public Imagery

                            {renderImageryRow(
                                institutionImagery.filter(imagery => imagery.visibility === "public"),
                                imageryId,
                                projectImageryList,
                                setProjectDetails,
                                "public"
                            )}

Private Institution Imagery*

                            {renderImageryRow(
                                institutionImagery.filter(imagery => imagery.visibility === "private"),
                                imageryId,
                                projectImageryList,
                                setProjectDetails,
                                "institution"
                            )}

                            * Institution imagery will only be visible to institution members, no matter the project visibility.

                ) : (

Default Imagery

Loading Imagery...

                ))}
        </ProjectContext.Consumer>
    );
}

const findImageryName = (id, institutionImagery) => {
    const imagery = institutionImagery.find(ii => ii.id === id);
    return imagery ? imagery.title : "Imagery Error";
};

export function ImageryReview() {
    return (
        <ProjectContext.Consumer>
            {({imageryId, projectImageryList, institutionImagery}) => (

                    Default Imagery:  {findImageryName(imageryId, institutionImagery)}
                    Additional Imagery:  {projectImageryList.length === 0 && "None"}
                    {projectImageryList.length > 0 && (

                            {projectImageryList
                                .filter(id => id !== imageryId)
                                .map(id =>
{findImageryName(id, institutionImagery)}
)}

                    )}

            )}
        </ProjectContext.Consumer>
    );
}
<commit_msg>Add 'Additional Imagery' to Imagery selection<commit_after>import React from "react";

import {ProjectContext} from "./constants";

const renderImageryRow = (filteredImageryList, defaultImageryId, projectImageryList, setProjectDetails, type) => (

        {filteredImageryList.length > 0
            ? filteredImageryList.map((imagery, idx) => (

                    <input
                        checked={projectImageryList.includes(imagery.id) || imagery.id === defaultImageryId}
                        className="form-check-input"
                        disabled={imagery.id === defaultImageryId}
                        id={imagery.id}
                        onChange={e => setProjectDetails({
                            projectImageryList: e.target.checked
                                ? [...projectImageryList, imagery.id]
                                : projectImageryList.filter(img => img !== imagery.id)
                        })}
                        type="checkbox"
                    />
                    {imagery.title}

            )) : {`No ${type} imagery found.`}}

);

export function ImagerySelection() {
    return (
        <ProjectContext.Consumer>
            {({imageryId, institutionImagery, projectImageryList, setProjectDetails}) => (institutionImagery
                ? (

Default Imagery

                            <select
                                className="form-control form-control-sm"
                                onChange={e => setProjectDetails({imageryId: parseInt(e.target.value)})}
                                size="1"
                                value={imageryId || -1}
                            >
                                {institutionImagery.map((imagery, uid) =>
                                    <option key={uid} value={imagery.id}>{imagery.title}</option>)}
                            </select>

Additional Imagery

Public Imagery

                            {renderImageryRow(
                                institutionImagery.filter(imagery => imagery.visibility === "public"),
                                imageryId,
                                projectImageryList,
                                setProjectDetails,
                                "public"
                            )}

Private Institution Imagery*

                            {renderImageryRow(
                                institutionImagery.filter(imagery => imagery.visibility === "private"),
                                imageryId,
                                projectImageryList,
                                setProjectDetails,
                                "institution"
                            )}

                            * Institution imagery will only be visible to institution members, no matter the project visibility.

                ) : (

Default Imagery

Loading Imagery...

                ))}
        </ProjectContext.Consumer>
    );
}

const findImageryName = (id, institutionImagery) => {
    const imagery = institutionImagery.find(ii => ii.id === id);
    return imagery ? imagery.title : "Imagery Error";
};

export function ImageryReview() {
    return (
        <ProjectContext.Consumer>
            {({imageryId, projectImageryList, institutionImagery}) => (

                    Default Imagery:  {findImageryName(imageryId, institutionImagery)}
                    Additional Imagery:  {projectImageryList.length === 0 && "None"}
                    {projectImageryList.length > 0 && (

                            {projectImageryList
                                .filter(id => id !== imageryId)
                                .map(id =>
{findImageryName(id, institutionImagery)}
)}

                    )}

            )}
        </ProjectContext.Consumer>
    );
}
<|endoftext|>


<commit_before>define([
	"dojo/_base/declare", // declare
	"dojo/_base/lang" // mixin
], function (declare, lang) {
	/**
	 * @class
	 * @name rishson.control.Response
	 * @description This class is used to wrap any server response.
	 */
	return declare('rishson.control.Response', null, {
		/**
		 * @field
		 * @name rishson.control.Request.isOk
		 * @type {boolean}
		 * @description is the response OK. This equates to HTTP status code 200.
		 */
		isOk: false,

		/**
		 * @field
		 * @name rishson.control.Request.isConflicted
		 * @type {boolean}
		 * @description is the response indicating a conflicted server state. This equates to HTTP status code 409.
		 */
		isConflicted: false,

		/**
		 * @field
		 * @name rishson.control.Request.isInvalid
		 * @type {boolean}
		 * @description is the response indicating that the request was invalid. This equates to HTTP status code 400.
		 * This is a bit of a judgement call. Technically this should probably be mapped to 403, section 10.4.4 of RFC 2119
		 * states that: "403 Forbidden The server understood the request, but is refusing to fulfill it. Authorization will
		 * not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make
		 * public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity.
		 * If the server does not wish to make this    information available to the client, the status code 404 (Not Found)
		 * can be used instead."
		 * However, convention (O'Reiley RESTful Web Services) tends to map invalid to 400 (BAD REQUEST) rather than 403.
		 * We could debate this ALL day long, but we should probably go write some code instead.
		 */
		isInvalid: false,

		/**
		 * @field
		 * @name rishson.control.Request.isUnauthorised
		 * @type {boolean}
		 * @description is the response indicating that the request was not authorised. This equates to HTTP status code 403.
		 */
		isUnauthorised: false,

		/**
		 * @field
		 * @name rishson.control.Request.isServerError
		 * @type {boolean}
		 * @description is the response indicating that the request was a server error. This equates to HTTP status code 500.
		 */
		isServerError: false,

		/**
		 * @field
		 * @name rishson.control.Request.payload
		 * @type {object}
		 * @description the contents of the server response.
		 */
		payload: null,

		/**
		 * @field
		 * @name rishson.control.Response.mappedStatusCodes
		 * @static
		 * @type {Array.<number>}
		 * @description The status codes that are handled in a rishson.control.Response.
		 */
		mappedStatusCodes: [200, 400, 403, 404, 409, 500],

		/**
		 * @constructor
		 * @param {Object} response the server response
		 * @param {boolean} wasRestRequest was the server request a REST request
		 * @param {Object} ioArgs the HTTP response header
		 */
		constructor: function (response, wasRestRequest, ioArgs) {
			//@todo remove {}&& prefix if added - should we be allowing comment-filtered anymore or is it an antipattern?
			this._processHttpStatusCodes(response, ioArgs);

			//service responses should not have a blank payload
			if (!wasRestRequest) {
				if (!this.payload) {
					console.error('Invalid server response. No payload.');
					throw ('Invalid server response. No payload.');
				}
			}
			lang.mixin(this, response);
		},

		/**
		 * @function
		 * @name rishson.control.Response._processHttpStatusCodes
		 * @param {rishson.control.Response} response the response to process
		 * @param {Object} ioArgs the ioArgs to check
		 * @private
		 * @description convert HTTP status codes into handy response properties and remove payload wrapper if present
		 */
		_processHttpStatusCodes: function (response, ioArgs) {
			switch (ioArgs.xhr.status) {
			case 200:
				this.isOk = true;
				break;
			case 400:
				this.isInvalid = true;
				break;
			case 403:
				this.isUnauthorised = true;
				break;
			case 404:
				break;
			case 409:
				this.isConflicted = true;
				break;
			case 500:
				this.isServerError = true;
				break;
			default:
				throw ('Unknown status code passed to Response constructor: ' + ioArgs.xhr.status);
			}

			//if the response just has data in its body, then make it a payload. If a payload is specified in the
			//response already then just add to this class.
			if (response) {
				this.payload = response.payload || response;
			}
		}
	});
});<commit_msg>Removed 404 as it is not captured by Rishson<commit_after>define([
	"dojo/_base/declare", // declare
	"dojo/_base/lang" // mixin
], function (declare, lang) {
	/**
	 * @class
	 * @name rishson.control.Response
	 * @description This class is used to wrap any server response.
	 */
	return declare('rishson.control.Response', null, {
		/**
		 * @field
		 * @name rishson.control.Request.isOk
		 * @type {boolean}
		 * @description is the response OK. This equates to HTTP status code 200.
		 */
		isOk: false,

		/**
		 * @field
		 * @name rishson.control.Request.isConflicted
		 * @type {boolean}
		 * @description is the response indicating a conflicted server state. This equates to HTTP status code 409.
		 */
		isConflicted: false,

		/**
		 * @field
		 * @name rishson.control.Request.isInvalid
		 * @type {boolean}
		 * @description is the response indicating that the request was invalid. This equates to HTTP status code 400.
		 * This is a bit of a judgement call. Technically this should probably be mapped to 403, section 10.4.4 of RFC 2119
		 * states that: "403 Forbidden The server understood the request, but is refusing to fulfill it. Authorization will
		 * not help and the request SHOULD NOT be repeated. If the request method was not HEAD and the server wishes to make
		 * public why the request has not been fulfilled, it SHOULD describe the reason for the refusal in the entity.
		 * If the server does not wish to make this    information available to the client, the status code 404 (Not Found)
		 * can be used instead."
		 * However, convention (O'Reiley RESTful Web Services) tends to map invalid to 400 (BAD REQUEST) rather than 403.
		 * We could debate this ALL day long, but we should probably go write some code instead.
		 */
		isInvalid: false,

		/**
		 * @field
		 * @name rishson.control.Request.isUnauthorised
		 * @type {boolean}
		 * @description is the response indicating that the request was not authorised. This equates to HTTP status code 403.
		 */
		isUnauthorised: false,

		/**
		 * @field
		 * @name rishson.control.Request.isServerError
		 * @type {boolean}
		 * @description is the response indicating that the request was a server error. This equates to HTTP status code 500.
		 */
		isServerError: false,

		/**
		 * @field
		 * @name rishson.control.Request.payload
		 * @type {object}
		 * @description the contents of the server response.
		 */
		payload: null,

		/**
		 * @field
		 * @name rishson.control.Response.mappedStatusCodes
		 * @static
		 * @type {Array.<number>}
		 * @description The status codes that are handled in a rishson.control.Response.
		 */
		mappedStatusCodes: [200, 400, 403, 409, 500],

		/**
		 * @constructor
		 * @param {Object} response the server response
		 * @param {boolean} wasRestRequest was the server request a REST request
		 * @param {Object} ioArgs the HTTP response header
		 */
		constructor: function (response, wasRestRequest, ioArgs) {
			//@todo remove {}&& prefix if added - should we be allowing comment-filtered anymore or is it an antipattern?
			this._processHttpStatusCodes(response, ioArgs);

			//service responses should not have a blank payload
			if (!wasRestRequest) {
				if (!this.payload) {
					console.error('Invalid server response. No payload.');
					throw ('Invalid server response. No payload.');
				}
			}
			lang.mixin(this, response);
		},

		/**
		 * @function
		 * @name rishson.control.Response._processHttpStatusCodes
		 * @param {rishson.control.Response} response the response to process
		 * @param {Object} ioArgs the ioArgs to check
		 * @private
		 * @description convert HTTP status codes into handy response properties and remove payload wrapper if present
		 */
		_processHttpStatusCodes: function (response, ioArgs) {
			switch (ioArgs.xhr.status) {
			case 200:
				this.isOk = true;
				break;
			case 400:
				this.isInvalid = true;
				break;
			case 403:
				this.isUnauthorised = true;
				break;
			case 409:
				this.isConflicted = true;
				break;
			case 500:
				this.isServerError = true;
				break;
			default:
				throw ('Unknown status code passed to Response constructor: ' + ioArgs.xhr.status);
			}

			//if the response just has data in its body, then make it a payload. If a payload is specified in the
			//response already then just add to this class.
			if (response) {
				this.payload = response.payload || response;
			}
		}
	});
});<|endoftext|>


<commit_before>
// ----------------------------------------------------------------
// Switch Class

class SwitchModel extends CommonModel {
  constructor({
    name = 'Switch',
    view = null,
    lsKeyView = null,
    triggerSelector = 'none',
    switchSelector = 'none',
    toggleTimeMs = 500
  } = {})
  {
    super({
      name: name
    });

    this.INIT_VIEW = true;

    if (lsKeyView != null) {
      lsKeyView = `View.${lsKeyView}`;
    }

    this.NAME = name;
    this.view = view;
    this.LS_KEY_VIEW = lsKeyView;
    this.TRIGGER_SELECTOR = triggerSelector;
    this.$TRIGGER_SELECTOR = $(this.TRIGGER_SELECTOR);
    this.SWITCH_SELECTOR = switchSelector;
    this.$SWITCH_SELECTOR = $(this.SWITCH_SELECTOR);
    this.TOGGLE_TIME_MS = toggleTimeMs;
  }
}

class SwitchView extends CommonView {
  constructor(_model = new SwitchModel()) {
    super(_model);

    // Init SwitchView
    this.initSwitchView();
  }

  initSwitchView() {
    this.setCurrentView();
    this.setView(this.model.view, 0);
    this.setOn();
  }

  setOn() {
    $(document).on('click', this.model.TRIGGER_SELECTOR, () => {this.switchView()});
  }

  setCurrentView() {
    if (this.model.view == null) {
      if (this.model.LS_KEY_VIEW == null) {
        this.model.view = this.model.INIT_VIEW;
      } else {
        const lsValView = LocalStorage.getItem(this.model.LS_KEY_VIEW);
        if (lsValView == null) {
          this.model.view = true;
        } else if (lsValView == 'true') {
          this.model.view = true;
        } else if (lsValView == 'false') {
          this.model.view = false;
        }
      }
    }
  }

  switchView() {
    Log.logClass('Switch', `${this.model.NAME} View`);

    this.setCurrentView();
    this.setView(!this.model.view);
  }

  setView(_view, _speed = this.model.TOGGLE_TIME_MS) {
    Log.logClassKey('View', this.model.NAME, _view, Log.ARROW_INPUT);

    if (_view) {
      this.model.$TRIGGER_SELECTOR.addClass(this.model.CURRENT);
      this.model.$SWITCH_SELECTOR.show(_speed);
    } else {
      this.model.$TRIGGER_SELECTOR.removeClass(this.model.CURRENT);
      this.model.$SWITCH_SELECTOR.hide(_speed);
    }

    // save
    if (this.model.LS_KEY_VIEW != null) {
      LocalStorage.setItem(this.model.LS_KEY_VIEW, _view);
    }
    this.model.view = _view;
  }
}

// ----------------------------------------------------------------
// Controllers

class SwitchController extends CommonController {
  constructor(_obj) {
    super({
      name: 'Switch Controller'
    });

    this.model = new SwitchModel(_obj);
    this.view = new SwitchView(this.model);
  }
}
<commit_msg>Upgrade Switch Classes<commit_after>
// ----------------------------------------------------------------
// Switch Class

class SwitchModel extends CommonModel {
  constructor({
    name = 'Common Switch',
    view = null,
    lsKeyView = null,
    triggerSelector = null,
    switchSelector = null,
    toggleTimeMs = 500
  } = {})
  {
    super({
      name: name
    });

    this.INIT_VIEW = true;

    if (lsKeyView != null) {
      lsKeyView = `View.${lsKeyView}`;
    }

    this.NAME = name;
    this.view = view;
    this.LS_KEY_VIEW = lsKeyView;
    this.TRIGGER_SELECTOR = triggerSelector;
    this.$TRIGGER_SELECTOR = $(this.TRIGGER_SELECTOR);
    this.SWITCH_SELECTOR = switchSelector;
    this.$SWITCH_SELECTOR = $(this.SWITCH_SELECTOR);
    this.TOGGLE_TIME_MS = toggleTimeMs;

    Log.logObj(this);
  }
}

class SwitchView extends CommonView {
  constructor(_model = new SwitchModel()) {
    super(_model);
  }

  setView(_view = null, _speed = this.model.TOGGLE_TIME_MS) {
    Log.logClassKey('View', this.model.NAME, _view, Log.ARROW_INPUT);

    if (_view != null) {
      if (_view) {
        this.model.$TRIGGER_SELECTOR.addClass(this.model.CURRENT);
        this.model.$SWITCH_SELECTOR.show(_speed);
      } else {
        this.model.$TRIGGER_SELECTOR.removeClass(this.model.CURRENT);
        this.model.$SWITCH_SELECTOR.hide(_speed);
      }

      // save
      if (this.model.LS_KEY_VIEW != null) {
        LocalStorage.setItem(this.model.LS_KEY_VIEW, _view);
      }
      this.model.view = _view;
    }
  }
}

// ----------------------------------------------------------------
// Controllers

class SwitchController extends CommonController {
  constructor(_obj) {
    super({
      name: _obj.name
    });

    this.model = new SwitchModel(_obj);
    this.view = new SwitchView(this.model);

    this.NAME = this.model.name;

    // Init SwitchView
    this.initSwitchView();
  }

  initSwitchView() {
    this.setCurrentView();
    this.view.setView(this.model.view, 0);
  }

  setCurrentView() {
    if (this.model.view == null) {
      if (this.model.LS_KEY_VIEW == null) {
        this.model.view = this.model.INIT_VIEW;
      } else {
        const lsValView = LocalStorage.getItem(this.model.LS_KEY_VIEW);
        if (lsValView == null) {
          this.model.view = true;
        } else if (lsValView == 'true') {
          this.model.view = true;
        } else if (lsValView == 'false') {
          this.model.view = false;
        }
      }
    }
  }

  switchView() {
    Log.logClass('Switch', `${this.NAME} View`);

    this.setCurrentView();
    this.view.setView(!this.model.view);
  }
}

class SwitchEvent extends CommonEvent {
  constructor(_obj) {
    super(_obj);

    this.NAME = _obj.name;
    this.CONTROLLER_SWITCH = new SwitchController(_obj);

    this.setOnSwitch();
  }

  setOnSwitch() {
    SetEvent.setOn(
      'click',
      this.CONTROLLER_SWITCH.model.TRIGGER_SELECTOR,
      () => {
        this.CONTROLLER_SWITCH.switchView();
      }
    );
  }
}
<|endoftext|>


<commit_before>import React from 'react';
import { conditionalClass, humanReadableTimeStamp } from '../utillities';

class BlockChat extends React.Component {

  renderMessage(message) {
    if(message.user) {
      const cssClass = conditionalClass({
        'c-message': true,
        'c-message--user': message.user.id == this.props.myId, // Check if current user is author
      });

      return (

{ message.user.firstName }

{ message.content }

{ humanReadableTimeStamp(message.created_at) }

      )
    }
  }

  render() {
    const blockChatClass = conditionalClass({
      's-block-chat': true,
      'state-empty': this.props.messages.length === 0 ? true : false,
    });

    const header = this.props.messages.length > 0 ?
      (

Unread messages

31 augustus

) :
      (

No messages, yet

);

    const messages = this.props.messages.length > 0 ?
      this.props.messages.map(m => this.renderMessage(m)) :
      (
Be the first to start the conversation!
);

    return (

        { header }

          { messages }

    );
  }

  // functions.
}

export default BlockChat;
<commit_msg>cssclass => messageclass<commit_after>import React from 'react';
import { conditionalClass, humanReadableTimeStamp } from '../utillities';

class BlockChat extends React.Component {

  renderMessage(message) {
    if(message.user) {
      const messageClass = conditionalClass({
        'c-message': true,
        'c-message--user': message.user.id == this.props.myId, // Check if current user is author
      });

      return (

{ message.user.firstName }

{ message.content }

{ humanReadableTimeStamp(message.created_at) }

      )
    }
  }

  render() {
    const blockChatClass = conditionalClass({
      's-block-chat': true,
      'state-empty': this.props.messages.length === 0 ? true : false,
    });

    const header = this.props.messages.length > 0 ?
      (

Unread messages

31 augustus

) :
      (

No messages, yet

);

    const messages = this.props.messages.length > 0 ?
      this.props.messages.map(m => this.renderMessage(m)) :
      (
Be the first to start the conversation!
);

    return (

        { header }

          { messages }

    );
  }

  // functions.
}

export default BlockChat;
<|endoftext|>


<commit_before>/*jshint asi: true*/

(function() {
  'use strict'
  window.app = angular.module('presenter', []);

  app.config(
    ['$routeProvider', function($routeProvider) {
      return $routeProvider.when('/', {
        templateUrl: 'views/index.html',
        controller: 'MainCtrl'
      }).when('/o/:id', {
        templateUrl: 'views/object.html',
        controller: 'ObjectCtrl'
      }).when('/a/:id', {
        templateUrl: 'views/annotations.html',
        controller: 'notesCtrl'
      }).otherwise({
        redirectTo: '/'
      })
    }]
  )
  app.config(
    ['$httpProvider', function($httpProvider) {
      return delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }]
  )

  app.constant('envConfig', {
    objects: 'objects.json',
    tilesaw: '//tilesaw.dx.artsmia.org/', // '//localhost:8887/'
    tileUrlSubdomain: function(tileUrl) {
      return tileUrl.replace('http://0.', 'http://') //{s}')
    },
    crashpad: 'fallback/crashpad.json' // 'http://new.artsmia.org/crashpad/json/'
  })

  app.factory('objects', ['$http', 'envConfig', function($http, config) {
    return function() {
      return $http.get(config.objects).then(function(result) { return result.data; })
    }
  }])
  app.factory('tilesaw', ['$http', 'envConfig', function($http, config) {
    return { get: function(image) {
      return $http.get(config.tilesaw + image + '.tif').then(function(result) { return result.data; })
    }}
  }])
  app.factory('notes', ['$http', 'envConfig', function($http, config) {
    return function() {
      // TODO: how do we want to cache/bundle the JSON? WP is slow
      // also cache it within ng so we aren't requesting/parsing it on each request
      return $http.get(config.crashpad).then(function(result) {
        return result.data;
      })
    }
  }])

  app.directive('flatmap', function(tilesaw, envConfig) {
    return {
      restrict: 'E',
      scope: {
        json: '@',
        image: '@'
      },
      replace: true,
      transclude: true,
      template: '

',
      controller: function($scope) {
        var scope = $scope

        var removeJsonLayer = function() {
          if(scope.jsonLayer) scope.zoom.map.removeLayer(scope.jsonLayer)
        }

        var loadImage = function(image) {
          scope.viewChanging = true
          scope.image = image
          removeJsonLayer(); scope.jsonLayer = null
          tilesaw.get(image).then(function(tileJson) {
            $('#'+scope.container).find('.leaflet-tile-pane').css('visibility', 'visible') // why is this necessary? when I re-init a zoomer it's visibility is hidden.
            var tileUrl = envConfig.tileUrlSubdomain(tileJson.tiles[0])
            scope.zoom = Zoomer.zoom_image({container: scope.container, tileURL: tileUrl, imageWidth: tileJson.width, imageHeight: tileJson.height})
            scope.$emit('viewChanged')
          })
        }
        loadImage(scope.image)

        var annotateAndZoom = function(geometry) {
          removeJsonLayer()
          if(geometry) scope.jsonLayer = L.GeoJSON.geometryToLayer(geometry)
          if(scope.viewChanging) return // hold off until the view changes, resulting in `viewChanged` triggering this again
          if(scope.jsonLayer) {
            scope.$parent.$broadcast('showAnnotationsPanel', 'annotations')
            scope.jsonLayer.setStyle({stroke: true, fill: false, weight: 2, color: '#eee', opacity: '0.5'})
            var map = scope.zoom.map
            map.addLayer(scope.jsonLayer)
            map.zoomOut(100) // zoom all the way out and back in. Leaflet is misbehaving when zooming outside the current bounds, and this is a bit sketchy of a fix
            setTimeout(function() { map.fitBounds(scope.jsonLayer.getBounds()) }, 500)
          }
        }

        scope.$on('changeGeometry', function(event, geometry) { annotateAndZoom(geometry) }, true)
        scope.$on('viewChanged', function(event, message) { scope.viewChanging = false; annotateAndZoom() }, true)
        scope.$on('changeView', function(event, message) {
          if(message.image != scope.image) loadImage(message.image)
        })

        return {
          loadImage: loadImage,
          annotateAndZoom: annotateAndZoom,
          removeJsonLayer: removeJsonLayer,
          scope: scope
        }
      },
      link: function(scope, element, attrs) {
        scope.container = 'zoom-' + scope.image + '-' + new Date().getUTCMilliseconds()
      }
    }
  })

  app.directive('note', function() {
    var divIcon = L.divIcon({className: 'noteMarker'})
    return {
      restrict: 'E',
      // scope: {note: '=', view: '='},
      controller: function($scope) {},
      require: '^flatmap',
      link: function(scope, element, attrs, flatmapCtrl)  {
        scope.flatmapCtrl = flatmapCtrl
        scope.map = scope.flatmapCtrl.scope.zoom.map
        scope.jsonLayer = L.GeoJSON.geometryToLayer(scope.note.firebase.geometry)
        scope.note.index = scope.$parent.$index + scope.$index + 1
        divIcon.options.html = "" + scope.note.index + ""
        scope.marker = L.marker(scope.jsonLayer.getBounds().getCenter(), {icon: divIcon})
        scope.note.active = false

        var zoomNote = function() {
          flatmapCtrl.scope.$broadcast('changeView', scope.view)
          flatmapCtrl.scope.$broadcast('changeGeometry', scope.note.firebase.geometry)
          scope.note.active = true
        }
        var toggleNoteZoom = function() {
          scope.$apply(function() { scope.note.active = !scope.note.active })
        }

        scope.$watch('note.active', function(newVal, oldVal) {
          if(!newVal && oldVal && scope.note == flatmapCtrl.scope.lastActiveNote) {
            flatmapCtrl.removeJsonLayer()
            scope.map.zoomOut(100)
          } else if(newVal && !oldVal) {
            var lastNote = flatmapCtrl.scope.lastActiveNote
            if(lastNote) lastNote.active = false
            zoomNote()
            flatmapCtrl.scope.lastActiveNote = scope.note
          }
        })

        flatmapCtrl.scope.$watch('image', function(newVal, oldVal) {
          // scope.marker.setOpacity(newVal == scope.$parent.view.image ? 1 : 0)
          if(newVal == scope.$parent.view.image) {
            scope.marker.setOpacity(1)
            scope.marker.on('click', toggleNoteZoom)
          } else {
            scope.marker.setOpacity(0)
            scope.marker.off('click', toggleNoteZoom)
          }
        })

        scope.marker.addTo(scope.map)
      }
    }
  })

  app.controller('ObjectCtrl', ['$scope', '$routeParams', '$location', 'objects', 'notes',
    function($scope, $routeParams, $location, objects, notes) {
      $scope.id = $routeParams.id
      objects().then(function(data) {
        $scope.json = data[$scope.id]
        $scope.objects = data
      })
      notes().then(function(_wp) {
        $scope.wp = _wp.objects[$scope.id]
        if($scope.wp) {
          $scope.$on('viewChanged', function() {
            $scope.notes = $scope.wp.views
            $scope.$$phase || $scope.$apply()
          })
        }
      })

      $scope.next = function(direction) {
        var next = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))+1]
        if(next) $location.url('/o/'+next)
      }
      $scope.prev = function(direction) {
        var prev = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))-1]
        if(prev) $location.url('/o/'+prev)
      }

      $scope.toggleView = function(nextView) {
        $scope.activeSection = nextView || 'about'
      }
      $scope.toggleView()
      $scope.$on('showAnnotationsPanel', function(view) {
        $scope.activeSection = 'annotations'
      })

      $scope.activateNote = function(note, view) {
        note.active = !note.active
      }

      $scope.deactivateAllNotes = function() {
        angular.forEach($scope.notes, function(view) {
          angular.forEach(view.annotations, function(ann) { ann.active = false; })
        })
        $scope.$$phase || $scope.$apply()
      }

      $scope.activateView = function(view) {
        // TODO: encapsulate active view the same way I do notes, with view.active?
        $scope.deactivateAllNotes()
        $scope.$broadcast('changeView', view)
      }
      $scope.activateViewAndShowFirstAnnotation = function(view) {
        $scope.activateView(view)
        var note = view.annotations[0]
        if(note) activateNote(note)
      }

      $scope.toggleSixbar = function(element) {
        $scope.sixBarClosed = !$scope.sixBarClosed
        setTimeout(Zoomer.windowResized, 0)
      }
    }
  ])

  app.controller('notesCtrl', ['$scope', '$routeParams', 'notes',
    function($scope, $routeParams, wp) {
      $scope.id = $routeParams.id
      wp().then(function(_wp) {
        $scope.notes = _wp.objects[$scope.id].views
        $scope.$apply()
      })
    }
  ])

  app.controller('MainCtrl', ['$scope', '$routeParams', 'objects',
    function($scope, $routeParams, objects) {
      objects().then(function(data) {
        $scope.objects = data
      })
    }
  ])
})()
<commit_msg>do $http with {cache: true}<commit_after>/*jshint asi: true*/

(function() {
  'use strict'
  window.app = angular.module('presenter', []);

  app.config(
    ['$routeProvider', function($routeProvider) {
      return $routeProvider.when('/', {
        templateUrl: 'views/index.html',
        controller: 'MainCtrl'
      }).when('/o/:id', {
        templateUrl: 'views/object.html',
        controller: 'ObjectCtrl'
      }).when('/a/:id', {
        templateUrl: 'views/annotations.html',
        controller: 'notesCtrl'
      }).otherwise({
        redirectTo: '/'
      })
    }]
  )
  app.config(
    ['$httpProvider', function($httpProvider) {
      return delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }]
  )

  app.constant('envConfig', {
    objects: 'objects.json',
    tilesaw: '//tilesaw.dx.artsmia.org/', // '//localhost:8887/'
    tileUrlSubdomain: function(tileUrl) {
      return tileUrl.replace('http://0.', 'http://') //{s}')
    },
    crashpad: 'fallback/crashpad.json' // 'http://new.artsmia.org/crashpad/json/'
  })

  app.factory('objects', ['$http', 'envConfig', function($http, config) {
    return function() {
      return $http.get(config.objects, {cache: true}).then(function(result) { return result.data; })
    }
  }])
  app.factory('tilesaw', ['$http', 'envConfig', function($http, config) {
    return { get: function(image) {
      return $http.get(config.tilesaw + image + '.tif', {cache: true}).then(function(result) { return result.data; })
    }}
  }])
  app.factory('notes', ['$http', 'envConfig', function($http, config) {
    return function() {
      // TODO: how do we want to cache/bundle the JSON? WP is slow
      // also cache it within ng so we aren't requesting/parsing it on each request
      return $http.get(config.crashpad, {cache: true}).then(function(result) {
        return result.data;
      })
    }
  }])

  app.directive('flatmap', function(tilesaw, envConfig) {
    return {
      restrict: 'E',
      scope: {
        json: '@',
        image: '@'
      },
      replace: true,
      transclude: true,
      template: '

',
      controller: function($scope) {
        var scope = $scope

        var removeJsonLayer = function() {
          if(scope.jsonLayer) scope.zoom.map.removeLayer(scope.jsonLayer)
        }

        var loadImage = function(image) {
          scope.viewChanging = true
          scope.image = image
          removeJsonLayer(); scope.jsonLayer = null
          tilesaw.get(image).then(function(tileJson) {
            $('#'+scope.container).find('.leaflet-tile-pane').css('visibility', 'visible') // why is this necessary? when I re-init a zoomer it's visibility is hidden.
            var tileUrl = envConfig.tileUrlSubdomain(tileJson.tiles[0])
            scope.zoom = Zoomer.zoom_image({container: scope.container, tileURL: tileUrl, imageWidth: tileJson.width, imageHeight: tileJson.height})
            scope.$emit('viewChanged')
          })
        }
        loadImage(scope.image)

        var annotateAndZoom = function(geometry) {
          removeJsonLayer()
          if(geometry) scope.jsonLayer = L.GeoJSON.geometryToLayer(geometry)
          if(scope.viewChanging) return // hold off until the view changes, resulting in `viewChanged` triggering this again
          if(scope.jsonLayer) {
            scope.$parent.$broadcast('showAnnotationsPanel', 'annotations')
            scope.jsonLayer.setStyle({stroke: true, fill: false, weight: 2, color: '#eee', opacity: '0.5'})
            var map = scope.zoom.map
            map.addLayer(scope.jsonLayer)
            map.zoomOut(100) // zoom all the way out and back in. Leaflet is misbehaving when zooming outside the current bounds, and this is a bit sketchy of a fix
            setTimeout(function() { map.fitBounds(scope.jsonLayer.getBounds()) }, 500)
          }
        }

        scope.$on('changeGeometry', function(event, geometry) { annotateAndZoom(geometry) }, true)
        scope.$on('viewChanged', function(event, message) { scope.viewChanging = false; annotateAndZoom() }, true)
        scope.$on('changeView', function(event, message) {
          if(message.image != scope.image) loadImage(message.image)
        })

        return {
          loadImage: loadImage,
          annotateAndZoom: annotateAndZoom,
          removeJsonLayer: removeJsonLayer,
          scope: scope
        }
      },
      link: function(scope, element, attrs) {
        scope.container = 'zoom-' + scope.image + '-' + new Date().getUTCMilliseconds()
      }
    }
  })

  app.directive('note', function() {
    var divIcon = L.divIcon({className: 'noteMarker'})
    return {
      restrict: 'E',
      // scope: {note: '=', view: '='},
      controller: function($scope) {},
      require: '^flatmap',
      link: function(scope, element, attrs, flatmapCtrl)  {
        scope.flatmapCtrl = flatmapCtrl
        scope.map = scope.flatmapCtrl.scope.zoom.map
        scope.jsonLayer = L.GeoJSON.geometryToLayer(scope.note.firebase.geometry)
        scope.note.index = scope.$parent.$index + scope.$index + 1
        divIcon.options.html = "" + scope.note.index + ""
        scope.marker = L.marker(scope.jsonLayer.getBounds().getCenter(), {icon: divIcon})
        scope.note.active = false

        var zoomNote = function() {
          flatmapCtrl.scope.$broadcast('changeView', scope.view)
          flatmapCtrl.scope.$broadcast('changeGeometry', scope.note.firebase.geometry)
          scope.note.active = true
        }
        var toggleNoteZoom = function() {
          scope.$apply(function() { scope.note.active = !scope.note.active })
        }

        scope.$watch('note.active', function(newVal, oldVal) {
          if(!newVal && oldVal && scope.note == flatmapCtrl.scope.lastActiveNote) {
            flatmapCtrl.removeJsonLayer()
            scope.map.zoomOut(100)
          } else if(newVal && !oldVal) {
            var lastNote = flatmapCtrl.scope.lastActiveNote
            if(lastNote) lastNote.active = false
            zoomNote()
            flatmapCtrl.scope.lastActiveNote = scope.note
          }
        })

        flatmapCtrl.scope.$watch('image', function(newVal, oldVal) {
          // scope.marker.setOpacity(newVal == scope.$parent.view.image ? 1 : 0)
          if(newVal == scope.$parent.view.image) {
            scope.marker.setOpacity(1)
            scope.marker.on('click', toggleNoteZoom)
          } else {
            scope.marker.setOpacity(0)
            scope.marker.off('click', toggleNoteZoom)
          }
        })

        scope.marker.addTo(scope.map)
      }
    }
  })

  app.controller('ObjectCtrl', ['$scope', '$routeParams', '$location', 'objects', 'notes',
    function($scope, $routeParams, $location, objects, notes) {
      $scope.id = $routeParams.id
      objects().then(function(data) {
        $scope.json = data[$scope.id]
        $scope.objects = data
      })
      notes().then(function(_wp) {
        $scope.wp = _wp.objects[$scope.id]
        if($scope.wp) {
          $scope.$on('viewChanged', function() {
            $scope.notes = $scope.wp.views
            $scope.$$phase || $scope.$apply()
          })
        }
      })

      $scope.next = function(direction) {
        var next = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))+1]
        if(next) $location.url('/o/'+next)
      }
      $scope.prev = function(direction) {
        var prev = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))-1]
        if(prev) $location.url('/o/'+prev)
      }

      $scope.toggleView = function(nextView) {
        $scope.activeSection = nextView || 'about'
      }
      $scope.toggleView()
      $scope.$on('showAnnotationsPanel', function(view) {
        $scope.activeSection = 'annotations'
      })

      $scope.activateNote = function(note, view) {
        note.active = !note.active
      }

      $scope.deactivateAllNotes = function() {
        angular.forEach($scope.notes, function(view) {
          angular.forEach(view.annotations, function(ann) { ann.active = false; })
        })
        $scope.$$phase || $scope.$apply()
      }

      $scope.activateView = function(view) {
        // TODO: encapsulate active view the same way I do notes, with view.active?
        $scope.deactivateAllNotes()
        $scope.$broadcast('changeView', view)
      }
      $scope.activateViewAndShowFirstAnnotation = function(view) {
        $scope.activateView(view)
        var note = view.annotations[0]
        if(note) activateNote(note)
      }

      $scope.toggleSixbar = function(element) {
        $scope.sixBarClosed = !$scope.sixBarClosed
        setTimeout(Zoomer.windowResized, 0)
      }
    }
  ])

  app.controller('notesCtrl', ['$scope', '$routeParams', 'notes',
    function($scope, $routeParams, wp) {
      $scope.id = $routeParams.id
      wp().then(function(_wp) {
        $scope.notes = _wp.objects[$scope.id].views
        $scope.$apply()
      })
    }
  ])

  app.controller('MainCtrl', ['$scope', '$routeParams', 'objects',
    function($scope, $routeParams, objects) {
      objects().then(function(data) {
        $scope.objects = data
      })
    }
  ])
})()
<|endoftext|>


<commit_before>define([
  'jquery',
  '../utils',
  '../keys'
], function ($, Utils, KEYS) {
  function Search (decorated, $element, options) {
    decorated.call(this, $element, options);
  }

  Search.prototype.render = function (decorated) {
    var $search = $(
      '
' +
        '<input class="select2-search__field" type="search" tabindex="-1"' +
        ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
      '
'
    );

    this.$searchContainer = $search;
    this.$search = $search.find('input');

    var $rendered = decorated.call(this);

    this._transferTabIndex();

    return $rendered;
  };

  Search.prototype.bind = function (decorated, container, $container) {
    var self = this;

    decorated.call(this, container, $container);

    container.on('open', function () {
      self.$search.trigger('focus');
    });

    container.on('close', function () {
      self.$search.val('');
      self.$search.removeAttr('aria-activedescendant');
      self.$search.trigger('focus');
    });

    container.on('enable', function () {
      self.$search.prop('disabled', false);

      self._transferTabIndex();
    });

    container.on('disable', function () {
      self.$search.prop('disabled', true);
    });

    container.on('focus', function (evt) {
      self.$search.trigger('focus');
    });

    container.on('results:focus', function (params) {
      self.$search.attr('aria-activedescendant', params.id);
    });

    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
      self.trigger('focus', evt);
    });

    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
      self._handleBlur(evt);
    });

    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
      evt.stopPropagation();

      self.trigger('keypress', evt);

      self._keyUpPrevented = evt.isDefaultPrevented();

      var key = evt.which;

      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
        var $previousChoice = self.$searchContainer
          .prev('.select2-selection__choice');

        if ($previousChoice.length > 0) {
          var item = $previousChoice.data('data');

          self.searchRemoveChoice(item);

          evt.preventDefault();
        }
      }
    });

    // Try to detect the IE version should the `documentMode` property that
    // is stored on the document. This is only implemented in IE and is
    // slightly cleaner than doing a user agent check.
    // This property is not available in Edge, but Edge also doesn't have
    // this bug.
    var msie = document.documentMode;
    var disableInputEvents = msie && msie <= 11;

    // Workaround for browsers which do not support the `input` event
    // This will prevent double-triggering of events for browsers which support
    // both the `keyup` and `input` events.
    this.$selection.on(
      'input.searchcheck',
      '.select2-search--inline',
      function (evt) {
        // IE will trigger the `input` event when a placeholder is used on a
        // search box. To get around this issue, we are forced to ignore all
        // `input` events in IE and keep using `keyup`.
        if (disableInputEvents) {
          self.$selection.off('input.search input.searchcheck');
          return;
        }

        // Unbind the duplicated `keyup` event
        self.$selection.off('keyup.search');
      }
    );

    this.$selection.on(
      'keyup.search input.search',
      '.select2-search--inline',
      function (evt) {
        // IE will trigger the `input` event when a placeholder is used on a
        // search box. To get around this issue, we are forced to ignore all
        // `input` events in IE and keep using `keyup`.
        if (disableInputEvents && evt.type === 'input') {
          self.$selection.off('input.search input.searchcheck');
          return;
        }

        var key = evt.which;

        // We can freely ignore events from modifier keys
        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
          return;
        }

        // Tabbing will be handled during the `keydown` phase
        if (key == KEYS.TAB) {
          return;
        }

        self.handleSearch(evt);
      }
    );
  };

  /**
   * This method will transfer the tabindex attribute from the rendered
   * selection to the search box. This allows for the search box to be used as
   * the primary focus instead of the selection container.
   *
   * @private
   */
  Search.prototype._transferTabIndex = function (decorated) {
    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
    this.$selection.attr('tabindex', '-1');
  };

  Search.prototype.createPlaceholder = function (decorated, placeholder) {
    this.$search.attr('placeholder', placeholder.text);
  };

  Search.prototype.update = function (decorated, data) {
    var searchHadFocus = this.$search[0] == document.activeElement;

    this.$search.attr('placeholder', '');

    decorated.call(this, data);

    this.$selection.find('.select2-selection__rendered')
                   .append(this.$searchContainer);

    this.resizeSearch();
    if (searchHadFocus) {
      this.$search.focus();
    }
  };

  Search.prototype.handleSearch = function () {
    this.resizeSearch();

    if (!this._keyUpPrevented) {
      var input = this.$search.val();

      this.trigger('query', {
        term: input
      });
    }

    this._keyUpPrevented = false;
  };

  Search.prototype.searchRemoveChoice = function (decorated, item) {
    this.trigger('unselect', {
      data: item
    });

    this.$search.val(item.text);
    this.handleSearch();
  };

  Search.prototype.resizeSearch = function () {
    this.$search.css('width', '25px');

    var width = '';

    if (this.$search.attr('placeholder') !== '') {
      width = this.$selection.find('.select2-selection__rendered').innerWidth();
    } else {
      var minimumWidth = this.$search.val().length + 1;

      width = (minimumWidth * 0.75) + 'em';
    }

    this.$search.css('width', width);
  };

  return Search;
});
<commit_msg>selection: usability improvements with multiselection<commit_after>define([
  'jquery',
  '../utils',
  '../keys'
], function ($, Utils, KEYS) {
  function Search (decorated, $element, options) {
    decorated.call(this, $element, options);
  }

  Search.prototype.render = function (decorated) {
    var $search = $(
      '
' +
        '<input class="select2-search__field" type="search" tabindex="-1"' +
        ' autocomplete="off" autocorrect="off" autocapitalize="off"' +
        ' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
      '
'
    );

    this.$searchContainer = $search;
    this.$search = $search.find('input');

    var $rendered = decorated.call(this);

    this._transferTabIndex();

    return $rendered;
  };

  Search.prototype.bind = function (decorated, container, $container) {
    var self = this;
	this.container = container;

    decorated.call(this, container, $container);

    container.on('open', function () {
      self.$search.trigger('focus');
    });

    container.on('close', function () {
      self.$search.val('');
      self.$search.removeAttr('aria-activedescendant');
      self.$search.trigger('focus');
    });

    container.on('enable', function () {
      self.$search.prop('disabled', false);

      self._transferTabIndex();
    });

    container.on('disable', function () {
      self.$search.prop('disabled', true);
    });

    container.on('focus', function (evt) {
      self.$search.trigger('focus');
    });

    container.on('results:focus', function (params) {
      self.$search.attr('aria-activedescendant', params.id);
    });

    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
      self.trigger('focus', evt);
    });

    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
      self._handleBlur(evt);
    });

    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
      evt.stopPropagation();

      self.trigger('keypress', evt);

      self._keyUpPrevented = evt.isDefaultPrevented();

      var key = evt.which;

      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
        var $previousChoice = self.$searchContainer
          .prev('.select2-selection__choice');

        if ($previousChoice.length > 0) {
          var item = $previousChoice.data('data');

          self.searchRemoveChoice(item);

          evt.preventDefault();
        }
      }
    });

    // Try to detect the IE version should the `documentMode` property that
    // is stored on the document. This is only implemented in IE and is
    // slightly cleaner than doing a user agent check.
    // This property is not available in Edge, but Edge also doesn't have
    // this bug.
    var msie = document.documentMode;
    var disableInputEvents = msie && msie <= 11;

    // Workaround for browsers which do not support the `input` event
    // This will prevent double-triggering of events for browsers which support
    // both the `keyup` and `input` events.
    this.$selection.on(
      'input.searchcheck',
      '.select2-search--inline',
      function (evt) {
        // IE will trigger the `input` event when a placeholder is used on a
        // search box. To get around this issue, we are forced to ignore all
        // `input` events in IE and keep using `keyup`.
        if (disableInputEvents) {
          self.$selection.off('input.search input.searchcheck');
          return;
        }

        // Unbind the duplicated `keyup` event
        self.$selection.off('keyup.search');
      }
    );

    this.$selection.on(
      'keyup.search input.search',
      '.select2-search--inline',
      function (evt) {
        // IE will trigger the `input` event when a placeholder is used on a
        // search box. To get around this issue, we are forced to ignore all
        // `input` events in IE and keep using `keyup`.
        if (disableInputEvents && evt.type === 'input') {
          self.$selection.off('input.search input.searchcheck');
          return;
        }

        var key = evt.which;

        // We can freely ignore events from modifier keys
        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
          return;
        }

        // Tabbing will be handled during the `keydown` phase
        if (key == KEYS.TAB) {
          return;
        }

		self.container.open();
        self.handleSearch(evt);
      }
    );
  };

  /**
   * This method will transfer the tabindex attribute from the rendered
   * selection to the search box. This allows for the search box to be used as
   * the primary focus instead of the selection container.
   *
   * @private
   */
  Search.prototype._transferTabIndex = function (decorated) {
    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
    this.$selection.attr('tabindex', '-1');
  };

  Search.prototype.createPlaceholder = function (decorated, placeholder) {
    this.$search.attr('placeholder', placeholder.text);
  };

  Search.prototype.update = function (decorated, data) {
    var searchHadFocus = this.$search[0] == document.activeElement;

    this.$search.attr('placeholder', '');
	this.$search.val('');

    decorated.call(this, data);

    this.$selection.find('.select2-selection__rendered')
                   .append(this.$searchContainer);

    this.handleSearch();
    if (searchHadFocus) {
      this.$search.focus();
    }
  };

  Search.prototype.handleSearch = function () {
    this.resizeSearch();

    if (!this._keyUpPrevented && this.container.isOpen()) {
      var input = this.$search.val();

      this.trigger('query', {
        term: input
      });
    }

    this._keyUpPrevented = false;
  };

  Search.prototype.searchRemoveChoice = function (decorated, item) {
    this.trigger('unselect', {
      data: item
    });

    this.$search.val(item.text);
    this.handleSearch();
  };

  Search.prototype.resizeSearch = function () {
    this.$search.css('width', '25px');

    var width = '';

    if (this.$search.attr('placeholder') !== '') {
      width = this.$selection.find('.select2-selection__rendered').innerWidth();
    } else {
      var minimumWidth = this.$search.val().length + 1;

      width = (minimumWidth * 0.75) + 'em';
    }

    this.$search.css('width', width);
  };

  return Search;
});
<|endoftext|>


<commit_before>import React from "react";

import BaseLayout from "../components/layouts/base";
import PhotoGallery from "../components/photo-gallery";

const photos = [
  {
    src: "/gallery/london-marathon-1.jpg",
    width: 1,
    height: 1
  },
  {
    src: "/gallery/ben-nevis.jpg",
    width: 2,
    height: 1
  },
  {
    src: "/gallery/ben-cruachan.jpg",
    width: 1,
    height: 1
  },
  {
    src: "/gallery/london-marathon-2.jpg",
    width: 1,
    height: 1
  },
  {
    src: "/gallery/dam.jpg",
    width: 1,
    height: 1
  },
  {
    src: "/gallery/reservoir.jpg",
    width: 2,
    height: 1
  },
  {
    src: "/gallery/kili.jpg",
    width: 1,
    height: 1
  },

  {
    src: "/gallery/me.jpg",
    width: 1,
    height: 1
  },
  {
    src: "/gallery/porto.jpg",
    width: 1,
    height: 1
  },
  {
    src: "/gallery/gsr-shoe.jpg",
    width: 1,
    height: 1
  }
];

export default () => {
  return (
    <BaseLayout title="Photos">
      <PhotoGallery photos={photos} />
    </BaseLayout>
  );
};
<commit_msg>Added photo gallery image captions.<commit_after>import React from "react";

import BaseLayout from "../components/layouts/base";
import PhotoGallery from "../components/photo-gallery";

const photos = [
  {
    src: "/gallery/london-marathon-1.jpg",
    width: 1,
    height: 1,
    caption: "Running 2:59:50 at The London Marathon 2019."
  },
  {
    src: "/gallery/ben-nevis.jpg",
    width: 2,
    height: 1,
    caption: "Ben Nevis 2018 - Taken on the West Highland Way."
  },
  {
    src: "/gallery/ben-cruachan.jpg",
    width: 1,
    height: 1,
    caption: "Ben Crauchan - Climed with Daniel during November 2018."
  },
  {
    src: "/gallery/london-marathon-2.jpg",
    width: 1,
    height: 1,
    caption: "Running 2:59:50 at The London Marathon 2019."
  },
  {
    src: "/gallery/dam.jpg",
    width: 1,
    height: 1,
    caption: "Ben Crauchan Dam - November 2018."
  },
  {
    src: "/gallery/reservoir.jpg",
    width: 2,
    height: 1,
    caption: "Ben Crauchan Dam Reservoir - November 2018."
  },
  {
    src: "/gallery/kili.jpg",
    width: 1,
    height: 1,
    caption: "I climed Mount Kilimanjaro with my mum in 2010."
  },

  {
    src: "/gallery/me.jpg",
    width: 1,
    height: 1,
    caption: "Taken near Callendar in 2018."
  },
  {
    src: "/gallery/porto.jpg",
    width: 1,
    height: 1,
    caption: "Taken while working in Porto, 2015."
  },
  {
    src: "/gallery/gsr-shoe.jpg",
    width: 1,
    height: 1,
    caption: "I ran 1:23:43 at the Great Scottish Run Half Marathon 2018."
  }
];

export default () => {
  return (
    <BaseLayout title="Photos">
      <PhotoGallery photos={photos} />
    </BaseLayout>
  );
};
<|endoftext|>


<commit_before>/*jshint asi: true*/

(function() {
  'use strict'
  window.app = angular.module('presenter', []);

  app.config(
    ['$routeProvider', function($routeProvider) {
      return $routeProvider.when('/', {
        templateUrl: 'views/index.html',
        controller: 'MainCtrl'
      }).when('/o/:id', {
        templateUrl: 'views/object.html',
        controller: 'ObjectCtrl'
      }).when('/a/:id', {
        templateUrl: 'views/annotations.html',
        controller: 'notesCtrl'
      }).otherwise({
        redirectTo: '/'
      })
    }]
  )
  app.config(
    ['$httpProvider', function($httpProvider) {
      return delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }]
  )

  app.factory('objects', ['$http', function($http) {
    return function() {
      return $http.get('objects.json').then(function(result) { return result.data; })
    }
  }])
  app.factory('tilesaw', ['$http', function($http) {
    return { get: function(image) {
      return $http.get('//tilesaw.dx.artsmia.org/'+image+'.tif').then(function(result) { return result.data; })
    }}
  }])
  app.factory('notes', ['$http', function($http) {
    return function() {
      // TODO: how do we want to cache/bundle the JSON? WP is slow
      // also cache it within ng so we aren't requesting/parsing it on each request
      return $http.get('http://new.artsmia.org/crashpad/json/').then(function(result) {
        return result.data;
      })
    }
  }])

  app.directive('flatmap', function(tilesaw) {
    return {
      restrict: 'E',
      scope: {
        json: '@',
        image: '@'
      },
      replace: true,
      transclude: true,
      template: '

',
      controller: function($scope) {
        var scope = $scope

        var loadImage = function(image) {
          scope.viewChanging = true
          scope.currentImage = image
          tilesaw.get(image).then(function(tileJson) {
            $('#'+scope.container).find('.leaflet-tile-pane').css('visibility', 'visible') // why is this necessary? when I re-init a zoomer it's visibility is hidden.
            var tileUrl = tileJson.tiles[0].replace('http://0', 'http://{s}')
            scope.zoom = Zoomer.zoom_image({container: scope.container, tileURL: tileUrl, imageWidth: tileJson.width, imageHeight: tileJson.height})
            scope.$broadcast('viewChanged')
          })
        }
        loadImage(scope.image)

        var annotateAndZoom = function(geometry) {
          if(scope.jsonLayer) removeJsonLayer()
          if(geometry) scope.jsonLayer = L.GeoJSON.geometryToLayer(geometry)
          if(scope.viewChanging) return // hold off until the view changes, resulting in `viewChanged` triggering this again
          if(scope.jsonLayer) {
            scope.$parent.$broadcast('showAnnotationsPanel', 'annotations')
            scope.jsonLayer.setStyle({stroke: true, fill: false, weight: 2, color: '#eee', opacity: '0.5'})
            scope.zoom.map.addLayer(scope.jsonLayer)
            scope.zoom.map.fitBounds(scope.jsonLayer.getBounds())
          }
        }

        var removeJsonLayer = function() {
          scope.zoom.map.removeLayer(scope.jsonLayer)
        }

        scope.$on('changeGeometry', function(event, geometry) { annotateAndZoom(geometry) }, true)
        scope.$on('viewChanged', function(event, message) { scope.viewChanging = false; annotateAndZoom() }, true)
        scope.$on('changeView', function(event, message) {
          if(message.image != scope.currentImage) loadImage(message.image)
        })

        return {
          loadImage: loadImage,
          annotateAndZoom: annotateAndZoom,
          removeJsonLayer: removeJsonLayer,
          scope: scope
        }
      },
      link: function(scope, element, attrs) {
        scope.container = 'zoom-' + scope.image + '-' + new Date().getUTCMilliseconds()
      }
    }
  })

  app.directive('note', function() {
    var divIcon = L.divIcon({className: 'noteMarker'})
    return {
      restrict: 'E',
      // scope: {note: '=', view: '='},
      controller: function($scope) {},
      require: '^flatmap',
      link: function(scope, element, attrs, flatmapCtrl)  {
        window.noteScope = scope
        scope.flatmapCtrl = flatmapCtrl
        scope.map = scope.flatmapCtrl.scope.zoom.map
        scope.jsonLayer = L.GeoJSON.geometryToLayer(scope.note.firebase.geometry)
        scope.note.index = divIcon.options.html = scope.$parent.$index + scope.$index + 1
        scope.marker = L.marker(scope.jsonLayer.getBounds().getCenter(), {icon: divIcon})

        var zoomNote = function() {
          flatmapCtrl.scope.$broadcast('changeView', scope.view)
          flatmapCtrl.scope.$broadcast('changeGeometry', scope.note.firebase.geometry)
          scope.$apply(function() { scope.note.active = true })
        }
        var toggleNoteZoom = function() {
          if(scope.note.active) {
            flatmapCtrl.removeJsonLayer()
            // TODO: reset back to main view? Show view changing chrome?
            scope.map.zoomOut(100)
            scope.$apply(function() { scope.note.active = false })
          } else {
            zoomNote()
            var lastNote = flatmapCtrl.scope.lastActiveNote
            if(lastNote) lastNote.active = false
          }
          flatmapCtrl.scope.lastActiveNote = scope.note
        }

        scope.marker.addTo(scope.map).on('click', toggleNoteZoom)
      }
    }
  })

  app.controller('ObjectCtrl', ['$scope', '$routeParams', '$location', 'objects', 'notes',
    function($scope, $routeParams, $location, objects, notes) {
      $scope.id = $routeParams.id
      objects().then(function(data) {
        $scope.json = data[$scope.id]
        $scope.objects = data
      })
      notes().then(function(_wp) {
        $scope.wp = _wp.objects[$scope.id]
        if($scope.wp) {
          $scope.notes = $scope.wp.views
          $scope.$$phase || $scope.$apply()
        }
      })

      $scope.next = function(direction) {
        var next = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))+1]
        if(next) $location.url('/o/'+next)
      }
      $scope.prev = function(direction) {
        var prev = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))-1]
        if(prev) $location.url('/o/'+prev)
      }

      $scope.toggleView = function(nextView) {
        $scope.activeSection = nextView || 'about'
      }
      $scope.toggleView()
      $scope.$on('showAnnotationsPanel', function(view) {
        $scope.activeSection = 'annotations'
      })

      function activateAnnotationAndChangeImageIfNeccessary(note, view) {
        note.active = true
        $scope.$broadcast('changeView', view)
        $scope.$broadcast('changeGeometry', note.firebase.geometry)
      }

      $scope.activateNote = function(note, view) {
        // TODO: bubble this up into the directive so it works when clicking markers
        var shouldActivate = !note.active
        angular.forEach(view.annotations, function(ann) { ann.active = false })
        if(shouldActivate) activateAnnotationAndChangeImageIfNeccessary(note, view)
      }

      $scope.activateView = function(view) {
        $scope.$broadcast('changeView', view)
      }

      $scope.toggleSixbar = function(element) {
        $scope.sixBarClosed = !$scope.sixBarClosed
        setTimeout(Zoomer.windowResized, 0)
      }
    }
  ])

  app.controller('notesCtrl', ['$scope', '$routeParams', 'notes',
    function($scope, $routeParams, wp) {
      $scope.id = $routeParams.id
      wp().then(function(_wp) {
        $scope.notes = _wp.objects[$scope.id].views
        $scope.$apply()
      })
    }
  ])

  app.controller('MainCtrl', ['$scope', '$routeParams', 'objects',
    function($scope, $routeParams, objects) {
      objects().then(function(data) {
        $scope.objects = data
      })
    }
  ])
})()
<commit_msg>Smooth out changing annotations across views<commit_after>/*jshint asi: true*/

(function() {
  'use strict'
  window.app = angular.module('presenter', []);

  app.config(
    ['$routeProvider', function($routeProvider) {
      return $routeProvider.when('/', {
        templateUrl: 'views/index.html',
        controller: 'MainCtrl'
      }).when('/o/:id', {
        templateUrl: 'views/object.html',
        controller: 'ObjectCtrl'
      }).when('/a/:id', {
        templateUrl: 'views/annotations.html',
        controller: 'notesCtrl'
      }).otherwise({
        redirectTo: '/'
      })
    }]
  )
  app.config(
    ['$httpProvider', function($httpProvider) {
      return delete $httpProvider.defaults.headers.common['X-Requested-With'];
    }]
  )

  app.factory('objects', ['$http', function($http) {
    return function() {
      return $http.get('objects.json').then(function(result) { return result.data; })
    }
  }])
  app.factory('tilesaw', ['$http', function($http) {
    return { get: function(image) {
      return $http.get('//tilesaw.dx.artsmia.org/'+image+'.tif').then(function(result) { return result.data; })
    }}
  }])
  app.factory('notes', ['$http', function($http) {
    return function() {
      // TODO: how do we want to cache/bundle the JSON? WP is slow
      // also cache it within ng so we aren't requesting/parsing it on each request
      return $http.get('http://new.artsmia.org/crashpad/json/').then(function(result) {
        return result.data;
      })
    }
  }])

  app.directive('flatmap', function(tilesaw) {
    return {
      restrict: 'E',
      scope: {
        json: '@',
        image: '@'
      },
      replace: true,
      transclude: true,
      template: '

',
      controller: function($scope) {
        var scope = $scope

        var removeJsonLayer = function() {
          if(scope.jsonLayer) scope.zoom.map.removeLayer(scope.jsonLayer)
        }

        var loadImage = function(image) {
          scope.viewChanging = true
          scope.image = image
          removeJsonLayer(); scope.jsonLayer = null
          tilesaw.get(image).then(function(tileJson) {
            $('#'+scope.container).find('.leaflet-tile-pane').css('visibility', 'visible') // why is this necessary? when I re-init a zoomer it's visibility is hidden.
            var tileUrl = tileJson.tiles[0].replace('http://0', 'http://{s}')
            scope.zoom = Zoomer.zoom_image({container: scope.container, tileURL: tileUrl, imageWidth: tileJson.width, imageHeight: tileJson.height})
            scope.$broadcast('viewChanged')
          })
        }
        loadImage(scope.image)

        var annotateAndZoom = function(geometry) {
          removeJsonLayer()
          if(geometry) scope.jsonLayer = L.GeoJSON.geometryToLayer(geometry)
          if(scope.viewChanging) return // hold off until the view changes, resulting in `viewChanged` triggering this again
          if(scope.jsonLayer) {
            scope.$parent.$broadcast('showAnnotationsPanel', 'annotations')
            scope.jsonLayer.setStyle({stroke: true, fill: false, weight: 2, color: '#eee', opacity: '0.5'})
            scope.zoom.map.addLayer(scope.jsonLayer)
            scope.zoom.map.fitBounds(scope.jsonLayer.getBounds())
          }
        }

        scope.$on('changeGeometry', function(event, geometry) { annotateAndZoom(geometry) }, true)
        scope.$on('viewChanged', function(event, message) { scope.viewChanging = false; annotateAndZoom() }, true)
        scope.$on('changeView', function(event, message) {
          if(message.image != scope.image) loadImage(message.image)
        })

        return {
          loadImage: loadImage,
          annotateAndZoom: annotateAndZoom,
          removeJsonLayer: removeJsonLayer,
          scope: scope
        }
      },
      link: function(scope, element, attrs) {
        scope.container = 'zoom-' + scope.image + '-' + new Date().getUTCMilliseconds()
      }
    }
  })

  app.directive('note', function() {
    var divIcon = L.divIcon({className: 'noteMarker'})
    return {
      restrict: 'E',
      // scope: {note: '=', view: '='},
      controller: function($scope) {},
      require: '^flatmap',
      link: function(scope, element, attrs, flatmapCtrl)  {
        scope.flatmapCtrl = flatmapCtrl
        scope.map = scope.flatmapCtrl.scope.zoom.map
        scope.jsonLayer = L.GeoJSON.geometryToLayer(scope.note.firebase.geometry)
        scope.note.index = divIcon.options.html = scope.$parent.$index + scope.$index + 1
        scope.marker = L.marker(scope.jsonLayer.getBounds().getCenter(), {icon: divIcon})
        scope.note.active = false

        var zoomNote = function() {
          flatmapCtrl.scope.$broadcast('changeView', scope.view)
          flatmapCtrl.scope.$broadcast('changeGeometry', scope.note.firebase.geometry)
          scope.note.active = true
        }
        var toggleNoteZoom = function() {
          scope.$apply(function() { scope.note.active = !scope.note.active })
        }

        scope.$watch('note.active', function(newVal, oldVal) {
          if(!newVal && oldVal && scope.note == flatmapCtrl.scope.lastActiveNote) {
            flatmapCtrl.removeJsonLayer()
            scope.map.zoomOut(100)
          } else if(newVal && !oldVal) {
            var lastNote = flatmapCtrl.scope.lastActiveNote
            if(lastNote) lastNote.active = false
            zoomNote()
            flatmapCtrl.scope.lastActiveNote = scope.note
          }
        })

        scope.marker.addTo(scope.map).on('click', toggleNoteZoom)
      }
    }
  })

  app.controller('ObjectCtrl', ['$scope', '$routeParams', '$location', 'objects', 'notes',
    function($scope, $routeParams, $location, objects, notes) {
      $scope.id = $routeParams.id
      objects().then(function(data) {
        $scope.json = data[$scope.id]
        $scope.objects = data
      })
      notes().then(function(_wp) {
        $scope.wp = _wp.objects[$scope.id]
        if($scope.wp) {
          $scope.notes = $scope.wp.views
          $scope.$$phase || $scope.$apply()
        }
      })

      $scope.next = function(direction) {
        var next = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))+1]
        if(next) $location.url('/o/'+next)
      }
      $scope.prev = function(direction) {
        var prev = $scope.objects.ids[$scope.objects.ids.indexOf(parseInt($scope.id))-1]
        if(prev) $location.url('/o/'+prev)
      }

      $scope.toggleView = function(nextView) {
        $scope.activeSection = nextView || 'about'
      }
      $scope.toggleView()
      $scope.$on('showAnnotationsPanel', function(view) {
        $scope.activeSection = 'annotations'
      })

      $scope.activateNote = function(note, view) {
        note.active = !note.active
      }

      $scope.deactivateAllNotes = function() {
        angular.forEach($scope.notes, function(view) {
          angular.forEach(view.annotations, function(ann) { ann.active = false; })
        })
        $scope.$$phase || $scope.$apply()
      }

      $scope.activateView = function(view) {
        // TODO: encapsulate active view the same way I do notes, with view.active?
        $scope.deactivateAllNotes()
        $scope.$broadcast('changeView', view)
      }
      $scope.activateViewAndShowFirstAnnotation = function(view) {
        $scope.activateView(view)
        var note = view.annotations[0]
        if(note) activateNote(note)
      }

      $scope.toggleSixbar = function(element) {
        $scope.sixBarClosed = !$scope.sixBarClosed
        setTimeout(Zoomer.windowResized, 0)
      }
    }
  ])

  app.controller('notesCtrl', ['$scope', '$routeParams', 'notes',
    function($scope, $routeParams, wp) {
      $scope.id = $routeParams.id
      wp().then(function(_wp) {
        $scope.notes = _wp.objects[$scope.id].views
        $scope.$apply()
      })
    }
  ])

  app.controller('MainCtrl', ['$scope', '$routeParams', 'objects',
    function($scope, $routeParams, objects) {
      objects().then(function(data) {
        $scope.objects = data
      })
    }
  ])
})()
<|endoftext|>