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

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOUpdateView',
  extends: 'foam.u2.View',

  documentation: `
    A configurable summary view for a specific instance
  `,

  // axioms: [
  //   foam.pattern.Faceted.create()
  // ],

  css:`
    ^ {
      padding: 32px
    }

    ^ .foam-u2-ActionView-back {
      display: flex;
      align-items: center;
    }

    ^account-name {
      font-size: 36px;
      font-weight: 600;
    }

    ^actions-header .foam-u2-ActionView {
      margin-right: 24px;
      line-height: 1.5
    }

    ^view-container {
      margin: auto;
    }
  `,

  requires: [
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.ControllerMode',
  ],
  imports: [
    'stack'
  ],
  exports: [
    'controllerMode'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config'
    },
    {
      name: 'controllerMode',
      factory: function() {
        return this.ControllerMode.VIEW;
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewView',
      expression: function() {
        return foam.u2.detail.SectionedDetailView;
      }
    },
    {
      name: 'primary',
      expression: function(config$of){
        var allActions = config$of.getAxiomsByClass(foam.core.Action)
        var defaultAction = allActions.filter(a => a.isDefault);
        return defaultAction.length >= 1 ? defaultAction[0] : allActions[0];
      }
    }
  ],
  actions: [
    {
      name: 'edit',
      code: function() {
        this.controllerMode = this.ControllerMode.EDIT;
      }
    },
    {
      name: 'delete',
      code: function() {
        alert('TODO');
      }
    }
  ],
  methods: [
    function initE() {
      var self = this;
      this.SUPER();
      this
        .addClass(this.myClass())
        .add(self.slot(function(data, config$viewBorder, data$name) {
          return self.E()
            .start(self.Rows)
              .start(self.Rows)
                // we will handle this in the StackView instead
                .startContext({ data: self.stack })
                    .tag(self.stack.BACK, {
                      buttonStyle: foam.u2.ButtonStyle.TERTIARY,
                      icon: 'images/back-icon.svg',
                      label: data$name
                    })
                .endContext()
                .start(self.Cols).style({ 'align-items': 'center' })
                  .start()
                    .add(data.toSummary())
                      .addClass(this.myClass('account-name'))
                  .end()
                  .startContext({data: data}).add(self.primary).endContext()
                .end()
              .end()

              .start(self.Cols)
                .start(self.Cols).addClass(this.myClass('actions-header'))
                  .startContext({data: self}).tag(self.EDIT, {
                    buttonStyle: foam.u2.ButtonStyle.TERTIARY,
                    icon: 'images/edit-icon.svg'
                  }).endContext()
                  .startContext({data: self}).tag(self.DELETE, {
                    buttonStyle: foam.u2.ButtonStyle.TERTIARY,
                    icon: 'images/delete-icon.svg'
                  }).endContext()
                .end()
              .end()

              .start(config$viewBorder)
                .start().addClass(this.myClass('view-container'))
                  .add(self.slot(function(viewView) {
                    return self.E().tag(viewView, {
                      data: data
                    });
                  }))
                .end()
              .end()
            .end();
        }));
    }
  ]
});
<commit_msg>Revived my code<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.comics.v2',
  name: 'DAOUpdateView',
  extends: 'foam.u2.View',

  topics: [
    'finished',
    'throwError'
  ],

  documentation: `
    A configurable summary view for a specific instance
  `,

  // axioms: [
  //   foam.pattern.Faceted.create()
  // ],

  css:`
    ^ {
      padding: 32px
    }

    ^ .foam-u2-ActionView-back {
      display: flex;
      align-items: center;
    }

    ^account-name {
      font-size: 36px;
      font-weight: 600;
    }

    ^actions-header .foam-u2-ActionView {
      margin-right: 24px;
      line-height: 1.5
    }

    ^view-container {
      margin: auto;
    }
  `,

  requires: [
    'foam.u2.layout.Cols',
    'foam.u2.layout.Rows',
    'foam.u2.ControllerMode',
  ],
  imports: [
    'stack'
  ],
  exports: [
    'controllerMode'
  ],
  properties: [
    {
      class: 'FObjectProperty',
      name: 'data'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.comics.v2.DAOControllerConfig',
      name: 'config'
    },
    {
      name: 'controllerMode',
      factory: function() {
        return this.ControllerMode.EDIT;
      }
    },
    {
      class: 'foam.u2.ViewSpecWithJava',
      name: 'viewView',
      expression: function() {
        return foam.u2.detail.SectionedDetailView;
      }
    }
  ],
  actions: [
    {
      name: 'save',
      code: function() {
        var self = this;
        this.config.dao.put(this.data.clone()).then(function() {
          self.finished.pub();
          self.stack.back();
        }, function() {
          self.throwError.pub();
        });
      }
    },
  ],
  methods: [
    function initE() {
      var self = this;
      this.SUPER();

      const originalName = this.data.name

      this
        .addClass(this.myClass())
        .add(self.slot(function(data, config$viewBorder) {
          return self.E()
            .start(self.Rows)
              .start(self.Rows)
                // we will handle this in the StackView instead
                .startContext({ data: self.stack })
                    .tag(self.stack.BACK, {
                      buttonStyle: foam.u2.ButtonStyle.TERTIARY,
                      icon: 'images/back-icon.svg',
                      label: originalName
                    })
                .endContext()
                .start(self.Cols).style({ 'align-items': 'center' })
                  .start()
                    .add(data.toSummary())
                      .addClass(this.myClass('account-name'))
                  .end()
                  .startContext({data: self}).add(self.SAVE).endContext()
                .end()
              .end()

              .start(config$viewBorder)
                .start().addClass(this.myClass('view-container'))
                  .add(self.slot(function(viewView) {
                    return self.E().tag(viewView, {
                      data: data
                    });
                  }))
                .end()
              .end()
            .end();
        }));
    }
  ]
});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'Capability',

  implements: [
    'foam.nanos.auth.EnabledAware'
  ],

  imports: [
    'capabilityDAO',
    'prerequisiteCapabilityJunctionDAO'
  ],

  javaImports: [
    'foam.core.X',
    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'foam.dao.Sink',
    'foam.mlang.sink.Count',
    'foam.nanos.auth.Subject',
    'foam.nanos.auth.User',
    'foam.nanos.logger.Logger',
    'java.util.Date',
    'java.util.List',
    'static foam.mlang.MLang.*'
  ],

  requires: [
    'foam.u2.crunch.EasyCrunchWizard'
  ],

  tableColumns: [
    'id',
    'name',
    'description',
    'version',
    'enabled',
    'visible',
    'expiry',
    'daoKey'
  ],

  sections: [
    {
      name: '_defaultSection',
      title: 'Administrative',
      permissionRequired: true
    },
    {
      name: 'basicInfo',
      title: 'Basic Info'
    },
    {
      name: 'uiSettings',
      title: 'UI Settings',
      help: 'These properties are used to control how this capability appears in the GUI.',
      permissionRequired: true
    },
    {
      name: 'capabilityRelationships',
      title: 'Capability Relationships',
      permissionRequired: true
    }
  ],

  properties: [
    {
      name: 'id',
      class: 'String',
      displayWidth: 40,
      createVisibility: 'HIDDEN',
      updateVisibility: 'RO',
      section: 'basicInfo'
    },
    {
      name: 'name',
      class: 'String',
      displayWidth: 70,
      section: 'basicInfo'
    },
    {
      name: 'icon',
      class: 'Image',
      documentation: `Path to capability icon`,
      section: 'uiSettings'
    },
    {
      name: 'description',
      class: 'String',
      documentation: `Description of capability`,
      section: 'uiSettings'
    },
    {
      name: 'notes',
      class: 'String',
      view: {
        class: 'foam.u2.tag.TextArea',
        rows: 12,
        cols: 120
      }
    },
    {
      name: 'price',
      class: 'Long'
    },
    {
      name: 'keywords',
      class: 'StringArray'
    },
    {
      name: 'version',
      class: 'String'
    },
    {
      name: 'enabled',
      class: 'Boolean',
      value: true,
      documentation: `Capability is ignored by system when enabled is false.
      user will lose permissions implied by this capability and upper level capabilities will ignore this prerequisite`
    },
    {
      name: 'expiry',
      class: 'DateTime',
      documentation: `Datetime of when capability is no longer valid`
    },
    {
      name: 'duration',
      class: 'Int',
      documentation: `To be used in the case where expiry is duration-based, represents the number of DAYS a junction is valid for before expiring.
      The UserCapabilityJunction object will have its expiry configured to a DateTime based on the lower value of the two, expiry and duration`
    },
    {
      name: 'gracePeriod',
      class: 'Int',
      value: 0,
      documentation: `To be used in the case where expiry is duration based, represents the number of DAYS the user can keep permissions
      granted by this capability after the duration runs out.
      If the gracePeriod is greater than 0, the UserCapabilityJunction will set isInGracePeriod property to true
      and set gracePeriod property to be equals to this. Otherwise, the UserCapabilityJunction will
      go into EXPIRED status.`
    },
    {
      name: 'of',
      class: 'Class',
      displayWidth: 70,
      documentation: `Model used to store information required by this credential`
    },
    {
      name: 'inherentPermissions',
      class: 'StringArray',
      documentation: `List of inherent permissions provided by this capability`,
      storageTransient: true,
      visibility: 'HIDDEN'
    },
    {
      name: 'permissionsGranted',
      class: 'StringArray',
      documentation: `List of permissions granted by this capability`,
      view: 'foam.u2.crunch.PermissionsStringArrayView'
    },
    {
      name: 'permissionsIntercepted',
      class: 'StringArray',
      documentation: `List of permissions intercepted by this capability`
    },
    {
      name: 'daoKey',
      class: 'String',
      documentation: `
      daoKey.put() done in UserCapabilityJunctionDAO.
      Uses contextDAOFindKey to find object to update/put.`
    },
    {
      name: 'contextDAOFindKey',
      class: 'String',
      documentation: 'need to find things dynamically, thus have a string here to specify the object in context to look up.'
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'interceptIf',
      networkTransient: true,
      javaFactory: 'return foam.mlang.MLang.TRUE;',
      documentation: 'condition under which the permissions that may be intercepted by this capability will be intercepted.'
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'availabilityPredicate',
      networkTransient: true,
      javaFactory: 'return foam.mlang.MLang.TRUE;',
      documentation: 'Predicate used to omit or include capabilities from capabilityDAO'
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'visibilityPredicate',
      javaFactory: 'return foam.mlang.MLang.FALSE;',
      documentation: 'Predicate of the visibility for capabilities in the capability store/keyword sections'
    },
    {
      name: 'grantMode',
      class: 'Enum',
      of: 'foam.nanos.crunch.CapabilityGrantMode',
      value: foam.nanos.crunch.CapabilityGrantMode.AUTOMATIC
    },
    {
      name: 'reviewRequired',
      class: 'Boolean',
      permissionRequired: true
    },
    {
      name: 'associatedEntity',
      class: 'Enum',
      of: 'foam.nanos.crunch.AssociatedEntity',
      hidden: true,
      permissionRequired: true,
      documentation: `
        Denotes which entity in the context subject the capability should be saved to when there are mutiple.
      `,
      factory: () => {
        return foam.nanos.crunch.AssociatedEntity.USER;
      },
      javaFactory: `
        return foam.nanos.crunch.AssociatedEntity.USER;
      `
    },
    {
      class: 'Object',
      name: 'wizardlet',
      documentation: `
        Defines a wizardlet to display this capability in a wizard. This
        wizardlet will display after this capability's prerequisites.
      `,
      factory: function() {
        return foam.nanos.crunch.ui.CapabilityWizardlet.create({}, this);
      }
    },
    {
      class: 'Object',
      name: 'beforeWizardlet',
      documentation: `
        A wizardlet to display before this capability's prerequisites, and only
        if this capability is at the end of a prerequisite group returned by
        CrunchService's getCapabilityPath method.
      `
    },
    {
      class: 'FObjectProperty',
      of: 'foam.u2.crunch.EasyCrunchWizard',
      name: 'wizardConfig',
      documentation: `
        Configuration placed on top level capabilities defining various
        configuration options supported by client capability wizards.
      `,
      factory: function() {
        return this.EasyCrunchWizard.create({}, this);
      }
    },
    {
      name: 'requirementViewTitle',
      class: 'String',
      documentation: `A short introduction displayed as subtitle in CapabilityRequirementView`,
      section: 'uiSettings'
    }
  ],

  methods: [
    {
      name: 'toSummary',
      type: 'String',
      code: function() {
        return this.name;
      },
      javaCode: `
        return getName();
      `
    },
    {
      name: 'implies',
      type: 'Boolean',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'permission', type: 'String' }
      ],
      documentation: `Checks if a permission or capability string is implied by the current capability`,
      javaCode: `
        if ( ! this.getEnabled() ) return false;

        // check if permission is a capability string implied by this permission
        if ( this.stringImplies(this.getName(), permission) ) return true;

        String[] inherentPermissions = this.getInherentPermissions();
        for ( String permissionName : inherentPermissions ) {
          if ( this.stringImplies(permissionName, permission) ) return true;
        }

        String[] permissionsGranted = this.getPermissionsGranted();
        for ( String permissionName : permissionsGranted ) {
          if ( this.stringImplies(permissionName, permission) ) return true;
        }

        CrunchService crunchService = (CrunchService) x.get("crunchService");
        var prereqs = crunchService.getPrereqs(getId());

        if ( prereqs != null && prereqs.size() > 0 ) {
          DAO capabilityDAO = (DAO) x.get("capabilityDAO");
          for ( var capId : prereqs ) {
            Capability capability = (Capability) capabilityDAO.find(capId);
            if ( capability != null && capability.implies(x, permission) ) return true;
          }
        }
        return false;
      `
    },
    {
      name: 'stringImplies',
      type: 'Boolean',
      args: [
        { name: 's1', type: 'String' },
        { name: 's2', type: 'String' }
      ],
      documentation: `check if s1 implies s2 where s1 and s2 are permission or capability strings`,
      javaCode: `
      if ( s1.equals(s2) ) return true;
      if ( s1.isBlank() || s2.isBlank() ) return false;
      if ( s1.charAt( s1.length() - 1) != '*' || ( s1.length() - 2 > s2.length() ) ) return false;
      if ( s2.length() <= s1.length() - 2 ) return s1.substring( 0, s1.length() -2 ).equals( s2.substring( 0, s1.length() - 2 ) );
      return s1.substring( 0, s1.length() - 1 ).equals( s2.substring( 0, s1.length() -1 ) );
      `
    },
    {
      name: 'isDeprecated',
      type: 'Boolean',
      args: [
        { name: 'x', type: 'Context' }
      ],
      documentation: 'check if a given capability is deprecated',
      javaCode: `
      Sink count = new Count();
      count = this.getDeprecating(x).getJunctionDAO()
        .where(
          EQ(Capability.ID, (String) this.getId())
        ).select(count);

      return ((Count) count).getValue() > 0;
      `
    },
    {
      name: 'isExpired',
      type: 'Boolean',
      documentation: `check if a given capability is expired.`,
      javaCode: `
      if ( getExpiry() == null ) return false;

      Date today = new Date();
      Date capabilityExpiry = getExpiry();

      return today.after(capabilityExpiry);
      `
    },
    {
      name: 'getPrereqsChainedStatus',
      type: 'CapabilityJunctionStatus',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'ucj', type: 'UserCapabilityJunction' }
      ],
      documentation: `
        Check statuses of all prerequisite capabilities - returning:
        GRANTED        : If all pre-reqs are in granted status
        PENDING        : If at least one pre-req is in pending/approved status and the rest are granted
        ACTION_REQUIRED: If at least one pre-req is not in pending/approved/granted status
      `,
      javaCode: `
        // CrunchService used to get capability junctions
        CrunchService crunchService = (CrunchService) x.get("crunchService");

        boolean allGranted = true;
        DAO capabilityDAO = (DAO) x.get("capabilityDAO");
        DAO userCapabilityJunctionDAO = (DAO) x.get("userCapabilityJunctionDAO");
        DAO userDAO = (DAO) x.get("userDAO");
        Subject currentSubject = (Subject) x.get("subject");

        Subject subject = new Subject(x);
        if ( ucj instanceof AgentCapabilityJunction ) {
          subject.setUser((User) userDAO.find(ucj.getSourceId()));
          AgentCapabilityJunction acj = (AgentCapabilityJunction) ucj;
          subject.setUser((User) userDAO.find(acj.getEffectiveUser())); // "user"
        } else if ( ucj.getSourceId() == currentSubject.getUser().getId() ) {
          subject.setUser(currentSubject.getRealUser());
          subject.setUser(currentSubject.getUser());
        } else {
          subject.setUser((User) userDAO.find(ucj.getSourceId()));
        }

        var prereqs = crunchService.getPrereqs(getId());
        CapabilityJunctionStatus prereqChainedStatus = null;
        if ( prereqs != null ) {
          for ( var capId : prereqs ) {
            var cap = (Capability) capabilityDAO.find(capId);
            if ( cap == null || ! cap.getEnabled() ) continue;

            UserCapabilityJunction prereqUcj = crunchService.getJunctionForSubject(x, capId, subject);

            prereqChainedStatus = getPrereqChainedStatus(x, ucj, prereqUcj);
            if ( prereqChainedStatus == CapabilityJunctionStatus.ACTION_REQUIRED ) return CapabilityJunctionStatus.ACTION_REQUIRED;
            if ( prereqChainedStatus != CapabilityJunctionStatus.GRANTED ) allGranted = false;
          }
        }
        return allGranted ? CapabilityJunctionStatus.GRANTED : CapabilityJunctionStatus.PENDING;
      `,
    },
    {
      name: 'getPrereqChainedStatus',
      args: [
        { name: 'x', javaType: 'foam.core.X' },
        { name: 'ucj', javaType: 'foam.nanos.crunch.UserCapabilityJunction' },
        { name: 'prereq', javaType: 'foam.nanos.crunch.UserCapabilityJunction' }
      ],
      static: true,
      javaType: 'foam.nanos.crunch.CapabilityJunctionStatus',
      javaCode: `
        CapabilityJunctionStatus status = ucj.getStatus();

        boolean reviewRequired = getReviewRequired();
        CapabilityJunctionStatus prereqStatus = prereq.getStatus();

        switch ( (CapabilityJunctionStatus) prereqStatus ) {
          case AVAILABLE :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          case ACTION_REQUIRED :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          case PENDING :
            status = reviewRequired &&
              ( status == CapabilityJunctionStatus.APPROVED ||
                status == CapabilityJunctionStatus.GRANTED
              ) ?
                CapabilityJunctionStatus.APPROVED : CapabilityJunctionStatus.PENDING;
            break;
          case APPROVED :
            status = reviewRequired &&
              ( status == CapabilityJunctionStatus.APPROVED ||
                status == CapabilityJunctionStatus.GRANTED
              ) ?
                CapabilityJunctionStatus.APPROVED : CapabilityJunctionStatus.PENDING;
              break;
          case EXPIRED :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          default :
            status = CapabilityJunctionStatus.GRANTED;
        }
        return status;

      `
    }
  ]
});

