<commit_before>'use strict';

var CRp = {};

CRp.safeDrawImage = function( context, img, ix, iy, iw, ih, x, y, w, h ){
  var r = this;

  try {
    context.drawImage( img, ix, iy, iw, ih, x, y, w, h );
  } catch(e){
    r.data.canvasNeedsRedraw[r.NODE] = true;
    r.data.canvasNeedsRedraw[r.DRAG] = true;

    r.drawingImage = true;

    r.redraw();
  }
};

CRp.drawInscribedImage = function(context, img, node) {
  var r = this;
  var nodeX = node._private.position.x;
  var nodeY = node._private.position.y;
  var style = node._private.style;
  var fit = style['background-fit'].value;
  var xPos = style['background-position-x'];
  var yPos = style['background-position-y'];
  var repeat = style['background-repeat'].value;
  var nodeW = node.width();
  var nodeH = node.height();
  var rs = node._private.rscratch;
  var clip = style['background-clip'].value;
  var shouldClip = clip === 'node';
  var imgOpacity = style['background-image-opacity'].value;

  var imgW = img.width || img.cachedW;
  var imgH = img.height || img.cachedH;

  // workaround for broken browsers like ie
  if( (img.width === undefined || img.height === undefined) ){
    document.body.appendChild( img );

    imgW = img.cachedW = img.width;
    imgW = img.cachedH = img.height;

    document.body.removeChild( img );
  }

  var w = imgW;
  var h = imgH;

  if( w === 0 || h === 0 ){
    return; // no point in drawing empty image (and chrome is broken in this case)
  }

  var bgW = style['background-width'];
  if( bgW.value !== 'auto' ){
    if( bgW.units === '%' ){
      w = bgW.value/100 * nodeW;
    } else {
      w = bgW.pfValue;
    }
  }

  var bgH = style['background-height'];
  if( bgH.value !== 'auto' ){
    if( bgH.units === '%' ){
      h = bgH.value/100 * nodeH;
    } else {
      h = bgH.pfValue;
    }
  }

  if( fit === 'contain' ){
    var scale = Math.min( nodeW/w, nodeH/h );

    w *= scale;
    h *= scale;

  } else if( fit === 'cover' ){
    var scale = Math.max( nodeW/w, nodeH/h );

    w *= scale;
    h *= scale;
  }

  var x = (nodeX - nodeW/2); // left
  if( xPos.units === '%' ){
    x += (nodeW - w) * xPos.value/100;
  } else {
    x += xPos.pfValue;
  }

  var y = (nodeY - nodeH/2); // top
  if( yPos.units === '%' ){
    y += (nodeH - h) * yPos.value/100;
  } else {
    y += yPos.pfValue;
  }

  if( rs.pathCache ){
    x -= nodeX;
    y -= nodeY;

    nodeX = 0;
    nodeY = 0;
  }

  var gAlpha = context.globalAlpha;

  context.globalAlpha = imgOpacity;

  if( repeat === 'no-repeat' ){

    if( shouldClip ){
      context.save();

      if( rs.pathCache ){
        context.clip( rs.pathCache );
      } else {
        r.nodeShapes[r.getNodeShape(node)].draw(
          context,
          nodeX, nodeY,
          nodeW, nodeH);

        context.clip();
      }
    }

    r.safeDrawImage( context, img, 0, 0, imgW, imgH, x, y, w, h );

    if( shouldClip ){
      context.restore();
    }
  } else {
    var pattern = context.createPattern( img, repeat );
    context.fillStyle = pattern;

    r.nodeShapes[r.getNodeShape(node)].draw(
        context,
        nodeX, nodeY,
        nodeW, nodeH);

      context.translate(x, y);
      context.fill();
      context.translate(-x, -y);
  }

  context.globalAlpha = gAlpha;

};

module.exports = CRp;
<commit_msg>more permissive checks re svg in ie #782<commit_after>'use strict';

var CRp = {};

CRp.safeDrawImage = function( context, img, ix, iy, iw, ih, x, y, w, h ){
  var r = this;

  try {
    context.drawImage( img, ix, iy, iw, ih, x, y, w, h );
  } catch(e){
    r.data.canvasNeedsRedraw[r.NODE] = true;
    r.data.canvasNeedsRedraw[r.DRAG] = true;

    r.drawingImage = true;

    r.redraw();
  }
};

CRp.drawInscribedImage = function(context, img, node) {
  var r = this;
  var nodeX = node._private.position.x;
  var nodeY = node._private.position.y;
  var style = node._private.style;
  var fit = style['background-fit'].value;
  var xPos = style['background-position-x'];
  var yPos = style['background-position-y'];
  var repeat = style['background-repeat'].value;
  var nodeW = node.width();
  var nodeH = node.height();
  var rs = node._private.rscratch;
  var clip = style['background-clip'].value;
  var shouldClip = clip === 'node';
  var imgOpacity = style['background-image-opacity'].value;

  var imgW = img.width || img.cachedW;
  var imgH = img.height || img.cachedH;

  // workaround for broken browsers like ie
  if( null == imgW || null == imgH ){
    document.body.appendChild( img );

    imgW = img.cachedW = img.width || img.offsetWidth;
    imgH = img.cachedH = img.height || img.offsetHeight;

    document.body.removeChild( img );
  }

  var w = imgW;
  var h = imgH;

  var bgW = style['background-width'];
  if( bgW.value !== 'auto' ){
    if( bgW.units === '%' ){
      w = bgW.value/100 * nodeW;
    } else {
      w = bgW.pfValue;
    }
  }

  var bgH = style['background-height'];
  if( bgH.value !== 'auto' ){
    if( bgH.units === '%' ){
      h = bgH.value/100 * nodeH;
    } else {
      h = bgH.pfValue;
    }
  }

  if( w === 0 || h === 0 ){
    return; // no point in drawing empty image (and chrome is broken in this case)
  }

  if( fit === 'contain' ){
    var scale = Math.min( nodeW/w, nodeH/h );

    w *= scale;
    h *= scale;

  } else if( fit === 'cover' ){
    var scale = Math.max( nodeW/w, nodeH/h );

    w *= scale;
    h *= scale;
  }

  var x = (nodeX - nodeW/2); // left
  if( xPos.units === '%' ){
    x += (nodeW - w) * xPos.value/100;
  } else {
    x += xPos.pfValue;
  }

  var y = (nodeY - nodeH/2); // top
  if( yPos.units === '%' ){
    y += (nodeH - h) * yPos.value/100;
  } else {
    y += yPos.pfValue;
  }

  if( rs.pathCache ){
    x -= nodeX;
    y -= nodeY;

    nodeX = 0;
    nodeY = 0;
  }

  var gAlpha = context.globalAlpha;

  context.globalAlpha = imgOpacity;

  if( repeat === 'no-repeat' ){

    if( shouldClip ){
      context.save();

      if( rs.pathCache ){
        context.clip( rs.pathCache );
      } else {
        r.nodeShapes[r.getNodeShape(node)].draw(
          context,
          nodeX, nodeY,
          nodeW, nodeH);

        context.clip();
      }
    }

    r.safeDrawImage( context, img, 0, 0, imgW, imgH, x, y, w, h );

    if( shouldClip ){
      context.restore();
    }
  } else {
    var pattern = context.createPattern( img, repeat );
    context.fillStyle = pattern;

    r.nodeShapes[r.getNodeShape(node)].draw(
        context,
        nodeX, nodeY,
        nodeW, nodeH);

      context.translate(x, y);
      context.fill();
      context.translate(-x, -y);
  }

  context.globalAlpha = gAlpha;

};

module.exports = CRp;
<|endoftext|>


<commit_before>import React from 'react'
import ReactDOM from 'react-dom'
import Draggable from 'react-draggable'
import {storiesOf} from '@kadira/storybook'
import {action, decorateAction} from '@kadira/storybook-addon-actions'
import {
  Button, Panel, Row, Table, Well, Checkbox, InputGroup, Col,
  FormControl, DropdownButton, MenuItem, ListGroup, ListGroupItem, PanelGroup,
  OverlayTrigger, Tooltip
}
  from 'react-bootstrap'
import {Icon, Modal} from '../../components'
import Lorem from 'react-lorem-component'

const tooltipSort = (
    <Tooltip id='tooltip'>Best match will be chosen based on the priority of
      selected projects. Exact matches take precendence.
    </Tooltip>
)

const tooltipReadOnly = (
    <Tooltip id='tooltip'>Read only
    </Tooltip>
)

const heading1 =
<Checkbox checked> Project A</Checkbox>

const heading2 =
<Checkbox> Project B
  <OverlayTrigger placement='top' overlay={tooltipReadOnly}>
    <Icon name='locked'
          className='s0 icon-locked'/>
  </OverlayTrigger>
</Checkbox>

const currentProject = 'Current project'
const currentVersion = 'Current version'

