<commit_before>import {compose} from 'compose'
import {activatable} from 'widget/activation/activatable'
import React from 'react'
import styles from './layersMenu.module.css'
import Labels from './labels'
import {Menu} from './menu'
import {select} from 'store'
import PropTypes from 'prop-types'
import {changeBaseLayer} from './baseLayer'
import {msg} from 'translate'
import ButtonSelect from 'widget/buttonSelect'
import {Form, form} from 'widget/form/form'
import {googleMap, sepalMap} from 'app/home/map/map'
import moment from 'moment'
import {getNorwayPlanetApiKey} from './map'
import actionBuilder from '../../../action-builder'

const fields = {
    // year: new Form.Field(),
    // month: new Form.Field(),
    dateRange: new Form.Field(),
    proc: new Form.Field()
}

const mapStateToProps = (state, ownProps) => ({
    labelsShown: select([ownProps.statePath, 'labelsShown']),
    baseLayer: select([ownProps.statePath, 'baseLayer']),
    overlayIndex: select([ownProps.statePath, 'overlayIndex']),
    dateRange: select([ownProps.statePath, 'mapLayer.dateRange']),
    proc: select([ownProps.statePath, 'mapLayer.proc'])
    // year: select([ownProps.statePath, 'options.year']),
    // month: select([ownProps.statePath, 'options.month'])
})

class _LayersMenu extends React.Component {

    getPlanetDateOptions() {
        // const {inputs: {year}} = this.props
        // const startDate = moment('2018-08-01')
        // const endDate = moment().subtract(1, 'months')
        //
        // const yearOptions = sequence(startDate.year(), moment().year())
        //     .map(year => ({value: `${year}`, label: `${year}`}))
        //
        // const allMonthOptions = moment.monthsShort()
        //     .map((label, i) => ({value: `${i + 1}`.padStart(2, '0'), label}))
        //
        // const monthOptions = year.value === `${startDate.year()}`
        //     ? allMonthOptions.slice(startDate.month() + 1)
        //     : year.value === `${endDate.year()}`
        //         ? allMonthOptions.slice(0, endDate.month() + 1)
        //         : allMonthOptions
        // return {yearOptions, monthOptions}

        const procOptions = [
            {value: 'rgb', label: 'RGB'},
            {value: 'cir', label: 'CIR'},
        ]
        const dateRangeOptions = [
            {value: '2018-12_2019-05', label: 'Dec 2018 (6 months)'},
            {value: '2019-06_2019-11', label: 'Jun 2019 (6 months)'},
            {value: '2019-12_2020-05', label: 'Dec 2019 (6 months)'},
            {value: '2020-06_2020-08', label: 'Jun 2020 (3 months)'},
            {value: '2020-09', label: 'Sep 2020 (1 month)'},
        ]
        return {procOptions, dateRangeOptions}
    }

    toggleOverlay = ({selectedOverlayIndex, mapContext, statePath}) => {
        const context = sepalMap.getContext(mapContext)
        const overlays = context.toggleableLayers()

        const prevOverlayIndex = this.selectedOverlayIndex()
        if (selectedOverlayIndex !== prevOverlayIndex) {
            const prevOverlay = overlays.find(layer => layer.layerIndex === prevOverlayIndex)
            prevOverlay && prevOverlay.hide(googleMap, true)
        }

        const selectedOverlay = overlays.find(layer => layer.layerIndex === selectedOverlayIndex)
        const hide = prevOverlayIndex === selectedOverlayIndex
        selectedOverlay && selectedOverlay.hide(googleMap, hide)
        const nextOverlayIndex = hide ? -1 : selectedOverlayIndex

        actionBuilder('SET_OVERLAY', {nextOverlayIndex})
            .set([statePath, 'overlayIndex'], nextOverlayIndex)
            .build()
            .dispatch()
    }

    // changeBaseLayer(type, year, month, proc) {
    changeBaseLayer(type, dateRange, proc) {
        console.log('changeBaseLayer', dateRange.value, proc.value)
        const {statePath, mapContext} = this.props
        const planetApiKey = getNorwayPlanetApiKey()
        changeBaseLayer({
            type,
            mapContext,
            statePath,
            options: {dateRange: dateRange.value, proc: proc.value, planetApiKey}
            // options: {year: year.value, month: month.value, proc: proc.value, planetApiKey}
        })
    }

    selectedOverlayIndex() {
        const {overlayIndex, mapContext} = this.props
        if (overlayIndex === undefined) {
            const context = sepalMap.getContext(mapContext)
            const overlays = context.toggleableLayers()
            const firstOverlayIndex = overlays.length
                ? overlays.map(layer => layer.layerIndex)[0]
                : -1
            return firstOverlayIndex
        } else {
            return overlayIndex
        }
    }

    render() {
        const {
            inputs: {dateRange, proc},
            // inputs: {year, month, proc},
            activatable: {deactivate},
            labelsShown, baseLayer, labelLayerIndex, statePath, mapContext
        } = this.props
        const context = sepalMap.getContext(mapContext)
        const overlays = context.toggleableLayers()
        const toggleableOptions = overlays.map(layer =>
            <Menu.Option
                key={layer.layerIndex}
                id={layer.layerIndex}
                label={layer.label}
                description={layer.description}/>)
        // const {yearOptions, monthOptions, procOptions} = this.getPlanetDateOptions()
        const {dateRangeOptions, procOptions} = this.getPlanetDateOptions()

        const procOption = procOptions.find(({value}) => proc.value === value)
        const dateRangeOption = dateRangeOptions.find(({value}) => dateRange.value === value)
        // const yearOption = yearOptions.find(({value}) => year.value === value)
        // const monthOption = monthOptions.find(({value}) => month.value === value)
        return (
            <Menu
                position='top-right'
                className={styles.panel}
                close={deactivate}>
                <Menu.Select
                    id={'BASE_LAYER'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.baseLayer')}
                    onSelect={type => this.changeBaseLayer(type, dateRange, proc)}
                    // onSelect={type => this.changeBaseLayer(type, year, month, proc)}
                    selected={baseLayer || 'SEPAL'}>
                    <Menu.Option
                        id={'SEPAL'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.description')}/>
                    <Menu.Option
                        id={'GOOGLE_SATELLITE'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.description')}/>
                    <Menu.Option
                        id={'PLANET'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.PLANET.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.PLANET.description')}
                        right={

                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={proc}
                                    tooltipPlacement='bottom'
                                    options={procOptions}
                                    label={procOption && procOption.label}
                                    onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.dateRange, proc)}
                                    // onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.year, this.props.inputs.month, proc)}
                                />
                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={dateRange}
                                    tooltipPlacement='bottom'
                                    options={dateRangeOptions}
                                    label={dateRangeOption && dateRangeOption.label}
                                    onSelect={dateRange => this.changeBaseLayer('PLANET', dateRange, this.props.inputs.proc)}
                                />
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={year}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={yearOptions}*/}
                                {/*    label={yearOption && yearOption.label}*/}
                                {/*    onSelect={year => this.changeBaseLayer('PLANET', year, this.props.inputs.month, this.props.inputs.proc)}*/}
                                {/*/>*/}
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={month}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={monthOptions}*/}
                                {/*    label={monthOption && monthOption.label}*/}
                                {/*    onSelect={month => this.changeBaseLayer('PLANET', this.props.inputs.year, month, this.props.inputs.proc)}*/}
                                {/*/>*/}

                        }/>
                </Menu.Select>
                <Menu.Separator/>
                <Menu.Select
                    id={'OVERLAYS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.overlay')}
                    onSelect={selectedOverlayIndex => this.toggleOverlay({selectedOverlayIndex, mapContext, statePath})}
                    selected={this.selectedOverlayIndex()}>
                    {toggleableOptions}
                </Menu.Select>
                <Menu.Toggle
                    id={'SHOW_LABELS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.labels.label')}
                    description={msg('process.mosaic.mapToolbar.layersMenu.labels.description')}
                    selected={labelsShown}
                    onChange={shown =>
                        Labels.showLabelsAction({
                            shown,
                            layerIndex: labelLayerIndex,
                            statePath,
                            mapContext
                        }).dispatch()}/>
            </Menu>
        )
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        // const {inputs: {year, month, proc}} = this.props
        // if (!year.value)
            // year.set(this.props.year || moment().format('YYYY'))
        // if (!month.value)
            // month.set(this.props.month || moment().subtract(1, 'months').format('MM'))
        // else {
            // const {monthOptions} = this.getPlanetDateOptions()
            // if (!monthOptions.find(({value}) => month.value === value)) {
            //     const monthOption = monthOptions[0]
            //     month.set(monthOption.value)
            //     this.changeBaseLayer('PLANET', year, monthOption, )
            // }
        // }
        const {dateRange: prevDateRange, proc: prevProc, inputs: {dateRange, proc}} = this.props
        console.log(dateRange.value, proc.value)
        if (!proc.value)
            proc.set(prevProc || 'rgb')
        if (!dateRange.value)
            dateRange.set(prevDateRange || '2020-09')
    }
}

const policy = () => ({
    _: 'allow'
})
export const LayersMenu = compose(
    _LayersMenu,
    form({fields, mapStateToProps}),
    activatable({id: 'layersMenu', policy})
)

const sequence = (start, end, step = 1) =>
    Array.apply(null, {length: Math.floor((end - start) / step) + 1})
        .map((_, i) => i * step + start)

LayersMenu.propTypes = {
    labelLayerIndex: PropTypes.any.isRequired,
    mapContext: PropTypes.string.isRequired,
    statePath: PropTypes.any.isRequired
}<commit_msg>Removed debug logs.<commit_after>import {compose} from 'compose'
import {activatable} from 'widget/activation/activatable'
import React from 'react'
import styles from './layersMenu.module.css'
import Labels from './labels'
import {Menu} from './menu'
import {select} from 'store'
import PropTypes from 'prop-types'
import {changeBaseLayer} from './baseLayer'
import {msg} from 'translate'
import ButtonSelect from 'widget/buttonSelect'
import {Form, form} from 'widget/form/form'
import {googleMap, sepalMap} from 'app/home/map/map'
import moment from 'moment'
import {getNorwayPlanetApiKey} from './map'
import actionBuilder from '../../../action-builder'

const fields = {
    // year: new Form.Field(),
    // month: new Form.Field(),
    dateRange: new Form.Field(),
    proc: new Form.Field()
}

const mapStateToProps = (state, ownProps) => ({
    labelsShown: select([ownProps.statePath, 'labelsShown']),
    baseLayer: select([ownProps.statePath, 'baseLayer']),
    overlayIndex: select([ownProps.statePath, 'overlayIndex']),
    dateRange: select([ownProps.statePath, 'mapLayer.dateRange']),
    proc: select([ownProps.statePath, 'mapLayer.proc'])
    // year: select([ownProps.statePath, 'options.year']),
    // month: select([ownProps.statePath, 'options.month'])
})