foam.RELATIONSHIP({
  package: 'foam.nanos.crunch',
  extends:'foam.nanos.crunch.Renewable',
  sourceModel: 'foam.nanos.auth.User',
  targetModel: 'foam.nanos.crunch.Capability',
  ids: [ 'id' ],
  cardinality: '*:*',
  forwardName: 'capabilities',
  inverseName: 'users',
  sourceProperty: {
    section: 'systemInformation',
    updateVisibility: 'RO'
  }
});

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'CRUNCHThemeRefinement',
  refines: 'foam.nanos.theme.Theme',

  properties: [
    {
      name: 'admissionCapability',
      class: 'String',
      // TODO: Why doesn't a Reference property work here?
      // class: 'Reference',
      of: 'foam.nanos.crunch.Capability',
      documentation: 'Specifies the top-level capability that must be granted before we admit a user to the system.'
    }
  ],
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'deprecated',
  inverseName: 'deprecating',
  junctionDAOKey: 'deprecatedCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'prerequisites',
  inverseName: 'dependents',
  junctionDAOKey: 'prerequisiteCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});
<commit_msg>PENDING_REVIEW should not default to GRANTED (#4496)<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'Capability',

  implements: [
    'foam.nanos.auth.EnabledAware'
  ],

  imports: [
    'capabilityDAO',
    'prerequisiteCapabilityJunctionDAO'
  ],

  javaImports: [
    'foam.core.X',
    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'foam.dao.Sink',
    'foam.mlang.sink.Count',
    'foam.nanos.auth.Subject',
    'foam.nanos.auth.User',
    'foam.nanos.logger.Logger',
    'java.util.Date',
    'java.util.List',
    'static foam.mlang.MLang.*'
  ],

  requires: [
    'foam.u2.crunch.EasyCrunchWizard'
  ],

  tableColumns: [
    'id',
    'name',
    'description',
    'version',
    'enabled',
    'visible',
    'expiry',
    'daoKey'
  ],

  sections: [
    {
      name: '_defaultSection',
      title: 'Administrative',
      permissionRequired: true
    },
    {
      name: 'basicInfo',
      title: 'Basic Info'
    },
    {
      name: 'uiSettings',
      title: 'UI Settings',
      help: 'These properties are used to control how this capability appears in the GUI.',
      permissionRequired: true
    },
    {
      name: 'capabilityRelationships',
      title: 'Capability Relationships',
      permissionRequired: true
    }
  ],

  properties: [
    {
      name: 'id',
      class: 'String',
      displayWidth: 40,
      createVisibility: 'HIDDEN',
      updateVisibility: 'RO',
      section: 'basicInfo'
    },
    {
      name: 'name',
      class: 'String',
      displayWidth: 70,
      section: 'basicInfo'
    },
    {
      name: 'icon',
      class: 'Image',
      documentation: `Path to capability icon`,
      section: 'uiSettings'
    },
    {
      name: 'description',
      class: 'String',
      documentation: `Description of capability`,
      section: 'uiSettings'
    },
    {
      name: 'notes',
      class: 'String',
      view: {
        class: 'foam.u2.tag.TextArea',
        rows: 12,
        cols: 120
      }
    },
    {
      name: 'price',
      class: 'Long'
    },
    {
      name: 'keywords',
      class: 'StringArray'
    },
    {
      name: 'version',
      class: 'String'
    },
    {
      name: 'enabled',
      class: 'Boolean',
      value: true,
      documentation: `Capability is ignored by system when enabled is false.
      user will lose permissions implied by this capability and upper level capabilities will ignore this prerequisite`
    },
    {
      name: 'expiry',
      class: 'DateTime',
      documentation: `Datetime of when capability is no longer valid`
    },
    {
      name: 'duration',
      class: 'Int',
      documentation: `To be used in the case where expiry is duration-based, represents the number of DAYS a junction is valid for before expiring.
      The UserCapabilityJunction object will have its expiry configured to a DateTime based on the lower value of the two, expiry and duration`
    },
    {
      name: 'gracePeriod',
      class: 'Int',
      value: 0,
      documentation: `To be used in the case where expiry is duration based, represents the number of DAYS the user can keep permissions
      granted by this capability after the duration runs out.
      If the gracePeriod is greater than 0, the UserCapabilityJunction will set isInGracePeriod property to true
      and set gracePeriod property to be equals to this. Otherwise, the UserCapabilityJunction will
      go into EXPIRED status.`
    },
    {
      name: 'of',
      class: 'Class',
      displayWidth: 70,
      documentation: `Model used to store information required by this credential`
    },
    {
      name: 'inherentPermissions',
      class: 'StringArray',
      documentation: `List of inherent permissions provided by this capability`,
      storageTransient: true,
      visibility: 'HIDDEN'
    },
    {
      name: 'permissionsGranted',
      class: 'StringArray',
      documentation: `List of permissions granted by this capability`,
      view: 'foam.u2.crunch.PermissionsStringArrayView'
    },
    {
      name: 'permissionsIntercepted',
      class: 'StringArray',
      documentation: `List of permissions intercepted by this capability`
    },
    {
      name: 'daoKey',
      class: 'String',
      documentation: `
      daoKey.put() done in UserCapabilityJunctionDAO.
      Uses contextDAOFindKey to find object to update/put.`
    },
    {
      name: 'contextDAOFindKey',
      class: 'String',
      documentation: 'need to find things dynamically, thus have a string here to specify the object in context to look up.'
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'interceptIf',
      networkTransient: true,
      javaFactory: 'return foam.mlang.MLang.TRUE;',
      documentation: 'condition under which the permissions that may be intercepted by this capability will be intercepted.'
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'availabilityPredicate',
      networkTransient: true,
      javaFactory: 'return foam.mlang.MLang.TRUE;',
      documentation: 'Predicate used to omit or include capabilities from capabilityDAO'
    },
    {
      class: 'foam.mlang.predicate.PredicateProperty',
      name: 'visibilityPredicate',
      javaFactory: 'return foam.mlang.MLang.FALSE;',
      documentation: 'Predicate of the visibility for capabilities in the capability store/keyword sections'
    },
    {
      name: 'grantMode',
      class: 'Enum',
      of: 'foam.nanos.crunch.CapabilityGrantMode',
      value: foam.nanos.crunch.CapabilityGrantMode.AUTOMATIC
    },
    {
      name: 'reviewRequired',
      class: 'Boolean',
      permissionRequired: true
    },
    {
      name: 'associatedEntity',
      class: 'Enum',
      of: 'foam.nanos.crunch.AssociatedEntity',
      hidden: true,
      permissionRequired: true,
      documentation: `
        Denotes which entity in the context subject the capability should be saved to when there are mutiple.
      `,
      factory: () => {
        return foam.nanos.crunch.AssociatedEntity.USER;
      },
      javaFactory: `
        return foam.nanos.crunch.AssociatedEntity.USER;
      `
    },
    {
      class: 'Object',
      name: 'wizardlet',
      documentation: `
        Defines a wizardlet to display this capability in a wizard. This
        wizardlet will display after this capability's prerequisites.
      `,
      factory: function() {
        return foam.nanos.crunch.ui.CapabilityWizardlet.create({}, this);
      }
    },
    {
      class: 'Object',
      name: 'beforeWizardlet',
      documentation: `
        A wizardlet to display before this capability's prerequisites, and only
        if this capability is at the end of a prerequisite group returned by
        CrunchService's getCapabilityPath method.
      `
    },
    {
      class: 'FObjectProperty',
      of: 'foam.u2.crunch.EasyCrunchWizard',
      name: 'wizardConfig',
      documentation: `
        Configuration placed on top level capabilities defining various
        configuration options supported by client capability wizards.
      `,
      factory: function() {
        return this.EasyCrunchWizard.create({}, this);
      }
    },
    {
      name: 'requirementViewTitle',
      class: 'String',
      documentation: `A short introduction displayed as subtitle in CapabilityRequirementView`,
      section: 'uiSettings'
    }
  ],

  methods: [
    {
      name: 'toSummary',
      type: 'String',
      code: function() {
        return this.name;
      },
      javaCode: `
        return getName();
      `
    },
    {
      name: 'implies',
      type: 'Boolean',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'permission', type: 'String' }
      ],
      documentation: `Checks if a permission or capability string is implied by the current capability`,
      javaCode: `
        if ( ! this.getEnabled() ) return false;

        // check if permission is a capability string implied by this permission
        if ( this.stringImplies(this.getName(), permission) ) return true;

        String[] inherentPermissions = this.getInherentPermissions();
        for ( String permissionName : inherentPermissions ) {
          if ( this.stringImplies(permissionName, permission) ) return true;
        }

        String[] permissionsGranted = this.getPermissionsGranted();
        for ( String permissionName : permissionsGranted ) {
          if ( this.stringImplies(permissionName, permission) ) return true;
        }

        CrunchService crunchService = (CrunchService) x.get("crunchService");
        var prereqs = crunchService.getPrereqs(getId());

        if ( prereqs != null && prereqs.size() > 0 ) {
          DAO capabilityDAO = (DAO) x.get("capabilityDAO");
          for ( var capId : prereqs ) {
            Capability capability = (Capability) capabilityDAO.find(capId);
            if ( capability != null && capability.implies(x, permission) ) return true;
          }
        }
        return false;
      `
    },
    {
      name: 'stringImplies',
      type: 'Boolean',
      args: [
        { name: 's1', type: 'String' },
        { name: 's2', type: 'String' }
      ],
      documentation: `check if s1 implies s2 where s1 and s2 are permission or capability strings`,
      javaCode: `
      if ( s1.equals(s2) ) return true;
      if ( s1.isBlank() || s2.isBlank() ) return false;
      if ( s1.charAt( s1.length() - 1) != '*' || ( s1.length() - 2 > s2.length() ) ) return false;
      if ( s2.length() <= s1.length() - 2 ) return s1.substring( 0, s1.length() -2 ).equals( s2.substring( 0, s1.length() - 2 ) );
      return s1.substring( 0, s1.length() - 1 ).equals( s2.substring( 0, s1.length() -1 ) );
      `
    },
    {
      name: 'isDeprecated',
      type: 'Boolean',
      args: [
        { name: 'x', type: 'Context' }
      ],
      documentation: 'check if a given capability is deprecated',
      javaCode: `
      Sink count = new Count();
      count = this.getDeprecating(x).getJunctionDAO()
        .where(
          EQ(Capability.ID, (String) this.getId())
        ).select(count);

      return ((Count) count).getValue() > 0;
      `
    },
    {
      name: 'isExpired',
      type: 'Boolean',
      documentation: `check if a given capability is expired.`,
      javaCode: `
      if ( getExpiry() == null ) return false;

      Date today = new Date();
      Date capabilityExpiry = getExpiry();

      return today.after(capabilityExpiry);
      `
    },
    {
      name: 'getPrereqsChainedStatus',
      type: 'CapabilityJunctionStatus',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'ucj', type: 'UserCapabilityJunction' }
      ],
      documentation: `
        Check statuses of all prerequisite capabilities - returning:
        GRANTED        : If all pre-reqs are in granted status
        PENDING        : If at least one pre-req is in pending/approved status and the rest are granted
        ACTION_REQUIRED: If at least one pre-req is not in pending/approved/granted status
      `,
      javaCode: `
        // CrunchService used to get capability junctions
        CrunchService crunchService = (CrunchService) x.get("crunchService");

        boolean allGranted = true;
        DAO capabilityDAO = (DAO) x.get("capabilityDAO");
        DAO userCapabilityJunctionDAO = (DAO) x.get("userCapabilityJunctionDAO");
        DAO userDAO = (DAO) x.get("userDAO");
        Subject currentSubject = (Subject) x.get("subject");

        Subject subject = new Subject(x);
        if ( ucj instanceof AgentCapabilityJunction ) {
          subject.setUser((User) userDAO.find(ucj.getSourceId()));
          AgentCapabilityJunction acj = (AgentCapabilityJunction) ucj;
          subject.setUser((User) userDAO.find(acj.getEffectiveUser())); // "user"
        } else if ( ucj.getSourceId() == currentSubject.getUser().getId() ) {
          subject.setUser(currentSubject.getRealUser());
          subject.setUser(currentSubject.getUser());
        } else {
          subject.setUser((User) userDAO.find(ucj.getSourceId()));
        }

        var prereqs = crunchService.getPrereqs(getId());
        CapabilityJunctionStatus prereqChainedStatus = null;
        if ( prereqs != null ) {
          for ( var capId : prereqs ) {
            var cap = (Capability) capabilityDAO.find(capId);
            if ( cap == null || ! cap.getEnabled() ) continue;

            UserCapabilityJunction prereqUcj = crunchService.getJunctionForSubject(x, capId, subject);

            prereqChainedStatus = getPrereqChainedStatus(x, ucj, prereqUcj);
            if ( prereqChainedStatus == CapabilityJunctionStatus.ACTION_REQUIRED ) return CapabilityJunctionStatus.ACTION_REQUIRED;
            if ( prereqChainedStatus != CapabilityJunctionStatus.GRANTED ) allGranted = false;
          }
        }
        return allGranted ? CapabilityJunctionStatus.GRANTED : CapabilityJunctionStatus.PENDING;
      `,
    },
    {
      name: 'getPrereqChainedStatus',
      args: [
        { name: 'x', javaType: 'foam.core.X' },
        { name: 'ucj', javaType: 'foam.nanos.crunch.UserCapabilityJunction' },
        { name: 'prereq', javaType: 'foam.nanos.crunch.UserCapabilityJunction' }
      ],
      static: true,
      javaType: 'foam.nanos.crunch.CapabilityJunctionStatus',
      javaCode: `
        CapabilityJunctionStatus status = ucj.getStatus();

        boolean reviewRequired = getReviewRequired();
        CapabilityJunctionStatus prereqStatus = prereq.getStatus();

        switch ( (CapabilityJunctionStatus) prereqStatus ) {
          case AVAILABLE :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          case ACTION_REQUIRED :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          case PENDING :
            status = reviewRequired &&
              ( status == CapabilityJunctionStatus.APPROVED ||
                status == CapabilityJunctionStatus.GRANTED
              ) ?
                CapabilityJunctionStatus.APPROVED : CapabilityJunctionStatus.PENDING;
            break;
          case APPROVED :
            status = reviewRequired &&
              ( status == CapabilityJunctionStatus.APPROVED ||
                status == CapabilityJunctionStatus.GRANTED
              ) ?
                CapabilityJunctionStatus.APPROVED : CapabilityJunctionStatus.PENDING;
              break;
          case EXPIRED :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          case PENDING_REVIEW :
            status = CapabilityJunctionStatus.ACTION_REQUIRED;
            break;
          default :
            status = CapabilityJunctionStatus.GRANTED;
        }
        return status;

      `
    }
  ]
});

