<commit_before>import layers, {
  addLayer,
  copyLayer,
  moveLayer,
  restoreDefaults,
  setCurrentLayer,
  setSelectedLayer,
  setNewLayerType,
  setShapeType,
  updateLayer,
  toggleSpin,
  toggleGrow,
  toggleRepeat,
  toggleTrack,
  toggleTrackGrow,
  toggleVisible
} from './layersSlice'

describe('layers reducer', () => {
  const initialState = {
    circleLobes: 1,
    type: 'circle',
    repeatEnabled: true,
    canTransform: true,
    selectGroup: 'Shapes',
    shouldCache: true,
    canChangeSize: true,
    startingSize: 10,
    offsetX: 0.0,
    offsetY: 0.0,
    rotation: 0,
    numLoops: 10,
    reverse: false,
    transformMethod: 'smear',
    growEnabled: true,
    growValue: 100,
    growMethod: 'constant',
    growMath: 'i+cos(i/2)',
    growMathInput: 'i+cos(i/2)',
    spinEnabled: false,
    spinValue: 2,
    spinMethod: 'constant',
    spinMath: '10*sin(i/4)',
    spinMathInput: '10*sin(i/4)',
    spinSwitchbacks: 0,
    trackEnabled: false,
    trackGrowEnabled: false,
    trackValue: 10,
    trackLength: 0.2,
    trackNumLoops: 1,
    trackGrow: 50.0,
    usesMachine: false,
    dragging: false,
    visible: true
  }

  it('should handle initial state', () => {
    expect(layers(undefined, {})).toEqual({
      current: null,
      selected: null,
      newLayerType: 'polygon',
      newLayerName: 'polygon',
      newLayerNameOverride: false,
      copyLayerName: null,
      byId: {},
      allIds: []
    })
  })

  it('should handle addLayer', () => {
    expect(
      layers({
        byId: {},
        allIds: []
      },
      addLayer({
        name: 'foo'
      }))
    ).toEqual({
      byId: {
        'layer-1': {
          id: 'layer-1',
          name: 'foo'
        }
      },
      allIds: ['layer-1'],
      current: 'layer-1',
      selected: 'layer-1',
      newLayerName: undefined,
      newLayerNameOverride: false
    })
  })

  it('should handle copyLayer', () => {
    expect(
      layers({
        byId: {
          'layer-1': {
            id: 'layer-1',
            name: 'foo'
          }
        },
        allIds: ['layer-1'],
        copyLayerName: 'foo'
      },
      copyLayer('layer-1'))
    ).toEqual({
      byId: {
        'layer-1': {
          id: 'layer-1',
          name: 'foo'
        },
        'layer-2': {
          id: 'layer-2',
          name: 'foo'
        }
      },
      allIds: ['layer-1', 'layer-2'],
      current: 'layer-2',
      selected: 'layer-2',
      copyLayerName: 'foo'
    })
  })

  it('should handle moveLayer', () => {
    expect(
      layers(
        {
          allIds: ['a', 'b', 'c', 'd', 'e']
        },
        moveLayer({oldIndex: 0, newIndex: 2})
      )
    ).toEqual({
      allIds: ['b', 'c', 'a', 'd', 'e'],
    })
  })

  it('should handle restoreDefaults', () => {
    expect(
      layers(
        {
          byId: {
            'layer-1': {
              id: 'layer-1',
              name: 'foo',
              type: 'circle',
              circleLobes: '2',
              polygonSides: '5'
            }
          }
        },
        restoreDefaults('layer-1')
      )
    ).toEqual({
      byId: {
        'layer-1': {
          id: 'layer-1',
          name: 'foo',
          ...initialState
        }
      }
    })
  })

  it('should handle setCurrentLayer', () => {
    expect(
      layers(
        {
          byId: {
            'layer-2': {
              id: 'layer-2'
            }
          },
          allIds: ['layer-1', 'layer-2']
        },
        setCurrentLayer('layer-2')
      )
    ).toEqual({
      byId: {
        'layer-2': {
          id: 'layer-2'
        }
      },
      allIds: ['layer-1', 'layer-2'],
      current: 'layer-2',
      selected: 'layer-2'
    })
  })

  it('should handle setSelectedLayer', () => {
    expect(
      layers(
        {
          byId: {
            'layer-2': {
              id: 'layer-2'
            }
          },
          allIds: ['layer-1', 'layer-2']
        },
        setSelectedLayer('layer-2')
      )
    ).toEqual({
      byId: {
        'layer-2': {
          id: 'layer-2'
        }
      },
      allIds: ['layer-1', 'layer-2'],
      selected: 'layer-2'
    })
  })

  describe('setShapeType', () => {
    it('should add default values', () => {
      expect(
        layers(
          {
            byId: {
              'layer-1': {
                id: 'layer-1'
              }
            }
          },
          setShapeType({id: 'layer-1', type: 'circle'})
        )
      ).toEqual({
        byId: {
          'layer-1': {
            id: 'layer-1',
            ...initialState
          }
        }
      })
    })

    it('should not override values if provided', () => {
      expect(
        layers(
          {
            byId: {
              'layer-1': {
                id: 'layer-1',
                circleLobes: 2
              }
            }
          },
          setShapeType({id: 'layer-1', type: 'circle'})
        )
      ).toEqual({
        byId: {
          'layer-1': {
            id: 'layer-1',
            ...initialState,
            circleLobes: 2
          }
        }
      })
    })

    it('should always override values of protected attributes', () => {
      expect(
        layers(
          {
            byId: {
              'layer-1': {
                id: 'layer-1',
                repeatEnabled: false,
                canChangeSize: false
              }
            }
          },
          setShapeType({id: 'layer-1', type: 'circle'})
        )
      ).toEqual({
        byId: {
          'layer-1': {
            id: 'layer-1',
            ...initialState,
          }
        }
      })
    })
  })

  it('should handle updateLayer', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              id: '1',
              name: 'foo'
            }
          }
        },
        updateLayer({id: '1', name: 'bar'})
      )
    ).toEqual({
      byId: {
        '1': {
          id: '1',
          name: 'bar'
        }
      }
    })
  })

  it('should handle toggleGrow', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              growEnabled: false
            }
          }
        },
        toggleGrow({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          growEnabled: true
        }
      }
    })
  })

  it('should handle toggleSpin', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              spinEnabled: false
            }
          }
        },
        toggleSpin({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          spinEnabled: true
        }
      }
    })
  })

  it('should handle toggleRepeat', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              repeatEnabled: false
            }
          }
        },
        toggleRepeat({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          repeatEnabled: true
        }
      }
    })
  })

  it('should handle toggleTrack', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              trackEnabled: false
            }
          }
        },
        toggleTrack({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          trackEnabled: true
        }
      }
    })
  })

  it('should handle toggleToggleGrow', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              trackGrowEnabled: false
            }
          }
        },
        toggleTrackGrow({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          trackGrowEnabled: true
        }
      }
    })
  })

  it('should handle toggleToggleVisible', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              visible: true
            }
          }
        },
        toggleVisible({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          visible: false
        }
      }
    })
  })

  it('should handle setNewLayerType', () => {
    expect(
      layers(
        {
          newLayerType: 'circle'
        },
        setNewLayerType('polygon')
      )
    ).toEqual({
      newLayerType: 'polygon',
      newLayerName: 'polygon'
    })
  })
})
<commit_msg>Fixed spec.<commit_after>import layers, {
  addLayer,
  copyLayer,
  moveLayer,
  restoreDefaults,
  setCurrentLayer,
  setSelectedLayer,
  setNewLayerType,
  setShapeType,
  updateLayer,
  toggleSpin,
  toggleGrow,
  toggleRepeat,
  toggleTrack,
  toggleTrackGrow,
  toggleVisible
} from './layersSlice'