storiesOf('Modal', module)
    .addDecorator((story) => (

          {story()}

    ))
    .add('default', () => (
        <Modal
            show={true}
            onHide={action('onHide')}>
          <Modal.Header>
            <Modal.Title>Modal heading</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <Lorem />
          </Modal.Body>
          <Modal.Footer>

            <Row>
              <Button bsStyle='link'
                      className='btn-left'
                      onClick={action('onClick')}>
                Close
              </Button>
              <Button
                  bsStyle='primary'
                  onClick={action('onClick')}>
                Save
              </Button>
            </Row>

          </Modal.Footer>
        </Modal>
    ))
    .add('version-TM', () => (
        <Modal
            show={true}
            onHide={action('onHide')}>
          <Modal.Header>
            <Modal.Title>Version TM</Modal.Title>
          </Modal.Header>
          <Modal.Body>

This feature copies existing translations from similar documents
                in other projects and versions into this project version if the source
                text and metadata both match.
                The translation state will be preserved (
                translated or
                 approved).

              <Well>
                <Checkbox>
                  If context matches but source text does not, copy as
                   fuzzy.
                </Checkbox>
              </Well>
              <Col xs={12} className='vmerge-row'>
                <Col xs={4}>
                  TM match percentage
                </Col>
                <Col xs={5}>
                  <DropdownButton bsStyle='default' bsSize='small'
                                  title='100%'
                                  id='dropdown-basic'
                                  className='vmerge-ddown'>
                    <MenuItem onClick={action('onClick')} eventKey='1'>
                      90%</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='2'>
                      80%</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='3' active>
                      70%</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='4'>
                      60%</MenuItem>
                  </DropdownButton>
                </Col>
              </Col>
              <Col xs={12} className='vmerge-row'>
                <Col xs={2}>
                  Language
                </Col>
                <Col xs={6}>
                  <DropdownButton bsStyle='default' bsSize='small'
                                  title='Japanese'
                                  id='dropdown-basic'
                                  className='vmerge-ddown'>
                    <MenuItem onClick={action('onClick')} eventKey='1'>
                      All</MenuItem>
                    <MenuItem divider/>
                    <MenuItem onClick={action('onClick')} eventKey='2'>
                      Korean</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='3' active>
                      Japanese</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='4'>
                      Russian</MenuItem>
                  </DropdownButton>
                </Col>
              </Col>
              <Col xs={12} className='vmerge-boxes'>
                <Panel>

                      To
                        Target

                        {currentProject}

                        {currentVersion}

                </Panel>
              </Col>
              <Col xs={12} className='vmerge-boxes'>
                <Panel>
                  <Col xs={3}>

                      From
                        Source

                  </Col>
                  <Col xs={9} className='vmerge-searchbox'>
                    <InputGroup>
                      <InputGroup.Addon>
                        <Icon name='search'
                              className='s0'
                              title='search'/>
                      </InputGroup.Addon>
                      <FormControl type='text'
                                   value='Project'
                                   className='vmerge-searchinput'
                      />
                    </InputGroup>
                  </Col>
                  <Col xs={6}>
                 Select source project versions to merge

                    <PanelGroup defaultActiveKey='1' accordion>
                      <Panel header={heading1} eventKey='1'>
                        <ListGroup fill>
                          <ListGroupItem className='v'><Checkbox checked>2.0
                          </Checkbox>
                          </ListGroupItem>
                          <ListGroupItem className='v'><Checkbox checked>1.0
                          </Checkbox>
                          </ListGroupItem>
                        </ListGroup>
                      </Panel>
                      <Panel header={heading2} eventKey='2'>
                        <ListGroup fill>
                          <ListGroupItem className='v'><Checkbox>1.0</Checkbox>
                          </ListGroupItem>
                        </ListGroup>
                      </Panel>
                    </PanelGroup>
                  </Col>
                  <Col xs={6}>
                    <ListGroup>

                      Adjust priority of selected versions

(best first)

                        <OverlayTrigger placement='top' overlay={tooltipSort}>
                          <Icon name='info' className='s0 info-icon'/>
                        </OverlayTrigger>

                      <Draggable bounds='parent' axis='y' grid={[57, 57]}>
                        <ListGroupItem className='v'>
                          <Button bsStyle='link' className='btn-link-sort'>

                          </Button>
                          2.0

                            Project A

                        </ListGroupItem>
                      </Draggable>
                      <Draggable bounds='parent' axis='y' grid={[57, 57]}>
                        <ListGroupItem className='v'>
                          <Button bsStyle='link' className='btn-link-sort'>

                          </Button>
                          1.0

                            Project A

                        </ListGroupItem>
                      </Draggable>
                    </ListGroup>
                  </Col>
                </Panel>
              </Col>

          </Modal.Body>
          <Modal.Footer>

            <Row>
              <Button bsStyle='link'
                      className='btn-left link-danger'
                      onClick={action('onClick')}>
                Cancel
              </Button>
              <Button
                  bsStyle='primary'
                  onClick={action('onClick')}>
                Merge translations
              </Button>
            </Row>

          </Modal.Footer>
        </Modal>
    ))
<commit_msg>feat(ZNTA-1969): changed wording in Version TM  modal<commit_after>import React from 'react'
import ReactDOM from 'react-dom'
import Draggable from 'react-draggable'
import {storiesOf} from '@kadira/storybook'
import {action, decorateAction} from '@kadira/storybook-addon-actions'
import {
  Button, Panel, Row, Table, Well, Checkbox, InputGroup, Col,
  FormControl, DropdownButton, MenuItem, ListGroup, ListGroupItem, PanelGroup,
  OverlayTrigger, Tooltip
}
  from 'react-bootstrap'
import {Icon, Modal} from '../../components'
import Lorem from 'react-lorem-component'

const tooltipSort = (
    <Tooltip id='tooltip'>Best match will be chosen based on the priority of
      selected projects. Exact matches take precendence.
    </Tooltip>
)

const tooltipReadOnly = (
    <Tooltip id='tooltip'>Read only
    </Tooltip>
)

const heading1 =
<Checkbox checked> Project A</Checkbox>

const heading2 =
<Checkbox> Project B
  <OverlayTrigger placement='top' overlay={tooltipReadOnly}>
    <Icon name='locked'
          className='s0 icon-locked'/>
  </OverlayTrigger>
</Checkbox>

const currentProject = 'Current project'
const currentVersion = 'Current version'