class _LayersMenu extends React.Component {

    getPlanetDateOptions() {
        // const {inputs: {year}} = this.props
        // const startDate = moment('2018-08-01')
        // const endDate = moment().subtract(1, 'months')
        //
        // const yearOptions = sequence(startDate.year(), moment().year())
        //     .map(year => ({value: `${year}`, label: `${year}`}))
        //
        // const allMonthOptions = moment.monthsShort()
        //     .map((label, i) => ({value: `${i + 1}`.padStart(2, '0'), label}))
        //
        // const monthOptions = year.value === `${startDate.year()}`
        //     ? allMonthOptions.slice(startDate.month() + 1)
        //     : year.value === `${endDate.year()}`
        //         ? allMonthOptions.slice(0, endDate.month() + 1)
        //         : allMonthOptions
        // return {yearOptions, monthOptions}

        const procOptions = [
            {value: 'rgb', label: 'RGB'},
            {value: 'cir', label: 'CIR'},
        ]
        const dateRangeOptions = [
            {value: '2018-12_2019-05', label: 'Dec 2018 (6 months)'},
            {value: '2019-06_2019-11', label: 'Jun 2019 (6 months)'},
            {value: '2019-12_2020-05', label: 'Dec 2019 (6 months)'},
            {value: '2020-06_2020-08', label: 'Jun 2020 (3 months)'},
            {value: '2020-09', label: 'Sep 2020 (1 month)'},
        ]
        return {procOptions, dateRangeOptions}
    }

    toggleOverlay = ({selectedOverlayIndex, mapContext, statePath}) => {
        const context = sepalMap.getContext(mapContext)
        const overlays = context.toggleableLayers()

        const prevOverlayIndex = this.selectedOverlayIndex()
        if (selectedOverlayIndex !== prevOverlayIndex) {
            const prevOverlay = overlays.find(layer => layer.layerIndex === prevOverlayIndex)
            prevOverlay && prevOverlay.hide(googleMap, true)
        }

        const selectedOverlay = overlays.find(layer => layer.layerIndex === selectedOverlayIndex)
        const hide = prevOverlayIndex === selectedOverlayIndex
        selectedOverlay && selectedOverlay.hide(googleMap, hide)
        const nextOverlayIndex = hide ? -1 : selectedOverlayIndex

        actionBuilder('SET_OVERLAY', {nextOverlayIndex})
            .set([statePath, 'overlayIndex'], nextOverlayIndex)
            .build()
            .dispatch()
    }

    // changeBaseLayer(type, year, month, proc) {
    changeBaseLayer(type, dateRange, proc) {
        const {statePath, mapContext} = this.props
        const planetApiKey = getNorwayPlanetApiKey()
        changeBaseLayer({
            type,
            mapContext,
            statePath,
            options: {dateRange: dateRange.value, proc: proc.value, planetApiKey}
            // options: {year: year.value, month: month.value, proc: proc.value, planetApiKey}
        })
    }

    selectedOverlayIndex() {
        const {overlayIndex, mapContext} = this.props
        if (overlayIndex === undefined) {
            const context = sepalMap.getContext(mapContext)
            const overlays = context.toggleableLayers()
            const firstOverlayIndex = overlays.length
                ? overlays.map(layer => layer.layerIndex)[0]
                : -1
            return firstOverlayIndex
        } else {
            return overlayIndex
        }
    }

    render() {
        const {
            inputs: {dateRange, proc},
            // inputs: {year, month, proc},
            activatable: {deactivate},
            labelsShown, baseLayer, labelLayerIndex, statePath, mapContext
        } = this.props
        const context = sepalMap.getContext(mapContext)
        const overlays = context.toggleableLayers()
        const toggleableOptions = overlays.map(layer =>
            <Menu.Option
                key={layer.layerIndex}
                id={layer.layerIndex}
                label={layer.label}
                description={layer.description}/>)
        // const {yearOptions, monthOptions, procOptions} = this.getPlanetDateOptions()
        const {dateRangeOptions, procOptions} = this.getPlanetDateOptions()

        const procOption = procOptions.find(({value}) => proc.value === value)
        const dateRangeOption = dateRangeOptions.find(({value}) => dateRange.value === value)
        // const yearOption = yearOptions.find(({value}) => year.value === value)
        // const monthOption = monthOptions.find(({value}) => month.value === value)
        return (
            <Menu
                position='top-right'
                className={styles.panel}
                close={deactivate}>
                <Menu.Select
                    id={'BASE_LAYER'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.baseLayer')}
                    onSelect={type => this.changeBaseLayer(type, dateRange, proc)}
                    // onSelect={type => this.changeBaseLayer(type, year, month, proc)}
                    selected={baseLayer || 'SEPAL'}>
                    <Menu.Option
                        id={'SEPAL'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.description')}/>
                    <Menu.Option
                        id={'GOOGLE_SATELLITE'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.description')}/>
                    <Menu.Option
                        id={'PLANET'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.PLANET.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.PLANET.description')}
                        right={

                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={proc}
                                    tooltipPlacement='bottom'
                                    options={procOptions}
                                    label={procOption && procOption.label}
                                    onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.dateRange, proc)}
                                    // onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.year, this.props.inputs.month, proc)}
                                />
                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={dateRange}
                                    tooltipPlacement='bottom'
                                    options={dateRangeOptions}
                                    label={dateRangeOption && dateRangeOption.label}
                                    onSelect={dateRange => this.changeBaseLayer('PLANET', dateRange, this.props.inputs.proc)}
                                />
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={year}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={yearOptions}*/}
                                {/*    label={yearOption && yearOption.label}*/}
                                {/*    onSelect={year => this.changeBaseLayer('PLANET', year, this.props.inputs.month, this.props.inputs.proc)}*/}
                                {/*/>*/}
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={month}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={monthOptions}*/}
                                {/*    label={monthOption && monthOption.label}*/}
                                {/*    onSelect={month => this.changeBaseLayer('PLANET', this.props.inputs.year, month, this.props.inputs.proc)}*/}
                                {/*/>*/}

                        }/>
                </Menu.Select>
                <Menu.Separator/>
                <Menu.Select
                    id={'OVERLAYS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.overlay')}
                    onSelect={selectedOverlayIndex => this.toggleOverlay({selectedOverlayIndex, mapContext, statePath})}
                    selected={this.selectedOverlayIndex()}>
                    {toggleableOptions}
                </Menu.Select>
                <Menu.Toggle
                    id={'SHOW_LABELS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.labels.label')}
                    description={msg('process.mosaic.mapToolbar.layersMenu.labels.description')}
                    selected={labelsShown}
                    onChange={shown =>
                        Labels.showLabelsAction({
                            shown,
                            layerIndex: labelLayerIndex,
                            statePath,
                            mapContext
                        }).dispatch()}/>
            </Menu>
        )
    }

    componentDidUpdate(prevProps, prevState, snapshot) {
        // const {inputs: {year, month, proc}} = this.props
        // if (!year.value)
            // year.set(this.props.year || moment().format('YYYY'))
        // if (!month.value)
            // month.set(this.props.month || moment().subtract(1, 'months').format('MM'))
        // else {
            // const {monthOptions} = this.getPlanetDateOptions()
            // if (!monthOptions.find(({value}) => month.value === value)) {
            //     const monthOption = monthOptions[0]
            //     month.set(monthOption.value)
            //     this.changeBaseLayer('PLANET', year, monthOption, )
            // }
        // }
        const {dateRange: prevDateRange, proc: prevProc, inputs: {dateRange, proc}} = this.props
        if (!proc.value)
            proc.set(prevProc || 'rgb')
        if (!dateRange.value)
            dateRange.set(prevDateRange || '2020-09')
    }
}

const policy = () => ({
    _: 'allow'
})
export const LayersMenu = compose(
    _LayersMenu,
    form({fields, mapStateToProps}),
    activatable({id: 'layersMenu', policy})
)

const sequence = (start, end, step = 1) =>
    Array.apply(null, {length: Math.floor((end - start) / step) + 1})
        .map((_, i) => i * step + start)

LayersMenu.propTypes = {
    labelLayerIndex: PropTypes.any.isRequired,
    mapContext: PropTypes.string.isRequired,
    statePath: PropTypes.any.isRequired
}<|endoftext|>


<commit_before>import {Form, form} from 'widget/form/form'
// import {Menu} from './menu'
import {Menu} from 'widget/menu/menu'
import {activatable} from 'widget/activation/activatable'
import {changeBaseLayer} from './baseLayer'
import {compose} from 'compose'
import {msg} from 'translate'
import {select} from 'store'
import ButtonSelect from 'widget/buttonSelect'
import Labels from './labels'
import PropTypes from 'prop-types'
import React from 'react'
import actionBuilder from '../../../action-builder'
// import moment from 'moment'
import styles from './layersMenu.module.css'

const fields = {
    // year: new Form.Field(),
    // month: new Form.Field(),
    dateRange: new Form.Field(),
    proc: new Form.Field()
}

const mapStateToProps = (state, ownProps) => ({
    labelsShown: select([ownProps.statePath, 'labelsShown']),
    baseLayer: select([ownProps.statePath, 'baseLayer']),
    overlayIndex: select([ownProps.statePath, 'overlayIndex']),
    dateRange: select([ownProps.statePath, 'mapLayer.dateRange']),
    proc: select([ownProps.statePath, 'mapLayer.proc'])
    // year: select([ownProps.statePath, 'options.year']),
    // month: select([ownProps.statePath, 'options.month'])
})

class _LayersMenu extends React.Component {

    getPlanetDateOptions() {
        // const {inputs: {year}} = this.props
        // const startDate = moment('2018-08-01')
        // const endDate = moment().subtract(1, 'months')
        //
        // const yearOptions = sequence(startDate.year(), moment().year())
        //     .map(year => ({value: `${year}`, label: `${year}`}))
        //
        // const allMonthOptions = moment.monthsShort()
        //     .map((label, i) => ({value: `${i + 1}`.padStart(2, '0'), label}))
        //
        // const monthOptions = year.value === `${startDate.year()}`
        //     ? allMonthOptions.slice(startDate.month() + 1)
        //     : year.value === `${endDate.year()}`
        //         ? allMonthOptions.slice(0, endDate.month() + 1)
        //         : allMonthOptions
        // return {yearOptions, monthOptions}

        const procOptions = [
            {value: 'rgb', label: 'RGB'},
            {value: 'cir', label: 'CIR'},
        ]
        const dateRangeOptions = [
            {value: '2018-12_2019-05', label: 'Dec 2018 (6 months)'},
            {value: '2019-06_2019-11', label: 'Jun 2019 (6 months)'},
            {value: '2019-12_2020-05', label: 'Dec 2019 (6 months)'},
            {value: '2020-06_2020-08', label: 'Jun 2020 (3 months)'},
            {value: '2020-09', label: 'Sep 2020 (1 month)'},
        ]
        return {procOptions, dateRangeOptions}
    }