foam.RELATIONSHIP({
  package: 'foam.nanos.crunch',
  extends:'foam.nanos.crunch.Renewable',
  sourceModel: 'foam.nanos.auth.User',
  targetModel: 'foam.nanos.crunch.Capability',
  ids: [ 'id' ],
  cardinality: '*:*',
  forwardName: 'capabilities',
  inverseName: 'users',
  sourceProperty: {
    section: 'systemInformation',
    updateVisibility: 'RO'
  }
});

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'CRUNCHThemeRefinement',
  refines: 'foam.nanos.theme.Theme',

  properties: [
    {
      name: 'admissionCapability',
      class: 'String',
      // TODO: Why doesn't a Reference property work here?
      // class: 'Reference',
      of: 'foam.nanos.crunch.Capability',
      documentation: 'Specifies the top-level capability that must be granted before we admit a user to the system.'
    }
  ],
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'deprecated',
  inverseName: 'deprecating',
  junctionDAOKey: 'deprecatedCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'prerequisites',
  inverseName: 'dependents',
  junctionDAOKey: 'prerequisiteCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'Capability',

  imports: [
    'capabilityDAO',
    'prerequisiteCapabilityJunctionDAO'
  ],

  javaImports: [

    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'foam.dao.Sink',
    'foam.mlang.sink.Count',
    'java.util.List',
    'static foam.mlang.MLang.*'
  ],

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

  tableColumns: [
    'id',
    'description',
    'version',
    'enabled',
    'visible',
    'expiry',
    'daoKey'
  ],

  sections: [
    {
      name: '_defaultSection',
      title: 'Administrative'
    },
    {
      name: 'uiSettings',
      title: 'UI Settings',
      help: 'These properties are used to control how this capability appears in the GUI.'
    },
    {
      name: 'capabilityRelationships',
      title: 'Capability Relationships'
    }
  ],

  properties: [
    {
      name: 'id',
      class: 'String',
      updateMode: 'RO'
    },
    {
      name: 'icon',
      class: 'Image',
      documentation: `Path to capability icon`,
      section: 'uiSettings'
    },
    {
      name: 'description',
      class: 'String',
      documentation: `Description of capability`,
      section: 'uiSettings'
    },
    {
      name: 'notes',
      class: 'String',
      view: {
        class: 'foam.u2.tag.TextArea',
        rows: 12,
        cols: 120
      }
    },
    {
      name: 'version',
      class: 'String'
    },
    {
      name: 'enabled',
      class: 'Boolean',
      value: true,
      documentation: `Capability is ignored by system when enabled is false.
      user will lose permissions implied by this capability and upper level capabilities will ignore this prerequisite`
    },
    {
      name: 'visible',
      class: 'Boolean',
      documentation: `Hide sub-capabilities which aren't top-level and individually selectable. when true, capability is visible to the user`,
      section: 'uiSettings'
    },
    {
      name: 'expiry',
      class: 'DateTime',
      documentation: `Datetime of when capability is no longer valid`
    },
    {
      name: 'duration',
      class: 'Int',
      documentation: `To be used in the case where expiry is duration-based, represents the number of DAYS a junction is valid for before expiring.
      The UserCapabilityJunction object will have its expiry configured to a DateTime based on the lower value of the two, expiry and duration`
    },
    {
      name: 'of',
      class: 'Class',
      documentation: `Model used to store information required by this credential`
    },
    {
      name: 'permissionsGranted',
      class: 'StringArray',
      documentation: `List of permissions granted by this capability`
    },
    {
      name: 'daoKey',
      class: 'String'
    }
  ],

  methods: [
    {
      name: 'implies',
      type: 'Boolean',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'permission', type: 'String' }
      ],
      documentation: `Checks if a permission or capability string is implied by the current capability`,
      javaCode: `
        if ( ! this.getEnabled() ) return false;

        // check if permission is a capability string implied by this permission
        if ( this.stringImplies(this.getId(), permission) ) return true;

        String[] permissionsGranted = this.getPermissionsGranted();
        for ( String permissionName : permissionsGranted ) {
          if ( this.stringImplies(permissionName, permission) ) return true;
        }

        List<CapabilityCapabilityJunction> prereqs = ((ArraySink) this.getPrerequisites(x).getJunctionDAO().where(EQ(CapabilityCapabilityJunction.SOURCE_ID, (String) this.getId())).select(new ArraySink())).getArray();

        DAO capabilityDAO = (DAO) x.get("capabilityDAO");
        for ( CapabilityCapabilityJunction prereqJunction : prereqs ) {
          Capability capability = (Capability) capabilityDAO.find(prereqJunction.getTargetId());
          if ( capability.implies(x, permission) ) return true;
        }
        return false;
      `
    },
    {
      name: 'stringImplies',
      type: 'Boolean',
      args: [
        {name: 's1', type: 'String'},
        {name: 's2', type: 'String'}
      ],
      documentation: `check if s1 implies s2 where s1 and s2 are permission or capability strings`,
      javaCode: `
      if ( s1.equals(s2) ) return true;
      if ( s1.charAt( s1.length() - 1) != '*' || ( s1.length() - 2 > s2.length() ) ) return false;

      if ( s2.length() <= s1.length() - 2 ) return s1.substring( 0, s1.length() -2 ).equals( s2.substring( 0, s1.length() - 2 ) );
      else return s1.substring( 0, s1.length() - 1 ).equals( s2.substring( 0, s1.length() -1 ) );
      `
    },
    {
      name: 'isDeprecated',
      type: 'Boolean',
      args: [
        {name: 'x', type: 'Context'}
      ],
      documentation: 'check if a given capability is deprecated',
      javaCode: `
      Sink count = new Count();
      count = this.getDeprecating(x).getJunctionDAO()
        .where(
          EQ(Capability.ID, (String) this.getId())
        ).select(count);

      return ((Count) count).getValue() > 0;
      `
    },
  ]
});