storiesOf('Modal', module)
    .addDecorator((story) => (

          {story()}

    ))
    .add('default', () => (
        <Modal
            show={true}
            onHide={action('onHide')}>
          <Modal.Header>
            <Modal.Title>Modal heading</Modal.Title>
          </Modal.Header>
          <Modal.Body>
            <Lorem />
          </Modal.Body>
          <Modal.Footer>

            <Row>
              <Button bsStyle='link'
                      className='btn-left'
                      onClick={action('onClick')}>
                Close
              </Button>
              <Button
                  bsStyle='primary'
                  onClick={action('onClick')}>
                Save
              </Button>
            </Row>

          </Modal.Footer>
        </Modal>
    ))
    .add('version-TM', () => (
        <Modal
            show={true}
            onHide={action('onHide')}>
          <Modal.Header>
            <Modal.Title>Version TM</Modal.Title>
          </Modal.Header>
          <Modal.Body>

This feature copies existing translations from similar documents
                in other projects and versions into this project version if the source
                text and context both match.
                The translation state will be preserved (
                translated or
                 approved).

              <Well>
                <Checkbox>
                  If context matches but source text does not, copy as
                   fuzzy.
                </Checkbox>
              </Well>
              <Col xs={12} className='vmerge-row'>
                <Col xs={4}>
                  TM match percentage
                </Col>
                <Col xs={5}>
                  <DropdownButton bsStyle='default' bsSize='small'
                                  title='100%'
                                  id='dropdown-basic'
                                  className='vmerge-ddown'>
                    <MenuItem onClick={action('onClick')} eventKey='1'>
                      90%</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='2'>
                      80%</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='3' active>
                      70%</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='4'>
                      60%</MenuItem>
                  </DropdownButton>
                </Col>
              </Col>
              <Col xs={12} className='vmerge-row'>
                <Col xs={2}>
                  Language
                </Col>
                <Col xs={6}>
                  <DropdownButton bsStyle='default' bsSize='small'
                                  title='Japanese'
                                  id='dropdown-basic'
                                  className='vmerge-ddown'>
                    <MenuItem onClick={action('onClick')} eventKey='1'>
                      All</MenuItem>
                    <MenuItem divider/>
                    <MenuItem onClick={action('onClick')} eventKey='2'>
                      Korean</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='3' active>
                      Japanese</MenuItem>
                    <MenuItem onClick={action('onClick')} eventKey='4'>
                      Russian</MenuItem>
                  </DropdownButton>
                </Col>
              </Col>
              <Col xs={12} className='vmerge-boxes'>
                <Panel>

                      To
                        Target

                        {currentProject}

                        {currentVersion}

                </Panel>
              </Col>
              <Col xs={12} className='vmerge-boxes'>
                <Panel>
                  <Col xs={3}>

                      From
                        Source

                  </Col>
                  <Col xs={9} className='vmerge-searchbox'>
                    <InputGroup>
                      <InputGroup.Addon>
                        <Icon name='search'
                              className='s0'
                              title='search'/>
                      </InputGroup.Addon>
                      <FormControl type='text'
                                   value='Project'
                                   className='vmerge-searchinput'
                      />
                    </InputGroup>
                  </Col>
                  <Col xs={6}>
                 Select source project versions to merge

                    <PanelGroup defaultActiveKey='1' accordion>
                      <Panel header={heading1} eventKey='1'>
                        <ListGroup fill>
                          <ListGroupItem className='v'><Checkbox checked>2.0
                          </Checkbox>
                          </ListGroupItem>
                          <ListGroupItem className='v'><Checkbox checked>1.0
                          </Checkbox>
                          </ListGroupItem>
                        </ListGroup>
                      </Panel>
                      <Panel header={heading2} eventKey='2'>
                        <ListGroup fill>
                          <ListGroupItem className='v'><Checkbox>1.0</Checkbox>
                          </ListGroupItem>
                        </ListGroup>
                      </Panel>
                    </PanelGroup>
                  </Col>
                  <Col xs={6}>
                    <ListGroup>

                      Adjust priority of selected versions

(best first)

                        <OverlayTrigger placement='top' overlay={tooltipSort}>
                          <Icon name='info' className='s0 info-icon'/>
                        </OverlayTrigger>

                      <Draggable bounds='parent' axis='y' grid={[57, 57]}>
                        <ListGroupItem className='v'>
                          <Button bsStyle='link' className='btn-link-sort'>

                          </Button>
                          2.0

                            Project A

                        </ListGroupItem>
                      </Draggable>
                      <Draggable bounds='parent' axis='y' grid={[57, 57]}>
                        <ListGroupItem className='v'>
                          <Button bsStyle='link' className='btn-link-sort'>

                          </Button>
                          1.0

                            Project A

                        </ListGroupItem>
                      </Draggable>
                    </ListGroup>
                  </Col>
                </Panel>
              </Col>

          </Modal.Body>
          <Modal.Footer>

            <Row>
              <Button bsStyle='link'
                      className='btn-left link-danger'
                      onClick={action('onClick')}>
                Cancel
              </Button>
              <Button
                  bsStyle='primary'
                  onClick={action('onClick')}>
                Merge translations
              </Button>
            </Row>

          </Modal.Footer>
        </Modal>
    ))
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.medusa',
  name: 'MedusaEntryConsensusDAO',
  extends: 'foam.dao.ProxyDAO',

  implements: [
    'foam.core.ContextAgent',
    'foam.nanos.NanoService'
  ],

  documentation: `Receive Entry's from the Nodes. Test for consensus on hash, cleanup, and notify.`,

  javaImports: [
    'foam.core.Agency',
    'foam.core.FObject',
    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'static foam.mlang.MLang.AND',
    'static foam.mlang.MLang.EQ',
    'static foam.mlang.MLang.HAS',
    'static foam.mlang.MLang.COUNT',
    'static foam.mlang.MLang.GROUP_BY',
    'static foam.mlang.MLang.NEQ',
    'foam.mlang.sink.Count',
    'foam.mlang.sink.GroupBy',
    'foam.nanos.logger.PrefixLogger',
    'foam.nanos.logger.Logger',
    'foam.util.SafetyUtil',
    'java.util.ArrayList',
    'java.util.concurrent.atomic.AtomicInteger',
    'java.util.HashMap',
    'java.util.List',
    'java.util.Map',
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(foam.java.Code.create({
          data: `
  protected Object indexLock_ = new Object();
  protected Object promoteLock_ = new Object();
  protected Object replayLock_ = new Object();
          `
        }));
      }
    }
  ],

  constants: [
    {
      name: 'INITIAL_INDEX_OFFSET',
      class: 'Long',
      value: 2
    }
  ],

  properties: [
    {
      // NOTE: starting at 2 as indexes 1 and 2 are used to prime the system.
      name: 'index',
      class: 'Long',
      value: 2, /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET,*/
      visibilty: 'RO',
    },
    {
      // NOTE: starting at 2 as indexes 1 and 2 are used to prime the system.
      name: 'replayIndex',
      class: 'Long',
      value: 2, /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET,*/
      visibilty: 'RO',
    },
    {
      name: 'replaying',
      class: 'Boolean',
      value: true,
      visibilty: 'RO',
    },
    {
      name: 'replayNodes',
      class: 'Map',
      javaFactory: 'return new HashMap();'
    },
    {
      name: 'threadPoolName',
      class: 'String',
      value: 'medusaThreadPool'
    },
    {
      name: 'logger',
      class: 'FObjectProperty',
      of: 'foam.nanos.logger.Logger',
      visibility: 'HIDDEN',
      javaFactory: `
        return new PrefixLogger(new Object[] {
          this.getClass().getSimpleName()
        }, (Logger) getX().get("logger"));
      `
    }
  ],

  methods: [
    {
      // TODO: this needs to be really fast.
      name: 'put_',
      javaCode: `
      MedusaEntry entry = (MedusaEntry) obj;
      getLogger().debug("put", getIndex(), entry.getIndex());

// TODO: move do own dao
      DaggerService service = (DaggerService) x.get("daggerService");
      if ( entry.getIndex() > service.getGlobalIndex(x) ) {
//        service.setGlobalIndex(x, entry.getIndex());
//        getLogger().debug("put", getIndex(), "setGlobalIndex", entry.getIndex());
        getLogger().error("put", "index > globalIndex", getIndex(), service.getGlobalIndex(x));
        // TODO: now what?
      }

      MedusaEntry ce = null;

      synchronized ( Long.toString(entry.getIndex()).intern() ) {

        if ( entry.getIndex() <= getIndex() ) {
          getLogger().info("put", getIndex(), "discarding", entry.getIndex());
          return entry;
        }

        MedusaEntry me = (MedusaEntry) getDelegate().put_(x, entry);

        ce = getConsensusEntry(x, me);
        if ( ce != null) {
          if ( ce.getIndex() == getIndex() + 1 ) {
            ce =  promote(x, ce);
          } else {
            synchronized ( promoteLock_ ) {
              getLogger().debug("put", "notify", getIndex(), ce.getIndex());
              promoteLock_.notify();
            }
          }
          return ce;
        }
      }

      return entry;
      `
    },
    {
      name: 'cmd_',
      javaCode: `
      if ( ! ( obj instanceof ReplayDetailsCmd ) ) {
        return getDelegate().cmd_(x, obj);
      }

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");

      ReplayDetailsCmd cmd = (ReplayDetailsCmd) obj;
      getLogger().debug("cmd", cmd);
      synchronized ( replayLock_ ) {
        if ( cmd.getMaxIndex() > getReplayIndex() ) {
          setReplayIndex(cmd.getMaxIndex());
          DaggerService dagger = (DaggerService) x.get("daggerService");
          dagger.setGlobalIndex(x, cmd.getMaxIndex());
        }
        getReplayNodes().put(cmd.getResponder(), cmd);
      }

      // if no replay data, then replay complete.
      getLogger().debug("cmd", "replayNodes", getReplayNodes().size(), "node quorum", service.getNodeQuorum(x), "replayIndex", getReplayIndex());
      if ( getReplayNodes().size() >= service.getNodeQuorum(x) &&
           getReplayIndex() == 2L /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET*/ ) {
        getLogger().debug("cmd", "replay complete");
        setReplaying(false);
        ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
        service.setOnline(x, true);
      }
      return obj;
      `
    },
    {
      documentation: 'Make an entry available for Dagger hashing.',
      name: 'promote',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      type: 'foam.nanos.medusa.MedusaEntry',
      javaCode: `
      DaggerService dagger = (DaggerService) x.get("daggerService");
      dagger.verify(x, entry);
      getLogger().info("promote", getIndex(), entry.getIndex());
      synchronized ( indexLock_ ) {
        if ( entry.getIndex() == getIndex() + 1 ) {
          setIndex(entry.getIndex());
        }
      }

      dagger.updateLinks(x, entry);

      if ( getReplaying() &&
         getIndex() >= getReplayIndex() + 2 /*TODO*/) {
        getLogger().debug("promote", "replay complete");
        setReplaying(false);
        ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
        ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
        service.setOnline(x, true);
        synchronized ( promoteLock_ ) {
          promoteLock_.notify();
        }
      }

      return entry;
      `
    },
    {
      documentation: 'Tally same index entries (one from each node) by hash. If a quorum of nodes have the same hash, then cleanup and return a match.',
      name: 'getConsensusEntry',
      type: 'foam.nanos.medusa.MedusaEntry',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      javaCode: `
      getLogger().debug("consensus", entry.getIndex());

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
      MedusaEntry match = null;
      String hash = null;
      long max = 0L;

      GroupBy groupBy = (GroupBy) getDelegate().where(
        EQ(MedusaEntry.INDEX, entry.getIndex())
      ).select(GROUP_BY(MedusaEntry.HASH, COUNT()));

      Map<String, Count> groups = groupBy.getGroups();
      for ( Map.Entry<String, Count> e : groups.entrySet() ) {
        if ( e.getValue().getValue() > max ) {
          max = e.getValue().getValue();
          hash = e.getKey();
        }
      }

      if ( max >= (service.getNodeCount(x) / 2 + 1) ) {
        // TODO: consider reporting the split if max
        // does not equal number of nodes.

        List<MedusaEntry> list = (ArrayList) ((ArraySink) getDelegate().where(
          EQ(MedusaEntry.INDEX, entry.getIndex())
        ).select(new ArraySink())).getArray();
        getLogger().debug("match");
        for ( MedusaEntry e : list ) {
          if ( match == null &&
               e.getHash().equals(hash) ) {
            match = (MedusaEntry) e.fclone();
            match.setHasConsensus(true);
            match = (MedusaEntry) getDelegate().put_(x, match);
          }
        }
        if ( match != null ) {
          getLogger().debug("cleanup");
          getDelegate().where(
            AND(
              EQ(MedusaEntry.INDEX, entry.getIndex()),
              NEQ(MedusaEntry.ID, match.getId())
            )
          ).removeAll();
        }
      }
      return match;
      `
    },
    {
      documentation: 'NanoService implementation.',
      name: 'start',
      javaCode: `
      getLogger().debug("start");
      ((Agency) getX().get(getThreadPoolName())).submit(getX(), this, "Consensus Promoter");
      `
    },
    {
      documentation: 'ContextAgent implementation. Handling out of order consensus updates. Check if next (index + 1) has reach consensus and promote.',
      name: 'execute',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
      getLogger().debug("promoter");
      try {
        while ( true ) {
          MedusaEntry entry = (MedusaEntry) getDelegate().find_(x, getIndex() + 1);
          if ( entry != null &&
               entry.getHasConsensus() ) {
            promote(x, entry);
          } else {
            getLogger().debug("promoter", "lock", getIndex());
            synchronized ( promoteLock_ ) {
              getLogger().debug("promoter", "wait", getIndex());
              promoteLock_.wait();
              getLogger().debug("promoter", "wake", getIndex());
            }
          }
        }
      } catch ( InterruptedException e ) {
        // nop
      } catch ( Exception e ) {
        getLogger().error("execute", e.getMessage(), e);
        // TODO: Alarm
      }
     `
    }
  ]
});
<commit_msg>Add a replayComplete method to capture the steps which were duplicated in a couple of places.<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.medusa',
  name: 'MedusaEntryConsensusDAO',
  extends: 'foam.dao.ProxyDAO',

  implements: [
    'foam.core.ContextAgent',
    'foam.nanos.NanoService'
  ],

  documentation: `Receive Entry's from the Nodes. Test for consensus on hash, cleanup, and notify.`,

  javaImports: [
    'foam.core.Agency',
    'foam.core.FObject',
    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'static foam.mlang.MLang.AND',
    'static foam.mlang.MLang.EQ',
    'static foam.mlang.MLang.HAS',
    'static foam.mlang.MLang.COUNT',
    'static foam.mlang.MLang.GROUP_BY',
    'static foam.mlang.MLang.NEQ',
    'foam.mlang.sink.Count',
    'foam.mlang.sink.GroupBy',
    'foam.nanos.logger.PrefixLogger',
    'foam.nanos.logger.Logger',
    'foam.util.SafetyUtil',
    'java.util.ArrayList',
    'java.util.concurrent.atomic.AtomicInteger',
    'java.util.HashMap',
    'java.util.List',
    'java.util.Map',
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(foam.java.Code.create({
          data: `
  protected Object indexLock_ = new Object();
  protected Object promoteLock_ = new Object();
  protected Object replayLock_ = new Object();
          `
        }));
      }
    }
  ],

  constants: [
    {
      name: 'INITIAL_INDEX_OFFSET',
      class: 'Long',
      value: 2
    }
  ],

  properties: [
    {
      // NOTE: starting at 2 as indexes 1 and 2 are used to prime the system.
      name: 'index',
      class: 'Long',
      value: 2, /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET,*/
      visibilty: 'RO',
    },
    {
      // NOTE: starting at 2 as indexes 1 and 2 are used to prime the system.
      name: 'replayIndex',
      class: 'Long',
      value: 2, /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET,*/
      visibilty: 'RO',
    },
    {
      name: 'replaying',
      class: 'Boolean',
      value: true,
      visibilty: 'RO',
    },
    {
      name: 'replayNodes',
      class: 'Map',
      javaFactory: 'return new HashMap();'
    },
    {
      name: 'threadPoolName',
      class: 'String',
      value: 'medusaThreadPool'
    },
    {
      name: 'logger',
      class: 'FObjectProperty',
      of: 'foam.nanos.logger.Logger',
      visibility: 'HIDDEN',
      javaFactory: `
        return new PrefixLogger(new Object[] {
          this.getClass().getSimpleName()
        }, (Logger) getX().get("logger"));
      `
    }
  ],

  methods: [
    {
      // TODO: this needs to be really fast.
      name: 'put_',
      javaCode: `
      MedusaEntry entry = (MedusaEntry) obj;
      getLogger().debug("put", getIndex(), entry.getIndex());

// TODO: move do own dao
      // DaggerService service = (DaggerService) x.get("daggerService");
      // if ( entry.getIndex() > service.getGlobalIndex(x) ) {
      //   service.setGlobalIndex(x, entry.getIndex());
      //   getLogger().debug("put", getIndex(), "setGlobalIndex", entry.getIndex());
      //   getLogger().error("put", "index > globalIndex", getIndex(), service.getGlobalIndex(x));
      //   // TODO: now what?
      // }

      MedusaEntry ce = null;
      synchronized ( Long.toString(entry.getIndex()).intern() ) {

        if ( entry.getIndex() <= getIndex() ) {
          getLogger().info("put", getIndex(), "discarding", entry.getIndex());
          return entry;
        }

        MedusaEntry me = (MedusaEntry) getDelegate().put_(x, entry);

        ce = getConsensusEntry(x, me);
        if ( ce != null &&
             ce.getIndex() == getIndex() + 1 ) {
          ce =  promote(x, ce);
          return ce;
        }
      } // release lock

      if ( ce != null &&
           ce.getIndex() > getIndex() ) {
        getLogger().debug("put", "promoteLock_.notify", getIndex(), ce.getIndex());
        synchronized ( promoteLock_ ) {
          promoteLock_.notify();
        }
        return ce;
      }

      return entry;
      `
    },
    {
      name: 'cmd_',
      javaCode: `
      if ( ! ( obj instanceof ReplayDetailsCmd ) ) {
        return getDelegate().cmd_(x, obj);
      }

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");

      ReplayDetailsCmd cmd = (ReplayDetailsCmd) obj;
      getLogger().debug("cmd", cmd);
      synchronized ( replayLock_ ) {
        if ( cmd.getMaxIndex() > getReplayIndex() ) {
          setReplayIndex(cmd.getMaxIndex());
          DaggerService dagger = (DaggerService) x.get("daggerService");
          dagger.setGlobalIndex(x, cmd.getMaxIndex());
          getLogger().debug("cmd", "update", "globlalIndex", cmd.getMaxIndex(), dagger.getGlobalIndex(x));
        }
        getReplayNodes().put(cmd.getResponder(), cmd);
      }

      // if no replay data, then replay complete.
      getLogger().debug("cmd", "replayNodes", getReplayNodes().size(), "node quorum", service.getNodeQuorum(x), "replayIndex", getReplayIndex());
      if ( getReplayNodes().size() >= service.getNodeQuorum(x) &&
           getReplayIndex() == 2L /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET*/ ) {
        getLogger().debug("cmd", "replayComplete");
        replayComplete(x);
      }
      return obj;
      `
    },
    {
      documentation: 'Make an entry available for Dagger hashing.',
      name: 'promote',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      type: 'foam.nanos.medusa.MedusaEntry',
      javaCode: `
      DaggerService dagger = (DaggerService) x.get("daggerService");
      dagger.verify(x, entry);
      getLogger().info("promote", getIndex(), entry.getIndex());
      synchronized ( indexLock_ ) {
        if ( entry.getIndex() == getIndex() + 1 ) {
          setIndex(entry.getIndex());
        }
      }

      dagger.updateLinks(x, entry);

      if ( getReplaying() &&
         getIndex() >= getReplayIndex() ) { //+ 2 /*MedusaEntryConsensusDAO.INITIAL_INDEX_OFFSET*/) {
        getLogger().debug("promote", "replayComplete");
        replayComplete(x);
        synchronized ( promoteLock_ ) {
          promoteLock_.notify();
        }
      }

      return entry;
      `
    },
    {
      documentation: 'Tally same index entries (one from each node) by hash. If a quorum of nodes have the same hash, then cleanup and return a match.',
      name: 'getConsensusEntry',
      type: 'foam.nanos.medusa.MedusaEntry',
      args: [
        {
          name: 'x',
          type: 'Context'
        },
        {
          name: 'entry',
          type: 'foam.nanos.medusa.MedusaEntry'
        }
      ],
      javaCode: `
      getLogger().debug("consensus", entry.getIndex());

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
      MedusaEntry match = null;
      String hash = null;
      long max = 0L;

      GroupBy groupBy = (GroupBy) getDelegate().where(
        EQ(MedusaEntry.INDEX, entry.getIndex())
      ).select(GROUP_BY(MedusaEntry.HASH, COUNT()));

      Map<String, Count> groups = groupBy.getGroups();
      for ( Map.Entry<String, Count> e : groups.entrySet() ) {
        if ( e.getValue().getValue() > max ) {
          max = e.getValue().getValue();
          hash = e.getKey();
        }
      }

      if ( max >= (service.getNodeCount(x) / 2 + 1) ) {
        // TODO: consider reporting the split if max
        // does not equal number of nodes.

        List<MedusaEntry> list = (ArrayList) ((ArraySink) getDelegate().where(
          EQ(MedusaEntry.INDEX, entry.getIndex())
        ).select(new ArraySink())).getArray();
        for ( MedusaEntry e : list ) {
          if ( match == null &&
               e.getHash().equals(hash) ) {
            match = (MedusaEntry) e.fclone();
            match.setHasConsensus(true);
            match = (MedusaEntry) getDelegate().put_(x, match);
            getLogger().debug("match", match.getIndex());
          }
        }
        if ( match != null ) {
          getLogger().debug("cleanup", entry.getIndex());
          getDelegate().where(
            AND(
              EQ(MedusaEntry.INDEX, entry.getIndex()),
              NEQ(MedusaEntry.ID, match.getId())
            )
          ).removeAll();
        }
      }
      return match;
      `
    },
    {
      documentation: 'NanoService implementation.',
      name: 'start',
      javaCode: `
      getLogger().debug("start");
      ((Agency) getX().get(getThreadPoolName())).submit(getX(), this, "Consensus Promoter");
      `
    },
    {
      documentation: 'ContextAgent implementation. Handling out of order consensus updates. Check if next (index + 1) has reach consensus and promote.',
      name: 'execute',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
      getLogger().debug("promoter");
      try {
        while ( true ) {
          MedusaEntry entry = (MedusaEntry) getDelegate().find_(x, getIndex() + 1);
          if ( entry != null &&
               entry.getHasConsensus() ) {
            promote(x, entry);
          } else {
            getLogger().debug("promoter", "lock", getIndex());
            synchronized ( promoteLock_ ) {
              getLogger().debug("promoter", "wait", getIndex());
              promoteLock_.wait();
              getLogger().debug("promoter", "wake", getIndex());
            }
          }
        }
      } catch ( InterruptedException e ) {
        // nop
      } catch ( Exception e ) {
        getLogger().error("execute", e.getMessage(), e);
        // TODO: Alarm
      }
     `
    },
    {
      name: 'replayComplete',
      args: [
        {
          name: 'x',
          type: 'Context'
        }
      ],
      javaCode: `
      getLogger().debug("replayComplete");
      setReplaying(false);
      ((DAO) x.get("localMedusaEntryDAO")).cmd(new ReplayCompleteCmd());
      ((ClusterConfigService) x.get("clusterConfigService")).setOnline(x, true);
      `
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.crunch',
  name: 'PermissionsStringArrayView',
  extends: 'foam.u2.View',

  implements: [
    'foam.mlang.Expressions'
  ],

  imports: [
    'permissionDAO'
  ],

  requires: [
    'foam.nanos.auth.Permission',
    'foam.u2.crunch.PermissionSelection',
    'foam.u2.TextField'
  ],

  css: `
    .foam-u2-crunch-PermissionsStringArrayView-padding {
      padding-top: 8px;
    }
  `,

  properties: [
    {
      name: 'search',
      class: 'String',
      view: {
        class: 'foam.u2.TextField',
        type: 'search',
        placeholder: 'capability search',
        onKey: true
      },
      readVisibility: foam.u2.DisplayMode.RW,
    },
    {
      name: 'views',
      class: 'Array',
      of: 'foam.u2.crunch.PermissionSelection',
      factory: function() {
        var self = this;
        this.permissionDAO.select(this.PROJECTION(this.Permission.ID, this.Permission.DESCRIPTION))
          .then(function(proj) {
            self.allPermissions = proj.projection.map(a => a[0]);
            self.preSetViewWithProjection(proj);
          });
      }
    },
    {
      name: 'allPermissions',
      class: 'StringArray'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      var self = this;

      this.data$.sub(function() {
        console.log('data change');
      });

      this.start()
      .startContext({ data: this })
      .start()
          .tag(this.SEARCH)
        .end()
        .start()
          .addClass(this.myClass('padding'))
          .add(this.slot(function(views) {
            if ( views ) {
              return this.E().forEach(views, function(v) {
                this.start()
                  .add(v)
                .end();
              });
            }
          }))
        .end()
        .endContext()
      .end();
    },
    function preSetViewWithProjection(proj) {
      this.views = [];
      var self = this;
      if ( proj.projection.length > 0 )
        this.views.push(this.PermissionSelection.create({
          permission: 'Select All',
          search$: this.search$,
          selectedPermissions$: this.data$,
          onSelect: this.onAllSelectedFunction.bind(this),
          isSelectedPermissionsContainThisPermission: this.isAllPermissionsSelected.bind(this),
          onSearchChange: this.onSearchChangedSelectAll
        }));
      proj.projection.forEach(a => {
        this.views.push(this.PermissionSelection.create({
          permission: a[0],
          search$: this.search$,
          description: a[1],
          selectedPermissions$: self.data$,
          onSelect: self.onSelectFunction.bind(self),
          isSelectedPermissionsContainThisPermission: self.isPermissionSelected.bind(self),
          onSearchChange: this.onSearchChanged
        }));
      });
    },
    function onSelectFunction(permission, isSelected) {
      var newArr =[];
      if ( isSelected ) {
        this.data.forEach(p => {
          newArr.push(p);
        });
        newArr.push(permission);
      } else {
        this.data.forEach(p => {
          if ( p !== permission )
            newArr.push(p);
        });
      }
      this.data = newArr;
    },
    function onAllSelectedFunction(_, isSelected) {
      var filteredPermissions = this.views.filter(v => v.show && v.permission !== 'Select All').map(v => v.permission);
      var newArr = [];
      if ( ! isSelected ) {
        if ( this.allPermissions.length !== filteredPermissions.length ) {
          for ( var p of this.data ) {
            if ( ! filteredPermissions.includes(p) ) {
              newArr.push(p);
            }
          }
        }
      } else {
        if ( filteredPermissions.length < this.data.length ) {
          for ( var p of this.data ) {
            newArr.push(p);
          }

        }
        for ( var p of filteredPermissions ) {
          if ( ! newArr.includes(p) ) {
            newArr.push(p);
          }
        }
      }
      this.data = newArr;
    },
    function isPermissionSelected(permission) {
      return this.data.includes(permission);
    },
    function isAllPermissionsSelected() {
      var filteredPermissions = this.views.filter(v => v.show && v.permission !== 'Select All').map(v => v.permission);
      if ( filteredPermissions.length < this.data.length ) {
        for ( var p of filteredPermissions ) {
          if ( ! this.data.includes(p) ) {
            return false;
          }
        }
        return true;
      }
      return this.data.length === filteredPermissions.length;
    },
    function onSearchChanged() {
      this.show = ! this.search || this.search.length === 0 || this.permission.toLowerCase().includes(this.search.toLowerCase() );
    },
    function onSearchChangedSelectAll() {
      this.stopOnSelect = true;
      this.isSelected = this.isSelectedPermissionsContainThisPermission(this.permission);
    }
  ]

});

foam.CLASS({
  package: 'foam.u2.crunch',
  name: 'PermissionSelection',
  extends: 'foam.u2.View',
  css: `
    ^ {
      height: 16px;
      width: 100%;
    }

    ^:hover {
      background: /*%PRIMARY5%*/ #e5f1fc;
    }

    .foam-u2-crunch-PermissionSelection-left {
      width: 55%;
      height: 16px;
      float: left;
    }

    .foam-u2-crunch-PermissionSelection-less-left {
      width: 40%;
      height: 16px;
      float: left;
    }

    .foam-u2-crunch-PermissionSelection-right {
      margin-right: 5px;
      height: 16px;
      float: right;
    }

    .property-isSelected {
      margin: 1.5px 0;
    }

    .foam-u2-crunch-PermissionSelection-tooltip {
      position: relative;
      visibility: hidden;
      border-radius: 4px;
      color: white;
      background: #999;
      max-height: 80px;
      width: 140px;
      overflow-y: scroll;
      word-break: break-word;
      padding: 4px;
      left: 80%;
    }

    .foam-u2-crunch-PermissionSelection-tooltiptext {
      bottom: 125%;
    }

    .foam-u2-crunch-PermissionSelection-center-tooltiptext {
      bottom: 250%;
  }

    .foam-u2-crunch-PermissionSelection-left:hover .foam-u2-crunch-PermissionSelection-tooltiptext {
      visibility: visible;
    }

    .foam-u2-crunch-PermissionSelection-less-left:hover .foam-u2-crunch-PermissionSelection-center-tooltiptext {
      visibility: visible;
    }

    .foam-u2-crunch-PermissionSelection-hide-text {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
  `,
  properties: [
    {
      name: 'permission',
      class: 'String'
    },
    {
      name: 'description'
    },
    {
      name: 'isSelected',
      class: 'Boolean',
      view: {
        class: 'foam.u2.CheckBox',
        showLabel: false
      },
      postSet: function() {
        if ( ( this.isSelected && ! this.isSelectedPermissionsContainThisPermission(this.permission) ) || (  ! this.isSelected && this.isSelectedPermissionsContainThisPermission(this.permission) ) && ! this.stopOnSelect ) {
          this.onSelect(this.permission, this.isSelected);
        }
        this.stopOnSelect = false;
      },
      factory: function() {
        return this.isSelectedPermissionsContainThisPermission(this.permission);
      }
    },
    {
      name: 'selectedPermissions',
    },
    'isSelectedPermissionsContainThisPermission',
    'onSelect',
    'search',
    {
      name: 'show',
      class: 'Boolean',
      value: true
    },
    {
      name: 'stopOnSelect',
      class: 'Boolean',
    },
    'onSearchChange'
  ],
  methods: [
    function initE() {
      this.SUPER();
      var self = this;

      this.search$.sub(function() {
        self.onSearchChange();
      });

      this.selectedPermissions$.sub(function() {
        if ( ! self.isSelected && self.isSelectedPermissionsContainThisPermission(self.permission) )
          self.isSelected = true;
        else if ( self.isSelected && ! self.isSelectedPermissionsContainThisPermission(self.permission) ) {
          self.isSelected = false;
        }
      });

      this
      .start()
        .show(this.show$)
        .addClass(this.myClass())
        .startContext({ data: this })
          .start()
            .addClass(this.myClass('left'))
            .start()
              .addClass(this.myClass('hide-text'))
              .add(this.permission)
            .end()
            .start()
              .show( this.permission && this.permission.length > 0 )
              .addClass(this.myClass('tooltip'))
              .addClass(this.myClass('tooltiptext'))
              .add(this.permission)
            .end()
          .end()
          .start()
            .addClass(this.myClass('less-left'))
            .start()
              .addClass(this.myClass('hide-text'))
              .add(this.description)
            .end()
            .start()
              .show( this.description && this.description.length > 0 )
              .addClass(this.myClass('tooltip'))
              .addClass(this.myClass('center-tooltiptext'))
              .add(this.description)
            .end()
          .end()
          .start()
            .addClass(this.myClass('right'))
            .tag(this.IS_SELECTED)
          .end()
        .endContext()
      .end();
    }
  ]
});<commit_msg>Cleanup.<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.crunch',
  name: 'PermissionsStringArrayView',
  extends: 'foam.u2.View',

  implements: [
    'foam.mlang.Expressions'
  ],

  imports: [
    'permissionDAO'
  ],

  requires: [
    'foam.nanos.auth.Permission',
    'foam.u2.crunch.PermissionSelection',
    'foam.u2.TextField'
  ],

  css: `
    .foam-u2-crunch-PermissionsStringArrayView-padding {
      padding-top: 8px;
    }
  `,

  properties: [
    {
      name: 'search',
      class: 'String',
      view: {
        class: 'foam.u2.TextField',
        type: 'search',
        placeholder: 'capability search',
        onKey: true
      },
      readVisibility: foam.u2.DisplayMode.RW
    },
    {
      name: 'views',
      class: 'Array',
      of: 'foam.u2.crunch.PermissionSelection',
      factory: function() {
        var self = this;
        this.permissionDAO.select(this.PROJECTION(this.Permission.ID, this.Permission.DESCRIPTION))
          .then(function(proj) {
            self.allPermissions = proj.projection.map(a => a[0]);
            self.preSetViewWithProjection(proj);
          });
      }
    },
    {
      name: 'allPermissions',
      class: 'StringArray'
    }
  ],

  methods: [
    function initE() {
      this.SUPER();
      var self = this;

      this.data$.sub(function() {
        console.log('data change');
      });

      this.start()
        .startContext({ data: this })
          .start()
            .tag(this.SEARCH)
          .end()
          .start()
            .addClass(this.myClass('padding'))
            .add(this.slot(function(views) {
              if ( views ) {
                return this.E().forEach(views, function(v) {
                  this.start()
                    .add(v)
                  .end();
                });
              }
            }))
          .end()
        .endContext()
      .end();
    },

    function preSetViewWithProjection(proj) {
      this.views = [];
      var self = this;
      if ( proj.projection.length > 0 )
        this.views.push(this.PermissionSelection.create({
          permission: 'Select All',
          search$: this.search$,
          selectedPermissions$: this.data$,
          onSelect: this.onAllSelectedFunction.bind(this),
          isSelectedPermissionsContainThisPermission: this.isAllPermissionsSelected.bind(this),
          onSearchChange: this.onSearchChangedSelectAll
        }));
      proj.projection.forEach(a => {
        this.views.push(this.PermissionSelection.create({
          permission: a[0],
          search$: this.search$,
          description: a[1],
          selectedPermissions$: self.data$,
          onSelect: self.onSelectFunction.bind(self),
          isSelectedPermissionsContainThisPermission: self.isPermissionSelected.bind(self),
          onSearchChange: this.onSearchChanged
        }));
      });
    },

    function onSelectFunction(permission, isSelected) {
      var newArr =[];
      if ( isSelected ) {
        this.data.forEach(p => {
          newArr.push(p);
        });
        newArr.push(permission);
      } else {
        this.data.forEach(p => {
          if ( p !== permission )
            newArr.push(p);
        });
      }
      this.data = newArr;
    },

    function onAllSelectedFunction(_, isSelected) {
      var filteredPermissions = this.views.filter(v => v.show && v.permission !== 'Select All').map(v => v.permission);
      var newArr = [];
      if ( ! isSelected ) {
        if ( this.allPermissions.length !== filteredPermissions.length ) {
          for ( var p of this.data ) {
            if ( ! filteredPermissions.includes(p) ) {
              newArr.push(p);
            }
          }
        }
      } else {
        if ( filteredPermissions.length < this.data.length ) {
          for ( var p of this.data ) {
            newArr.push(p);
          }
        }
        for ( var p of filteredPermissions ) {
          if ( ! newArr.includes(p) ) {
            newArr.push(p);
          }
        }
      }
      this.data = newArr;
    },

    function isPermissionSelected(permission) {
      return this.data.includes(permission);
    },

    function isAllPermissionsSelected() {
      var filteredPermissions = this.views.filter(v => v.show && v.permission !== 'Select All').map(v => v.permission);
      if ( filteredPermissions.length < this.data.length ) {
        for ( var p of filteredPermissions ) {
          if ( ! this.data.includes(p) ) {
            return false;
          }
        }
        return true;
      }
      return this.data.length === filteredPermissions.length;
    },

    function onSearchChanged() {
      this.show = ! this.search || this.search.length === 0 || this.permission.toLowerCase().includes(this.search.toLowerCase() );
    },

    function onSearchChangedSelectAll() {
      this.stopOnSelect = true;
      this.isSelected = this.isSelectedPermissionsContainThisPermission(this.permission);
    }
  ]
});

foam.CLASS({
  package: 'foam.u2.crunch',
  name: 'PermissionSelection',
  extends: 'foam.u2.View',
  css: `
    ^ {
      height: 16px;
      width: 100%;
    }

    ^:hover {
      background: /*%PRIMARY5%*/ #e5f1fc;
    }

    .foam-u2-crunch-PermissionSelection-left {
      width: 55%;
      height: 16px;
      float: left;
    }

    .foam-u2-crunch-PermissionSelection-less-left {
      width: 40%;
      height: 16px;
      float: left;
    }

    .foam-u2-crunch-PermissionSelection-right {
      margin-right: 5px;
      height: 16px;
      float: right;
    }

    .property-isSelected {
      margin: 1.5px 0;
    }

    .foam-u2-crunch-PermissionSelection-tooltip {
      position: relative;
      visibility: hidden;
      border-radius: 4px;
      color: white;
      background: #999;
      max-height: 80px;
      width: 140px;
      overflow-y: scroll;
      word-break: break-word;
      padding: 4px;
      left: 80%;
    }

    .foam-u2-crunch-PermissionSelection-tooltiptext {
      bottom: 125%;
    }

    .foam-u2-crunch-PermissionSelection-center-tooltiptext {
      bottom: 250%;
  }

    .foam-u2-crunch-PermissionSelection-left:hover .foam-u2-crunch-PermissionSelection-tooltiptext {
      visibility: visible;
    }

    .foam-u2-crunch-PermissionSelection-less-left:hover .foam-u2-crunch-PermissionSelection-center-tooltiptext {
      visibility: visible;
    }

    .foam-u2-crunch-PermissionSelection-hide-text {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
    }
  `,

  properties: [
    {
      name: 'permission',
      class: 'String'
    },
    {
      name: 'description'
    },
    {
      name: 'isSelected',
      class: 'Boolean',
      view: {
        class: 'foam.u2.CheckBox',
        showLabel: false
      },
      postSet: function() {
        if ( ( this.isSelected && ! this.isSelectedPermissionsContainThisPermission(this.permission) ) || (  ! this.isSelected && this.isSelectedPermissionsContainThisPermission(this.permission) ) && ! this.stopOnSelect ) {
          this.onSelect(this.permission, this.isSelected);
        }
        this.stopOnSelect = false;
      },
      factory: function() {
        return this.isSelectedPermissionsContainThisPermission(this.permission);
      }
    },
    {
      name: 'selectedPermissions',
    },
    'isSelectedPermissionsContainThisPermission',
    'onSelect',
    'search',
    {
      name: 'show',
      class: 'Boolean',
      value: true
    },
    {
      name: 'stopOnSelect',
      class: 'Boolean'
    },
    'onSearchChange'
  ],

  methods: [
    function initE() {
      this.SUPER();
      var self = this;

      this.search$.sub(function() {
        self.onSearchChange();
      });

      this.selectedPermissions$.sub(function() {
        if ( ! self.isSelected && self.isSelectedPermissionsContainThisPermission(self.permission) )
          self.isSelected = true;
        else if ( self.isSelected && ! self.isSelectedPermissionsContainThisPermission(self.permission) ) {
          self.isSelected = false;
        }
      });

      this
      .start()
        .show(this.show$)
        .addClass(this.myClass())
        .startContext({ data: this })
          .start()
            .addClass(this.myClass('left'))
            .start()
              .addClass(this.myClass('hide-text'))
              .add(this.permission)
            .end()
            .start()
              .show( this.permission && this.permission.length > 0 )
              .addClass(this.myClass('tooltip'))
              .addClass(this.myClass('tooltiptext'))
              .add(this.permission)
            .end()
          .end()
          .start()
            .addClass(this.myClass('less-left'))
            .start()
              .addClass(this.myClass('hide-text'))
              .add(this.description)
            .end()
            .start()
              .show( this.description && this.description.length > 0 )
              .addClass(this.myClass('tooltip'))
              .addClass(this.myClass('center-tooltiptext'))
              .add(this.description)
            .end()
          .end()
          .start()
            .addClass(this.myClass('right'))
            .tag(this.IS_SELECTED)
          .end()
        .endContext()
      .end();
    }
  ]
});
<|endoftext|>


<commit_before>/*!
 Copyright (C) 2017 Google Inc.
 Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
 */

(function (GGRC, _) {
  'use strict';
  /**
   * Tree View Widgets Configuration module
   */
  var allTypes = [
    'AccessGroup', 'Audit', 'Clause', 'Contract', 'Control', 'Assessment',
    'DataAsset', 'Facility', 'Issue', 'Market', 'Objective', 'OrgGroup',
    'Person', 'Policy', 'Process', 'Product', 'Program', 'Project',
    'Regulation', 'Section', 'Standard', 'System', 'Vendor'
  ];
  // Items allowed for mapping via snapshot.
  var snapshotWidgetsConfig = GGRC.config.snapshotable_objects || [];
  // Items allowed for relationship mapping
  var excludeMappingConfig = [
    'Assessment',
    'AssessmentTemplate',
    'Issue'
  ];
  // Extra Tree View Widgets require to be rendered on Audit View
  var auditInclusion = [
    'Person',
    'Program'
  ];
  var baseWidgetsByType;

  var filteredTypes = _.difference(allTypes, excludeMappingConfig);
  // Audit is excluded and created a separate logic for it
  baseWidgetsByType = {
    AccessGroup: _.difference(filteredTypes, ['AccessGroup']),
    Audit: [].concat(snapshotWidgetsConfig, excludeMappingConfig,
      auditInclusion).sort(),
    Clause: _.difference(filteredTypes, ['Clause']),
    Contract: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    Control: filteredTypes,
    Assessment: _.difference(filteredTypes, ['Assessment']),
    DataAsset: filteredTypes,
    Facility: filteredTypes,
    Issue: filteredTypes,
    Market: filteredTypes,
    Objective: filteredTypes,
    OrgGroup: filteredTypes,
    Person: _.difference(filteredTypes, ['Person']),
    Policy: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    Process: filteredTypes,
    Product: filteredTypes,
    Program: _.difference(filteredTypes, ['Program']),
    Project: filteredTypes,
    Regulation: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    Section: filteredTypes,
    Standard: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    System: filteredTypes,
    Vendor: filteredTypes
  };

  GGRC.tree_view = GGRC.tree_view || new can.Map();
  GGRC.tree_view.attr('base_widgets_by_type', baseWidgetsByType);
})(this.GGRC, this._);
<commit_msg>Update Widgets Lists for Assessment, Assessment Templates and Issue<commit_after>/*!
 Copyright (C) 2017 Google Inc.
 Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
 */

(function (GGRC, _) {
  'use strict';
  /**
   * Tree View Widgets Configuration module
   */
  var allTypes = [
    'AccessGroup', 'Audit', 'Clause', 'Contract', 'Control', 'Assessment',
    'DataAsset', 'Facility', 'Issue', 'Market', 'Objective', 'OrgGroup',
    'Person', 'Policy', 'Process', 'Product', 'Program', 'Project',
    'Regulation', 'Section', 'Standard', 'System', 'Vendor'
  ];
  // Items allowed for mapping via snapshot.
  var snapshotWidgetsConfig = GGRC.config.snapshotable_objects || [];
  // Items allowed for relationship mapping
  var excludeMappingConfig = [
    'Assessment',
    'AssessmentTemplate',
    'Issue'
  ];
  // Extra Tree View Widgets require to be rendered on Audit View
  var auditInclusion = [
    'Person',
    'Program'
  ];
  var baseWidgetsByType;

  var filteredTypes = _.difference(allTypes, excludeMappingConfig);
  // Audit is excluded and created a separate logic for it
  baseWidgetsByType = {
    AccessGroup: _.difference(filteredTypes, ['AccessGroup']),
    Audit: [].concat(snapshotWidgetsConfig, excludeMappingConfig,
      auditInclusion).sort(),
    Clause: _.difference(filteredTypes, ['Clause']),
    Contract: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    Control: filteredTypes,
    Assessment: snapshotWidgetsConfig.concat('Audit').sort(),
    DataAsset: filteredTypes,
    Facility: filteredTypes,
    Issue: snapshotWidgetsConfig.concat('Audit').sort(),
    Market: filteredTypes,
    Objective: filteredTypes,
    OrgGroup: filteredTypes,
    Person: _.difference(filteredTypes, ['Person']),
    Policy: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    Process: filteredTypes,
    Product: filteredTypes,
    Program: _.difference(filteredTypes, ['Program']),
    Project: filteredTypes,
    Regulation: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    Section: filteredTypes,
    Standard: _.difference(filteredTypes,
      ['Contract', 'Policy', 'Regulation', 'Standard']),
    System: filteredTypes,
    Vendor: filteredTypes
  };

  GGRC.tree_view = GGRC.tree_view || new can.Map();
  GGRC.tree_view.attr('base_widgets_by_type', baseWidgetsByType);
})(window.GGRC, window._);
<|endoftext|>


<commit_before>/*!
    Copyright (C) 2015 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: anze@reciprocitylabs.com
    Maintained By: anze@reciprocitylabs.com
*/

(function(can, $) {

  /*  GGRC.SaveQueue
   *
   *  SaveQueue is used by CMS.Models.Cacheable to prevent firing
   *  multiple requests to the server at once. It makes sure the requests
   *  are grouped together (inside _queue) and then resolved in batches.
   *
   *  enqueue(obj: CMS.Models.Cacheable, save_args) -> null
   */
  can.Construct("GGRC.SaveQueue", {

    DELAY: 100, // Number of ms to wait before the first batch is fired
    BATCH: GGRC.config.MAX_INSTANCES || 3, // Maximum number of POST/PUT requests at any given time
    _queue: [],
    _buckets: {},
    _timeout: null,

    _enqueue_bucket: function (bucket) {
      var that = this;
      var objs = bucket.objs;
      bucket.objs = [];
      return function () {
        var body = _.map(objs, function (obj) {
          var o = {};
          o[bucket.type] = obj.serialize();
          return o;
        });
        return $.ajax({
          type: "POST",
          url: "/api/" + bucket.plural,
          data: body
        }).always(function(data, type) {
          if (type === "error") {
            data = data.responseJSON;
            if (data === undefined) {
              return;
            }
          }
          var cb = function(single) {
            return function () {
              this.created(single[1][bucket.type]);
              return new $.Deferred().resolve();
            };
          };
          for (var i = 0; i < objs.length; i++) {
            var single = data[i];
            if (single[0] >= 200 && single[0] < 300) {
              var obj = objs[i];
              obj._save(cb(single));
            }
          }
        }).always(function() {
          if (bucket.objs.length > 0) {
            that._step(that._enqueue_bucket(bucket));
          } else {
            bucket.in_flight = false;
          }
        });
      };
    },

    _step: function (elem) {
      this._queue.push(elem);
      if (typeof this._timeout === "number") {
        clearTimeout(this._timeout);
      }
      this._timeout = setTimeout(function () {
        new GGRC.SaveQueue(this._queue.splice(0, this._queue.length));
      }.bind(this), this.DELAY);
    },

    enqueue: function (obj, args) {
      var elem = function () {
          return obj._save.apply(obj, args);
      };
      if (obj.isNew()) {
        var type = obj.constructor.table_singular;
        var bucket = this._buckets[type];
        if (bucket === undefined) {
          var plural = obj.constructor.table_plural;
          bucket = {
            objs: [],
            type: type,
            plural: plural,
            in_flight: false // is there a "thread" running for this bucket
          };
          this._buckets[type] = bucket;
        }
        bucket.objs.push(obj);
        if (!bucket.in_flight) {
          elem = this._enqueue_bucket(bucket);
          bucket.in_flight = true;
        }
      }
      this._step(elem);
     },
  }, {
    init: function (queue) {
      this._queue = queue;
      this._resolve();
    },
    _resolve: function() {
      if (!this._queue.length) {
        // Finished
        return;
      }
      var objs = this._queue.splice(0, this.constructor.BATCH);
      $.when.apply($, objs.map(function (f) {
        return f.apply(this);
      }.bind(this.constructor))).always(this._resolve.bind(this)); // Move on to the next one
    }
  });

})(window.can, window.can.$);
<commit_msg>Update SaveQueue documentation<commit_after>/*!
    Copyright (C) 2015 Google Inc., authors, and contributors <see AUTHORS file>
    Licensed under http://www.apache.org/licenses/LICENSE-2.0 <see LICENSE file>
    Created By: anze@reciprocitylabs.com
    Maintained By: anze@reciprocitylabs.com
*/

(function(can, $) {

  /*  GGRC.SaveQueue
   *
   *  SaveQueue is used by CMS.Models.Cacheable to prevent firing
   *  multiple requests to the server at once. It makes sure the requests
   *  are grouped together (inside _queue) and then resolved in batches.
   *
   *  It will also try to group POST request and use the custom collection post
   *  API and then redistribute responses in order to trace in latency for
   *  throughput. This is done by a "thread" (of timeouts) per object type (per
   *  bucket) that enqueues as a regular request but then greedily dispatches
   *  requests that arrived while it was in the queue.
   *
   *  enqueue(obj: CMS.Models.Cacheable, save_args) -> null
   */
  can.Construct("GGRC.SaveQueue", {

    DELAY: 100, // Number of ms to wait before the first batch is fired
    BATCH: GGRC.config.MAX_INSTANCES || 3, // Maximum number of POST/PUT requests at any given time
    _queue: [],
    _buckets: {},
    _timeout: null,

    _enqueue_bucket: function (bucket) {
      var that = this;
      var objs = bucket.objs;
      bucket.objs = [];
      return function () {
        var body = _.map(objs, function (obj) {
          var o = {};
          o[bucket.type] = obj.serialize();
          return o;
        });
        return $.ajax({
          type: "POST",
          url: "/api/" + bucket.plural,
          data: body
        }).always(function(data, type) {
          if (type === "error") {
            data = data.responseJSON;
            if (data === undefined) {
              return;
            }
          }
          var cb = function(single) {
            return function () {
              this.created(single[1][bucket.type]);
              return new $.Deferred().resolve();
            };
          };
          for (var i = 0; i < objs.length; i++) {
            var single = data[i];
            if (single[0] >= 200 && single[0] < 300) {
              var obj = objs[i];
              obj._save(cb(single));
            }
          }
        }).always(function() {
          if (bucket.objs.length > 0) {
            that._step(that._enqueue_bucket(bucket));
          } else {
            bucket.in_flight = false;
          }
        });
      };
    },

    _step: function (elem) {
      this._queue.push(elem);
      if (typeof this._timeout === "number") {
        clearTimeout(this._timeout);
      }
      this._timeout = setTimeout(function () {
        new GGRC.SaveQueue(this._queue.splice(0, this._queue.length));
      }.bind(this), this.DELAY);
    },

    enqueue: function (obj, args) {
      var elem = function () {
          return obj._save.apply(obj, args);
      };
      if (obj.isNew()) {
        var type = obj.constructor.table_singular;
        var bucket = this._buckets[type];
        if (bucket === undefined) {
          var plural = obj.constructor.table_plural;
          bucket = {
            objs: [],
            type: type,
            plural: plural,
            in_flight: false // is there a "thread" running for this bucket
          };
          this._buckets[type] = bucket;
        }
        bucket.objs.push(obj);
        if (!bucket.in_flight) {
          elem = this._enqueue_bucket(bucket);
          bucket.in_flight = true;
        }
      }
      this._step(elem);
     },
  }, {
    init: function (queue) {
      this._queue = queue;
      this._resolve();
    },
    _resolve: function() {
      if (!this._queue.length) {
        // Finished
        return;
      }
      var objs = this._queue.splice(0, this.constructor.BATCH);
      $.when.apply($, objs.map(function (f) {
        return f.apply(this);
      }.bind(this.constructor))).always(this._resolve.bind(this)); // Move on to the next one
    }
  });

})(window.can, window.can.$);
<|endoftext|>


<commit_before>(function() {
    'use strict';

    Game.ObjectiveManager = function(game, parent, x, y) {
        Phaser.Group.call(this, game, parent, 'ObjectiveManager');

        this.position.setTo(x, y);

        /**
         * The vertical padding in pixel of every objective
         * @type {Number}
         */
        this.padding = 10;

        /**
         * The speed of the animations made in ObjectiveManager
         * @type {Number}
         */
        this.animationSpeed = 300;

        /**
         * The easing function to use for animations
         * @type {Phaser.Easing}
         */
        this.easing = Phaser.Easing.Quadratic.Out;

        var bmd = this.game.make.bitmapData(1, 1);
        /**
         * The objective managers background
         * @type {Phaser.Image}
         */
        this.bg = new Phaser.Image(this.game, 0, 0, bmd);
        this.bg.bmd = bmd;
        this.bg.position.x = -this.padding * 0.5;
        this.bg.position.y = -this.padding * 0.5;
        this.add(this.bg);

        /**
         * The title of the objective manager
         * @type {Phaser.Text}
         */
        this.titleText = this.game.add.bitmapText(0, 0, 'font', 'Objectives');
        this.titleText.tint = 0x010101;
        this.add(this.titleText);

        /**
         * An array of all current objectives
         * @type {Array#Game.ObjectiveManager.Objective}
         */
        this.objectives = [];

        /**
         * An array containing all objectives that have ever been added to ObjectiveManager.objectives
         * @type {Array#Game.ObjectiveManager.Objective}
         */
        this.objectiveLog = [];

        /**
         * An array containing all objectives that exists in the tilemap
         * @type {Array#Objective}
         */
        this.allObjectives = [];

        /**
         * The sound effect that is played on objective completion
         * @type {[type]}
         */
        this.objectiveCompleteSFX = this.game.add.audio('objectComplete');

        /**
         * Triggers when any objective has been completed
         * @type {Phaser.Signal}
         */
        this.onObjectiveComplete = new Phaser.Signal();

        this._expandButton = new Game.GUI.Button(this.game, 0, 10, undefined, 'Hide', 'font');
        this._expandButton.anchor.setTo(0, 0);
        this._expandButton.fontSize = 26;
        this._expandButton.onInputDown.add(this.toggleExpand, this);
        this.add(this._expandButton);

        this._isExpanded = true;

        this._updatePositioning();
        this.onObjectiveComplete.add(this.onObjectiveCompleteHandler, this);

        this.alpha = 0;
    };

    Game.ObjectiveManager.prototype = Object.create(Phaser.Group.prototype);
    Game.ObjectiveManager.prototype.constructor = Game.ObjectiveManager;

    Game.ObjectiveManager.prototype.onObjectiveCompleteHandler = function() {
        this.objectiveCompleteSFX.play();
    };

    Game.ObjectiveManager.prototype.toggleExpand = function() {
        this._isExpanded = !this._isExpanded;

        if (!this._isExpanded) {
            _.forEach(this.objectives, function(objective, index) {
                objective.alpha = 0;
            }, this);

            this._expandButton.text = 'Show';
        } else {
            _.forEach(this.objectives, function(objective, index) {
                objective.alpha = 1;
            }, this);

            this._expandButton.text = 'Hide';
        }

        this._updatePositioning();
    };

    Game.ObjectiveManager.prototype.createObjectives = function(map, objectivesLayer, player) {
        _.forEach(objectivesLayer, function(objective) {
            switch (objective.type) {
                case 'collect':
                    this.allObjectives.push(this.createCollectObjective(map, objective, player));
                    break;
                case 'a2b':
                    this.allObjectives.push(this.createA2BObjective(map, objective, player));
                    break;
                case 'criteria':
                    this.allObjectives.push(this.createCriteriaObjective(map, objective, player));
                    break;
                default:
                    console.log('Objective of type ' + objective.type + ' not yet implemented');
                    break;
            }
        }, this);
    };

    Game.ObjectiveManager.prototype.createCollectObjective = function(map, objective, player) {
        var activeRect = new Phaser.Rectangle(objective.x, objective.y, objective.width, objective.height);
        var trigger = new Game.Trigger.ZoneTrigger(this.game, true, activeRect, player);

        var objectLayer = map.objects[objective.properties.itemLayer];
        var itemsGroup = this.game.add.group(undefined, objective.name);
        itemsGroup.enableBody = true;

        var endTrigger;

        _.forEach(objectLayer, function(object) {
            if (object.type && object.type === 'end') {
                endTrigger = new Game.Trigger.ZoneTrigger(this.game, true, new Phaser.Rectangle(object.x, object.y, object.width, object.height), player);
            } else {
                var key = object.properties.spritesheet || objective.properties.spritesheet;
                var frame = object.properties.frame || objective.properties.frame;

                var sprite = itemsGroup.create(object.x, object.y, key, parseInt(frame));

                if (object.properties['activate']) {
                    sprite.activate = true;
                }

                sprite.body.allowGravity = object.properties.allowGravity || false;
            }
        }, this);

        var dependencies = objective.properties.dependencies ? objective.properties.dependencies.split(',') : undefined;

        var collectObjective = new Game.ObjectiveManager.CollectObjective(this.game, this, trigger, map, objective, player, dependencies, itemsGroup, endTrigger);
        this.game.triggerManager.addTrigger(trigger);
        if (endTrigger) this.game.triggerManager.addTrigger(endTrigger);

        return collectObjective;
    };

    Game.ObjectiveManager.prototype.createA2BObjective = function(map, objective, player) {
        var activeRect = new Phaser.Rectangle(objective.x, objective.y, objective.width, objective.height);
        var trigger = new Game.Trigger.ZoneTrigger(this.game, true, activeRect, player);

        var endLayer = map.objects[objective.properties.endLayer];

        var rectangles = [];

        _.forEach(endLayer, function(object) {
            rectangles.push(new Phaser.Rectangle(object.x, object.y, object.width, object.height));
        });

        var endTrigger = new Game.Trigger.ZoneTrigger(this.game, false, rectangles, player);

        var dependencies = objective.properties.dependencies ? objective.properties.dependencies.split(',') : undefined;

        var a2bObjective = new Game.ObjectiveManager.A2BObjective(this.game, this, trigger, map, objective, player, dependencies, endTrigger);
        this.game.triggerManager.addTrigger(trigger);
        this.game.triggerManager.addTrigger(endTrigger);

        return a2bObjective;
    };

    Game.ObjectiveManager.prototype.createCriteriaObjective = function(map, objective, player) {
        var activeRect = new Phaser.Rectangle(objective.x, objective.y, objective.width, objective.height);
        var trigger = new Game.Trigger.ZoneTrigger(this.game, true, activeRect, player);

        var dependencies = objective.properties.dependencies ? objective.properties.dependencies.split(',') : undefined;
        var criterias = objective.properties.criterias ? objective.properties.criterias.split(',') : [];

        var criteriaObjective = new Game.ObjectiveManager.CriteriaObjective(this.game, this, trigger, map, objective, player, dependencies, criterias);
        this.game.triggerManager.addTrigger(trigger);

        return criteriaObjective;
    };

    Game.ObjectiveManager.prototype.addObjective = function(objective) {
        objective.y = this._calculateHeightTo(this.objectives.length - 1);
        this.objectives.push(objective);
        this.objectiveLog.push(objective);

        objective.game.add.tween(objective).to({alpha: 1}, this.animationSpeed, this.easing, true);

        objective.onCompletion.add(this._removeObjective, this);
        objective.onFailure.add(this._removeObjective, this);

        this._updatePositioning();
    };

    Game.ObjectiveManager.prototype.isActive = function(objective) {
        return _.contains(this.objectives, objective);
    };

    Game.ObjectiveManager.prototype.isCompleted = function(objectives) {
        if (!objectives) return true;

        var toCheck = _.filter(this.allObjectives, function(objective) {
                return _.contains(objectives, objective.properties.id);
            });

        if (toCheck.length < objectives.length) {
            console.log('Could not find all dependencies. Did you spell all correctly?');
            return false;
        }

        return _.every(toCheck, 'completed');
    };

    Game.ObjectiveManager.prototype._removeObjective = function(objective) {
        if (!objective.isActive) return;

        objective.game.add.tween(objective)
            .to({alpha: 0}, this.animationSpeed, this.easing, true);

        var index = this.objectives.indexOf(objective);
        this.objectives.splice(index, 1);

        this._updatePositioning();
    };

    Game.ObjectiveManager.prototype._updatePositioning = function() {
        // this.width = this._getVisibleWidth();

        if (this.objectives.length === 0) {
            this.game.add.tween(this).to({alpha: 0}, this.animationSpeed, this.easing, true);
            return;
        } else {
            this.game.add.tween(this).to({alpha: 1}, this.animationSpeed, this.easing, true);
        }

        this.pivot.x = this._getVisibleWidth() * 0.5;

        this.game.add.tween(this.titleText.position).to({x: this._getVisibleWidth() * 0.5 - this.titleText.width * 0.5 }, this.animationSpeed, this.easing, true);
        this._expandButton.position.x = this._getVisibleWidth() - this._expandButton.width;

        _.forEach(this.objectives, function(objective, index) {
            this.game.add.tween(objective).to({y: this._calculateHeightTo(index - 1) }, this.animationSpeed, this.easing, true);
        }, this);

        this.bg.width = this._getVisibleWidth() + this.padding;
        this.bg.height = this._calculateHeightTo(this._isExpanded ? this.objectives.length - 1 : -1) + this.padding;
        this.bg.bmd.clear();
        this.bg.bmd.fill(255, 255, 255, 0.75);
    };

    Game.ObjectiveManager.prototype._calculateHeightTo = function(index) {
        var _height = this.titleText.height + this.padding;

        index = Math.min(this.objectives.length - 1, index);

        for (var i = 0; i <= index; i++) {
            _height += this.objectives[i].height + this.padding;
        }

        return _height;

    };

    Game.ObjectiveManager.prototype._getVisibleWidth = function() {
        var width = 0;

        for (var i = 0; i < this.objectives.length; i++) {
            width = Math.max(width, this.objectives[i].width);
        }

        return width;
    };

})();
<commit_msg>Bugfix so that new objectives don't popup if objective manager isnt expanded<commit_after>(function() {
    'use strict';

    Game.ObjectiveManager = function(game, parent, x, y) {
        Phaser.Group.call(this, game, parent, 'ObjectiveManager');

        this.position.setTo(x, y);

        /**
         * The vertical padding in pixel of every objective
         * @type {Number}
         */
        this.padding = 10;

        /**
         * The speed of the animations made in ObjectiveManager
         * @type {Number}
         */
        this.animationSpeed = 300;

        /**
         * The easing function to use for animations
         * @type {Phaser.Easing}
         */
        this.easing = Phaser.Easing.Quadratic.Out;

        var bmd = this.game.make.bitmapData(1, 1);
        /**
         * The objective managers background
         * @type {Phaser.Image}
         */
        this.bg = new Phaser.Image(this.game, 0, 0, bmd);
        this.bg.bmd = bmd;
        this.bg.position.x = -this.padding * 0.5;
        this.bg.position.y = -this.padding * 0.5;
        this.add(this.bg);

        /**
         * The title of the objective manager
         * @type {Phaser.Text}
         */
        this.titleText = this.game.add.bitmapText(0, 0, 'font', 'Objectives');
        this.titleText.tint = 0x010101;
        this.add(this.titleText);

        /**
         * An array of all current objectives
         * @type {Array#Game.ObjectiveManager.Objective}
         */
        this.objectives = [];

        /**
         * An array containing all objectives that have ever been added to ObjectiveManager.objectives
         * @type {Array#Game.ObjectiveManager.Objective}
         */
        this.objectiveLog = [];

        /**
         * An array containing all objectives that exists in the tilemap
         * @type {Array#Objective}
         */
        this.allObjectives = [];

        /**
         * The sound effect that is played on objective completion
         * @type {[type]}
         */
        this.objectiveCompleteSFX = this.game.add.audio('objectComplete');

        /**
         * Triggers when any objective has been completed
         * @type {Phaser.Signal}
         */
        this.onObjectiveComplete = new Phaser.Signal();

        this._expandButton = new Game.GUI.Button(this.game, 0, 10, undefined, 'Hide', 'font');
        this._expandButton.anchor.setTo(0, 0);
        this._expandButton.fontSize = 26;
        this._expandButton.onInputDown.add(this.toggleExpand, this);
        this.add(this._expandButton);

        this._isExpanded = true;

        this._updatePositioning();
        this.onObjectiveComplete.add(this.onObjectiveCompleteHandler, this);

        this.alpha = 0;
    };

    Game.ObjectiveManager.prototype = Object.create(Phaser.Group.prototype);
    Game.ObjectiveManager.prototype.constructor = Game.ObjectiveManager;

    Game.ObjectiveManager.prototype.onObjectiveCompleteHandler = function() {
        this.objectiveCompleteSFX.play();
    };

    Game.ObjectiveManager.prototype.toggleExpand = function() {
        this._isExpanded = !this._isExpanded;

        if (!this._isExpanded) {
            _.forEach(this.objectives, function(objective, index) {
                objective.alpha = 0;
            }, this);

            this._expandButton.text = 'Show';
        } else {
            _.forEach(this.objectives, function(objective, index) {
                objective.alpha = 1;
            }, this);

            this._expandButton.text = 'Hide';
        }

        this._updatePositioning();
    };

    Game.ObjectiveManager.prototype.createObjectives = function(map, objectivesLayer, player) {
        _.forEach(objectivesLayer, function(objective) {
            switch (objective.type) {
                case 'collect':
                    this.allObjectives.push(this.createCollectObjective(map, objective, player));
                    break;
                case 'a2b':
                    this.allObjectives.push(this.createA2BObjective(map, objective, player));
                    break;
                case 'criteria':
                    this.allObjectives.push(this.createCriteriaObjective(map, objective, player));
                    break;
                default:
                    console.log('Objective of type ' + objective.type + ' not yet implemented');
                    break;
            }
        }, this);
    };

    Game.ObjectiveManager.prototype.createCollectObjective = function(map, objective, player) {
        var activeRect = new Phaser.Rectangle(objective.x, objective.y, objective.width, objective.height);
        var trigger = new Game.Trigger.ZoneTrigger(this.game, true, activeRect, player);

        var objectLayer = map.objects[objective.properties.itemLayer];
        var itemsGroup = this.game.add.group(undefined, objective.name);
        itemsGroup.enableBody = true;

        var endTrigger;

        _.forEach(objectLayer, function(object) {
            if (object.type && object.type === 'end') {
                endTrigger = new Game.Trigger.ZoneTrigger(this.game, true, new Phaser.Rectangle(object.x, object.y, object.width, object.height), player);
            } else {
                var key = object.properties.spritesheet || objective.properties.spritesheet;
                var frame = object.properties.frame || objective.properties.frame;

                var sprite = itemsGroup.create(object.x, object.y, key, parseInt(frame));

                if (object.properties['activate']) {
                    sprite.activate = true;
                }

                sprite.body.allowGravity = object.properties.allowGravity || false;
            }
        }, this);

        var dependencies = objective.properties.dependencies ? objective.properties.dependencies.split(',') : undefined;

        var collectObjective = new Game.ObjectiveManager.CollectObjective(this.game, this, trigger, map, objective, player, dependencies, itemsGroup, endTrigger);
        this.game.triggerManager.addTrigger(trigger);
        if (endTrigger) this.game.triggerManager.addTrigger(endTrigger);

        return collectObjective;
    };

    Game.ObjectiveManager.prototype.createA2BObjective = function(map, objective, player) {
        var activeRect = new Phaser.Rectangle(objective.x, objective.y, objective.width, objective.height);
        var trigger = new Game.Trigger.ZoneTrigger(this.game, true, activeRect, player);

        var endLayer = map.objects[objective.properties.endLayer];

        var rectangles = [];

        _.forEach(endLayer, function(object) {
            rectangles.push(new Phaser.Rectangle(object.x, object.y, object.width, object.height));
        });

        var endTrigger = new Game.Trigger.ZoneTrigger(this.game, false, rectangles, player);

        var dependencies = objective.properties.dependencies ? objective.properties.dependencies.split(',') : undefined;

        var a2bObjective = new Game.ObjectiveManager.A2BObjective(this.game, this, trigger, map, objective, player, dependencies, endTrigger);
        this.game.triggerManager.addTrigger(trigger);
        this.game.triggerManager.addTrigger(endTrigger);

        return a2bObjective;
    };

    Game.ObjectiveManager.prototype.createCriteriaObjective = function(map, objective, player) {
        var activeRect = new Phaser.Rectangle(objective.x, objective.y, objective.width, objective.height);
        var trigger = new Game.Trigger.ZoneTrigger(this.game, true, activeRect, player);

        var dependencies = objective.properties.dependencies ? objective.properties.dependencies.split(',') : undefined;
        var criterias = objective.properties.criterias ? objective.properties.criterias.split(',') : [];

        var criteriaObjective = new Game.ObjectiveManager.CriteriaObjective(this.game, this, trigger, map, objective, player, dependencies, criterias);
        this.game.triggerManager.addTrigger(trigger);

        return criteriaObjective;
    };

    Game.ObjectiveManager.prototype.addObjective = function(objective) {
        objective.y = this._calculateHeightTo(this.objectives.length - 1);
        this.objectives.push(objective);
        this.objectiveLog.push(objective);

        if (this._isExpanded) {
            objective.game.add.tween(objective).to({alpha: 1}, this.animationSpeed, this.easing, true);
        }

        objective.onCompletion.add(this._removeObjective, this);
        objective.onFailure.add(this._removeObjective, this);

        this._updatePositioning();
    };

    Game.ObjectiveManager.prototype.isActive = function(objective) {
        return _.contains(this.objectives, objective);
    };

    Game.ObjectiveManager.prototype.isCompleted = function(objectives) {
        if (!objectives) return true;

        var toCheck = _.filter(this.allObjectives, function(objective) {
                return _.contains(objectives, objective.properties.id);
            });

        if (toCheck.length < objectives.length) {
            console.log('Could not find all dependencies. Did you spell all correctly?');
            return false;
        }

        return _.every(toCheck, 'completed');
    };

    Game.ObjectiveManager.prototype._removeObjective = function(objective) {
        if (!objective.isActive) return;

        objective.game.add.tween(objective)
            .to({alpha: 0}, this.animationSpeed, this.easing, true);

        var index = this.objectives.indexOf(objective);
        this.objectives.splice(index, 1);

        this._updatePositioning();
    };

    Game.ObjectiveManager.prototype._updatePositioning = function() {
        // this.width = this._getVisibleWidth();

        if (this.objectives.length === 0) {
            this.game.add.tween(this).to({alpha: 0}, this.animationSpeed, this.easing, true);
            return;
        } else {
            this.game.add.tween(this).to({alpha: 1}, this.animationSpeed, this.easing, true);
        }

        this.pivot.x = this._getVisibleWidth() * 0.5;

        this.game.add.tween(this.titleText.position).to({x: this._getVisibleWidth() * 0.5 - this.titleText.width * 0.5 }, this.animationSpeed, this.easing, true);
        this._expandButton.position.x = this._getVisibleWidth() - this._expandButton.width;

        _.forEach(this.objectives, function(objective, index) {
            this.game.add.tween(objective).to({y: this._calculateHeightTo(index - 1) }, this.animationSpeed, this.easing, true);
        }, this);

        this.bg.width = this._getVisibleWidth() + this.padding;
        this.bg.height = this._calculateHeightTo(this._isExpanded ? this.objectives.length - 1 : -1) + this.padding;
        this.bg.bmd.clear();
        this.bg.bmd.fill(255, 255, 255, 0.75);
    };

    Game.ObjectiveManager.prototype._calculateHeightTo = function(index) {
        var _height = this.titleText.height + this.padding;

        index = Math.min(this.objectives.length - 1, index);

        for (var i = 0; i <= index; i++) {
            _height += this.objectives[i].height + this.padding;
        }

        return _height;

    };

    Game.ObjectiveManager.prototype._getVisibleWidth = function() {
        var width = 0;

        for (var i = 0; i < this.objectives.length; i++) {
            width = Math.max(width, this.objectives[i].width);
        }

        return width;
    };

})();
<|endoftext|>