describe('layers reducer', () => {
  const initialState = {
    circleLobes: 1,
    type: 'circle',
    repeatEnabled: true,
    canTransform: true,
    selectGroup: 'Shapes',
    shouldCache: true,
    canChangeSize: true,
    startingSize: 10,
    offsetX: 0.0,
    offsetY: 0.0,
    rotation: 0,
    numLoops: 10,
    reverse: false,
    transformMethod: 'smear',
    growEnabled: true,
    growValue: 100,
    growMethod: 'constant',
    growMath: 'i+cos(i/2)',
    growMathInput: 'i+cos(i/2)',
    spinEnabled: false,
    spinValue: 2,
    spinMethod: 'constant',
    spinMath: '10*sin(i/4)',
    spinMathInput: '10*sin(i/4)',
    spinSwitchbacks: 0,
    trackEnabled: false,
    trackGrowEnabled: false,
    trackValue: 10,
    trackLength: 0.2,
    trackNumLoops: 1,
    trackGrow: 50.0,
    usesMachine: false,
    dragging: false,
    visible: true
  }

  it('should handle initial state', () => {
    expect(layers(undefined, {})).toEqual({
      current: null,
      selected: null,
      newLayerType: 'polygon',
      newLayerName: 'polygon',
      newLayerNameOverride: false,
      copyLayerName: null,
      byId: {},
      allIds: []
    })
  })

  it('should handle addLayer', () => {
    expect(
      layers({
        byId: {},
        allIds: []
      },
      addLayer({
        name: 'foo'
      }))
    ).toEqual({
      byId: {
        'layer-1': {
          id: 'layer-1',
          name: 'foo'
        }
      },
      allIds: ['layer-1'],
      current: 'layer-1',
      selected: 'layer-1',
      newLayerName: 'foo',
      newLayerNameOverride: false
    })
  })

  it('should handle copyLayer', () => {
    expect(
      layers({
        byId: {
          'layer-1': {
            id: 'layer-1',
            name: 'foo'
          }
        },
        allIds: ['layer-1'],
        copyLayerName: 'foo'
      },
      copyLayer('layer-1'))
    ).toEqual({
      byId: {
        'layer-1': {
          id: 'layer-1',
          name: 'foo'
        },
        'layer-2': {
          id: 'layer-2',
          name: 'foo'
        }
      },
      allIds: ['layer-1', 'layer-2'],
      current: 'layer-2',
      selected: 'layer-2',
      copyLayerName: 'foo'
    })
  })

  it('should handle moveLayer', () => {
    expect(
      layers(
        {
          allIds: ['a', 'b', 'c', 'd', 'e']
        },
        moveLayer({oldIndex: 0, newIndex: 2})
      )
    ).toEqual({
      allIds: ['b', 'c', 'a', 'd', 'e'],
    })
  })

  it('should handle restoreDefaults', () => {
    expect(
      layers(
        {
          byId: {
            'layer-1': {
              id: 'layer-1',
              name: 'foo',
              type: 'circle',
              circleLobes: '2',
              polygonSides: '5'
            }
          }
        },
        restoreDefaults('layer-1')
      )
    ).toEqual({
      byId: {
        'layer-1': {
          id: 'layer-1',
          name: 'foo',
          ...initialState
        }
      }
    })
  })

  it('should handle setCurrentLayer', () => {
    expect(
      layers(
        {
          byId: {
            'layer-2': {
              id: 'layer-2'
            }
          },
          allIds: ['layer-1', 'layer-2']
        },
        setCurrentLayer('layer-2')
      )
    ).toEqual({
      byId: {
        'layer-2': {
          id: 'layer-2'
        }
      },
      allIds: ['layer-1', 'layer-2'],
      current: 'layer-2',
      selected: 'layer-2'
    })
  })

  it('should handle setSelectedLayer', () => {
    expect(
      layers(
        {
          byId: {
            'layer-2': {
              id: 'layer-2'
            }
          },
          allIds: ['layer-1', 'layer-2']
        },
        setSelectedLayer('layer-2')
      )
    ).toEqual({
      byId: {
        'layer-2': {
          id: 'layer-2'
        }
      },
      allIds: ['layer-1', 'layer-2'],
      selected: 'layer-2'
    })
  })

  describe('setShapeType', () => {
    it('should add default values', () => {
      expect(
        layers(
          {
            byId: {
              'layer-1': {
                id: 'layer-1'
              }
            }
          },
          setShapeType({id: 'layer-1', type: 'circle'})
        )
      ).toEqual({
        byId: {
          'layer-1': {
            id: 'layer-1',
            ...initialState
          }
        }
      })
    })

    it('should not override values if provided', () => {
      expect(
        layers(
          {
            byId: {
              'layer-1': {
                id: 'layer-1',
                circleLobes: 2
              }
            }
          },
          setShapeType({id: 'layer-1', type: 'circle'})
        )
      ).toEqual({
        byId: {
          'layer-1': {
            id: 'layer-1',
            ...initialState,
            circleLobes: 2
          }
        }
      })
    })

    it('should always override values of protected attributes', () => {
      expect(
        layers(
          {
            byId: {
              'layer-1': {
                id: 'layer-1',
                repeatEnabled: false,
                canChangeSize: false
              }
            }
          },
          setShapeType({id: 'layer-1', type: 'circle'})
        )
      ).toEqual({
        byId: {
          'layer-1': {
            id: 'layer-1',
            ...initialState,
          }
        }
      })
    })
  })

  it('should handle updateLayer', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              id: '1',
              name: 'foo'
            }
          }
        },
        updateLayer({id: '1', name: 'bar'})
      )
    ).toEqual({
      byId: {
        '1': {
          id: '1',
          name: 'bar'
        }
      }
    })
  })

  it('should handle toggleGrow', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              growEnabled: false
            }
          }
        },
        toggleGrow({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          growEnabled: true
        }
      }
    })
  })

  it('should handle toggleSpin', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              spinEnabled: false
            }
          }
        },
        toggleSpin({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          spinEnabled: true
        }
      }
    })
  })

  it('should handle toggleRepeat', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              repeatEnabled: false
            }
          }
        },
        toggleRepeat({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          repeatEnabled: true
        }
      }
    })
  })

  it('should handle toggleTrack', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              trackEnabled: false
            }
          }
        },
        toggleTrack({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          trackEnabled: true
        }
      }
    })
  })

  it('should handle toggleToggleGrow', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              trackGrowEnabled: false
            }
          }
        },
        toggleTrackGrow({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          trackGrowEnabled: true
        }
      }
    })
  })

  it('should handle toggleToggleVisible', () => {
    expect(
      layers(
        {
          byId: {
            '1': {
              visible: true
            }
          }
        },
        toggleVisible({id: '1'})
      )
    ).toEqual({
      byId: {
        '1': {
          visible: false
        }
      }
    })
  })

  it('should handle setNewLayerType', () => {
    expect(
      layers(
        {
          newLayerType: 'circle'
        },
        setNewLayerType('polygon')
      )
    ).toEqual({
      newLayerType: 'polygon',
      newLayerName: 'polygon'
    })
  })
})
<|endoftext|>


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