foam.RELATIONSHIP({
  package: 'foam.nanos.crunch',
  sourceModel: 'foam.nanos.auth.User',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'capabilities',
  inverseName: 'users',
  sourceProperty: {
    section: 'administrative'
  }
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'deprecated',
  inverseName: 'deprecating',
  junctionDAOKey: 'deprecatedCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'prerequisites',
  inverseName: 'dependents',
  junctionDAOKey: 'prerequisiteCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});
<commit_msg>Move User.capabilites into its own section<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'Capability',

  imports: [
    'capabilityDAO',
    'prerequisiteCapabilityJunctionDAO'
  ],

  javaImports: [

    'foam.dao.ArraySink',
    'foam.dao.DAO',
    'foam.dao.Sink',
    'foam.mlang.sink.Count',
    'java.util.List',
    'static foam.mlang.MLang.*'
  ],

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

  tableColumns: [
    'id',
    'description',
    'version',
    'enabled',
    'visible',
    'expiry',
    'daoKey'
  ],

  sections: [
    {
      name: '_defaultSection',
      title: 'Administrative'
    },
    {
      name: 'uiSettings',
      title: 'UI Settings',
      help: 'These properties are used to control how this capability appears in the GUI.'
    },
    {
      name: 'capabilityRelationships',
      title: 'Capability Relationships'
    }
  ],

  properties: [
    {
      name: 'id',
      class: 'String',
      updateMode: 'RO'
    },
    {
      name: 'icon',
      class: 'Image',
      documentation: `Path to capability icon`,
      section: 'uiSettings'
    },
    {
      name: 'description',
      class: 'String',
      documentation: `Description of capability`,
      section: 'uiSettings'
    },
    {
      name: 'notes',
      class: 'String',
      view: {
        class: 'foam.u2.tag.TextArea',
        rows: 12,
        cols: 120
      }
    },
    {
      name: 'version',
      class: 'String'
    },
    {
      name: 'enabled',
      class: 'Boolean',
      value: true,
      documentation: `Capability is ignored by system when enabled is false.
      user will lose permissions implied by this capability and upper level capabilities will ignore this prerequisite`
    },
    {
      name: 'visible',
      class: 'Boolean',
      documentation: `Hide sub-capabilities which aren't top-level and individually selectable. when true, capability is visible to the user`,
      section: 'uiSettings'
    },
    {
      name: 'expiry',
      class: 'DateTime',
      documentation: `Datetime of when capability is no longer valid`
    },
    {
      name: 'duration',
      class: 'Int',
      documentation: `To be used in the case where expiry is duration-based, represents the number of DAYS a junction is valid for before expiring.
      The UserCapabilityJunction object will have its expiry configured to a DateTime based on the lower value of the two, expiry and duration`
    },
    {
      name: 'of',
      class: 'Class',
      documentation: `Model used to store information required by this credential`
    },
    {
      name: 'permissionsGranted',
      class: 'StringArray',
      documentation: `List of permissions granted by this capability`
    },
    {
      name: 'daoKey',
      class: 'String'
    }
  ],

  methods: [
    {
      name: 'implies',
      type: 'Boolean',
      args: [
        { name: 'x', type: 'Context' },
        { name: 'permission', type: 'String' }
      ],
      documentation: `Checks if a permission or capability string is implied by the current capability`,
      javaCode: `
        if ( ! this.getEnabled() ) return false;

        // check if permission is a capability string implied by this permission
        if ( this.stringImplies(this.getId(), permission) ) return true;

        String[] permissionsGranted = this.getPermissionsGranted();
        for ( String permissionName : permissionsGranted ) {
          if ( this.stringImplies(permissionName, permission) ) return true;
        }

        List<CapabilityCapabilityJunction> prereqs = ((ArraySink) this.getPrerequisites(x).getJunctionDAO().where(EQ(CapabilityCapabilityJunction.SOURCE_ID, (String) this.getId())).select(new ArraySink())).getArray();

        DAO capabilityDAO = (DAO) x.get("capabilityDAO");
        for ( CapabilityCapabilityJunction prereqJunction : prereqs ) {
          Capability capability = (Capability) capabilityDAO.find(prereqJunction.getTargetId());
          if ( capability.implies(x, permission) ) return true;
        }
        return false;
      `
    },
    {
      name: 'stringImplies',
      type: 'Boolean',
      args: [
        {name: 's1', type: 'String'},
        {name: 's2', type: 'String'}
      ],
      documentation: `check if s1 implies s2 where s1 and s2 are permission or capability strings`,
      javaCode: `
      if ( s1.equals(s2) ) return true;
      if ( s1.charAt( s1.length() - 1) != '*' || ( s1.length() - 2 > s2.length() ) ) return false;

      if ( s2.length() <= s1.length() - 2 ) return s1.substring( 0, s1.length() -2 ).equals( s2.substring( 0, s1.length() - 2 ) );
      else return s1.substring( 0, s1.length() - 1 ).equals( s2.substring( 0, s1.length() -1 ) );
      `
    },
    {
      name: 'isDeprecated',
      type: 'Boolean',
      args: [
        {name: 'x', type: 'Context'}
      ],
      documentation: 'check if a given capability is deprecated',
      javaCode: `
      Sink count = new Count();
      count = this.getDeprecating(x).getJunctionDAO()
        .where(
          EQ(Capability.ID, (String) this.getId())
        ).select(count);

      return ((Count) count).getValue() > 0;
      `
    },
  ]
});