    toggleOverlay = selectedOverlayIndex => {
        const {mapContext: {sepalMap, googleMap}, statePath} = this.props
        const overlays = sepalMap.toggleableLayers()

        const prevOverlayIndex = this.selectedOverlayIndex()
        if (selectedOverlayIndex !== prevOverlayIndex) {
            const prevOverlay = overlays.find(layer => layer.layerIndex === prevOverlayIndex)
            prevOverlay && prevOverlay.hide(googleMap, true)
        }

        const selectedOverlay = overlays.find(layer => layer.layerIndex === selectedOverlayIndex)
        const hide = prevOverlayIndex === selectedOverlayIndex
        selectedOverlay && selectedOverlay.hide(googleMap, hide)
        const nextOverlayIndex = hide ? -1 : selectedOverlayIndex

        actionBuilder('SET_OVERLAY', {nextOverlayIndex})
            .set([statePath, 'overlayIndex'], nextOverlayIndex)
            .build()
            .dispatch()
    }

    // changeBaseLayer(type, year, month, proc) {
    changeBaseLayer(type, dateRange, proc) {
        // console.log('changeBaseLayer', dateRange.value, proc.value)
        const {statePath, mapContext} = this.props
        const planetApiKey = mapContext.norwayPlanetApiKey
        changeBaseLayer({
            type,
            mapContext,
            statePath,
            options: {dateRange: dateRange.value, proc: proc.value, planetApiKey}
            // options: {year: year.value, month: month.value, proc: proc.value, planetApiKey}
        })
    }

    selectedOverlayIndex() {
        const {overlayIndex, mapContext: {sepalMap}} = this.props
        if (overlayIndex === undefined) {
            const overlays = sepalMap.toggleableLayers()
            const firstOverlayIndex = overlays.length
                ? overlays.map(layer => layer.layerIndex)[0]
                : -1
            return firstOverlayIndex
        } else {
            return overlayIndex
        }
    }

    render() {
        const {
            inputs: {dateRange, proc},
            // inputs: {year, month, proc},
            activatable: {deactivate},
            labelsShown, baseLayer, labelLayerIndex, statePath, mapContext
        } = this.props
        const overlays = mapContext.sepalMap.toggleableLayers()
        const toggleableOptions = overlays.map(layer =>
            <Menu.Option
                key={layer.layerIndex}
                id={layer.layerIndex}
                label={layer.label}
                description={layer.description}/>)
        // const {yearOptions, monthOptions, procOptions} = this.getPlanetDateOptions()
        const {dateRangeOptions, procOptions} = this.getPlanetDateOptions()

        const procOption = procOptions.find(({value}) => proc.value === value)
        const dateRangeOption = dateRangeOptions.find(({value}) => dateRange.value === value)
        // const yearOption = yearOptions.find(({value}) => year.value === value)
        // const monthOption = monthOptions.find(({value}) => month.value === value)
        return (
            <Menu
                position='top-right'
                className={styles.panel}
                close={deactivate}>
                <Menu.Select
                    id={'BASE_LAYER'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.baseLayer')}
                    onSelect={type => this.changeBaseLayer(type, dateRange, proc)}
                    // onSelect={type => this.changeBaseLayer(type, year, month, proc)}
                    selected={baseLayer || 'SEPAL'}>
                    <Menu.Option
                        id={'SEPAL'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.description')}/>
                    <Menu.Option
                        id={'GOOGLE_SATELLITE'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.description')}/>
                    <Menu.Option
                        id={'PLANET'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.PLANET.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.PLANET.description')}
                        right={

                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={proc}
                                    tooltipPlacement='bottom'
                                    options={procOptions}
                                    label={procOption && procOption.label}
                                    onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.dateRange, proc)}
                                    // onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.year, this.props.inputs.month, proc)}
                                />
                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={dateRange}
                                    tooltipPlacement='bottom'
                                    options={dateRangeOptions}
                                    label={dateRangeOption && dateRangeOption.label}
                                    onSelect={dateRange => this.changeBaseLayer('PLANET', dateRange, this.props.inputs.proc)}
                                />
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={year}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={yearOptions}*/}
                                {/*    label={yearOption && yearOption.label}*/}
                                {/*    onSelect={year => this.changeBaseLayer('PLANET', year, this.props.inputs.month, this.props.inputs.proc)}*/}
                                {/*/>*/}
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={month}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={monthOptions}*/}
                                {/*    label={monthOption && monthOption.label}*/}
                                {/*    onSelect={month => this.changeBaseLayer('PLANET', this.props.inputs.year, month, this.props.inputs.proc)}*/}
                                {/*/>*/}

                        }/>
                </Menu.Select>
                <Menu.Separator/>
                <Menu.Select
                    id={'OVERLAYS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.overlay')}
                    onSelect={selectedOverlayIndex => this.toggleOverlay(selectedOverlayIndex)}
                    selected={this.selectedOverlayIndex()}>
                    {toggleableOptions}
                </Menu.Select>
                <Menu.Toggle
                    id={'SHOW_LABELS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.labels.label')}
                    description={msg('process.mosaic.mapToolbar.layersMenu.labels.description')}
                    selected={labelsShown}
                    onChange={shown =>
                        Labels.showLabelsAction({
                            mapContext,
                            shown,
                            layerIndex: labelLayerIndex,
                            statePath
                        }).dispatch()}/>
            </Menu>
        )
    }

    componentDidUpdate() {
        // const {inputs: {year, month, proc}} = this.props
        // if (!year.value)
        // year.set(this.props.year || moment().format('YYYY'))
        // if (!month.value)
        // month.set(this.props.month || moment().subtract(1, 'months').format('MM'))
        // else {
        // const {monthOptions} = this.getPlanetDateOptions()
        // if (!monthOptions.find(({value}) => month.value === value)) {
        //     const monthOption = monthOptions[0]
        //     month.set(monthOption.value)
        //     this.changeBaseLayer('PLANET', year, monthOption, )
        // }
        // }
        const {dateRange: prevDateRange, proc: prevProc, inputs: {dateRange, proc}} = this.props
        // console.log(dateRange.value, proc.value)
        if (!proc.value)
            proc.set(prevProc || 'rgb')
        if (!dateRange.value)
            dateRange.set(prevDateRange || '2020-09')
    }
}

const policy = () => ({
    _: 'allow'
})

export const LayersMenu = compose(
    _LayersMenu,
    form({fields, mapStateToProps}),
    activatable({id: 'layersMenu', policy})
)

// const sequence = (start, end, step = 1) =>
//     Array.apply(null, {length: Math.floor((end - start) / step) + 1})
//         .map((_, i) => i * step + start)

LayersMenu.propTypes = {
    labelLayerIndex: PropTypes.any.isRequired,
    mapContext: PropTypes.object.isRequired,
    statePath: PropTypes.any.isRequired
}
<commit_msg>Added additional Planet dates.<commit_after>import {Form, form} from 'widget/form/form'
// import {Menu} from './menu'
import {Menu} from 'widget/menu/menu'
import {activatable} from 'widget/activation/activatable'
import {changeBaseLayer} from './baseLayer'
import {compose} from 'compose'
import {msg} from 'translate'
import {select} from 'store'
import ButtonSelect from 'widget/buttonSelect'
import Labels from './labels'
import PropTypes from 'prop-types'
import React from 'react'
import actionBuilder from '../../../action-builder'
// import moment from 'moment'
import styles from './layersMenu.module.css'

const fields = {
    // year: new Form.Field(),
    // month: new Form.Field(),
    dateRange: new Form.Field(),
    proc: new Form.Field()
}

const mapStateToProps = (state, ownProps) => ({
    labelsShown: select([ownProps.statePath, 'labelsShown']),
    baseLayer: select([ownProps.statePath, 'baseLayer']),
    overlayIndex: select([ownProps.statePath, 'overlayIndex']),
    dateRange: select([ownProps.statePath, 'mapLayer.dateRange']),
    proc: select([ownProps.statePath, 'mapLayer.proc'])
    // year: select([ownProps.statePath, 'options.year']),
    // month: select([ownProps.statePath, 'options.month'])
})

class _LayersMenu extends React.Component {

    getPlanetDateOptions() {
        // const {inputs: {year}} = this.props
        // const startDate = moment('2018-08-01')
        // const endDate = moment().subtract(1, 'months')
        //
        // const yearOptions = sequence(startDate.year(), moment().year())
        //     .map(year => ({value: `${year}`, label: `${year}`}))
        //
        // const allMonthOptions = moment.monthsShort()
        //     .map((label, i) => ({value: `${i + 1}`.padStart(2, '0'), label}))
        //
        // const monthOptions = year.value === `${startDate.year()}`
        //     ? allMonthOptions.slice(startDate.month() + 1)
        //     : year.value === `${endDate.year()}`
        //         ? allMonthOptions.slice(0, endDate.month() + 1)
        //         : allMonthOptions
        // return {yearOptions, monthOptions}

        const procOptions = [
            {value: 'rgb', label: 'RGB'},
            {value: 'cir', label: 'CIR'},
        ]
        const dateRangeOptions = [
            {value: '2015-06_2015-11', label: 'Jun 2015 (6 months)'},
            {value: '2015-12_2016-05', label: 'Dec 2015 (6 months)'},
            {value: '2016-06_2016-11', label: 'Jun 2016 (6 months)'},
            {value: '2016-12_2017-05', label: 'Dec 2016 (6 months)'},
            {value: '2017-06_2017-11', label: 'Jun 2017 (6 months)'},
            {value: '2017-12_2018-05', label: 'Dec 2017 (6 months)'},
            {value: '2018-06_2018-11', label: 'Jun 2018 (6 months)'},
            {value: '2018-12_2019-05', label: 'Dec 2018 (6 months)'},
            {value: '2019-06_2019-11', label: 'Jun 2019 (6 months)'},
            {value: '2019-12_2020-05', label: 'Dec 2019 (6 months)'},
            {value: '2020-06_2020-08', label: 'Jun 2020 (3 months)'},
            {value: '2020-09', label: 'Sep 2020 (1 month)'},
            {value: '2020-10', label: 'Oct 2020 (1 month)'},
            {value: '2020-11', label: 'Nov 2020 (1 month)'},
            {value: '2020-12', label: 'Dec 2020 (1 month)'},
            {value: '2021-01', label: 'Jan 2021 (1 month)'},
        ]
        return {procOptions, dateRangeOptions}
    }