foam.CLASS({
  package: 'foam.nanos.fs',
  name: 'SupportFileTypeDAO',
  extends: 'foam.dao.ProxyDAO',
  documentation: ` A fileDAO decorator use to store the file type`,

  javaImports: [
    'foam.comics.v2.userfeedback.UserFeedback',
    'foam.comics.v2.userfeedback.UserFeedbackException',
    'foam.comics.v2.userfeedback.UserFeedbackStatus',
    'foam.core.X',
    'foam.dao.*',
    'foam.nanos.fs.File',
    'foam.nanos.fs.FileType',
    'static foam.mlang.MLang.EQ'
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(`
          public SupportFileTypeDAO(X x, DAO delegate) {
            setX(x);
            setDelegate(delegate);
          }
        `
        );
      }
    }
  ],

  messages: [
    { name: 'FILE_TYPE_NOT_SUPPORT', message: 'File type not supported.' },
  ],

  methods: [
    {
      name: 'put_',
      javaCode: `
      if (!(obj instanceof File)) {
        return super.put_(x, obj);
      }

      File file = (File) obj;
      DAO fileTypeDAO = (DAO) x.get("fileTypeDAO");
      FileType fileType = (FileType) fileTypeDAO.find(
        EQ(FileType.MIME, file.getMimeType())
      );

      if ( fileType == null ) {
        throw new UserFeedbackException.Builder(x)
        .setUserFeedback(new UserFeedback.Builder(x)
          .setStatus(UserFeedbackStatus.ERROR)
          .setMessage(FILE_TYPE_NOT_SUPPORT)
          .build()
        ).build();
      }
      return file;
      `
    }
  ]
});
<commit_msg>fix supportFileDAO type<commit_after>/**
 * @license
 * Copyright 2020 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.fs',
  name: 'SupportFileTypeDAO',
  extends: 'foam.dao.ProxyDAO',
  documentation: ` A fileDAO decorator use to store the file type`,

  javaImports: [
    'foam.comics.v2.userfeedback.UserFeedback',
    'foam.comics.v2.userfeedback.UserFeedbackException',
    'foam.comics.v2.userfeedback.UserFeedbackStatus',
    'foam.core.X',
    'foam.dao.*',
    'foam.nanos.fs.File',
    'foam.nanos.fs.FileType',
    'static foam.mlang.MLang.EQ'
  ],

  axioms: [
    {
      name: 'javaExtras',
      buildJavaClass: function(cls) {
        cls.extras.push(`
          public SupportFileTypeDAO(X x, DAO delegate) {
            setX(x);
            setDelegate(delegate);
          }
        `
        );
      }
    }
  ],

  messages: [
    { name: 'FILE_TYPE_NOT_SUPPORT', message: 'File type not supported.' },
  ],

  methods: [
    {
      name: 'put_',
      javaCode: `
      if (!(obj instanceof File)) {
        return super.put_(x, obj);
      }

      File file = (File) obj;
      DAO fileTypeDAO = (DAO) x.get("fileTypeDAO");
      String mimeType = file.getMimeType()
      String subType = mimeType.substring(mimeType.indexOf("/") + 1)
      FileType fileType = (FileType) fileTypeDAO.find(
        EQ(FileType.SUBTYPE, subType)
      );

      if ( fileType == null ) {
        throw new UserFeedbackException.Builder(x)
        .setUserFeedback(new UserFeedback.Builder(x)
          .setStatus(UserFeedbackStatus.ERROR)
          .setMessage(FILE_TYPE_NOT_SUPPORT)
          .build()
        ).build();
      }
      return file;
      `
    }
  ]
});
<|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.mrac',
  name: 'ClusterClientDAO',
  extends: 'foam.dao.ProxyDAO',

  documentation: 'Marshall put and remove operations to the ClusterServer.',

  javaImports: [
    'foam.core.FObject',
    'foam.core.X',
    'foam.dao.DAO',
    'foam.nanos.logger.Logger',
    'foam.nanos.logger.PrefixLogger'
  ],

  properties: [
    {
      documentation: `nSpec service name at the remote node.`,
      name: 'serviceName',
      class: 'String'
    },
    {
      name: 'mdao',
      class: 'foam.dao.DAOProperty',
      visibility: 'HIDDEN'
    }
  ],

  methods: [
    {
      name: 'put_',
      javaCode: `
      Logger logger = new PrefixLogger(new Object[] {
        this.getClass().getSimpleName(),
        getServiceName(),
        "put_",
      }, (Logger) x.get("logger"));

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
      if ( service != null &&
           service.getConfig() != null &&
           ! service.getIsPrimary() ) {
        foam.core.FObject old = getMdao().find_(x, obj.getProperty("id"));
        foam.lib.json.Outputter outputter = new foam.lib.json.Outputter(x).setPropertyPredicate(new foam.lib.ClusterPropertyPredicate());
        String record = ( old != null ) ?
          outputter.stringifyDelta(old, obj) :
          outputter.stringify(obj);
        logger.debug("record", record);
        if ( foam.util.SafetyUtil.isEmpty(record) ||
            "{}".equals(record.trim()) ) {
          logger.debug("no changes");
          // temporarily store locally until Medusa
          //return obj;
          return getDelegate().put_(x, obj);
        }

        ClusterCommand cmd = new ClusterCommand(x, getServiceName(), ClusterCommand.PUT, record);
        logger.debug("to primary", cmd);
        FObject result = (FObject) service.getPrimaryDAO(x, getServiceName(), (foam.dao.DAO) getMdao()).cmd_(x, cmd);
        logger.debug("from primary", result.getClass().getSimpleName(), result);
        obj = obj.copyFrom(result);
        // temporarily store locally until Medusa
        // return obj;
        logger.debug("obj after copyFrom", obj);
        //return getMdao().put_(x, obj); // does not work for password updates.
        return obj;
      } else {
        return getDelegate().put_(x, obj);
      }
      `
    },
    {
      name: 'remove_',
      javaCode: `
      Logger logger = new PrefixLogger(new Object[] {
        this.getClass().getSimpleName(),
        getServiceName(),
        "remove_",
      }, (Logger) x.get("logger"));

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
      if ( service != null &&
           service.getConfig() != null &&
           ! service.getIsPrimary() ) {
        foam.lib.json.Outputter outputter = new foam.lib.json.Outputter(x).setPropertyPredicate(new foam.lib.ClusterPropertyPredicate());
        String record = outputter.stringify(obj);

        ClusterCommand cmd = new ClusterCommand(x, getServiceName(), ClusterCommand.REMOVE, record);
        logger.debug("to primary", cmd);
        FObject result = (FObject) service.getPrimaryDAO(x, getServiceName(), (foam.dao.DAO) getMdao()).cmd_(x, cmd);
        logger.debug("from primary", result.getClass().getSimpleName(), result);
        obj = obj.copyFrom(result);
        // temporarily store locally until Medusa
        // return obj;
        logger.debug("obj after copyFrom", obj);
        return getMdao().remove_(x, obj);
      } else {
        return getDelegate().remove_(x, obj);
      }
      `
    }
  ]
});
<commit_msg>temerature resolve outputerDelta bug in ClusterClientDAO<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.mrac',
  name: 'ClusterClientDAO',
  extends: 'foam.dao.ProxyDAO',

  documentation: 'Marshall put and remove operations to the ClusterServer.',

  javaImports: [
    'foam.core.FObject',
    'foam.core.X',
    'foam.dao.DAO',
    'foam.nanos.logger.Logger',
    'foam.nanos.logger.PrefixLogger'
  ],

  properties: [
    {
      documentation: `nSpec service name at the remote node.`,
      name: 'serviceName',
      class: 'String'
    },
    {
      name: 'mdao',
      class: 'foam.dao.DAOProperty',
      visibility: 'HIDDEN'
    }
  ],

  methods: [
    {
      name: 'put_',
      javaCode: `
      Logger logger = new PrefixLogger(new Object[] {
        this.getClass().getSimpleName(),
        getServiceName(),
        "put_",
      }, (Logger) x.get("logger"));

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
      if ( service != null &&
           service.getConfig() != null &&
           ! service.getIsPrimary() ) {
        foam.core.FObject old = getMdao().find_(x, obj.getProperty("id"));
        foam.lib.json.Outputter outputter = new foam.lib.json.Outputter(x).setPropertyPredicate(new foam.lib.ClusterPropertyPredicate());
        //TODO: outputDelta has problem when output array. Fix bugs then use output delta.
        // String record = ( old != null ) ?
        //   outputter.stringifyDelta(old, obj) :
        //   outputter.stringify(obj);
        String record = outputter.stringify(obj);
        logger.debug("record", record);
        if ( foam.util.SafetyUtil.isEmpty(record) ||
            "{}".equals(record.trim()) ) {
          logger.debug("no changes");
          // temporarily store locally until Medusa
          //return obj;
          return getDelegate().put_(x, obj);
        }

        ClusterCommand cmd = new ClusterCommand(x, getServiceName(), ClusterCommand.PUT, record);
        logger.debug("to primary", cmd);
        FObject result = (FObject) service.getPrimaryDAO(x, getServiceName(), (foam.dao.DAO) getMdao()).cmd_(x, cmd);
        logger.debug("from primary", result.getClass().getSimpleName(), result);
        obj = obj.copyFrom(result);
        // temporarily store locally until Medusa
        // return obj;
        logger.debug("obj after copyFrom", obj);
        //return getMdao().put_(x, obj); // does not work for password updates.
        return obj;
      } else {
        return getDelegate().put_(x, obj);
      }
      `
    },
    {
      name: 'remove_',
      javaCode: `
      Logger logger = new PrefixLogger(new Object[] {
        this.getClass().getSimpleName(),
        getServiceName(),
        "remove_",
      }, (Logger) x.get("logger"));

      ClusterConfigService service = (ClusterConfigService) x.get("clusterConfigService");
      if ( service != null &&
           service.getConfig() != null &&
           ! service.getIsPrimary() ) {
        foam.lib.json.Outputter outputter = new foam.lib.json.Outputter(x).setPropertyPredicate(new foam.lib.ClusterPropertyPredicate());
        String record = outputter.stringify(obj);

        ClusterCommand cmd = new ClusterCommand(x, getServiceName(), ClusterCommand.REMOVE, record);
        logger.debug("to primary", cmd);
        FObject result = (FObject) service.getPrimaryDAO(x, getServiceName(), (foam.dao.DAO) getMdao()).cmd_(x, cmd);
        logger.debug("from primary", result.getClass().getSimpleName(), result);
        obj = obj.copyFrom(result);
        // temporarily store locally until Medusa
        // return obj;
        logger.debug("obj after copyFrom", obj);
        return getMdao().remove_(x, obj);
      } else {
        return getDelegate().remove_(x, obj);
      }
      `
    }
  ]
});
<|endoftext|>


<commit_before>// ----------------------------------------------------------------------------
//
// feed2summary.js
//
// Copyright 2013 Andrew Chilton <andychilton@gmail.com>
//
// ----------------------------------------------------------------------------

var fs = require('fs');
var http = require('http');
var https = require('https');
var util = require('util');

var async = require('async');
var sax = require('sax');
var levelup = require('levelup');
var fmt = require('fmt');

// ----------------------------------------------------------------------------

var filename = process.argv[2];
var data = fs.readFileSync(filename, 'utf8');
var feeds = data.split(/\n/);

// filter out blank lines and comment lines
feeds = feeds.filter(function(feed, i) {
    // if feed is a blank line then filter out
    if ( !feed.match(/\S/) ) {
        return false;
    }

    // if feed begins with //, then filter out
    if ( feed.match(/^\s*\#/) ) {
        return false;
    }

    return true;
});

// convert each URL into an object
feeds = feeds.map(function(feed, i) {
    // convert to an object so we can save stuff to it
    return {
        url   : feed,
        items : [],
    };
});

// open the LevelDB which we use as temporary storage
var db = levelup(filename + '.db');

// ----------------------------------------------------------------------------

async.eachSeries(
    feeds,
    function(feed, done) {
        // remember some state when parsing the file
        var type = 'unknown';
        var title;
        var link;
        var text; // the text in the last tag
        var state = 'new';

        debug('-------------------------------------------------------------------------------');
        debug('Fetching ' + feed.url);

        // create an XML streaming parser and save each item to the 'entries'
        var parser = sax.createStream(false);

        parser.on("opentag", function (node) {
            debug('Detected open tag : ' + node.name);

            if ( type === 'unknown' && node.name === 'RSS' ) {
                debug('Detected RSS feed');
                type = 'rss';
            }
            if ( type === 'unknown' && node.name === 'FEED' ) {
                debug('Detected Atom feed');
                type = 'atom';
            }

            // ok, let's look for an ITEM (if rss)
            if ( type === 'rss' && node.name === 'ITEM' ) {
                debug('Detected opening <item>');
                state = 'gotitemstart';
                title = undefined;
                link = undefined;
            }

            // ok, let's look for an <entry> (if atom)
            if ( type === 'atom' && node.name === 'ENTRY' ) {
                debug('Detected opening <entry>');
                state = 'gotitemstart';
                title = undefined;
                link = undefined;
            }

            // let's also look for a <link> (if atom)
            if ( type === 'atom' && state === 'gotitemstart' && node.name === 'LINK' ) {
                debug('Found link : ' + node.attributes.HREF);
                link = node.attributes.HREF;
            }
        });

        parser.on("closetag", function (node) {
            debug('Detected close tag : ' + node);

            // if we have seen an <item>, then we should see a <title> and <link>
            if ( type === 'rss' && state === 'gotitemstart' && node === 'TITLE' ) {
                debug('Saving title : ' + text);
                title = text;
            }
            if ( type === 'rss' && state === 'gotitemstart' && node === 'LINK' ) {
                debug('Saving link : ' + text);
                link = text;
            }

            // if we have seen an <entry>, then we should also see a <title>
            if ( type === 'atom' && state === 'gotitemstart' && node === 'TITLE' ) {
                debug('Saving title : ' + text);
                title = text;
            }
            // (link is an attribute on <entry>, so we've already seen that

            // if we have a </item> then, reset
            if ( type == 'rss' && state === 'gotitemstart' && node === 'ITEM' ) {
                debug('Got </item>, saving item');

                // save this for later processing
                feed.items.push({
                    title : title,
                    link  : link,
                });

                // reset some state
                state = 'new';
                title = undefined;
                link = undefined;
            }

            // if we have a </entry> then, reset
            if ( type == 'atom' && state === 'gotitemstart' && node === 'ENTRY' ) {
                debug('Got </entry>, saving item');

                // save this for later processing
                feed.items.push({
                    title : title,
                    link  : link,
                });

                // reset some state
                state = 'new';
                title = undefined;
                link = undefined;
            }

        });

        // always save what we have see when we get a text node
        parser.on("text", function (t) {
            debug('Text=' + t);
            text = t;
        });

        // stream each RSS file to the XML Parser
        request(feed, function(err, res) {
            if (err) {
                debug('');
                debug('' + err + "\n");
                debug('Finished ' + feed.url);
                return done();
            }

            // all good, so pipe into the parser
            res.pipe(parser);
            res.on('end', function() {
                // save the number if items found
                feed.total = feed.items.length;

                debug('Found ' + feed.total + ' item(s)');
                done();
            });
        });
    },
    function(results) {
        debug('-------------------------------------------------------------------------------');

        // filter out any results which we've already seen
        async.eachSeries(
            feeds,
            function(feed, callback) {
                // filter out any items we have already seen
                async.filter(
                    feed.items,
                    function(item, filter) {
                        // check if this feed.url exists in LevelDB and if not, allow it
                        db.get(item.link, function(err, value) {
                            if (err) {
                                if (err.name === 'NotFoundError' ) {
                                    debug('Putting ' + item.link);
                                    db.put(item.link, (new Date()).toISOString(), function(err) {
                                        filter(true);
                                    });
                                }
                                else {
                                    // something went wrong
                                    console.warn('Error:' + err);
                                    process.exit(2);
                                }
                            }
                            else {
                                // already exists
                                debug('Exists ' + item.link);
                                filter(false);
                            }
                        });
                    },
                    function(newItems) {
                        // finished filtering feed.items
                        feed.items = newItems;

                        // save the number if new items
                        feed.new = newItems.length;

                        callback();
                    }
                );
            },
            function(err) {
                // finally, print out all of the feeds as they now stand
                // console.log(util.inspect(feeds, false, null, false));

                feeds.forEach(function(feed) {
                    if ( feed.statusCode === 200 ) {
                        // see if there are any new feeds
                        if ( feed.new > 0 ) {
                            fmt.sep();
                            fmt.title(feed.url);
                            feed.items.forEach(function(item, i) {
                                fmt.li(item.title);
                                fmt.li(' -> ' + item.link + "\n");
                            });
                        }
                    }
                    else {
                        // something went wrong
                        fmt.sep();
                        fmt.title(feed.url);
                        fmt.field('status', feed.statusCode);
                        if ( feed.redirect ) {
                            fmt.field('redirect', feed.redirect);
                        }
                    }
                });

                fmt.sep();
            }
        );
    }
);

function request(feed, callback) {
    var protocol;
    if ( feed.url.match(/^http:\/\//) ) {
        protocol = http;
    }
    else if ( feed.url.match(/^https:\/\//) ) {
        protocol = https;
    }
    else {
        return callback(new Error('Unknown protocol'));
    }

    protocol.get(feed.url, function(res) {
        debug("Got response " + res.statusCode);

        // save the statusCode so we can see it
        feed.statusCode = res.statusCode;

        // if this looks a bit suspicious, return an error
        if ( res.statusCode !== 200 ) {
            if ( res.statusCode === 301 || res.statusCode === 302 ) {
                // save this so we can print it out later
                feed.redirect = res.headers.location;
            }
            return callback(new Error('Status code was not 200 (' + res.statusCode + ')'));
        }

        // everything looks ok
        callback(null, res);
    }).on('error', function(e) {
        callback(e);
    });
}

// ----------------------------------------------------------------------------

function debug(msg) {
    if ( false ) {
        console.log(msg);
    }
};
function log(msg) {
    console.log(msg);
}

function line() {
    console.log('-------------------------------------------------------------------------------');
}

// ----------------------------------------------------------------------------
<commit_msg>Allow comments in the feeds file<commit_after>// ----------------------------------------------------------------------------
//
// feed2summary.js
//
// Copyright 2013 Andrew Chilton <andychilton@gmail.com>
//
// ----------------------------------------------------------------------------

var fs = require('fs');
var http = require('http');
var https = require('https');
var util = require('util');

var async = require('async');
var sax = require('sax');
var levelup = require('levelup');
var fmt = require('fmt');

// ----------------------------------------------------------------------------

var filename = process.argv[2];
var data = fs.readFileSync(filename, 'utf8');
var feeds = data.split(/\n/);

// filter out blank lines and comment lines
feeds = feeds.filter(function(feed, i) {
    // if feed is a blank line then filter out
    if ( !feed.match(/\S/) ) {
        return false;
    }

    // if feed begins with #, then filter out
    if ( feed.match(/^\s*\#/) ) {
        return false;
    }

    return true;
});

// convert each URL into an object
feeds = feeds.map(function(feed, i) {
    // firstly, strip any intitial spaces and anything after the first space
    feed = feed.replace(/^\s*/, '');
    feed = feed.replace(/\s.*$/, '');

    // convert to an object so we can save stuff to it
    return {
        url   : feed,
        items : [],
    };
});