foam.RELATIONSHIP({
  package: 'foam.nanos.crunch',
  sourceModel: 'foam.nanos.auth.User',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'capabilities',
  inverseName: 'users',
  sourceProperty: {
    section: 'capabilities',
    updateMode: 'HIDDEN'
  }
});

foam.CLASS({
  package: 'foam.nanos.crunch',
  name: 'CRUNCHUserRefinement',
  refines: 'foam.nanos.auth.User',
  sections: [{ name: 'capabilities' }]
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'deprecated',
  inverseName: 'deprecating',
  junctionDAOKey: 'deprecatedCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});

foam.RELATIONSHIP({
  sourceModel: 'foam.nanos.crunch.Capability',
  targetModel: 'foam.nanos.crunch.Capability',
  cardinality: '*:*',
  forwardName: 'prerequisites',
  inverseName: 'dependents',
  junctionDAOKey: 'prerequisiteCapabilityJunctionDAO',
  sourceProperty: {
    section: 'capabilityRelationships'
  },
  targetProperty: {
    section: 'capabilityRelationships'
  }
});
<|endoftext|>


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

foam.CLASS({
  package: 'foam.u2.view',
  name: 'EditColumnsView',
  extends: 'foam.u2.Element',

  requires: [
      'foam.u2.CheckBox'
  ],

  properties: [
      {
        name: 'columns'
      },
      {
        name: 'table'
      },
      {
        name: 'columns_'
      },
      {
        name: 'selected'
      },
      {
        class: 'Boolean',
        name: 'displaySorted',
        value: false
      }
  ],

  methods: [
    function initE() {
      // if ( this.displaySorted ) {
      //   // TODO: How should this block be tested?
      //   var props = this.properties;
      //   props = this.properties.slice();
      //   props.sort(function(a, b) {
      //     return a.label.toLowerCase().compareTo(b.label.toLowerCase());
      //   });
      // } else { ...

      this.selected = []

      for (let i = 0; i < this.columns_.length; i++) {
        var cb = this.CheckBox.create({
          label: this.columns_[i].label,
          data: true
        });

        this.selected.push(cb.data$);
        var name = this.columns_[i].name;

        // Subscribes updateTable listener to checkbox data
        cb.data$.sub(this.updateTable.bind(this, name));

        this.add(cb);

        // Ensures each selection is on a new line
        if ( i != this.columns_.length - 1 ) this.start('br').end();
      }
    }
  ],

  listeners: [
    function updateTable(changedProp) {
      var cols = [];

      // if ( this.displaySorted ) {
      //   // TODO: How should this block be tested?
      //   out = this.selectedProperties.slice();
      //   if ( nu && !selected[changedProp.name] ) {
      //     out.push(changedProp);
      //   }
      //   if ( !nu && selected[changedProp.name] ) {
      //     out.splice(out.indexOf(changedProp), 1);
      //   }
      // }
      // else { ...

      for (var i = 0; i < this.columns.length; i++) {
        var cbData = this.selected[i].obj.data;
        var isColShown = this.columns_.some(c => c.name === this.columns[i]);
        var curProp = this.columns[i];

        // Determines if the curProp is the one which has changed,
        // if so adds col if cb is checked. Otherwise if curProp hasn't
        // changed, then checks if it was previously shown, if so, keeps in view.
        if ( ((changedProp == curProp) && cbData) ||
             ((changedProp != curProp) && isColShown) ) {
          // Gets the table column from the column name, and pushes to cols array
          cols.push(this.table.getAxiomByName(curProp))
        }
      }

      this.columns_ = cols;
    }
  ]
});
<commit_msg>Updated `for` spacing<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.u2.view',
  name: 'EditColumnsView',
  extends: 'foam.u2.Element',

  requires: [
      'foam.u2.CheckBox'
  ],

  properties: [
      {
        name: 'columns'
      },
      {
        name: 'table'
      },
      {
        name: 'columns_'
      },
      {
        name: 'selected'
      },
      {
        class: 'Boolean',
        name: 'displaySorted',
        value: false
      }
  ],

  methods: [
    function initE() {
      // if ( this.displaySorted ) {
      //   // TODO: How should this block be tested?
      //   var props = this.properties;
      //   props = this.properties.slice();
      //   props.sort(function(a, b) {
      //     return a.label.toLowerCase().compareTo(b.label.toLowerCase());
      //   });
      // } else { ...

      this.selected = []

      for ( var i = 0; i < this.columns_.length; i++ ) {
        var cb = this.CheckBox.create({
          label: this.columns_[i].label,
          data: true
        });

        this.selected.push(cb.data$);
        var name = this.columns_[i].name;

        // Subscribes updateTable listener to checkbox data
        cb.data$.sub(this.updateTable.bind(this, name));

        this.add(cb);

        // Ensures each selection is on a new line
        if ( i != this.columns_.length - 1 ) this.start('br').end();
      }
    }
  ],

  listeners: [
    function updateTable(changedProp) {
      var cols = [];

      // if ( this.displaySorted ) {
      //   // TODO: How should this block be tested?
      //   out = this.selectedProperties.slice();
      //   if ( nu && !selected[changedProp.name] ) {
      //     out.push(changedProp);
      //   }
      //   if ( !nu && selected[changedProp.name] ) {
      //     out.splice(out.indexOf(changedProp), 1);
      //   }
      // }
      // else { ...

      for ( var i = 0; i < this.columns.length; i++ ) {
        var cbData = this.selected[i].obj.data;
        var isColShown = this.columns_.some(c => c.name === this.columns[i]);
        var curProp = this.columns[i];

        // Determines if the curProp is the one which has changed,
        // if so adds col if cb is checked. Otherwise if curProp hasn't
        // changed, then checks if it was previously shown, if so, keeps in view.
        if ( ((changedProp == curProp) && cbData) ||
             ((changedProp != curProp) && isColShown) ) {
          // Gets the table column from the column name, and pushes to cols array
          cols.push(this.table.getAxiomByName(curProp))
        }
      }

      this.columns_ = cols;
    }
  ]
});
<|endoftext|>


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

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  css: `
    ^ {
      overflow: scroll;
    }
    ^table {
      position: relative;
    }
    ^table  .foam-u2-view-TableView-thead {
      z-index: 1;
      overflow: visible;
    }
  `,

  constants: [
    {
      type: 'Float',
      name: 'MIN_TOP_PAGE_PROGRESS',
      documentation: `
        If the "top" page isn't scrolled by at least this amount, make
        the top page the previous page.
        i.e. If the page that's currently in view is only scrolled 10% of the way,
        we consider the page that's above it to be the "top" page. If the page
        that's on screen is scrolled 51% of the way, then it is considered the top
        page.
      `,
      value: 0.5
    },
    {
      type: 'Integer',
      name: 'NUM_PAGES_TO_RENDER',
      value: 3
    },
    {
      type: 'Integer',
      name: 'TABLE_HEAD_HEIGHT',
      value: 51
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    'columns',
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions'
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'selection',
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      documentation: `
        Set to true if users should be allowed to choose which columns to use.
      `,
      value: true
    },
    {
      class: 'Int',
      name: 'rowHeight',
      documentation: 'The height of one row of the table in px.',
      value: 49
    },
    {
      name: 'table_',
      documentation: `
        A reference to the table element we use in various calculations.
      `
    },
    {
      type: 'Int',
      name: 'scrollHeight',
      expression: function(daoCount, rowHeight) {
        return rowHeight * daoCount + this.TABLE_HEAD_HEIGHT;
      }
    },
    {
      type: 'Int',
      name: 'pageSize',
      value: 30,
      documentation: 'The number of items in each "page". There are three pages.'
    },
    {
      type: 'Boolean',
      name: 'enableDynamicTableHeight',
      value: true,
    },
    {
      class: 'Boolean',
      name: 'multiSelectEnabled',
      documentation: 'Pass through to UnstyledTableView.'
    },
    {
      class: 'Map',
      name: 'selectedObjects',
      documentation: `
        The objects selected by the user when multi-select support is enabled.
        It's a map where the key is the object id and the value is the object.
        Here we simply bind it to the selectedObjects property on TableView.
      `
    },
    {
      class: 'Int',
      name: 'scrollPos_'
    },
    {
      class: 'Int',
      name: 'numPages_',
      expression: function(daoCount, pageSize) {
        return Math.ceil(daoCount / pageSize);
      }
    },
    {
      class: 'Int',
      name: 'currentTopPage_',
      expression: function(numPages_, scrollPos_, scrollHeight) {
        var scrollPercent = scrollPos_ / scrollHeight;
        var topPage = Math.floor(scrollPercent * numPages_);
        var topPageProgress = (scrollPercent * numPages_) % 1;
        if ( topPageProgress < this.MIN_TOP_PAGE_PROGRESS ) topPage = topPage - 1;
        return Math.min(Math.max(0, numPages_ - this.NUM_PAGES_TO_RENDER), Math.max(0, topPage));
      }
    },
    {
      class: 'Map',
      name: 'renderedPages_'
    },
  ],

  reactions: [
    ['', 'propertyChange.currentTopPage_', 'updateRenderedPages_'],
    ['', 'propertyChange.table_', 'updateRenderedPages_'],
    ['', 'propertyChange.daoCount', 'refresh'],
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({ fn: this.updateCount })));
      this.updateCount();
    },

    function initE() {
      this.
        addClass(this.myClass()).
        on('scroll', this.onScroll).
        start(this.TableView, {
          data: foam.dao.NullDAO.create({of: this.data.of}),
          columns: this.columns,
          contextMenuActions: this.contextMenuActions,
          selection$: this.selection$,
          editColumnsEnabled: this.editColumnsEnabled,
          multiSelectEnabled: this.multiSelectEnabled,
          selectedObjects$: this.selectedObjects$
        }, this.table_$).
          addClass(this.myClass('table')).
          style({
            height: this.scrollHeight$.map(h => h + 'px')
          }).
        end();

      /*
        to be used in cases where we don't want the whole table to
        take the whole page (i.e. we need multiple tables)
        and enableDynamicTableHeight can be switched off
      */
      if (this.enableDynamicTableHeight) {
        this.onDetach(this.onload.sub(this.updateTableHeight));
        window.addEventListener('resize', this.updateTableHeight);
        this.onDetach(() => {
          window.removeEventListener('resize', this.updateTableHeight);
        });
      }
    }
  ],

  listeners: [
    {
      name: 'refresh',
      isFramed: true,
      code: function() {
        Object.keys(this.renderedPages_).forEach(i => {
          this.renderedPages_[i].remove();
          delete this.renderedPages_[i];
        });
        this.updateRenderedPages_();
        if ( this.el() ) this.el().scrollTop = 0;
      }
    },
    {
      name: 'updateCount',
      isFramed: true,
      code: function() {
        return this.data$proxy.select(this.Count.create()).then((s) => {
          this.daoCount = s.value;
        });
      }
    },
    {
      name: 'updateRenderedPages_',
      isFramed: true,
      code: function() {
        if ( ! this.table_ ) return;
        Object.keys(this.renderedPages_).forEach(i => {
          if ( i >= this.currentTopPage_ && i <= this.currentTopPage_ + 2 ) return;
          this.renderedPages_[i].remove();
          delete this.renderedPages_[i];
        });

        for ( var i = 0; i < Math.min(this.numPages_, this.NUM_PAGES_TO_RENDER) ; i++) {
          var page = this.currentTopPage_ + i;
          if ( this.renderedPages_[page] ) continue;
          var dao = this.data$proxy.limit(this.pageSize).skip(page * this.pageSize);
          var tbody = this.table_.slotE_(this.table_.rowsFrom(dao));
          tbody.style({
            position: 'absolute',
            width: '100%',
            top: this.TABLE_HEAD_HEIGHT + page * this.pageSize * this.rowHeight + 'px'
          });
          this.table_.add(tbody);
          this.renderedPages_[page] = tbody;
        }

      }
    },
    {
      name: 'onScroll',
      isFramed: true,
      code: function(e) {
        this.scrollPos_ = e.target.scrollTop;
      }
    },
    {
      name: 'updateTableHeight',
      code: function() {
        // Find the distance from the top of the table to the top of the screen.
        var distanceFromTop = this.el().getBoundingClientRect().y;

        // Calculate the remaining space we have to make use of.
        var remainingSpace = window.innerHeight - distanceFromTop;

        // TODO: Do we want to do this?
        // Leave space for the footer.
        remainingSpace -= 44;

        this.style({ height: `${remainingSpace}px` });
      }
    }
  ]
});<commit_msg>Documentation<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  css: `
    ^ {
      overflow: scroll;
    }
    ^table {
      position: relative;
    }
    ^table  .foam-u2-view-TableView-thead {
      z-index: 1;
      overflow: visible;
    }
  `,

  constants: [
    {
      type: 'Float',
      name: 'MIN_TOP_PAGE_PROGRESS',
      documentation: `
        If the "top" page isn't scrolled by at least this amount, make
        the top page the previous page.
        i.e. If the page that's currently in view is only scrolled 10% of the way,
        we consider the page that's above it to be the "top" page. If the page
        that's on screen is scrolled 51% of the way, then it is considered the top
        page.
      `,
      value: 0.5
    },
    {
      type: 'Integer',
      name: 'NUM_PAGES_TO_RENDER',
      value: 3
    },
    {
      type: 'Integer',
      name: 'TABLE_HEAD_HEIGHT',
      value: 51
    }
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    'columns',
    {
      class: 'FObjectArray',
      of: 'foam.core.Action',
      name: 'contextMenuActions'
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'selection',
    {
      class: 'Boolean',
      name: 'editColumnsEnabled',
      documentation: `
        Set to true if users should be allowed to choose which columns to use.
      `,
      value: true
    },
    {
      class: 'Int',
      name: 'rowHeight',
      documentation: 'The height of one row of the table in px.',
      value: 49
    },
    {
      name: 'table_',
      documentation: `
        A reference to the table element we use in various calculations.
      `
    },
    {
      type: 'Int',
      name: 'scrollHeight',
      expression: function(daoCount, rowHeight) {
        return rowHeight * daoCount + this.TABLE_HEAD_HEIGHT;
      }
    },
    {
      type: 'Int',
      name: 'pageSize',
      value: 30,
      documentation: 'The number of items in each "page". There are three pages.'
    },
    {
      type: 'Boolean',
      name: 'enableDynamicTableHeight',
      value: true,
    },
    {
      class: 'Boolean',
      name: 'multiSelectEnabled',
      documentation: 'Pass through to UnstyledTableView.'
    },
    {
      class: 'Map',
      name: 'selectedObjects',
      documentation: `
        The objects selected by the user when multi-select support is enabled.
        It's a map where the key is the object id and the value is the object.
        Here we simply bind it to the selectedObjects property on TableView.
      `
    },
    {
      class: 'Int',
      name: 'scrollPos_'
    },
    {
      class: 'Int',
      name: 'numPages_',
      expression: function(daoCount, pageSize) {
        return Math.ceil(daoCount / pageSize);
      }
    },
    {
      class: 'Int',
      name: 'currentTopPage_',
      expression: function(numPages_, scrollPos_, scrollHeight) {
        var scrollPercent = scrollPos_ / scrollHeight;
        var topPage = Math.floor(scrollPercent * numPages_);
        var topPageProgress = (scrollPercent * numPages_) % 1;
        if ( topPageProgress < this.MIN_TOP_PAGE_PROGRESS ) topPage = topPage - 1;
        return Math.min(Math.max(0, numPages_ - this.NUM_PAGES_TO_RENDER), Math.max(0, topPage));
      }
    },
    {
      class: 'Map',
      name: 'renderedPages_'
    },
  ],

  reactions: [
    ['', 'propertyChange.currentTopPage_', 'updateRenderedPages_'],
    ['', 'propertyChange.table_', 'updateRenderedPages_'],
    ['', 'propertyChange.daoCount', 'refresh'],
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({ fn: this.updateCount })));
      this.updateCount();
    },

    function initE() {
      this.
        addClass(this.myClass()).
        on('scroll', this.onScroll).
        start(this.TableView, {
          data: foam.dao.NullDAO.create({of: this.data.of}),
          columns: this.columns,
          contextMenuActions: this.contextMenuActions,
          selection$: this.selection$,
          editColumnsEnabled: this.editColumnsEnabled,
          multiSelectEnabled: this.multiSelectEnabled,
          selectedObjects$: this.selectedObjects$
        }, this.table_$).
          addClass(this.myClass('table')).
          style({
            height: this.scrollHeight$.map(h => h + 'px')
          }).
        end();

      /*
        to be used in cases where we don't want the whole table to
        take the whole page (i.e. we need multiple tables)
        and enableDynamicTableHeight can be switched off
      */
      if (this.enableDynamicTableHeight) {
        this.onDetach(this.onload.sub(this.updateTableHeight));
        window.addEventListener('resize', this.updateTableHeight);
        this.onDetach(() => {
          window.removeEventListener('resize', this.updateTableHeight);
        });
      }
    }
  ],

  listeners: [
    {
      name: 'refresh',
      isFramed: true,
      code: function() {
        Object.keys(this.renderedPages_).forEach(i => {
          this.renderedPages_[i].remove();
          delete this.renderedPages_[i];
        });
        this.updateRenderedPages_();
        if ( this.el() ) this.el().scrollTop = 0;
      }
    },
    {
      name: 'updateCount',
      isFramed: true,
      code: function() {
        return this.data$proxy.select(this.Count.create()).then((s) => {
          this.daoCount = s.value;
        });
      }
    },
    {
      name: 'updateRenderedPages_',
      isFramed: true,
      code: function() {
        if ( ! this.table_ ) return;

        // Remove any pages that are no longer on screen to save on
        // the amount of DOM we add to the page.
        Object.keys(this.renderedPages_).forEach(i => {
          if ( i >= this.currentTopPage_ && i < this.currentTopPage_ + this.NUM_PAGES_TO_RENDER ) return;
          this.renderedPages_[i].remove();
          delete this.renderedPages_[i];
        });

        // Add any pages that are not already rendered.
        for ( var i = 0; i < Math.min(this.numPages_, this.NUM_PAGES_TO_RENDER) ; i++) {
          var page = this.currentTopPage_ + i;
          if ( this.renderedPages_[page] ) continue;
          var dao = this.data$proxy.limit(this.pageSize).skip(page * this.pageSize);
          var tbody = this.table_.slotE_(this.table_.rowsFrom(dao));
          tbody.style({
            position: 'absolute',
            width: '100%',
            top: this.TABLE_HEAD_HEIGHT + page * this.pageSize * this.rowHeight + 'px'
          });
          this.table_.add(tbody);
          this.renderedPages_[page] = tbody;
        }

      }
    },
    {
      name: 'onScroll',
      isFramed: true,
      code: function(e) {
        this.scrollPos_ = e.target.scrollTop;
      }
    },
    {
      name: 'updateTableHeight',
      code: function() {
        // Find the distance from the top of the table to the top of the screen.
        var distanceFromTop = this.el().getBoundingClientRect().y;

        // Calculate the remaining space we have to make use of.
        var remainingSpace = window.innerHeight - distanceFromTop;

        // TODO: Do we want to do this?
        // Leave space for the footer.
        remainingSpace -= 44;

        this.style({ height: `${remainingSpace}px` });
      }
    }
  ]
});<|endoftext|>


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

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.graphics.ScrollCView',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'Int',
      name: 'limit',
      value: 18,
      // TODO make this a funciton of the height.
    },
    {
      class: 'Int',
      name: 'skip',
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'scrolledDao',
      expression: function(data, limit, skip) {
        return data.limit(limit).skip(skip);
      },
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
    'mouseEvent'
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({fn:this.onDaoUpdate})));
      this.onDaoUpdate();
    },

    function initE() {
      // TODO probably shouldn't be using a table.
      this.start('table').
        on('wheel', this.onWheel).
        start('tr').
          start('td').
            style({ 'vertical-align': 'top' }).
            start(this.TableView, {data$: this.scrolledDao$}).
            end().
          end().
          start('td').style({ 'vertical-align': 'top' }).
            add(this.ScrollCView.create({
              value$: this.skip$,
              extent$: this.limit$,
              height: 40*18+41, // TODO use window height.
              width: 22,
              size$: this.daoCount$,
              mouseEvent$: this.mouseEvent$
              // TODO clicking away from scroller should deselect it.
            })).
            on('mousedown', this.scrollBar).
          end().
        end().
      end();
    }
  ],

  listeners: [
    {
      name: 'onWheel',
      code: function(e) {
        var negative = e.deltaY < 0;
        // Convert to rows, rounding up. (Therefore minumum 1.)
        var rows = Math.ceil(Math.abs(e.deltaY) / /*self.rowHeight*/ 40);
        this.skip += negative ? -rows : rows;
        e.preventDefault();
      }
    },
    {
      // TODO Avoid onDaoUpdate approaches.
      name: 'onDaoUpdate',
      isFramed: true,
      code: function() {
        var self = this;
        this.data$proxy.select(this.Count.create()).then(function(s) {
          self.daoCount = s.value;
        })
      },
    },
    {
      name: 'scrollBar',
      code: function(e) {
        var self = this;
        function onMouseMove(e) {
          // update mouseEvent with DOM Mouse Event
          self.mouseEvent = e;
        }
        function onMouseUp(e) {
          self.mouseEvent = e;
          // remove event listeners when the user releases the mouse click
          global.window.removeEventListener('mousemove', onMouseMove);
          global.window.removeEventListener('mouseup', onMouseUp);
        }
        // add event listeners to the scrollBar when the user holds down the mouse
        global.window.addEventListener('mousemove', onMouseMove);
        global.window.addEventListener('mouseup', onMouseUp);
      }
    }
  ]
});
<commit_msg>Remove some code that's no longer necessary in ScrollTableView.<commit_after>/**
 * @license
 * Copyright 2017 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

 foam.CLASS({
  package: 'foam.u2.view',
  name: 'ScrollTableView',
  extends: 'foam.u2.Element',

  requires: [
    'foam.dao.FnSink',
    'foam.graphics.ScrollCView',
    'foam.mlang.sink.Count',
    'foam.u2.view.TableView'
  ],

  properties: [
    {
      class: 'foam.dao.DAOProperty',
      name: 'data'
    },
    {
      class: 'Int',
      name: 'limit',
      value: 18,
      // TODO make this a funciton of the height.
    },
    {
      class: 'Int',
      name: 'skip',
    },
    {
      class: 'foam.dao.DAOProperty',
      name: 'scrolledDao',
      expression: function(data, limit, skip) {
        return data.limit(limit).skip(skip);
      },
    },
    {
      class: 'Int',
      name: 'daoCount'
    },
  ],

  methods: [
    function init() {
      this.onDetach(this.data$proxy.listen(this.FnSink.create({fn:this.onDaoUpdate})));
      this.onDaoUpdate();
    },

    function initE() {
      // TODO probably shouldn't be using a table.
      this.start('table').
        on('wheel', this.onWheel).
        start('tr').
          start('td').
            style({ 'vertical-align': 'top' }).
            start(this.TableView, {data$: this.scrolledDao$}).
            end().
          end().
          start('td').style({ 'vertical-align': 'top' }).
            add(this.ScrollCView.create({
              value$: this.skip$,
              extent$: this.limit$,
              height: 40*18+41, // TODO use window height.
              width: 22,
              size$: this.daoCount$,
            })).
          end().
        end().
      end();
    }
  ],

  listeners: [
    {
      name: 'onWheel',
      code: function(e) {
        var negative = e.deltaY < 0;
        // Convert to rows, rounding up. (Therefore minumum 1.)
        var rows = Math.ceil(Math.abs(e.deltaY) / /*self.rowHeight*/ 40);
        this.skip += negative ? -rows : rows;
        e.preventDefault();
      }
    },
    {
      // TODO Avoid onDaoUpdate approaches.
      name: 'onDaoUpdate',
      isFramed: true,
      code: function() {
        var self = this;
        this.data$proxy.select(this.Count.create()).then(function(s) {
          self.daoCount = s.value;
        })
      },
    },
  ]
});
<|endoftext|>