    toggleOverlay = selectedOverlayIndex => {
        const {mapContext: {sepalMap, googleMap}, statePath} = this.props
        const overlays = sepalMap.toggleableLayers()

        const prevOverlayIndex = this.selectedOverlayIndex()
        if (selectedOverlayIndex !== prevOverlayIndex) {
            const prevOverlay = overlays.find(layer => layer.layerIndex === prevOverlayIndex)
            prevOverlay && prevOverlay.hide(googleMap, true)
        }

        const selectedOverlay = overlays.find(layer => layer.layerIndex === selectedOverlayIndex)
        const hide = prevOverlayIndex === selectedOverlayIndex
        selectedOverlay && selectedOverlay.hide(googleMap, hide)
        const nextOverlayIndex = hide ? -1 : selectedOverlayIndex

        actionBuilder('SET_OVERLAY', {nextOverlayIndex})
            .set([statePath, 'overlayIndex'], nextOverlayIndex)
            .build()
            .dispatch()
    }

    // changeBaseLayer(type, year, month, proc) {
    changeBaseLayer(type, dateRange, proc) {
        // console.log('changeBaseLayer', dateRange.value, proc.value)
        const {statePath, mapContext} = this.props
        const planetApiKey = mapContext.norwayPlanetApiKey
        changeBaseLayer({
            type,
            mapContext,
            statePath,
            options: {dateRange: dateRange.value, proc: proc.value, planetApiKey}
            // options: {year: year.value, month: month.value, proc: proc.value, planetApiKey}
        })
    }

    selectedOverlayIndex() {
        const {overlayIndex, mapContext: {sepalMap}} = this.props
        if (overlayIndex === undefined) {
            const overlays = sepalMap.toggleableLayers()
            const firstOverlayIndex = overlays.length
                ? overlays.map(layer => layer.layerIndex)[0]
                : -1
            return firstOverlayIndex
        } else {
            return overlayIndex
        }
    }

    render() {
        const {
            inputs: {dateRange, proc},
            // inputs: {year, month, proc},
            activatable: {deactivate},
            labelsShown, baseLayer, labelLayerIndex, statePath, mapContext
        } = this.props
        const overlays = mapContext.sepalMap.toggleableLayers()
        const toggleableOptions = overlays.map(layer =>
            <Menu.Option
                key={layer.layerIndex}
                id={layer.layerIndex}
                label={layer.label}
                description={layer.description}/>)
        // const {yearOptions, monthOptions, procOptions} = this.getPlanetDateOptions()
        const {dateRangeOptions, procOptions} = this.getPlanetDateOptions()

        const procOption = procOptions.find(({value}) => proc.value === value)
        const dateRangeOption = dateRangeOptions.find(({value}) => dateRange.value === value)
        // const yearOption = yearOptions.find(({value}) => year.value === value)
        // const monthOption = monthOptions.find(({value}) => month.value === value)
        return (
            <Menu
                position='top-right'
                className={styles.panel}
                close={deactivate}>
                <Menu.Select
                    id={'BASE_LAYER'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.baseLayer')}
                    onSelect={type => this.changeBaseLayer(type, dateRange, proc)}
                    // onSelect={type => this.changeBaseLayer(type, year, month, proc)}
                    selected={baseLayer || 'SEPAL'}>
                    <Menu.Option
                        id={'SEPAL'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.SEPAL.description')}/>
                    <Menu.Option
                        id={'GOOGLE_SATELLITE'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.GOOGLE_SATELLITE.description')}/>
                    <Menu.Option
                        id={'PLANET'}
                        label={msg('process.mosaic.mapToolbar.layersMenu.PLANET.label')}
                        description={msg('process.mosaic.mapToolbar.layersMenu.PLANET.description')}
                        right={

                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={proc}
                                    tooltipPlacement='bottom'
                                    options={procOptions}
                                    label={procOption && procOption.label}
                                    onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.dateRange, proc)}
                                    // onSelect={proc => this.changeBaseLayer('PLANET', this.props.inputs.year, this.props.inputs.month, proc)}
                                />
                                <ButtonSelect
                                    placement='below'
                                    alignment='left'
                                    input={dateRange}
                                    tooltipPlacement='bottom'
                                    options={dateRangeOptions}
                                    label={dateRangeOption && dateRangeOption.label}
                                    onSelect={dateRange => this.changeBaseLayer('PLANET', dateRange, this.props.inputs.proc)}
                                />
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={year}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={yearOptions}*/}
                                {/*    label={yearOption && yearOption.label}*/}
                                {/*    onSelect={year => this.changeBaseLayer('PLANET', year, this.props.inputs.month, this.props.inputs.proc)}*/}
                                {/*/>*/}
                                {/*<ButtonSelect*/}
                                {/*    placement='below'*/}
                                {/*    alignment='left'*/}
                                {/*    input={month}*/}
                                {/*    tooltipPlacement='bottom'*/}
                                {/*    options={monthOptions}*/}
                                {/*    label={monthOption && monthOption.label}*/}
                                {/*    onSelect={month => this.changeBaseLayer('PLANET', this.props.inputs.year, month, this.props.inputs.proc)}*/}
                                {/*/>*/}

                        }/>
                </Menu.Select>
                <Menu.Separator/>
                <Menu.Select
                    id={'OVERLAYS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.overlay')}
                    onSelect={selectedOverlayIndex => this.toggleOverlay(selectedOverlayIndex)}
                    selected={this.selectedOverlayIndex()}>
                    {toggleableOptions}
                </Menu.Select>
                <Menu.Toggle
                    id={'SHOW_LABELS'}
                    label={msg('process.mosaic.mapToolbar.layersMenu.labels.label')}
                    description={msg('process.mosaic.mapToolbar.layersMenu.labels.description')}
                    selected={labelsShown}
                    onChange={shown =>
                        Labels.showLabelsAction({
                            mapContext,
                            shown,
                            layerIndex: labelLayerIndex,
                            statePath
                        }).dispatch()}/>
            </Menu>
        )
    }

    componentDidUpdate() {
        // const {inputs: {year, month, proc}} = this.props
        // if (!year.value)
        // year.set(this.props.year || moment().format('YYYY'))
        // if (!month.value)
        // month.set(this.props.month || moment().subtract(1, 'months').format('MM'))
        // else {
        // const {monthOptions} = this.getPlanetDateOptions()
        // if (!monthOptions.find(({value}) => month.value === value)) {
        //     const monthOption = monthOptions[0]
        //     month.set(monthOption.value)
        //     this.changeBaseLayer('PLANET', year, monthOption, )
        // }
        // }
        const {dateRange: prevDateRange, proc: prevProc, inputs: {dateRange, proc}} = this.props
        // console.log(dateRange.value, proc.value)
        if (!proc.value)
            proc.set(prevProc || 'rgb')
        if (!dateRange.value)
            dateRange.set(prevDateRange || '2020-09')
    }
}

const policy = () => ({
    _: 'allow'
})

export const LayersMenu = compose(
    _LayersMenu,
    form({fields, mapStateToProps}),
    activatable({id: 'layersMenu', policy})
)

// const sequence = (start, end, step = 1) =>
//     Array.apply(null, {length: Math.floor((end - start) / step) + 1})
//         .map((_, i) => i * step + start)

LayersMenu.propTypes = {
    labelLayerIndex: PropTypes.any.isRequired,
    mapContext: PropTypes.object.isRequired,
    statePath: PropTypes.any.isRequired
}
<|endoftext|>


<commit_before>$(document).ready(function(){

    /* Adjust dropdown alignment according to panel position */
    $('.dropdown-toggle').on("click", function() {
        var dropdown = $(this).parent().children('.dropdown-menu'),
            offsetLeft = $(this).offset().left,
            dropdownWidth = dropdown.width(),
            docWidth = $(window).width();

        if (!(offsetLeft + dropdownWidth <= docWidth))
            dropdown.addClass('pull-right');
    });

    /* Prevent challenge dropdown close on click to allow copy of text */
    $('.btn-group-chal .dropdown-menu').on("click", function(e) {
        e.stopPropagation();
    });

    /* Allow multi-level dropdowns */
    $('div.dropdown-menu .dropdown-toggle').on('click', function() {
        $(this).parent().toggleClass('open');
    });

});<commit_msg>js: dropdowns - use dropdown-menu-right for dropdown alignment - adjust open dropdowns (needed later for buying hints) - Prevent dropdown close on click to allow copy of text - make code more generic<commit_after>$(document).ready(function(){

    /* Adjust dropdown alignment according to panel position */
    function adjustDDAlign(el) {
        var dropdown = el.parent().children('.dropdown-menu'),
            offsetLeft = el.offset().left,
            dropdownWidth = dropdown.width(),
            docWidth = $(window).width();

        if (!(offsetLeft + dropdownWidth <= docWidth))
            dropdown.addClass('dropdown-menu-right');
    }
    $('.dropdown-toggle').on("click", function() {
        adjustDDAlign($(this));
    }).each(function() {
        adjustDDAlign($(this));
    });

    /* Prevent dropdown close on click to allow copy of text */
    $('.dropdown-menu').on("click", function(e) {
        $('.dropdown-menu').not(
            $(this)).not($('.dropdown-menu .btn-group').closest('.dropdown-menu')
        ).parent().removeClass('open');
        e.stopPropagation();
    });

    /* Allow multi-level dropdowns */
    $('.dropdown-menu .dropdown-toggle').on('click', function(e) {
        if(!$(this).parent().hasClass('open'))
            $('.dropdown-menu').not(
                $('.dropdown-menu .btn-group').closest('.dropdown-menu')
            ).parent().removeClass('open');
        $(this).parent().toggleClass('open');
        e.stopPropagation(); // prevent close caused by button click event on parent
    });

});<|endoftext|>


<commit_before>// @flow
import chroma from 'chroma-js';
import type { BackgroundShades, ErrorShades } from '../types';

export const createBackgroundShades = (color: string): BackgroundShades => {
  const lightShades = chroma.scale([color, '#fff']);
  const darkShades = chroma.scale([color, '#000']);

  return {
    lightest: lightShades(0.6).hex(),
    lighter: lightShades(0.4).hex(),
    light: lightShades(0.2).hex(),
    regular: color,
    dark: darkShades(0.1).hex(),
    darker: darkShades(0.2).hex(),
    darkest: darkShades(0.3).hex(),
  };
};

export const createErrorShades = (color: string): ErrorShades => {
  const lightShades = chroma.scale([color, '#fff']);
  const darkShades = chroma.scale([color, '#000']);

  return {
    ultralight: lightShades(0.8).hex(),
    light: lightShades(0.2).hex(),
    regular: color,
    dark: darkShades(0.1).hex(),
  };
};
<commit_msg>[DDW-604] Adds more dark shades for error colors in createShades.js<commit_after>// @flow
import chroma from 'chroma-js';
import type { BackgroundShades, ErrorShades } from '../types';