// open the LevelDB which we use as temporary storage
var db = levelup(filename + '.db');

// ----------------------------------------------------------------------------

async.eachSeries(
    feeds,
    function(feed, done) {
        // remember some state when parsing the file
        var type = 'unknown';
        var title;
        var link;
        var text; // the text in the last tag
        var state = 'new';

        debug('-------------------------------------------------------------------------------');
        debug('Fetching ' + feed.url);

        // create an XML streaming parser and save each item to the 'entries'
        var parser = sax.createStream(false);

        parser.on("opentag", function (node) {
            debug('Detected open tag : ' + node.name);

            if ( type === 'unknown' && node.name === 'RSS' ) {
                debug('Detected RSS feed');
                type = 'rss';
            }
            if ( type === 'unknown' && node.name === 'FEED' ) {
                debug('Detected Atom feed');
                type = 'atom';
            }

            // ok, let's look for an ITEM (if rss)
            if ( type === 'rss' && node.name === 'ITEM' ) {
                debug('Detected opening <item>');
                state = 'gotitemstart';
                title = undefined;
                link = undefined;
            }

            // ok, let's look for an <entry> (if atom)
            if ( type === 'atom' && node.name === 'ENTRY' ) {
                debug('Detected opening <entry>');
                state = 'gotitemstart';
                title = undefined;
                link = undefined;
            }

            // let's also look for a <link> (if atom)
            if ( type === 'atom' && state === 'gotitemstart' && node.name === 'LINK' ) {
                debug('Found link : ' + node.attributes.HREF);
                link = node.attributes.HREF;
            }
        });

        parser.on("closetag", function (node) {
            debug('Detected close tag : ' + node);

            // if we have seen an <item>, then we should see a <title> and <link>
            if ( type === 'rss' && state === 'gotitemstart' && node === 'TITLE' ) {
                debug('Saving title : ' + text);
                title = text;
            }
            if ( type === 'rss' && state === 'gotitemstart' && node === 'LINK' ) {
                debug('Saving link : ' + text);
                link = text;
            }

            // if we have seen an <entry>, then we should also see a <title>
            if ( type === 'atom' && state === 'gotitemstart' && node === 'TITLE' ) {
                debug('Saving title : ' + text);
                title = text;
            }
            // (link is an attribute on <entry>, so we've already seen that

            // if we have a </item> then, reset
            if ( type == 'rss' && state === 'gotitemstart' && node === 'ITEM' ) {
                debug('Got </item>, saving item');

                // save this for later processing
                feed.items.push({
                    title : title,
                    link  : link,
                });

                // reset some state
                state = 'new';
                title = undefined;
                link = undefined;
            }

            // if we have a </entry> then, reset
            if ( type == 'atom' && state === 'gotitemstart' && node === 'ENTRY' ) {
                debug('Got </entry>, saving item');

                // save this for later processing
                feed.items.push({
                    title : title,
                    link  : link,
                });

                // reset some state
                state = 'new';
                title = undefined;
                link = undefined;
            }

        });

        // always save what we have see when we get a text node
        parser.on("text", function (t) {
            debug('Text=' + t);
            text = t;
        });

        // stream each RSS file to the XML Parser
        request(feed, function(err, res) {
            if (err) {
                debug('');
                debug('' + err + "\n");
                debug('Finished ' + feed.url);
                return done();
            }

            // all good, so pipe into the parser
            res.pipe(parser);
            res.on('end', function() {
                // save the number if items found
                feed.total = feed.items.length;

                debug('Found ' + feed.total + ' item(s)');
                done();
            });
        });
    },
    function(results) {
        debug('-------------------------------------------------------------------------------');

        // filter out any results which we've already seen
        async.eachSeries(
            feeds,
            function(feed, callback) {
                // filter out any items we have already seen
                async.filter(
                    feed.items,
                    function(item, filter) {
                        // check if this feed.url exists in LevelDB and if not, allow it
                        db.get(item.link, function(err, value) {
                            if (err) {
                                if (err.name === 'NotFoundError' ) {
                                    debug('Putting ' + item.link);
                                    db.put(item.link, (new Date()).toISOString(), function(err) {
                                        filter(true);
                                    });
                                }
                                else {
                                    // something went wrong
                                    console.warn('Error:' + err);
                                    process.exit(2);
                                }
                            }
                            else {
                                // already exists
                                debug('Exists ' + item.link);
                                filter(false);
                            }
                        });
                    },
                    function(newItems) {
                        // finished filtering feed.items
                        feed.items = newItems;

                        // save the number if new items
                        feed.new = newItems.length;

                        callback();
                    }
                );
            },
            function(err) {
                // finally, print out all of the feeds as they now stand
                // console.log(util.inspect(feeds, false, null, false));

                feeds.forEach(function(feed) {
                    if ( feed.statusCode === 200 ) {
                        // see if there are any new feeds
                        if ( feed.new > 0 ) {
                            fmt.sep();
                            fmt.title(feed.url);
                            feed.items.forEach(function(item, i) {
                                fmt.li(item.title);
                                fmt.li(' -> ' + item.link + "\n");
                            });
                        }
                    }
                    else {
                        // something went wrong
                        fmt.sep();
                        fmt.title(feed.url);
                        fmt.field('status', feed.statusCode);
                        if ( feed.redirect ) {
                            fmt.field('redirect', feed.redirect);
                        }
                    }
                });

                fmt.sep();
            }
        );
    }
);