<commit_before>/*
 * (c) Copyright Ascensio System SIA 2010-2020
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

"use strict";
/**
 * User: Ilja.Kirillov
 * Date: 02.04.2020
 * Time: 15:50
 */

/**
 * Базовый класс для контент контролов
 * @constructor
 */
function CSdtBase()
{}

/**
 * Получаем текст плейсхолдера
 * @returns {string}
 */
CSdtBase.prototype.GetPlaceholderText = function()
{
	var oDocPart = this.GetLogicDocument().GetGlossaryDocument().GetDocPartByName(this.GetPlaceholder());
	if (oDocPart)
	{
		var oFirstParagraph = oDocPart.GetFirstParagraph();
		return oFirstParagraph.GetText({ParaEndToSpace : false});
	}

	return String.fromCharCode(nbsp_charcode, nbsp_charcode, nbsp_charcode, nbsp_charcode);
};
/**
 * Выставляем простой плейсхолдера в виде текста
 * @param {string} sText
 */
CSdtBase.prototype.SetPlaceholderText = function(sText)
{
	var oLogicDocument = this.GetLogicDocument();
	var oGlossary      = oLogicDocument.GetGlossaryDocument();

	var oDocPart = oGlossary.GetDocPartByName(this.GetPlaceholder());
	if (!oDocPart || oGlossary.IsDefaultDocPart(oDocPart))
	{
		var oNewDocPart;
		if (!oDocPart)
			oNewDocPart = oGlossary.CreateDocPart(oGlossary.GetNewName());
		else
			oNewDocPart = oDocPart.Copy(oGlossary.GetNewName());

		this.SetPlaceholder(oNewDocPart.GetDocPartName());
		oDocPart = oNewDocPart;
	}

	oDocPart.RemoveSelection();
	oDocPart.MoveCursorToStartPos();

	var oTextPr = oDocPart.GetDirectTextPr();
	var oParaPr = oDocPart.GetDirectParaPr();

	oDocPart.ClearContent(true);
	if (this.IsForm())
		oDocPart.MakeSingleParagraphContent();

	oDocPart.SelectAll();

	var oParagraph = oDocPart.GetFirstParagraph();
	oParagraph.CorrectContent();

	var oRun = null;
	if (this.IsForm())
		oRun = oParagraph.MakeSingleRunParagraph();

	oParagraph.SetDirectParaPr(oParaPr);
	oParagraph.SetDirectTextPr(oTextPr);

	if (oRun)
		oRun.AddText(sText);
	else
		oDocPart.AddText(sText);

	oDocPart.RemoveSelection();

	var isPlaceHolder = this.IsPlaceHolder();
	if (isPlaceHolder && this.IsPicture())
		this.private_UpdatePictureContent();
	else
		this.private_FillPlaceholderContent();

	return oDocPart;
};
/**
 * Выставляем параметр, что данный контрол должен быть простым текстовым
 * @param {boolean} isText
 */