export const createBackgroundShades = (color: string): BackgroundShades => {
  const lightShades = chroma.scale([color, '#fff']);
  const darkShades = chroma.scale([color, '#000']);

  return {
    lightest: lightShades(0.6).hex(),
    lighter: lightShades(0.4).hex(),
    light: lightShades(0.2).hex(),
    regular: color,
    dark: darkShades(0.1).hex(),
    darker: darkShades(0.2).hex(),
    darkest: darkShades(0.3).hex(),
  };
};

export const createErrorShades = (color: string): ErrorShades => {
  const lightShades = chroma.scale([color, '#fff']);
  const darkShades = chroma.scale([color, '#000']);

  return {
    ultralight: lightShades(0.8).hex(),
    light: lightShades(0.2).hex(),
    regular: color,
    dark: darkShades(0.1).hex(),
    darker: darkShades(0.2).hex(),
    darkest: darkShades(0.3).hex(),
  };
};
<|endoftext|>


<commit_before>
// TODO: eliminate this global
RJSDemoApp = {};

require(['jquery', 'underscore', 'bootstrap', 'Readium'],
    function ($, _, bootstrap, Readium) {

    RJSDemoApp.lastAnnotationId = 1;

    RJSDemoApp.currentSpineIndex = -1;

    RJSDemoApp.getInputValue = function (inputId) {
        return $("#" + inputId).val();
    };

    RJSDemoApp.setModuleContainerHeight = function () {
        $("#epub-reader-container").css({ "height" : $(window).height() * 0.85 + "px" });
    };

    RJSDemoApp.addLibraryList = function ($ulElementContainer, libraryJson) {

        _.each(libraryJson.library_epubs, function (currEpub) {

            var $currLi = $('
' + currEpub.title + '
');
            $currLi.on("click", function () {
                RJSDemoApp.loadAndRenderEpub(currEpub.url_to_package_doc);
            });
            $ulElementContainer.append($currLi);
        });
    };

    // This function will retrieve a package document and load an EPUB
    RJSDemoApp.loadAndRenderEpub = function (packageDocumentURL) {

        // Set a fixed height for the epub viewer container
        RJSDemoApp.setModuleContainerHeight();

        var that = this;
        var jsLibDir = './lib/';
        var elementToBindReaderTo = $("#epub-reader-container");
        var fontSize = 100;
        $(elementToBindReaderTo).html("");

        // Clear the viewer, if it has been defined -> to load a new epub
        RJSDemoApp.epubViewer = undefined;

        RJSDemoApp.readium = new Readium(elementToBindReaderTo, packageDocumentURL, jsLibDir, function (epubViewer) {
            RJSDemoApp.epubViewer = epubViewer;
            RJSDemoApp.epubViewer.openBook();

            ReadiumSDK.reader.on(ReadiumSDK.Events.PAGINATION_CHANGED, function (args) {
                var newSpineIndex = args.paginationInfo.openPages[0].spineItemIndex;
                if (newSpineIndex !== RJSDemoApp.currentSpineIndex) {
                    RJSDemoApp.resetAnnotations();
                    RJSDemoApp.currentSpineIndex = newSpineIndex;
                }
                console.log("PAGINATION_CHANGED: Current spine=" + newSpineIndex);
            });

            ReadiumSDK.reader.on("annotationClicked", function () {
                console.log("Annotation clicked:" + JSON.stringify(arguments));
            });

        });

        // These are application specific handlers that wire the HTML to the SimpleReadiumJs module API

        // Set handlers for click events
        $("#previous-page-btn").unbind("click");
        $("#previous-page-btn").on("click", function () {
            RJSDemoApp.epubViewer.openPageLeft();
        });

        $("#next-page-btn").unbind("click");
        $("#next-page-btn").on("click", function () {
            RJSDemoApp.epubViewer.openPageRight();
        });

        $("#toggle-synthetic-btn").unbind("click");
        $("#toggle-synthetic-btn").on("click", function () {

            if (RJSDemoApp.currLayoutIsSynthetic) {
                RJSDemoApp.epubViewer.updateSettings({ "isSyntheticSpread" : false });
                RJSDemoApp.currLayoutIsSynthetic = false;
                $("#toggle-synthetic-btn").removeClass('synthetic-page-ico');
                $("#toggle-synthetic-btn").addClass('single-page-ico');
            }
            else {
                RJSDemoApp.epubViewer.updateSettings({ "isSyntheticSpread" : true });
                RJSDemoApp.currLayoutIsSynthetic = true;
                $("#toggle-synthetic-btn").removeClass('single-page-ico');
                $("#toggle-synthetic-btn").addClass('synthetic-page-ico');
            }
        });

        $("#increase-font-size-btn").on("click", function () {
            fontSize = fontSize + 10;
            RJSDemoApp.epubViewer.updateSettings({ "fontSize" : fontSize });
        });

        $("#decrease-font-size-btn").on("click", function () {
            fontSize = fontSize - 10;
            RJSDemoApp.epubViewer.updateSettings({ "fontSize" : fontSize });
        });

        $("#annotations-highlight").on("click", function () {
            RJSDemoApp.epubViewer.addSelectionHighlight(RJSDemoApp.lastAnnotationId, "highlight");
            RJSDemoApp.lastAnnotationId++;
        });

        $("#annotations-underline").on("click", function () {
            RJSDemoApp.epubViewer.addSelectionHighlight(RJSDemoApp.lastAnnotationId, "underline");
            RJSDemoApp.lastAnnotationId++;
        });

        $("#annotations-image-highlight").on("click", function () {
            RJSDemoApp.epubViewer.addSelectionImageAnnotation(RJSDemoApp.lastAnnotationId, "highlight");
            RJSDemoApp.lastAnnotationId++;
        });

    };

    // When the document is ready, choose an EPUB to show.
    $(document).ready(function () {

        RJSDemoApp.currLayoutIsSynthetic = true;

        // "epub_content/moby_dick/OPS/package.opf"

        // Load Moby Dick by default
        RJSDemoApp.loadAndRenderEpub("epub_content/moby_dick/OPS/package.opf");

        // Generate the library
        $.getJSON('epub_content/epub_library.json', function (data) {

            $(".show-on-load").hide();
            $("#library-list").html("");
            RJSDemoApp.addLibraryList($("#library-list"), data);
            $(".show-on-load").show();

        }).fail(function (result) {
            console.log("The library could not be loaded");
        });
    });

    RJSDemoApp.resetAnnotations = function() {
        console.log("Resetting annotations");
//        RJSDemoApp.epubViewer.addHighlight("epubcfi(/6/2!/4/2,/3:5,/3:23)",1, "underline");
//        RJSDemoApp.epubViewer.addHighlight("epubcfi(/6/12!/4/2/6,/1:656,/1:669)",2, "highlight");
    };

});
<commit_msg>add a listener for text selection event<commit_after>
// TODO: eliminate this global
RJSDemoApp = {};

require(['jquery', 'underscore', 'bootstrap', 'Readium'],
    function ($, _, bootstrap, Readium) {

    RJSDemoApp.lastAnnotationId = 1;

    RJSDemoApp.currentSpineIndex = -1;

    RJSDemoApp.getInputValue = function (inputId) {
        return $("#" + inputId).val();
    };

    RJSDemoApp.setModuleContainerHeight = function () {
        $("#epub-reader-container").css({ "height" : $(window).height() * 0.85 + "px" });
    };

    RJSDemoApp.addLibraryList = function ($ulElementContainer, libraryJson) {

        _.each(libraryJson.library_epubs, function (currEpub) {

            var $currLi = $('
' + currEpub.title + '
');
            $currLi.on("click", function () {
                RJSDemoApp.loadAndRenderEpub(currEpub.url_to_package_doc);
            });
            $ulElementContainer.append($currLi);
        });
    };

    // This function will retrieve a package document and load an EPUB
    RJSDemoApp.loadAndRenderEpub = function (packageDocumentURL) {

        // Set a fixed height for the epub viewer container
        RJSDemoApp.setModuleContainerHeight();

        var that = this;
        var jsLibDir = './lib/';
        var elementToBindReaderTo = $("#epub-reader-container");
        var fontSize = 100;
        $(elementToBindReaderTo).html("");

        // Clear the viewer, if it has been defined -> to load a new epub
        RJSDemoApp.epubViewer = undefined;

        RJSDemoApp.readium = new Readium(elementToBindReaderTo, packageDocumentURL, jsLibDir, function (epubViewer) {
            RJSDemoApp.epubViewer = epubViewer;
            RJSDemoApp.epubViewer.openBook();

            ReadiumSDK.reader.on(ReadiumSDK.Events.PAGINATION_CHANGED, function (args) {
                var newSpineIndex = args.paginationInfo.openPages[0].spineItemIndex;
                if (newSpineIndex !== RJSDemoApp.currentSpineIndex) {
                    RJSDemoApp.resetAnnotations();
                    RJSDemoApp.currentSpineIndex = newSpineIndex;
                }
                console.log("PAGINATION_CHANGED: Current spine=" + newSpineIndex);
            });

            ReadiumSDK.reader.on("annotationClicked", function () {
                console.log("Annotation clicked:"  + arguments);
            });

            ReadiumSDK.reader.on("textSelectionEvent", function () {
                console.log("Selection event:"  + arguments);
            });

        });

        // These are application specific handlers that wire the HTML to the SimpleReadiumJs module API

        // Set handlers for click events
        $("#previous-page-btn").unbind("click");
        $("#previous-page-btn").on("click", function () {
            RJSDemoApp.epubViewer.openPageLeft();
        });

        $("#next-page-btn").unbind("click");
        $("#next-page-btn").on("click", function () {
            RJSDemoApp.epubViewer.openPageRight();
        });

        $("#toggle-synthetic-btn").unbind("click");
        $("#toggle-synthetic-btn").on("click", function () {

            if (RJSDemoApp.currLayoutIsSynthetic) {
                RJSDemoApp.epubViewer.updateSettings({ "isSyntheticSpread" : false });
                RJSDemoApp.currLayoutIsSynthetic = false;
                $("#toggle-synthetic-btn").removeClass('synthetic-page-ico');
                $("#toggle-synthetic-btn").addClass('single-page-ico');
            }
            else {
                RJSDemoApp.epubViewer.updateSettings({ "isSyntheticSpread" : true });
                RJSDemoApp.currLayoutIsSynthetic = true;
                $("#toggle-synthetic-btn").removeClass('single-page-ico');
                $("#toggle-synthetic-btn").addClass('synthetic-page-ico');
            }
        });

        $("#increase-font-size-btn").on("click", function () {
            fontSize = fontSize + 10;
            RJSDemoApp.epubViewer.updateSettings({ "fontSize" : fontSize });
        });

        $("#decrease-font-size-btn").on("click", function () {
            fontSize = fontSize - 10;
            RJSDemoApp.epubViewer.updateSettings({ "fontSize" : fontSize });
        });

        $("#annotations-highlight").on("click", function () {
            RJSDemoApp.epubViewer.addSelectionHighlight(RJSDemoApp.lastAnnotationId, "highlight");
            RJSDemoApp.lastAnnotationId++;
        });

        $("#annotations-underline").on("click", function () {
            RJSDemoApp.epubViewer.addSelectionHighlight(RJSDemoApp.lastAnnotationId, "underline");
            RJSDemoApp.lastAnnotationId++;
        });

        $("#annotations-image-highlight").on("click", function () {
            RJSDemoApp.epubViewer.addSelectionImageAnnotation(RJSDemoApp.lastAnnotationId, "highlight");
            RJSDemoApp.lastAnnotationId++;
        });

    };

    // When the document is ready, choose an EPUB to show.
    $(document).ready(function () {

        RJSDemoApp.currLayoutIsSynthetic = true;

        // "epub_content/moby_dick/OPS/package.opf"

        // Load Moby Dick by default
        RJSDemoApp.loadAndRenderEpub("epub_content/moby_dick/OPS/package.opf");

        // Generate the library
        $.getJSON('epub_content/epub_library.json', function (data) {

            $(".show-on-load").hide();
            $("#library-list").html("");
            RJSDemoApp.addLibraryList($("#library-list"), data);
            $(".show-on-load").show();

        }).fail(function (result) {
            console.log("The library could not be loaded");
        });
    });

    RJSDemoApp.resetAnnotations = function() {
        console.log("Resetting annotations");
//        RJSDemoApp.epubViewer.addHighlight("epubcfi(/6/2!/4/2,/3:5,/3:23)",1, "underline");
//        RJSDemoApp.epubViewer.addHighlight("epubcfi(/6/12!/4/2/6,/1:656,/1:669)",2, "highlight");
    };

});
<|endoftext|>


<commit_before>/**
  @module ember-flexberry
*/

import Ember from 'ember';

export default Ember.Mixin.create({
  actions: {
    /**
      Table row click handler.

      @method actions.objectListViewRowClick
      @public

      @param {Ember.Object} record Record related to clicked table row
    */
    objectListViewRowClick(record, editFormRoute) {
      this.transitionTo(editFormRoute, record.get('id'));
    },

    /**
      This action is called when user click on refresh button.

      @method actions.refreshList
      @public
    */
    refreshList() {
      this.refresh();
    }
  },

  /**
    It forms the limit predicate for loaded data.

    By default it returns `undefined`.
    In order to set specific limit predicate, this method have to be overriden on applied-specific route.

    @example
      ``` js
      // app/routes/limit-function-example.js
      import Ember from 'ember';
      import ListFormRoute from 'ember-flexberry/routes/list-form';
      import { StringPredicate } from 'ember-flexberry-data/query/predicate';

      export default ListFormRoute.extend({
        modelProjection: 'FolvWithLimitFunctionExampleView',

        modelName: 'ember-flexberry-dummy-suggestion',

        objectListViewLimitPredicate: function(options) {
          let methodOptions = Ember.merge({
            modelName: undefined,
            projectionName: undefined,
            params: undefined
          }, options);

          if (methodOptions.modelName === this.get('modelName') &&
              methodOptions.projectionName === this.get('modelProjection')) {
            let currentPerPageValue = methodOptions.params ? methodOptions.params.perPage : undefined;
            let limitFunction = (currentPerPageValue && currentPerPageValue % 2 === 0) ?
                                new StringPredicate('address').contains('S') :
                                new StringPredicate('address').contains('п');
            return limitFunction;
          }

          return undefined;
        }
      });

      ```
  @method objectListViewLimitPredicate
  @public

  @param {Object} options Method options
  @param {String} [options.modelName] Type of records to load
  @param {String} [options.projectionName] Projection name to load data by
  @param {String} [options.params] Current route query parameters
  @return {BasePredicate} The predicate to limit loaded data
  */
  objectListViewLimitPredicate: function(options) {
    return undefined;
  }
});
<commit_msg>Fix yuidoc for `FlexberryObjectlistviewRouteMixin`<commit_after>/**
  @module ember-flexberry
*/

import Ember from 'ember';

/**
  Mixin for {{#crossLink "DS.Route"}}Route{{/crossLink}}
  to support work with {{#crossLink "FlexberryObjectlistviewComponent"}}{{/crossLink}}.

  @class FlexberryObjectlistviewRouteMixin
  @extends Ember.Mixin
  @public
*/
export default Ember.Mixin.create({
  actions: {
    /**
      Table row click handler.

      @method actions.objectListViewRowClick
      @public

      @param {Ember.Object} record Record related to clicked table row
    */
    objectListViewRowClick(record, editFormRoute) {
      this.transitionTo(editFormRoute, record.get('id'));
    },

    /**
      This action is called when user click on refresh button.

      @method actions.refreshList
      @public
    */
    refreshList() {
      this.refresh();
    }
  },

  /**
    It forms the limit predicate for loaded data.

    By default it returns `undefined`.
    In order to set specific limit predicate, this method have to be overriden on applied-specific route.

    @example
      ``` js
      // app/routes/limit-function-example.js
      import Ember from 'ember';
      import ListFormRoute from 'ember-flexberry/routes/list-form';
      import { StringPredicate } from 'ember-flexberry-data/query/predicate';

      export default ListFormRoute.extend({
        modelProjection: 'FolvWithLimitFunctionExampleView',

        modelName: 'ember-flexberry-dummy-suggestion',

        objectListViewLimitPredicate: function(options) {
          let methodOptions = Ember.merge({
            modelName: undefined,
            projectionName: undefined,
            params: undefined
          }, options);

          if (methodOptions.modelName === this.get('modelName') &&
              methodOptions.projectionName === this.get('modelProjection')) {
            let currentPerPageValue = methodOptions.params ? methodOptions.params.perPage : undefined;
            let limitFunction = (currentPerPageValue && currentPerPageValue % 2 === 0) ?
                                new StringPredicate('address').contains('S') :
                                new StringPredicate('address').contains('п');
            return limitFunction;
          }

          return undefined;
        }
      });

      ```
  @method objectListViewLimitPredicate
  @public

  @param {Object} options Method options
  @param {String} [options.modelName] Type of records to load
  @param {String} [options.projectionName] Projection name to load data by
  @param {String} [options.params] Current route query parameters
  @return {BasePredicate} The predicate to limit loaded data
  */
  objectListViewLimitPredicate: function(options) {
    return undefined;
  }
});
<|endoftext|>


<commit_before>'use strict';

var _ = require('lodash');
var async = require('async');
var core = require('../core.js');

// @todo: add logging!

var steps = [];

var intf = {
  name: null,
  deps: [],
  all: {
    darwin: null,
    linux: {
      debian: null,
      fedora: null,
      other: null
    },
    win32: null
  }
};

var state = {
  foo: 'bar'
};

var validKeys = ['name', 'deps', 'all'];

var prettyPrint = function(step) {
  return JSON.stringify(step);
};

var invariant = function(step) {
  if (!step.name) {
    throw new Error('Install step is missing a name: ' +
      prettyPrint(step));
  }
  if (!step.deps || !Array.isArray(step.deps)) {
    throw new Error('Install step with invalid deps: ' +
      prettyPrint(step));
  }
  _.keys(step).forEach(function(key) {
    if (!_.contains(validKeys, key)) {
      throw new Error('Install step with invalid key [' + key + ']: ' +
        prettyPrint(step));
    }
  });
  // @todo: validate function tree makes sense
  /*var funs = [];
  var rec = function(node) {
  };
  rec(step.all);*/
};

var registerStep = exports.registerStep = function(handler) {
  var step = _.cloneDeep(intf);
  // If handler doesn't include a callback, treat it as sync.
  if (handler.length === 1) {
    handler(step);
    invariant(step);
    steps.push(step);
  } else if (handler.length === 2) {
    handler(step, function() {
      invariant(step);
      steps.push(step);
    });
  } else {
    throw new Error('Invalid install step registration handler: ' +
      handler.toString());
  }
};

var clearSteps = exports.clearSteps = function() {
  steps = [];
};

var requires = function(x, y) {
  // Does x require y?
  return _.contains(x.deps, y.name);
};

var compareSteps = function(x, y) {
  if (requires(x, y)) {
    return 1;
  } else if (requires(y, x)) {
    return -1;
  } else {
    return 0;
  }
};

var unpackStep = function(step, platform) {
  if (!platform) {
    throw new Error('Invalid platform: ' + platform);
  }

  if (typeof step.all === 'function') {
    return step.all;
  } else if (platform === 'darwin' && step.all.darwin) {
    return step.all.darwin;
  } else if (platform === 'win32' && step.all.win32) {
    return step.all.win32;
  } else if (platform === 'linux') {
    throw new Error('not implemented yet :(');
  } else {
    return null;
  }
};

var filterSteps = function(platform) {
  if (!platform) {
    throw new Error('Invalid platform: ' + platform);
  }

  return _.filter(steps, function(step) {
    return unpackStep(step, platform) !== null;
  });
};

var getSteps = exports.getSteps = function(platform) {
  if (!platform) {
    throw new Error('Invalid platform: ' + platform);
  }

  var filteredSteps = filterSteps(platform);
  filteredSteps.sort(compareSteps);
  return filteredSteps;
};

var getInstall = exports.getInstall = function(platform) {
  // @todo: validate all dependencies were found.
  if (!platform) {
    platform = core.env.platform;
  }
  return function(callback) {
    async.eachSeries(getSteps(platform), function(step, next) {
      var fun = unpackStep(step, platform);
      if (!fun) {
        next();
      } else {
        if (fun.length === 1) {
          fun(state);
          next();
        } else if (fun.length === 2) {
          fun(state, next);
        } else {
          var err = new Error('Invalid install step callback: ' +
            fun.toString());
          next(err);
        }
      }
    },
    function(err) {
      callback(err, state);
    });
  };
};

/*

registerStep(function(step, done) {
  step.name = 'ASYNC - Download cat!';
  step.deps = ['downloader'];
  step.all.darwin = function(state, next) {
    state.downloader.download('http://catsrkewl.com/tabby4.gif', function(err) {
      next(err);
    });
  };
  done();
});

registerStep(function(step) {
  step.name = 'SYNC - Set state with cat names!';
  step.deps = ['ASYNC - Download cat!'];
  step.all.darwin = function(state) {
    state.catsRKewl = true;
  };
});

*/
<commit_msg>Added easier syntax for defining steps<commit_after>'use strict';

var _ = require('lodash');
var async = require('async');
var core = require('../core.js');

// @todo: add logging!

var steps = [];

var intf = {
  name: null,
  deps: [],
  all: {
    darwin: null,
    linux: {
      debian: null,
      fedora: null,
      other: null
    },
    win32: null
  }
};

var state = {
  foo: 'bar'
};

var validKeys = ['name', 'deps', 'all'];

var prettyPrint = function(step) {
  return JSON.stringify(step);
};

var invariant = function(step) {
  if (!step.name) {
    throw new Error('Install step is missing a name: ' +
      prettyPrint(step));
  }
  if (!step.deps || !Array.isArray(step.deps)) {
    throw new Error('Install step with invalid deps: ' +
      prettyPrint(step));
  }
  _.keys(step).forEach(function(key) {
    if (!_.contains(validKeys, key)) {
      throw new Error('Install step with invalid key [' + key + ']: ' +
        prettyPrint(step));
    }
  });
  // @todo: validate function tree makes sense
  /*var funs = [];
  var rec = function(node) {
  };
  rec(step.all);*/
};

var registerStep = exports.registerStep = function(handler) {
  var step = _.cloneDeep(intf);
  // If handler doesn't include a callback, treat it as sync.
  if (handler.length === 1) {
    handler(step);
    invariant(step);
    steps.push(step);
  } else if (handler.length === 2) {
    handler(step, function() {
      invariant(step);
      steps.push(step);
    });
  } else {
    throw new Error('Invalid install step registration handler: ' +
      handler.toString());
  }
};

var add = exports.step = function(handler) {
  var step = _.cloneDeep(intf);
  // If handler doesn't include a callback, treat it as sync.
  if (handler.length === 0) {
    handler.apply(step, []);
    invariant(step);
    steps.push(step);
  } else if (handler.length === 1) {
    handler.apply(step, [function() {
      invariant(step);
      steps.push(step);
    }]);
  } else {
    throw new Error('Invalid install step registration handler: ' +
      handler.toString());
  }
};

var clearSteps = exports.clearSteps = function() {
  steps = [];
};

var requires = function(x, y) {
  // Does x require y?
  return _.contains(x.deps, y.name);
};

var compareSteps = function(x, y) {
  if (requires(x, y)) {
    return 1;
  } else if (requires(y, x)) {
    return -1;
  } else {
    return 0;
  }
};

var unpackStep = function(step, platform) {
  if (!platform) {
    throw new Error('Invalid platform: ' + platform);
  }

  if (typeof step.all === 'function') {
    return step.all;
  } else if (platform === 'darwin' && step.all.darwin) {
    return step.all.darwin;
  } else if (platform === 'win32' && step.all.win32) {
    return step.all.win32;
  } else if (platform === 'linux') {
    throw new Error('not implemented yet :(');
  } else {
    return null;
  }
};

var filterSteps = function(platform) {
  if (!platform) {
    throw new Error('Invalid platform: ' + platform);
  }

  return _.filter(steps, function(step) {
    return unpackStep(step, platform) !== null;
  });
};

var getSteps = exports.getSteps = function(platform) {
  if (!platform) {
    throw new Error('Invalid platform: ' + platform);
  }

  var filteredSteps = filterSteps(platform);
  filteredSteps.sort(compareSteps);
  return filteredSteps;
};

var getInstall = exports.getInstall = function(platform) {
  // @todo: validate all dependencies were found.
  if (!platform) {
    platform = core.env.platform;
  }
  return function(callback) {
    async.eachSeries(getSteps(platform), function(step, next) {
      var fun = unpackStep(step, platform);
      if (!fun) {
        next();
      } else {
        if (fun.length === 1) {
          fun(state);
          next();
        } else if (fun.length === 2) {
          fun(state, next);
        } else {
          var err = new Error('Invalid install step callback: ' +
            fun.toString());
          next(err);
        }
      }
    },
    function(err) {
      callback(err, state);
    });
  };
};

/*

registerStep(function(step, done) {
  step.name = 'ASYNC - Download cat!';
  step.deps = ['downloader'];
  step.all.darwin = function(state, next) {
    state.downloader.download('http://catsrkewl.com/tabby4.gif', function(err) {
      next(err);
    });
  };
  done();
});

registerStep(function(step) {
  step.name = 'SYNC - Set state with cat names!';
  step.deps = ['ASYNC - Download cat!'];
  step.all.darwin = function(state) {
    state.catsRKewl = true;
  };
});

add(function() {
  this.name = 'foo';
  this.deps = [];
  this.all.darwin = function(state) {
    state.foo = 'bar';
  };
});

add(function() {
  this.name = 'bazz';
  this.deps = [downloader];
  this.all.win32 = function(state, next) {
    state.downloader.download('http://howtowin.edu/how2.pdf', function() {
      next();
    });
  };
});

*/
<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var url = require('url');
var http = require('http');
var lbPool = require('lb_pool');
var os = require('os');

var supportContentType = {
    'binary': 'application/vnd.kafka.binary.v1',
    'json': 'application/vnd.kafka.json.v1+json'
};

var emptyFunction = function EmptyFunction() {
};

// Kafka 8 Rest Proxy Client.
// Initialization: takes host and port and send a GET call to get all the
// <topic, host:port> mapping.
// Produce: for sending request, forkafka client will send a POST call
function KafkaRestClient(options, callback) {
    var self = this;
    callback = callback || emptyFunction;
    self.enable = false;
    self.connecting = false;
    self.proxyHost = options.proxyHost;
    self.urlToHttpClientMapping = {};
    self.proxyPort = options.proxyPort;
    self.requestUrl = self.proxyHost + ':' + self.proxyPort;
    self.mainHttpClient = new lbPool.Pool(http, [self.requestUrl], {
        /* eslint-disable camelcase */
        /*jshint camelcase: false */
        keep_alive: true
        /*jshint camelcase: true */
        /* eslint-enable camelcase */
    });
    self.proxyRefreshTime = options.refreshTime;
    if ('blacklistMigratorHttpClient' in options) {
        self.blacklistMigratorHttpClient = options.blacklistMigratorHttpClient;
    } else {
        self.blacklistMigratorHttpClient = null;
    }
    self.topicDiscoveryTimes = 0;
    self.maxRetries = options.maxRetries;

    self.discoverTopics(function discoverTopicsCallback(err, isDone) {
        if (err || !isDone) {
            self.enable = false;
            callback(new Error('Failed to discover topics mapping'));
        } else {
            self.enable = true;
            callback();
        }
    });
    self.scheduleTopicMapRefresh();
    self.statsd = options.statsd || null;
    self.metricsPrefix = 'kafka.rest-client.' + os.hostname();
}

KafkaRestClient.prototype.scheduleTopicMapRefresh = function scheduleTopicMapRefresh() {
    var self = this;
    if (self.proxyRefreshTime > 0) {
        // console.log('Trying to schedule topic refresh job every: ' +
        // self.proxyRefreshTime / 1000 + ' seconds.');
        /* eslint-disable no-undef,block-scoped-var */
        self.refreshIntervalId = setInterval(function refreshTopics() {
            self.discoverTopics(function refreshTopicsCallback(err, isDone) {
                if (err === null && isDone) {
                    self.enable = true;
                }
            });
        }, self.proxyRefreshTime);
        /* eslint-enable no-undef,block-scoped-var */
    }
};

KafkaRestClient.prototype.discoverTopics = function discoverTopics(callback) {
    var self = this;
    self.connecting = true;
    this.getTopicRequestBody(self.proxyHost, self.proxyPort, function updateTopicToUrlMapping(err, res) {
        if (err) {
            self.connecting = false;
            callback(err, false);
        } else {
            self.connecting = false;
            self.enable = true;
            self.cachedTopicToUrlMapping = self.getTopicToUrlMapping(res);
            self.topicDiscoveryTimes++;
            callback(err, true);
        }
    });
};

KafkaRestClient.prototype.getTopicRequestBody = function getTopicRequestBody(proxyHost, proxyPort, callback) {
    var self = this;
    self.mainHttpClient.get('/topics', function handleGetCall(err, res, body) {
        if (err) {
            callback(err, null);
        } else {
            callback(null, body);
        }
    });
};

KafkaRestClient.prototype.getTopicToUrlMapping = function getTopicToUrlMapping(body) {
    var urlToTopicsJson = JSON.parse(body);
    var topicToUrlMapping = {};
    var urlArray = Object.keys(urlToTopicsJson);
    for (var i = 0; i < urlArray.length; i++) {
        var reqUrl = urlArray[i];
        var topicsArray = urlToTopicsJson[reqUrl];
        for (var j = 0; j < topicsArray.length; j++) {
            topicToUrlMapping[topicsArray[j]] = reqUrl;
        }
    }
    return topicToUrlMapping;
};

// ProduceMessage {
// string topic,
// binary message,
// string type,
// long timeStamp
// }
KafkaRestClient.prototype.produce = function produce(produceMessage, callback) {
    var self = this;
    if (self.blacklistMigratorHttpClient) {
        self.blacklistMigratorHttpClient.blacklistTopic(produceMessage.topic,
            function blacklistTopicCallback(isBlacklisted) {
            if (isBlacklisted) {
                self.produceWithRetry(produceMessage, 0, callback);
            } else {
                callback(null, 'Topic is not blacklisted, not produce data to kafka rest proxy.');
            }
        });
    } else {
        self.produceWithRetry(produceMessage, 0, callback);
        if (this.statsd && typeof this.statsd.increment === 'function') {
          this.statsd.increment(self.metricsPrefix + '.produced'+ produceMessage.topic);
        }
    }
};

KafkaRestClient.prototype.produceWithRetry = function produceWithRetry(produceMessage, retry, callback) {
    var self = this;
    if (self.enable) {
        if (produceMessage.topic in self.cachedTopicToUrlMapping) {
            var pathUrl = self.cachedTopicToUrlMapping[produceMessage.topic];
            var httpClient;
            if (self.urlToHttpClientMapping[pathUrl] === undefined) {
                self.urlToHttpClientMapping[pathUrl] = new lbPool.Pool(http, [pathUrl], {
                    /* eslint-disable camelcase */
                    /*jshint camelcase: false */
                    keep_alive: true
                    /*jshint camelcase: true */
                    /* eslint-enable camelcase */
                });
            }
            httpClient = self.urlToHttpClientMapping[pathUrl];

            // http post call
            // console.log('Trying to produce msg: ' + message + ',to topic: ' +
            // topic + ', through url: ' + pathUrl);
            var reqOpts = url.parse('http://' + pathUrl);
            reqOpts.method = 'POST';
            reqOpts.path = '/topics/' + produceMessage.topic;
            reqOpts.headers = {
                'Content-Type': supportContentType[produceMessage.type],
                'TimeStamp': produceMessage.timeStamp.toString()
            };
            httpClient.post(reqOpts, produceMessage.message, function handlePostCall(err, res, body) {
                if (this.statsd && typeof this.statsd.increment === 'function') {
                  this.statsd.timing(self.metricsPrefix + '.error'+ produceMessage.topic, res.duration);
                }

                if (err) {
                    // Only retry with exception: Connection refused
                    if (err.reason === 'connect ECONNREFUSED' && retry < self.maxRetries) {
                        /* eslint-disable no-undef,block-scoped-var */
                        setTimeout(function delayedProduce() {
                            self.produceWithRetry(produceMessage, retry + 1, callback);
                        }, 600 * Math.pow(5, retry + 1)); // 3sec, 15sec, 75sec.
                        /* eslint-enable no-undef,block-scoped-var */

                        if (this.statsd && typeof this.statsd.increment === 'function') {
                          this.statsd.increment(self.metricsPrefix + '.retry'+ produceMessage.topic);
                        }
                    } else {
                        callback(err);

                        if (this.statsd && typeof this.statsd.increment === 'function') {
                          this.statsd.increment(self.metricsPrefix + '.error'+ produceMessage.topic);
                        }
                    }
                } else {
                    callback(null, body);
                }
            });

        } else {
            return callback(new Error('Topics Not Found.'));
        }
    } else if (self.connecting) {
        /* eslint-disable no-undef,block-scoped-var */
        setTimeout(function waitForInitDone() {
            self.produceWithRetry(produceMessage, retry, callback);
        }, 100);
        /* eslint-enable no-undef,block-scoped-var */
    } else {
        callback(new Error('Kafka Rest Client is not enabled yet.'));
    }

};

KafkaRestClient.prototype.close = function close() {
    // Trying to close kafka rest client, need to cancel timer scheduler.
    var self = this;
    self.enable = false;
    self.connecting = false;
    if (self.proxyRefreshTime > 0) {
        // Trying to cancel TimeInterval
        /* eslint-disable no-undef,block-scoped-var */
        clearInterval(self.refreshIntervalId);
        /* eslint-enable no-undef,block-scoped-var */
        self.proxyFreshTime = 0;
    }
    self.mainHttpClient.close();
    if (self.blacklistMigratorHttpClient) {
        self.blacklistMigratorHttpClient.close();
    }
    Object.keys(self.urlToHttpClientMapping).forEach(function closeHttpClient(reqUrl) {
        self.urlToHttpClientMapping[reqUrl].close();
    });
};

module.exports = KafkaRestClient;
<commit_msg>latency metrics<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var url = require('url');
var http = require('http');
var lbPool = require('lb_pool');
var os = require('os');

var supportContentType = {
    'binary': 'application/vnd.kafka.binary.v1',
    'json': 'application/vnd.kafka.json.v1+json'
};

var emptyFunction = function EmptyFunction() {
};

// Kafka 8 Rest Proxy Client.
// Initialization: takes host and port and send a GET call to get all the
// <topic, host:port> mapping.
// Produce: for sending request, forkafka client will send a POST call
function KafkaRestClient(options, callback) {
    var self = this;
    callback = callback || emptyFunction;
    self.enable = false;
    self.connecting = false;
    self.proxyHost = options.proxyHost;
    self.urlToHttpClientMapping = {};
    self.proxyPort = options.proxyPort;
    self.requestUrl = self.proxyHost + ':' + self.proxyPort;
    self.mainHttpClient = new lbPool.Pool(http, [self.requestUrl], {
        /* eslint-disable camelcase */
        /*jshint camelcase: false */
        keep_alive: true
        /*jshint camelcase: true */
        /* eslint-enable camelcase */
    });
    self.proxyRefreshTime = options.refreshTime;
    if ('blacklistMigratorHttpClient' in options) {
        self.blacklistMigratorHttpClient = options.blacklistMigratorHttpClient;
    } else {
        self.blacklistMigratorHttpClient = null;
    }
    self.topicDiscoveryTimes = 0;
    self.maxRetries = options.maxRetries;

    self.discoverTopics(function discoverTopicsCallback(err, isDone) {
        if (err || !isDone) {
            self.enable = false;
            callback(new Error('Failed to discover topics mapping'));
        } else {
            self.enable = true;
            callback();
        }
    });
    self.scheduleTopicMapRefresh();
    self.statsd = options.statsd || null;
    self.metricsPrefix = 'kafka.rest-client.' + os.hostname();
}

KafkaRestClient.prototype.scheduleTopicMapRefresh = function scheduleTopicMapRefresh() {
    var self = this;
    if (self.proxyRefreshTime > 0) {
        // console.log('Trying to schedule topic refresh job every: ' +
        // self.proxyRefreshTime / 1000 + ' seconds.');
        /* eslint-disable no-undef,block-scoped-var */
        self.refreshIntervalId = setInterval(function refreshTopics() {
            self.discoverTopics(function refreshTopicsCallback(err, isDone) {
                if (err === null && isDone) {
                    self.enable = true;
                }
            });
        }, self.proxyRefreshTime);
        /* eslint-enable no-undef,block-scoped-var */
    }
};

KafkaRestClient.prototype.discoverTopics = function discoverTopics(callback) {
    var self = this;
    self.connecting = true;
    this.getTopicRequestBody(self.proxyHost, self.proxyPort, function updateTopicToUrlMapping(err, res) {
        if (err) {
            self.connecting = false;
            callback(err, false);
        } else {
            self.connecting = false;
            self.enable = true;
            self.cachedTopicToUrlMapping = self.getTopicToUrlMapping(res);
            self.topicDiscoveryTimes++;
            callback(err, true);
        }
    });
};

KafkaRestClient.prototype.getTopicRequestBody = function getTopicRequestBody(proxyHost, proxyPort, callback) {
    var self = this;
    self.mainHttpClient.get('/topics', function handleGetCall(err, res, body) {
        if (err) {
            callback(err, null);
        } else {
            callback(null, body);
        }
    });
};

KafkaRestClient.prototype.getTopicToUrlMapping = function getTopicToUrlMapping(body) {
    var urlToTopicsJson = JSON.parse(body);
    var topicToUrlMapping = {};
    var urlArray = Object.keys(urlToTopicsJson);
    for (var i = 0; i < urlArray.length; i++) {
        var reqUrl = urlArray[i];
        var topicsArray = urlToTopicsJson[reqUrl];
        for (var j = 0; j < topicsArray.length; j++) {
            topicToUrlMapping[topicsArray[j]] = reqUrl;
        }
    }
    return topicToUrlMapping;
};

// ProduceMessage {
// string topic,
// binary message,
// string type,
// long timeStamp
// }
KafkaRestClient.prototype.produce = function produce(produceMessage, callback) {
    var self = this;
    if (self.blacklistMigratorHttpClient) {
        self.blacklistMigratorHttpClient.blacklistTopic(produceMessage.topic,
            function blacklistTopicCallback(isBlacklisted) {
            if (isBlacklisted) {
                self.produceWithRetry(produceMessage, 0, callback);
            } else {
                callback(null, 'Topic is not blacklisted, not produce data to kafka rest proxy.');
            }
        });
    } else {
        self.produceWithRetry(produceMessage, 0, callback);
        if (this.statsd && typeof this.statsd.increment === 'function') {
          this.statsd.increment(self.metricsPrefix + '.produced'+ produceMessage.topic);
        }
    }
};

KafkaRestClient.prototype.produceWithRetry = function produceWithRetry(produceMessage, retry, callback) {
    var self = this;
    if (self.enable) {
        if (produceMessage.topic in self.cachedTopicToUrlMapping) {
            var pathUrl = self.cachedTopicToUrlMapping[produceMessage.topic];
            var httpClient;
            if (self.urlToHttpClientMapping[pathUrl] === undefined) {
                self.urlToHttpClientMapping[pathUrl] = new lbPool.Pool(http, [pathUrl], {
                    /* eslint-disable camelcase */
                    /*jshint camelcase: false */
                    keep_alive: true
                    /*jshint camelcase: true */
                    /* eslint-enable camelcase */
                });
            }
            httpClient = self.urlToHttpClientMapping[pathUrl];

            // http post call
            // console.log('Trying to produce msg: ' + message + ',to topic: ' +
            // topic + ', through url: ' + pathUrl);
            var reqOpts = url.parse('http://' + pathUrl);
            reqOpts.method = 'POST';
            reqOpts.path = '/topics/' + produceMessage.topic;
            reqOpts.headers = {
                'Content-Type': supportContentType[produceMessage.type],
                'TimeStamp': produceMessage.timeStamp.toString()
            };
            httpClient.post(reqOpts, produceMessage.message, function handlePostCall(err, res, body) {
                if (this.statsd && typeof this.statsd.increment === 'function') {
                  this.statsd.timing(self.metricsPrefix + '.error'+ produceMessage.topic, res.timing.duration);
                }

                if (err) {
                    // Only retry with exception: Connection refused
                    if (err.reason === 'connect ECONNREFUSED' && retry < self.maxRetries) {
                        /* eslint-disable no-undef,block-scoped-var */
                        setTimeout(function delayedProduce() {
                            self.produceWithRetry(produceMessage, retry + 1, callback);
                        }, 600 * Math.pow(5, retry + 1)); // 3sec, 15sec, 75sec.
                        /* eslint-enable no-undef,block-scoped-var */

                        if (this.statsd && typeof this.statsd.increment === 'function') {
                          this.statsd.increment(self.metricsPrefix + '.retry'+ produceMessage.topic);
                        }
                    } else {
                        callback(err);

                        if (this.statsd && typeof this.statsd.increment === 'function') {
                          this.statsd.increment(self.metricsPrefix + '.error'+ produceMessage.topic);
                        }
                    }
                } else {
                    callback(null, body);
                }
            });

        } else {
            return callback(new Error('Topics Not Found.'));
        }
    } else if (self.connecting) {
        /* eslint-disable no-undef,block-scoped-var */
        setTimeout(function waitForInitDone() {
            self.produceWithRetry(produceMessage, retry, callback);
        }, 100);
        /* eslint-enable no-undef,block-scoped-var */
    } else {
        callback(new Error('Kafka Rest Client is not enabled yet.'));
    }

};

KafkaRestClient.prototype.close = function close() {
    // Trying to close kafka rest client, need to cancel timer scheduler.
    var self = this;
    self.enable = false;
    self.connecting = false;
    if (self.proxyRefreshTime > 0) {
        // Trying to cancel TimeInterval
        /* eslint-disable no-undef,block-scoped-var */
        clearInterval(self.refreshIntervalId);
        /* eslint-enable no-undef,block-scoped-var */
        self.proxyFreshTime = 0;
    }
    self.mainHttpClient.close();
    if (self.blacklistMigratorHttpClient) {
        self.blacklistMigratorHttpClient.close();
    }
    Object.keys(self.urlToHttpClientMapping).forEach(function closeHttpClient(reqUrl) {
        self.urlToHttpClientMapping[reqUrl].close();
    });
};

module.exports = KafkaRestClient;
<|endoftext|>