function request(feed, callback) {
    var protocol;
    if ( feed.url.match(/^http:\/\//) ) {
        protocol = http;
    }
    else if ( feed.url.match(/^https:\/\//) ) {
        protocol = https;
    }
    else {
        return callback(new Error('Unknown protocol'));
    }

    protocol.get(feed.url, function(res) {
        debug("Got response " + res.statusCode);

        // save the statusCode so we can see it
        feed.statusCode = res.statusCode;

        // if this looks a bit suspicious, return an error
        if ( res.statusCode !== 200 ) {
            if ( res.statusCode === 301 || res.statusCode === 302 ) {
                // save this so we can print it out later
                feed.redirect = res.headers.location;
            }
            return callback(new Error('Status code was not 200 (' + res.statusCode + ')'));
        }

        // everything looks ok
        callback(null, res);
    }).on('error', function(e) {
        callback(e);
    });
}

// ----------------------------------------------------------------------------

function debug(msg) {
    if ( false ) {
        console.log(msg);
    }
};
function log(msg) {
    console.log(msg);
}

function line() {
    console.log('-------------------------------------------------------------------------------');
}

// ----------------------------------------------------------------------------
<|endoftext|>


<commit_before>/**
 * Default Configuration
 *
 * Extend the class exported from this module to create your own local
 * configuration objects or use this directly.
 * A configuration file is any ES6 file that exports a single default
 * object containing all of the configuration options.
 *
 * Avoid requiring this file into any file that might need this one.
 * In general game logic should never need this, a config object should
 * be passed around through constructors.
 *
 * Try to keep the structure as flat as possible while grouping things logically
 */

import COLORS from '../constants/colors';
import WEBCOLORS from '../constants/webColors';
import GAMES from '../constants/games';
import assign from 'assign-deep';

export default class DefaultConfig {
  constructor({
    me = "",
    sculpture1 = "sculpture1",
    sculpture2 = "sculpture2",
    sculpture3 = "sculpture3",
  } = {}) {
    this.defaultSculptureId = 'anyware'; // Read-only sculpture
    this.sculpture1 = sculpture1;
    this.sculpture2 = sculpture2;
    this.sculpture3 = sculpture3;

    // The ID of this sculpture
    this.me = me || this.defaultSculptureId;

    this.DEBUG = {
      status: false,     // Persistent status icons
      console: false,    // Console debug output
    };

    // Will enter alone mode after this numner of seconds
    this.ALONE_MODE_SECONDS = 60;

    // A mapping between sculpture IDs and the colors that represent them
    this.COLORS = {
      LOCATIONS: {
        // sculpture : color
        [this.sculpture1]: COLORS.SCULPTURE1,
        [this.sculpture2]: COLORS.SCULPTURE2,
        [this.sculpture3]: COLORS.SCULPTURE3
      },
      ERROR: 'error'
    };

    // The sequence of the games to be run. The first game is run on startup
    this.GAMES_SEQUENCE = [
      GAMES.MOLE,
      GAMES.DISK,
      GAMES.SIMON
    ];

    /******* LIGHTS  ********************/
    this.LIGHTS = {
      // Name : strip Id (corresponds to hardware)
      STRIP_A: '0',
      STRIP_B: '1',
      STRIP_C: '2',
      RGB_STRIPS: '3',
      HANDSHAKE_STRIP: '5',
      ART_LIGHTS_STRIP: '6'
    };
    this.GAME_STRIPS = [
      this.LIGHTS.STRIP_A,
      this.LIGHTS.STRIP_B,
      this.LIGHTS.STRIP_C
    ];
    this.PANELS = {
      // stripId : [all associated panel Ids]
      [this.LIGHTS.STRIP_A]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_B]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_C]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    };

    // These settings effect the default behaviour of panels outside of
    // any custom logic in any of the games
    this.PANEL_DEFAULTS = {
      ACTIVE_INTENSITY: 100,
      INACTIVE_INTENSITY: 0
    };

    this.HANDSHAKE = {
      TRANSITION_OUT_TIME: 4000 // Time (ms) from handshake is touched until we start the next game
    };

    /******* GAMES CONFIGURATION *******/

    this.MOLE_GAME = {
      GAME_END: 30,
      INITIAL_PANELS: [
        {stripId: this.LIGHTS.STRIP_C, panelId: '3'},
        {stripId: this.LIGHTS.STRIP_C, panelId: '7'},
        {stripId: this.LIGHTS.STRIP_B, panelId: '6'},
        {stripId: this.LIGHTS.STRIP_A, panelId: '5'}
      ],
      NUM_ACTIVE_PANELS: {
//        10: 1, // At panelCount of 10, increase # of simultaneusly active panels
//        20: 1,
//        25: -1, // At panelCount of 25, decrease # of simultaneusly active panels
//        27: -1
      },
      PANEL_LIFETIME: [
        {count: 0, range: [10, 10]}, // Initial timeout
        {count: 4, range: [4, 6]}, // At panelCount of 4, set panel lifetime to 4-6 seconds. Gradually interpolate to next timeout level
        {count: 20, range: [2, 3]},
        {count: 30, range: [1.5, 2]}
      ],
      // How long to wait before enabling the next panel, on success
      PANEL_SUCCESS_DELAY: 1000,
      // How long to wait before enabling the next panel, on automatic panel move
      PANEL_MOVE_DELAY: 500,
      // The intensity to use on active panels
      ACTIVE_PANEL_INTENSITY: 100,
      // The intensity to use on inactive panels
      INACTIVE_PANEL_INTENSITY: 0,
      // The intensity to use on ignored panels (panels turned to location color)
      COLORED_PANEL_INTENSITY: 75,
    };

    this.DISK_GAME = {
      SPEEDS: [6, 15, 21, 30, 72], // degrees/sec
      SINGLE_DISK_TOLERANCE: 10, // tolerance (in degrees) for locking a single disk into place
      // The intensity of the panels that the user can use to play the sequence
      CONTROL_PANEL_COLOR: COLORS.WHITE,
      CONFLICT_INTENSITY: 20,
      CONTROL_PANEL_INTENSITY: 50,
      ACTIVE_CONTROL_PANEL_INTENSITY: 100,
      SHADOW_LIGHTS: {
        // stripId: [panelId..]
        [this.LIGHTS.ART_LIGHTS_STRIP]: ['0', '1', '2', '3']
      },
      SHADOW_LIGHT_INTENSITY: 100,
      LEVELS: [
        // rule: 'absolute' or 'relative'
        // disks: { diskId: initial position }

        // level 0
        { rule: 'absolute', disks: { disk0: -90, disk1: 90, disk2: 120 } },

        // level 1
        { rule: 'absolute', disks: { disk0: 90, disk1: -90, disk2: -120 } },

        // level 2
        { rule: 'absolute', disks: { disk0: 180, disk1: 45, disk2: -90 } },

      ],
      CONTROL_MAPPINGS: {
        STRIP_TO_DISK: {
          [this.LIGHTS.STRIP_A]: 'disk0',
          [this.LIGHTS.STRIP_B]: 'disk1',
          [this.LIGHTS.STRIP_C]: 'disk2',
        },
      }
    };

    this.SIMON_GAME = {
      PATTERN_LEVELS: [
        // level 0 sequence
        {
          stripId: this.LIGHTS.STRIP_C,
          // Each array of panel IDs is lit up one at a time
          // Each array within this array is called a "frame" in the "sequence"
          panelSequences: [
            ['1', '2', '3'],
            ['4', '5', '6'],
            ['7', '8', '9'],
          ],
          frameDelay: 750 // Overriding default frame delay to make first level slower
        },
        // level 1 sequence
        {
          stripId: this.LIGHTS.STRIP_B,
          panelSequences: [
            ['1', '8', '6'],
            ['2', '7', '0'],
            ['4', '9', '5'],
          ],
        },
        // level 2 sequence
        {
          stripId: this.LIGHTS.STRIP_A,
          panelSequences: [
            ['3', '6', '2', '9'],
            ['5', '1', '8', '7'],
            ['0', '4', '7', '6'],
          ],
        }
      ],
      // Can be set to null to disable the RGB strips
      RGB_STRIP: this.LIGHTS.RGB_STRIPS,
      // The intensity of the panels when they are pressed or when the sequence is playing
      TARGET_PANEL_INTENSITY: 100,
      // The intensity of the panels that the user can use to play the sequence
      AVAILABLE_PANEL_INTENSITY: 1,
      // The delay in ms between sequence frames
      SEQUENCE_ANIMATION_FRAME_DELAY: 500,
      // The delay in ms to wait before replaying the sequence
      // Only replayed if no input is received from the user
      DELAY_BETWEEN_PLAYS: 5000,
      // The time after input to wait for the user to finish the sequence
      INPUT_TIMEOUT: 10000,
      // The default color to set the panels to when
      DEFAULT_SIMON_PANEL_COLOR: COLORS.WHITE,
      // Wait while playing final sound
      TRANSITION_OUT_TIME: 10000
    };
  }

  getLocationColor(loc) {
    return this.COLORS.LOCATIONS[loc] || COLORS.BLACK;
  }

  getWebColor(name) {
    return WEBCOLORS[name];
  }

  applyLocalConfig(config) {
    assign(this, config);
  }
}

<commit_msg>Don't use 180 degrees due to shuffling bug<commit_after>/**
 * Default Configuration
 *
 * Extend the class exported from this module to create your own local
 * configuration objects or use this directly.
 * A configuration file is any ES6 file that exports a single default
 * object containing all of the configuration options.
 *
 * Avoid requiring this file into any file that might need this one.
 * In general game logic should never need this, a config object should
 * be passed around through constructors.
 *
 * Try to keep the structure as flat as possible while grouping things logically
 */

import COLORS from '../constants/colors';
import WEBCOLORS from '../constants/webColors';
import GAMES from '../constants/games';
import assign from 'assign-deep';