CSdtBase.prototype.SetContentControlText = function(isText)
{
	if (this.Pr.Text !== isText)
	{
		History.Add(new CChangesSdtPrText(this, this.Pr.Text, isText));
		this.Pr.Text = isText;
	}
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsContentControlText = function()
{
	return this.Pr.Text;
};
/**
 * Выставляем параметр, что данный контрол специальный для вставки формул
 * @param {boolean} isEquation
 */
CSdtBase.prototype.SetContentControlEquation = function(isEquation)
{
	if (this.Pr.Equation !== isEquation)
	{
		History.Add(new CChangesSdtPrEquation(this, this.Pr.Equation, isEquation));
		this.Pr.Equation = isEquation;
	}
};
/**
 * Применяем настройку, что данный контрол будет специальным для формул
 * @constructor
 */
CSdtBase.prototype.ApplyContentControlEquationPr = function()
{
	var oTextPr = new CTextPr();
	oTextPr.SetItalic(true);
	oTextPr.SetFontFamily("Cambria Math");

	this.SetDefaultTextPr(oTextPr);
	this.SetContentControlEquation(true);
	this.SetContentControlTemporary(true);

	if (!this.IsContentControlEquation())
		return;

	this.SetPlaceholder(c_oAscDefaultPlaceholderName.Equation);
	if (this.IsPlaceHolder())
		this.private_FillPlaceholderContent();
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsContentControlEquation = function()
{
	return this.Pr.Equation;
};
/**
 * Выставляем параметр, что данный контрол при редактировании должен быть удален
 * @param {boolean} isTemporary
 */
CSdtBase.prototype.SetContentControlTemporary = function(isTemporary)
{
	if (this.Pr.Temporary !== isTemporary)
	{
		History.Add(new CChangesSdtPrTemporary(this, this.Pr.Temporary, isTemporary));
		this.Pr.Temporary = isTemporary;
	}
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsContentControlTemporary = function()
{
	return this.Pr.Temporary;
};
/**
 * @param {CSdtFormPr} oFormPr
 */
CSdtBase.prototype.SetFormPr = function(oFormPr)
{
	if ((!this.Pr.FormPr && oFormPr) || !this.Pr.FormPr.IsEqual(oFormPr))
	{
		History.Add(new CChangesSdtPrFormPr(this, this.Pr.FormPr, oFormPr));
		this.Pr.FormPr = oFormPr;

		var oLogicDocument = this.GetLogicDocument();
		if (oLogicDocument)
			oLogicDocument.RegisterForm(this);

		this.private_OnAddFormPr();
	}
}
/**
 * Удаляем настройки специальных форм
 */
CSdtBase.prototype.RemoveFormPr = function()
{
	if (this.Pr.FormPr)
	{
		History.Add(new CChangesSdtPrFormPr(this, this.Pr.FormPr, undefined));
		this.Pr.FormPr = undefined;

		var oLogicDocument = this.GetLogicDocument();
		if (oLogicDocument)
			oLogicDocument.UnregisterForm(this);

		this.private_OnAddFormPr();
	}
};
/**
 * @returns {?CSdtFormPr}
 */
CSdtBase.prototype.GetFormPr = function()
{
	return this.Pr.FormPr;
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsForm = function()
{
	return (undefined !== this.Pr.FormPr);
};
/**
 * Получаем ключ для специальной формы, если он задан
 * @returns {?string}
 */
CSdtBase.prototype.GetFormKey = function()
{
	if (!this.IsForm())
		return undefined;

	return (this.Pr.FormPr.Key);
};
/**
 * Проверяем, является ли заданный контрол радио-кнопкой
 * @returns {boolean}
 */
CSdtBase.prototype.IsRadioButton = function()
{
	return !!(this.IsCheckBox() && this.Pr.CheckBox && this.Pr.CheckBox.GroupKey);
};
/**
 * Является ли данный контейнер специальной текстовой формой
 * @returns {boolean}
 */
CSdtBase.prototype.IsTextForm = function()
{
	return false;
};
/**
 * Получаем ключ для группы радио-кнопок
 * @returns {?string}
 */
CSdtBase.prototype.GetRadioButtonGroupKey = function()
{
	if (!this.IsRadioButton())
		return undefined;

	return (this.Pr.CheckBox.GroupKey);
};
/**
 * Для чекбоксов и радио-кнопок получаем состояние
 * @returns {bool}
 */
CSdtBase.prototype.IsCheckBoxChecked = function()
{
	if (this.IsCheckBox())
		return this.Pr.CheckBox.Checked;

	return false;
};

<commit_msg>Fix issue with settings placeholder text<commit_after>/*
 * (c) Copyright Ascensio System SIA 2010-2020
 *
 * This program is a free software product. You can redistribute it and/or
 * modify it under the terms of the GNU Affero General Public License (AGPL)
 * version 3 as published by the Free Software Foundation. In accordance with
 * Section 7(a) of the GNU AGPL its Section 15 shall be amended to the effect
 * that Ascensio System SIA expressly excludes the warranty of non-infringement
 * of any third-party rights.
 *
 * This program is distributed WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR  PURPOSE. For
 * details, see the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
 *
 * You can contact Ascensio System SIA at 20A-12 Ernesta Birznieka-Upisha
 * street, Riga, Latvia, EU, LV-1050.
 *
 * The  interactive user interfaces in modified source and object code versions
 * of the Program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU AGPL version 3.
 *
 * Pursuant to Section 7(b) of the License you must retain the original Product
 * logo when distributing the program. Pursuant to Section 7(e) we decline to
 * grant you any rights under trademark law for use of our trademarks.
 *
 * All the Product's GUI elements, including illustrations and icon sets, as
 * well as technical writing content are licensed under the terms of the
 * Creative Commons Attribution-ShareAlike 4.0 International. See the License
 * terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
 *
 */

"use strict";
/**
 * User: Ilja.Kirillov
 * Date: 02.04.2020
 * Time: 15:50
 */

/**
 * Базовый класс для контент контролов
 * @constructor
 */
function CSdtBase()
{}

/**
 * Получаем текст плейсхолдера
 * @returns {string}
 */
CSdtBase.prototype.GetPlaceholderText = function()
{
	var oDocPart = this.GetLogicDocument().GetGlossaryDocument().GetDocPartByName(this.GetPlaceholder());
	if (oDocPart)
	{
		var oFirstParagraph = oDocPart.GetFirstParagraph();
		return oFirstParagraph.GetText({ParaEndToSpace : false});
	}

	return String.fromCharCode(nbsp_charcode, nbsp_charcode, nbsp_charcode, nbsp_charcode);
};
/**
 * Выставляем простой плейсхолдера в виде текста
 * @param {string} sText
 */
CSdtBase.prototype.SetPlaceholderText = function(sText)
{
	var oLogicDocument = this.GetLogicDocument();
	var oGlossary      = oLogicDocument.GetGlossaryDocument();

	var oDocPart = oGlossary.GetDocPartByName(this.GetPlaceholder());
	if (!oDocPart || oGlossary.IsDefaultDocPart(oDocPart))
	{
		var oNewDocPart;
		if (!oDocPart)
			oNewDocPart = oGlossary.CreateDocPart(oGlossary.GetNewName());
		else
			oNewDocPart = oDocPart.Copy(oGlossary.GetNewName());

		this.SetPlaceholder(oNewDocPart.GetDocPartName());
		oDocPart = oNewDocPart;
	}

	oDocPart.RemoveSelection();
	oDocPart.MoveCursorToStartPos();

	var oTextPr = oDocPart.GetDirectTextPr();
	var oParaPr = oDocPart.GetDirectParaPr();

	oDocPart.ClearContent(true);
	if (this.IsForm())
		oDocPart.MakeSingleParagraphContent();

	oDocPart.SelectAll();

	var oParagraph = oDocPart.GetFirstParagraph();
	oParagraph.CorrectContent();

	var oRun = null;
	if (this.IsForm())
		oRun = oParagraph.MakeSingleRunParagraph();

	oParagraph.SetDirectParaPr(oParaPr);
	oParagraph.SetDirectTextPr(oTextPr);

	if (oRun)
		oRun.AddText(sText);
	else
		oDocPart.AddText(sText);

	oDocPart.RemoveSelection();

	var isPlaceHolder = this.IsPlaceHolder();
	if (isPlaceHolder && this.IsPicture())
		this.private_UpdatePictureContent();
	else if (isPlaceHolder)
		this.private_FillPlaceholderContent();

	return oDocPart;
};
/**
 * Выставляем параметр, что данный контрол должен быть простым текстовым
 * @param {boolean} isText
 */
CSdtBase.prototype.SetContentControlText = function(isText)
{
	if (this.Pr.Text !== isText)
	{
		History.Add(new CChangesSdtPrText(this, this.Pr.Text, isText));
		this.Pr.Text = isText;
	}
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsContentControlText = function()
{
	return this.Pr.Text;
};
/**
 * Выставляем параметр, что данный контрол специальный для вставки формул
 * @param {boolean} isEquation
 */
CSdtBase.prototype.SetContentControlEquation = function(isEquation)
{
	if (this.Pr.Equation !== isEquation)
	{
		History.Add(new CChangesSdtPrEquation(this, this.Pr.Equation, isEquation));
		this.Pr.Equation = isEquation;
	}
};
/**
 * Применяем настройку, что данный контрол будет специальным для формул
 * @constructor
 */
CSdtBase.prototype.ApplyContentControlEquationPr = function()
{
	var oTextPr = new CTextPr();
	oTextPr.SetItalic(true);
	oTextPr.SetFontFamily("Cambria Math");

	this.SetDefaultTextPr(oTextPr);
	this.SetContentControlEquation(true);
	this.SetContentControlTemporary(true);

	if (!this.IsContentControlEquation())
		return;

	this.SetPlaceholder(c_oAscDefaultPlaceholderName.Equation);
	if (this.IsPlaceHolder())
		this.private_FillPlaceholderContent();
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsContentControlEquation = function()
{
	return this.Pr.Equation;
};
/**
 * Выставляем параметр, что данный контрол при редактировании должен быть удален
 * @param {boolean} isTemporary
 */
CSdtBase.prototype.SetContentControlTemporary = function(isTemporary)
{
	if (this.Pr.Temporary !== isTemporary)
	{
		History.Add(new CChangesSdtPrTemporary(this, this.Pr.Temporary, isTemporary));
		this.Pr.Temporary = isTemporary;
	}
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsContentControlTemporary = function()
{
	return this.Pr.Temporary;
};
/**
 * @param {CSdtFormPr} oFormPr
 */
CSdtBase.prototype.SetFormPr = function(oFormPr)
{
	if ((!this.Pr.FormPr && oFormPr) || !this.Pr.FormPr.IsEqual(oFormPr))
	{
		History.Add(new CChangesSdtPrFormPr(this, this.Pr.FormPr, oFormPr));
		this.Pr.FormPr = oFormPr;

		var oLogicDocument = this.GetLogicDocument();
		if (oLogicDocument)
			oLogicDocument.RegisterForm(this);

		this.private_OnAddFormPr();
	}
}
/**
 * Удаляем настройки специальных форм
 */
CSdtBase.prototype.RemoveFormPr = function()
{
	if (this.Pr.FormPr)
	{
		History.Add(new CChangesSdtPrFormPr(this, this.Pr.FormPr, undefined));
		this.Pr.FormPr = undefined;

		var oLogicDocument = this.GetLogicDocument();
		if (oLogicDocument)
			oLogicDocument.UnregisterForm(this);

		this.private_OnAddFormPr();
	}
};
/**
 * @returns {?CSdtFormPr}
 */
CSdtBase.prototype.GetFormPr = function()
{
	return this.Pr.FormPr;
};
/**
 * @returns {boolean}
 */
CSdtBase.prototype.IsForm = function()
{
	return (undefined !== this.Pr.FormPr);
};
/**
 * Получаем ключ для специальной формы, если он задан
 * @returns {?string}
 */
CSdtBase.prototype.GetFormKey = function()
{
	if (!this.IsForm())
		return undefined;

	return (this.Pr.FormPr.Key);
};
/**
 * Проверяем, является ли заданный контрол радио-кнопкой
 * @returns {boolean}
 */
CSdtBase.prototype.IsRadioButton = function()
{
	return !!(this.IsCheckBox() && this.Pr.CheckBox && this.Pr.CheckBox.GroupKey);
};
/**
 * Является ли данный контейнер специальной текстовой формой
 * @returns {boolean}
 */
CSdtBase.prototype.IsTextForm = function()
{
	return false;
};
/**
 * Получаем ключ для группы радио-кнопок
 * @returns {?string}
 */
CSdtBase.prototype.GetRadioButtonGroupKey = function()
{
	if (!this.IsRadioButton())
		return undefined;

	return (this.Pr.CheckBox.GroupKey);
};
/**
 * Для чекбоксов и радио-кнопок получаем состояние
 * @returns {bool}
 */
CSdtBase.prototype.IsCheckBoxChecked = function()
{
	if (this.IsCheckBox())
		return this.Pr.CheckBox.Checked;

	return false;
};

<|endoftext|>