export default class DefaultConfig {
  constructor({
    me = "",
    sculpture1 = "sculpture1",
    sculpture2 = "sculpture2",
    sculpture3 = "sculpture3",
  } = {}) {
    this.defaultSculptureId = 'anyware'; // Read-only sculpture
    this.sculpture1 = sculpture1;
    this.sculpture2 = sculpture2;
    this.sculpture3 = sculpture3;

    // The ID of this sculpture
    this.me = me || this.defaultSculptureId;

    this.DEBUG = {
      status: false,     // Persistent status icons
      console: false,    // Console debug output
    };

    // Will enter alone mode after this numner of seconds
    this.ALONE_MODE_SECONDS = 60;

    // A mapping between sculpture IDs and the colors that represent them
    this.COLORS = {
      LOCATIONS: {
        // sculpture : color
        [this.sculpture1]: COLORS.SCULPTURE1,
        [this.sculpture2]: COLORS.SCULPTURE2,
        [this.sculpture3]: COLORS.SCULPTURE3
      },
      ERROR: 'error'
    };

    // The sequence of the games to be run. The first game is run on startup
    this.GAMES_SEQUENCE = [
      GAMES.MOLE,
      GAMES.DISK,
      GAMES.SIMON
    ];

    /******* LIGHTS  ********************/
    this.LIGHTS = {
      // Name : strip Id (corresponds to hardware)
      STRIP_A: '0',
      STRIP_B: '1',
      STRIP_C: '2',
      RGB_STRIPS: '3',
      HANDSHAKE_STRIP: '5',
      ART_LIGHTS_STRIP: '6'
    };
    this.GAME_STRIPS = [
      this.LIGHTS.STRIP_A,
      this.LIGHTS.STRIP_B,
      this.LIGHTS.STRIP_C
    ];
    this.PANELS = {
      // stripId : [all associated panel Ids]
      [this.LIGHTS.STRIP_A]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_B]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_C]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    };

    // These settings effect the default behaviour of panels outside of
    // any custom logic in any of the games
    this.PANEL_DEFAULTS = {
      ACTIVE_INTENSITY: 100,
      INACTIVE_INTENSITY: 0
    };

    this.HANDSHAKE = {
      TRANSITION_OUT_TIME: 4000 // Time (ms) from handshake is touched until we start the next game
    };

    /******* GAMES CONFIGURATION *******/

    this.MOLE_GAME = {
      GAME_END: 30,
      INITIAL_PANELS: [
        {stripId: this.LIGHTS.STRIP_C, panelId: '3'},
        {stripId: this.LIGHTS.STRIP_C, panelId: '7'},
        {stripId: this.LIGHTS.STRIP_B, panelId: '6'},
        {stripId: this.LIGHTS.STRIP_A, panelId: '5'}
      ],
      NUM_ACTIVE_PANELS: {
//        10: 1, // At panelCount of 10, increase # of simultaneusly active panels
//        20: 1,
//        25: -1, // At panelCount of 25, decrease # of simultaneusly active panels
//        27: -1
      },
      PANEL_LIFETIME: [
        {count: 0, range: [10, 10]}, // Initial timeout
        {count: 4, range: [4, 6]}, // At panelCount of 4, set panel lifetime to 4-6 seconds. Gradually interpolate to next timeout level
        {count: 20, range: [2, 3]},
        {count: 30, range: [1.5, 2]}
      ],
      // How long to wait before enabling the next panel, on success
      PANEL_SUCCESS_DELAY: 1000,
      // How long to wait before enabling the next panel, on automatic panel move
      PANEL_MOVE_DELAY: 500,
      // The intensity to use on active panels
      ACTIVE_PANEL_INTENSITY: 100,
      // The intensity to use on inactive panels
      INACTIVE_PANEL_INTENSITY: 0,
      // The intensity to use on ignored panels (panels turned to location color)
      COLORED_PANEL_INTENSITY: 75,
    };

    this.DISK_GAME = {
      SPEEDS: [6, 15, 21, 30, 72], // degrees/sec
      SINGLE_DISK_TOLERANCE: 10, // tolerance (in degrees) for locking a single disk into place
      // The intensity of the panels that the user can use to play the sequence
      CONTROL_PANEL_COLOR: COLORS.WHITE,
      CONFLICT_INTENSITY: 20,
      CONTROL_PANEL_INTENSITY: 50,
      ACTIVE_CONTROL_PANEL_INTENSITY: 100,
      SHADOW_LIGHTS: {
        // stripId: [panelId..]
        [this.LIGHTS.ART_LIGHTS_STRIP]: ['0', '1', '2', '3']
      },
      SHADOW_LIGHT_INTENSITY: 100,
      LEVELS: [
        // rule: 'absolute' or 'relative'
        // disks: { diskId: initial position }

        // level 0
        { rule: 'absolute', disks: { disk0: -90, disk1: 90, disk2: 120 } },

        // level 1
        { rule: 'absolute', disks: { disk0: 90, disk1: -90, disk2: -120 } },

        // level 2
        { rule: 'absolute', disks: { disk0: 170, disk1: 45, disk2: -90 } },

      ],
      CONTROL_MAPPINGS: {
        STRIP_TO_DISK: {
          [this.LIGHTS.STRIP_A]: 'disk0',
          [this.LIGHTS.STRIP_B]: 'disk1',
          [this.LIGHTS.STRIP_C]: 'disk2',
        },
      }
    };

    this.SIMON_GAME = {
      PATTERN_LEVELS: [
        // level 0 sequence
        {
          stripId: this.LIGHTS.STRIP_C,
          // Each array of panel IDs is lit up one at a time
          // Each array within this array is called a "frame" in the "sequence"
          panelSequences: [
            ['1', '2', '3'],
            ['4', '5', '6'],
            ['7', '8', '9'],
          ],
          frameDelay: 750 // Overriding default frame delay to make first level slower
        },
        // level 1 sequence
        {
          stripId: this.LIGHTS.STRIP_B,
          panelSequences: [
            ['1', '8', '6'],
            ['2', '7', '0'],
            ['4', '9', '5'],
          ],
        },
        // level 2 sequence
        {
          stripId: this.LIGHTS.STRIP_A,
          panelSequences: [
            ['3', '6', '2', '9'],
            ['5', '1', '8', '7'],
            ['0', '4', '7', '6'],
          ],
        }
      ],
      // Can be set to null to disable the RGB strips
      RGB_STRIP: this.LIGHTS.RGB_STRIPS,
      // The intensity of the panels when they are pressed or when the sequence is playing
      TARGET_PANEL_INTENSITY: 100,
      // The intensity of the panels that the user can use to play the sequence
      AVAILABLE_PANEL_INTENSITY: 1,
      // The delay in ms between sequence frames
      SEQUENCE_ANIMATION_FRAME_DELAY: 500,
      // The delay in ms to wait before replaying the sequence
      // Only replayed if no input is received from the user
      DELAY_BETWEEN_PLAYS: 5000,
      // The time after input to wait for the user to finish the sequence
      INPUT_TIMEOUT: 10000,
      // The default color to set the panels to when
      DEFAULT_SIMON_PANEL_COLOR: COLORS.WHITE,
      // Wait while playing final sound
      TRANSITION_OUT_TIME: 10000
    };
  }

  getLocationColor(loc) {
    return this.COLORS.LOCATIONS[loc] || COLORS.BLACK;
  }

  getWebColor(name) {
    return WEBCOLORS[name];
  }

  applyLocalConfig(config) {
    assign(this, config);
  }
}

<|endoftext|>


<commit_before>/**
 * Default Configuration
 *
 * Extend the class exported from this module to create your own local
 * configuration objects or use this directly.
 * A configuration file is any ES6 file that exports a single default
 * object containing all of the configuration options.
 *
 * Avoid requiring this file into any file that might need this one.
 * In general game logic should never need this, a config object should
 * be passed around through constructors.
 *
 * Try to keep the structure as flat as possible while grouping things logically
 */

import COLORS from '../constants/colors';
import GAMES from '../constants/games';

export default class DefaultConfig {
  constructor({
      username="",
      user0="sculpture0",
      user1="sculpture1",
      user2="sculpture2"
  } = {}) {
    this.user0 = user0;
    this.user1 = user1;
    this.user2 = user2;

    // The username of the current user
    this.username = username || user0;

    // A mapping between usernames and the colors that represent them
    this.COLORS = {
      USERS: {
        // username : color
        [this.user0]: COLORS.USER0,
        [this.user1]: COLORS.USER1,
        [this.user2]: COLORS.USER2
      },
      ERROR: 'error'
    };

    // The sequence of the games to be run. The first game is run on startup
    this.GAMES_SEQUENCE = [
      GAMES.HANDSHAKE,
      GAMES.MOLE,
      GAMES.DISK,
      GAMES.SIMON
    ];

    /******* LIGHTS  ********************/
    this.LIGHTS = {
      // Name : strip Id (corresponds to hardware)
      STRIP_A: '0',
      STRIP_B: '1',
      STRIP_C: '2',
      HANDSHAKE_STRIP: '5',
      ART_LIGHTS_STRIP: '6'
    };
    this.GAME_STRIPS = [
      this.LIGHTS.STRIP_A,
      this.LIGHTS.STRIP_B,
      this.LIGHTS.STRIP_C
    ];
    this.PANELS = {
      // stripId : [all associated panel Ids]
      [this.LIGHTS.STRIP_A]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_B]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_C]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    };

    // These settings effect the default behaviour of panels outside of
    // any custom logic in any of the games
    this.PANEL_DEFAULTS = {
      ACTIVE_INTENSITY: 100,
      INACTIVE_INTENSITY: 0
    };

    /******* GAMES CONFIGURATION *******/

    this.HANDSHAKE_GAME = {
      TRANSITION_OUT_TIME: 4000 // Time (ms) from handshake is touched until we start the next game
    };
    this.MOLE_GAME = {
      GAME_END: 30,
      INITIAL_PANELS: [
        {stripId: this.LIGHTS.STRIP_A, panelId: '3'},
        {stripId: this.LIGHTS.STRIP_A, panelId: '7'},
        {stripId: this.LIGHTS.STRIP_C, panelId: '6'},
        {stripId: this.LIGHTS.STRIP_B, panelId: '5'}
      ],
      NUM_ACTIVE_PANELS: {
        10: 1, // At panelCount of 10, increase # of simultaneusly active panels
        20: 1,
        25: -1, // At panelCount of 25, decrease # of simultaneusly active panels
        27: -1
      },
      PANEL_LIFETIME: [
        {count: 0, range: [10, 10]}, // Initial timeout
        {count: 4, range: [4, 6]}, // At panelCount of 4, set panel lifetime to 4-6 seconds. Gradually interpolate to next timeout level
        {count: 20, range: [2, 3]},
        {count: 30, range: [1.5, 2]}
      ],
      // How long to wait before enabling the next panel, on success
      PANEL_SUCCESS_DELAY: 1000,
      // How long to wait before enabling the next panel, on automatic panel move
      PANEL_MOVE_DELAY: 200,
      // The intensity to use on active panels
      ACTIVE_PANEL_INTENSITY: 100,
      // The intensity to use on inactive panels
      INACTIVE_PANEL_INTENSITY: 0,
      // The intensity to use on ignored panels (panels turned to location color)
      COLORED_PANEL_INTENSITY: 75,
      // We don't use failure sounds in the default setup due to too many accidental touches
      ENABLE_FAILURE_SOUND: false
    };

    this.DISK_GAME = {
      MAX_SPEED: 360 / 5, // degrees/sec
      RELATIVE_TOLERANCE: 5, // degrees tolerance for disks relative to each other
      ABSOLUTE_TOLERANCE: 8, // degrees tolerance for the absolute disk positions
      // The intensity of the panels that the user can use to play the sequence
      CONTROL_PANEL_COLOR: COLORS.WHITE,
      CONTROL_PANEL_INTENSITY: 20,
      CONFLICT_INTENSITY: 20,
      ACTIVE_CONTROL_PANEL_INTENSITY: 100,
      SHADOW_LIGHTS: {
        // stripId: [panelId..]
        '6': ['0', '1', '2']
      },
      SHADOW_LIGHT_INTENSITY: 100,
      LEVELS: [
        // level 0
        {
          marker0: {
            disk0: [310, 156],
            disk1: [133, 48],
            disk2: [168, 0],
          },
          marker1: {
            disk0: [347, 85],
            disk1: [211, 265],
            disk2: [165, 291],
          },
          marker2: {
            disk0: [352, 173],
            disk1: [168, 270],
            disk2: [182, 235],
          },
        },
      ],
      CONTROL_MAPPINGS: {
        STRIP_TO_DISK: {
          [this.LIGHTS.STRIP_A]: 'disk0',
          [this.LIGHTS.STRIP_B]: 'disk1',
          [this.LIGHTS.STRIP_C]: 'disk2',
        },
      }
    };

    this.SIMON_GAME = {
      PATTERN_LEVELS: [
        // level 0 sequence
        {
          stripId: this.LIGHTS.STRIP_A,
          // Each array of panel IDs is lit up one at a time
          // Each array within this array is called a "frame" in the "sequence"
          panelSequence: [['3'], ['5'], ['7']],
          frameDelay: 750 // Overriding default frame delay to make first level slower
        },
        // level 1 sequence
        {
          stripId: '1',
          panelSequence: [['1'], ['8'], ['5']]
        },
        // level 2 sequence
        {
          stripId: '2',
          panelSequence: [['3'], ['6'], ['2'], ['9']]
        }
      ],
      // The intensity of the panels when they are pressed or when the sequence is playing
      TARGET_PANEL_INTENSITY: 100,
      // The intensity of the panels that the user can use to play the sequence
      AVAILABLE_PANEL_INTENSITY: 1,
      // The delay in ms between sequence frames
      SEQUENCE_ANIMATION_FRAME_DELAY: 500,
      // The delay in ms to wait before replaying the sequence
      // Only replayed if no input is received from the user
      DELAY_BETWEEN_PLAYS: 5000,
      // The time after input to wait for the user to finish the sequence
      INPUT_TIMEOUT: 10000,
      // The default color to set the panels to when
      DEFAULT_SIMON_PANEL_COLOR: COLORS.WHITE,
      // Wait while playing final sound
      TRANSITION_OUT_TIME: 10000
    };
  }

  getUserColor(username) {
    return this.COLORS.USERS[username];
  }
}

<commit_msg>Cosmetics<commit_after>/**
 * Default Configuration
 *
 * Extend the class exported from this module to create your own local
 * configuration objects or use this directly.
 * A configuration file is any ES6 file that exports a single default
 * object containing all of the configuration options.
 *
 * Avoid requiring this file into any file that might need this one.
 * In general game logic should never need this, a config object should
 * be passed around through constructors.
 *
 * Try to keep the structure as flat as possible while grouping things logically
 */

import COLORS from '../constants/colors';
import GAMES from '../constants/games';

export default class DefaultConfig {
  constructor({
    username = "",
    user0 = "sculpture0",
    user1 = "sculpture1",
    user2 = "sculpture2",
  } = {}) {
    this.user0 = user0;
    this.user1 = user1;
    this.user2 = user2;

    // The username of the current user
    this.username = username || user0;

    // A mapping between usernames and the colors that represent them
    this.COLORS = {
      USERS: {
        // username : color
        [this.user0]: COLORS.USER0,
        [this.user1]: COLORS.USER1,
        [this.user2]: COLORS.USER2
      },
      ERROR: 'error'
    };

    // The sequence of the games to be run. The first game is run on startup
    this.GAMES_SEQUENCE = [
      GAMES.HANDSHAKE,
      GAMES.MOLE,
      GAMES.DISK,
      GAMES.SIMON
    ];

    /******* LIGHTS  ********************/
    this.LIGHTS = {
      // Name : strip Id (corresponds to hardware)
      STRIP_A: '0',
      STRIP_B: '1',
      STRIP_C: '2',
      HANDSHAKE_STRIP: '5',
      ART_LIGHTS_STRIP: '6'
    };
    this.GAME_STRIPS = [
      this.LIGHTS.STRIP_A,
      this.LIGHTS.STRIP_B,
      this.LIGHTS.STRIP_C
    ];
    this.PANELS = {
      // stripId : [all associated panel Ids]
      [this.LIGHTS.STRIP_A]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_B]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
      [this.LIGHTS.STRIP_C]: ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
    };

    // These settings effect the default behaviour of panels outside of
    // any custom logic in any of the games
    this.PANEL_DEFAULTS = {
      ACTIVE_INTENSITY: 100,
      INACTIVE_INTENSITY: 0
    };

    /******* GAMES CONFIGURATION *******/

    this.HANDSHAKE_GAME = {
      TRANSITION_OUT_TIME: 4000 // Time (ms) from handshake is touched until we start the next game
    };
    this.MOLE_GAME = {
      GAME_END: 30,
      INITIAL_PANELS: [
        {stripId: this.LIGHTS.STRIP_A, panelId: '3'},
        {stripId: this.LIGHTS.STRIP_A, panelId: '7'},
        {stripId: this.LIGHTS.STRIP_C, panelId: '6'},
        {stripId: this.LIGHTS.STRIP_B, panelId: '5'}
      ],
      NUM_ACTIVE_PANELS: {
        10: 1, // At panelCount of 10, increase # of simultaneusly active panels
        20: 1,
        25: -1, // At panelCount of 25, decrease # of simultaneusly active panels
        27: -1
      },
      PANEL_LIFETIME: [
        {count: 0, range: [10, 10]}, // Initial timeout
        {count: 4, range: [4, 6]}, // At panelCount of 4, set panel lifetime to 4-6 seconds. Gradually interpolate to next timeout level
        {count: 20, range: [2, 3]},
        {count: 30, range: [1.5, 2]}
      ],
      // How long to wait before enabling the next panel, on success
      PANEL_SUCCESS_DELAY: 1000,
      // How long to wait before enabling the next panel, on automatic panel move
      PANEL_MOVE_DELAY: 200,
      // The intensity to use on active panels
      ACTIVE_PANEL_INTENSITY: 100,
      // The intensity to use on inactive panels
      INACTIVE_PANEL_INTENSITY: 0,
      // The intensity to use on ignored panels (panels turned to location color)
      COLORED_PANEL_INTENSITY: 75,
      // We don't use failure sounds in the default setup due to too many accidental touches
      ENABLE_FAILURE_SOUND: false
    };

    this.DISK_GAME = {
      MAX_SPEED: 360 / 5, // degrees/sec
      RELATIVE_TOLERANCE: 5, // degrees tolerance for disks relative to each other
      ABSOLUTE_TOLERANCE: 8, // degrees tolerance for the absolute disk positions
      // The intensity of the panels that the user can use to play the sequence
      CONTROL_PANEL_COLOR: COLORS.WHITE,
      CONTROL_PANEL_INTENSITY: 20,
      CONFLICT_INTENSITY: 20,
      ACTIVE_CONTROL_PANEL_INTENSITY: 100,
      SHADOW_LIGHTS: {
        // stripId: [panelId..]
        '6': ['0', '1', '2']
      },
      SHADOW_LIGHT_INTENSITY: 100,
      LEVELS: [
        // level 0
        {
          marker0: {
            disk0: [310, 156],
            disk1: [133, 48],
            disk2: [168, 0],
          },
          marker1: {
            disk0: [347, 85],
            disk1: [211, 265],
            disk2: [165, 291],
          },
          marker2: {
            disk0: [352, 173],
            disk1: [168, 270],
            disk2: [182, 235],
          },
        },
      ],
      CONTROL_MAPPINGS: {
        STRIP_TO_DISK: {
          [this.LIGHTS.STRIP_A]: 'disk0',
          [this.LIGHTS.STRIP_B]: 'disk1',
          [this.LIGHTS.STRIP_C]: 'disk2',
        },
      }
    };

    this.SIMON_GAME = {
      PATTERN_LEVELS: [
        // level 0 sequence
        {
          stripId: this.LIGHTS.STRIP_A,
          // Each array of panel IDs is lit up one at a time
          // Each array within this array is called a "frame" in the "sequence"
          panelSequence: [['3'], ['5'], ['7']],
          frameDelay: 750 // Overriding default frame delay to make first level slower
        },
        // level 1 sequence
        {
          stripId: '1',
          panelSequence: [['1'], ['8'], ['5']]
        },
        // level 2 sequence
        {
          stripId: '2',
          panelSequence: [['3'], ['6'], ['2'], ['9']]
        }
      ],
      // The intensity of the panels when they are pressed or when the sequence is playing
      TARGET_PANEL_INTENSITY: 100,
      // The intensity of the panels that the user can use to play the sequence
      AVAILABLE_PANEL_INTENSITY: 1,
      // The delay in ms between sequence frames
      SEQUENCE_ANIMATION_FRAME_DELAY: 500,
      // The delay in ms to wait before replaying the sequence
      // Only replayed if no input is received from the user
      DELAY_BETWEEN_PLAYS: 5000,
      // The time after input to wait for the user to finish the sequence
      INPUT_TIMEOUT: 10000,
      // The default color to set the panels to when
      DEFAULT_SIMON_PANEL_COLOR: COLORS.WHITE,
      // Wait while playing final sound
      TRANSITION_OUT_TIME: 10000
    };
  }

  getUserColor(username) {
    return this.COLORS.USERS[username];
  }
}

<|endoftext|>


<commit_before>export function toDashedString(value) {
    return value.toLowerCase().replace('_', '-');
}

export function toSpacedString(value) {
    return value.replace('_', '\xa0'); // \xa0 ===
}

export function toTitleCase(value) {
    return value
        .split(/(\s+)/)
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
}

export function isSameLocation(a, b) {
    return a.lat === b.lat && a.lng === b.lng;
}

function isLastInSequence(s) {
    let isLast = true;

    [...s].forEach((c) => {
        if (c !== 'Z') {
            isLast = false;
        }
    });

    return isLast;
}

function getNextLetter(c) {
    const cc = c.charCodeAt(0);

    if (cc < 90) {
        return String.fromCharCode(cc + 1);
    }

    return 'A';
}

// Adapted from https://stackoverflow.com/a/34483399/6995854
export function getNextInSequence(s) {
    if (isLastInSequence(s)) {
        return 'A'.repeat(s.length + 1);
    }

    const prefix = s.slice(0, -1);
    const penultimate = prefix.slice(-1);
    const final = s.slice(-1);

    if (final === 'Z') {
        return prefix.slice(0, -1)
            + getNextLetter(penultimate)
            + getNextLetter(final);
    }

    return prefix + getNextLetter(final);
}

export function getMarkerClass({ selected, starred, surveyed }) {
    if (surveyed) {
        return 'marker--starred-survey';
    }

    if (starred) {
        return 'marker--starred';
    }

    if (selected) {
        return 'marker--selected';
    }

    return '';
}

export const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
    'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

export function monthToText(month) {
    // Counts months from 0-11
    return monthNames[month];
}

export function listMonthYearsSinceCreation(apiary) {
    // Count months from 0-11, where January is 0
    const createdYear = Number(apiary.created_at.substring(0, 4));
    const createdMonth = Number(apiary.created_at.substring(5, 7)) + 1;

    // The Date API counts months from 0
    const timeNow = new Date();
    const monthNow = timeNow.getMonth();
    const yearNow = timeNow.getFullYear();

    let months = 0;
    months = (yearNow - createdYear) * 12;
    months -= createdMonth;
    months += monthNow;
    const monthDiff = months <= 0 ? 0 : months;

    let monthCounter = monthNow;
    let yearCounter = yearNow;
    const monthYears = [];
    let i = 0;
    for (i = 0; i < monthDiff + 1; i += 1) {
        monthYears.push(`${monthToText(monthCounter)}-${String(yearCounter)}`);
        monthCounter -= 1;
        if (monthCounter < 1) {
            yearCounter -= 1;
            monthCounter = 11;
        }
    }

    return monthYears;
}
<commit_msg>Use full month names in UI for survey cards<commit_after>export function toDashedString(value) {
    return value.toLowerCase().replace('_', '-');
}

export function toSpacedString(value) {
    return value.replace('_', '\xa0'); // \xa0 ===
}

export function toTitleCase(value) {
    return value
        .split(/(\s+)/)
        .map(word => word.charAt(0).toUpperCase() + word.slice(1))
        .join('');
}

export function isSameLocation(a, b) {
    return a.lat === b.lat && a.lng === b.lng;
}

function isLastInSequence(s) {
    let isLast = true;

    [...s].forEach((c) => {
        if (c !== 'Z') {
            isLast = false;
        }
    });

    return isLast;
}

function getNextLetter(c) {
    const cc = c.charCodeAt(0);

    if (cc < 90) {
        return String.fromCharCode(cc + 1);
    }

    return 'A';
}

// Adapted from https://stackoverflow.com/a/34483399/6995854
export function getNextInSequence(s) {
    if (isLastInSequence(s)) {
        return 'A'.repeat(s.length + 1);
    }

    const prefix = s.slice(0, -1);
    const penultimate = prefix.slice(-1);
    const final = s.slice(-1);

    if (final === 'Z') {
        return prefix.slice(0, -1)
            + getNextLetter(penultimate)
            + getNextLetter(final);
    }

    return prefix + getNextLetter(final);
}

export function getMarkerClass({ selected, starred, surveyed }) {
    if (surveyed) {
        return 'marker--starred-survey';
    }

    if (starred) {
        return 'marker--starred';
    }

    if (selected) {
        return 'marker--selected';
    }

    return '';
}

export const monthNames = ['January', 'February', 'March', 'April', 'May',
    'June', 'July', 'August', 'September', 'October', 'November', 'December'];

export function monthToText(month) {
    // Counts months from 0-11
    return monthNames[month];
}

export function listMonthYearsSinceCreation(apiary) {
    // Count months from 0-11, where January is 0
    const createdYear = Number(apiary.created_at.substring(0, 4));
    const createdMonth = Number(apiary.created_at.substring(5, 7)) + 1;

    // The Date API counts months from 0
    const timeNow = new Date();
    const monthNow = timeNow.getMonth();
    const yearNow = timeNow.getFullYear();

    let months = 0;
    months = (yearNow - createdYear) * 12;
    months -= createdMonth;
    months += monthNow;
    const monthDiff = months <= 0 ? 0 : months;

    let monthCounter = monthNow;
    let yearCounter = yearNow;
    const monthYears = [];
    let i = 0;
    for (i = 0; i < monthDiff + 1; i += 1) {
        monthYears.push(`${monthToText(monthCounter)}-${String(yearCounter)}`);
        monthCounter -= 1;
        if (monthCounter < 1) {
            yearCounter -= 1;
            monthCounter = 11;
        }
    }

    return monthYears;
}
<|endoftext|>


<commit_before>const CommonFunctions = require('../../../_common/common_functions');
const localize        = require('../../../_common/localize').localize;

/*
 * Handle Reset option
 */
const Reset = (() => {
    /*
     * Displays reset time
     */
    const displayResetTime = (duration_value, duration_unit) => {
        let reset_time_str = '';
        let val;

        const mid_point = duration_value / 2; // a Reset happens at midpoint

        const duration_map = DurationMap.get();

        if (mid_point % 1 !== 0 && /[mh]/.test(duration_unit)) {
            val = Math.floor(mid_point);
            switch (duration_unit) {
                case 'm':
                    reset_time_str = `${val ? `${val} ${duration_map.m}` : ''} 30 ${duration_map.s}`;
                    break;
                case 'h':
                    reset_time_str = `${val || ''} 30 ${duration_map.m}`;
                    break;
                default: // no default
            }
        } else {
            val = (duration_unit === 't') ? Math.floor(mid_point) : Math.ceil(mid_point);
            reset_time_str = `${val} ${duration_map[duration_unit]}`;
        }

        CommonFunctions.getElementById('reset_time')
            .html(localize('The reset time is [_1]', reset_time_str))
            .setAttribute('style', '');
    };

    const DurationMap = (() => {
        let duration_map;

        const initDurationMap = () => ({
            s: localize('seconds'),
            m: localize('minutes'),
            h: localize('hour'), // currently the max possible Reset time for h is 1hr
            t: localize('ticks'),
        });

        return {
            get: () => {
                if (!duration_map) {
                    duration_map = initDurationMap();
                }
                return duration_map;
            },
        };
    })();

    const hideResetTime = () => { CommonFunctions.getElementById('reset_time').style.display = 'none'; };

    const isNewBarrier = (entry_barrier, current_barrier) => (+entry_barrier !== +current_barrier);

    const isReset = (contract_type) => /reset/i.test(contract_type);

    return {
        displayResetTime,
        hideResetTime,
        isNewBarrier,
        isReset,
    };
})();

module.exports = Reset;
<commit_msg>Fixed issue where reset barrier was shown inappropriately<commit_after>const CommonFunctions = require('../../../_common/common_functions');
const localize        = require('../../../_common/localize').localize;

/*
 * Handle Reset option
 */
const Reset = (() => {
    /*
     * Displays reset time
     */
    const displayResetTime = (duration_value, duration_unit) => {
        let reset_time_str = '';
        let val;

        const mid_point = duration_value / 2; // a Reset happens at midpoint

        const duration_map = DurationMap.get();

        if (mid_point % 1 !== 0 && /[mh]/.test(duration_unit)) {
            val = Math.floor(mid_point);
            switch (duration_unit) {
                case 'm':
                    reset_time_str = `${val ? `${val} ${duration_map.m}` : ''} 30 ${duration_map.s}`;
                    break;
                case 'h':
                    reset_time_str = `${val || ''} 30 ${duration_map.m}`;
                    break;
                default: // no default
            }
        } else {
            val = (duration_unit === 't') ? Math.floor(mid_point) : Math.ceil(mid_point);
            reset_time_str = `${val} ${duration_map[duration_unit]}`;
        }

        CommonFunctions.getElementById('reset_time')
            .html(localize('The reset time is [_1]', reset_time_str))
            .setAttribute('style', '');
    };

    const DurationMap = (() => {
        let duration_map;

        const initDurationMap = () => ({
            s: localize('seconds'),
            m: localize('minutes'),
            h: localize('hour'), // currently the max possible Reset time for h is 1hr
            t: localize('ticks'),
        });

        return {
            get: () => {
                if (!duration_map) {
                    duration_map = initDurationMap();
                }
                return duration_map;
            },
        };
    })();

    const hideResetTime = () => { CommonFunctions.getElementById('reset_time').style.display = 'none'; };

    const isNewBarrier = (entry_barrier, current_barrier) => (+(entry_barrier.toString().replace(',', '')) !== +(current_barrier.toString().replace(',', '')));

    const isReset = (contract_type) => /reset/i.test(contract_type);

    return {
        displayResetTime,
        hideResetTime,
        isNewBarrier,
        isReset,
    };
})();

module.exports = Reset;
<|endoftext|>


<commit_before>var dependencies = ['$scope', 'growl', '$analytics', '$state', 'currentUser', 'Community'];
dependencies.push(function($scope, growl, $analytics, $state, currentUser, Community) {

  var user = $scope.user = currentUser,
    editing = $scope.editing = {},
    edited = $scope.edited = {};

  $analytics.eventTrack('User Settings: Viewed');

  $scope.close = function() {
    $state.go('profile', {id: user.id});
  };

  $scope.needsRevalidation = function() {
    return user.provider_key == 'password';
  };

  $scope.edit = function(field) {
    edited[field] = user[field];
    editing[field] = true;
  };

  $scope.cancelEdit = function(field) {
    editing[field] = false;
  };

  $scope.saveEdit = function(field) {
    editing[field] = false;
    var data = {};
    data[field] = edited[field];

    user.update(data, function() {
      user[field] = edited[field];
      $analytics.eventTrack('User Settings: Changed ' + field, {user_id: user.id});
      growl.addSuccessMessage('Saved change.');
      if (field === 'email' && $scope.needsRevalidation()) {
        growl.addSuccessMessage('Reloading...')
        setTimeout(function() {
          window.location = '/accounts/unverified';
        }, 3000);
      }
    }, function(err) {
      growl.addErrorMessage(err.data);
    });
  };

  $scope.toggle = function(field) {
    var data = {};
    data[field] = user[field];
    user.update(data, function() {
      growl.addSuccessMessage('Saved change.');
    }, function(err) {
      growl.addErrorMessage(err.data);
    })
  };

  $scope.leaveCommunity = function(communityId, index) {
    if (!confirm('Are you sure you want to leave this community?'))
      return;

    Community.removeMember({id: communityId, userId: user.id}, function() {
      user.memberships.splice(index, 1);
    }, function(err) {
      growl.addErrorMessage(err.data);
    });
  };

});

module.exports = function(angularModule) {
  angularModule.controller('UserSettingsCtrl', dependencies);
};
<commit_msg>make user settings close button respect history<commit_after>var controller = function($scope, growl, $analytics, currentUser, Community, $history) {

  var user = $scope.user = currentUser,
    editing = $scope.editing = {},
    edited = $scope.edited = {};

  $analytics.eventTrack('User Settings: Viewed');

  $scope.close = function() {
    if ($history.isEmpty()) {
      $scope.$state.go('profile', {id: user.id});
    } else {
      $history.go(-1);
    }
  };

  $scope.needsRevalidation = function() {
    return user.provider_key == 'password';
  };

  $scope.edit = function(field) {
    edited[field] = user[field];
    editing[field] = true;
  };

  $scope.cancelEdit = function(field) {
    editing[field] = false;
  };

  $scope.saveEdit = function(field) {
    editing[field] = false;
    var data = {};
    data[field] = edited[field];

    user.update(data, function() {
      user[field] = edited[field];
      $analytics.eventTrack('User Settings: Changed ' + field, {user_id: user.id});
      growl.addSuccessMessage('Saved change.');
      if (field === 'email' && $scope.needsRevalidation()) {
        growl.addSuccessMessage('Reloading...')
        setTimeout(function() {
          window.location = '/accounts/unverified';
        }, 3000);
      }
    }, function(err) {
      growl.addErrorMessage(err.data);
    });
  };

  $scope.toggle = function(field) {
    var data = {};
    data[field] = user[field];
    user.update(data, function() {
      growl.addSuccessMessage('Saved change.');
    }, function(err) {
      growl.addErrorMessage(err.data);
    })
  };

  $scope.leaveCommunity = function(communityId, index) {
    if (!confirm('Are you sure you want to leave this community?'))
      return;

    Community.removeMember({id: communityId, userId: user.id}, function() {
      user.memberships.splice(index, 1);
    }, function(err) {
      growl.addErrorMessage(err.data);
    });
  };

};

module.exports = function(angularModule) {
  angularModule.controller('UserSettingsCtrl', controller);
};
<|endoftext|>