<commit_before>// @flow

import { SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED } from '../../video-layout/actionTypes';
import { ReducerRegistry, set } from '../redux';

import {
    DOMINANT_SPEAKER_CHANGED,
    PARTICIPANT_ID_CHANGED,
    PARTICIPANT_JOINED,
    PARTICIPANT_LEFT,
    PARTICIPANT_UPDATED,
    PIN_PARTICIPANT,
    RAISE_HAND_UPDATED,
    SET_LOADABLE_AVATAR_URL
} from './actionTypes';
import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
import { isParticipantModerator } from './functions';

declare var interfaceConfig: Object;

/**
 * Participant object.
 * @typedef {Object} Participant
 * @property {string} id - Participant ID.
 * @property {string} name - Participant name.
 * @property {string} avatar - Path to participant avatar if any.
 * @property {string} role - Participant role.
 * @property {boolean} local - If true, participant is local.
 * @property {boolean} pinned - If true, participant is currently a
 * "PINNED_ENDPOINT".
 * @property {boolean} dominantSpeaker - If this participant is the dominant
 * speaker in the (associated) conference, {@code true}; otherwise,
 * {@code false}.
 * @property {string} email - Participant email.
 */

/**
 * The participant properties which cannot be updated through
 * {@link PARTICIPANT_UPDATED}. They either identify the participant or can only
 * be modified through property-dedicated actions.
 *
 * @type {string[]}
 */
const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [

    // The following properties identify the participant:
    'conference',
    'id',
    'local',

    // The following properties can only be modified through property-dedicated
    // actions:
    'dominantSpeaker',
    'pinned'
];

const DEFAULT_STATE = {
    dominantSpeaker: undefined,
    everyoneIsModerator: false,
    fakeParticipants: new Map(),
    haveParticipantWithScreenSharingFeature: false,
    local: undefined,
    pinnedParticipant: undefined,
    raisedHandsQueue: [],
    remote: new Map(),
    sortedRemoteParticipants: new Map(),
    sortedRemoteScreenshares: new Map(),
    speakersList: new Map()
};

/**
 * Listen for actions which add, remove, or update the set of participants in
 * the conference.
 *
 * @param {Participant[]} state - List of participants to be modified.
 * @param {Object} action - Action object.
 * @param {string} action.type - Type of action.
 * @param {Participant} action.participant - Information about participant to be
 * added/removed/modified.
 * @returns {Participant[]}
 */
ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, action) => {
    switch (action.type) {
    case PARTICIPANT_ID_CHANGED: {
        const { local } = state;

        if (local) {
            state.local = {
                ...local,
                id: action.newValue
            };

            return {
                ...state
            };
        }

        return state;
    }
    case DOMINANT_SPEAKER_CHANGED: {
        const { participant } = action;
        const { id, previousSpeakers = [] } = participant;
        const { dominantSpeaker, local } = state;
        const newSpeakers = [ id, ...previousSpeakers ];
        const sortedSpeakersList = [];

        for (const speaker of newSpeakers) {
            if (speaker !== local?.id) {
                const remoteParticipant = state.remote.get(speaker);

                remoteParticipant && sortedSpeakersList.push([ speaker, _getDisplayName(remoteParticipant.name) ]);
            }
        }

        // Keep the remote speaker list sorted alphabetically.
        sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));

        // Only one dominant speaker is allowed.
        if (dominantSpeaker) {
            _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
        }

        if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
            return {
                ...state,
                dominantSpeaker: id,
                speakersList: new Map(sortedSpeakersList)
            };
        }

        delete state.dominantSpeaker;

        return {
            ...state
        };
    }
    case PIN_PARTICIPANT: {
        const { participant } = action;
        const { id } = participant;
        const { pinnedParticipant } = state;

        // Only one pinned participant is allowed.
        if (pinnedParticipant) {
            _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
        }

        if (_updateParticipantProperty(state, id, 'pinned', true)) {
            return {
                ...state,
                pinnedParticipant: id
            };
        }

        delete state.pinnedParticipant;

        return {
            ...state
        };
    }
    case SET_LOADABLE_AVATAR_URL:
    case PARTICIPANT_UPDATED: {
        const { participant } = action;
        let { id } = participant;
        const { local } = participant;

        if (!id && local) {
            id = LOCAL_PARTICIPANT_DEFAULT_ID;
        }

        let newParticipant;

        if (state.remote.has(id)) {
            newParticipant = _participant(state.remote.get(id), action);
            state.remote.set(id, newParticipant);
        } else if (id === state.local?.id) {
            newParticipant = state.local = _participant(state.local, action);
        }

        if (newParticipant) {

            // everyoneIsModerator calculation:
            const isModerator = isParticipantModerator(newParticipant);

            if (state.everyoneIsModerator && !isModerator) {
                state.everyoneIsModerator = false;
            } else if (!state.everyoneIsModerator && isModerator) {
                state.everyoneIsModerator = _isEveryoneModerator(state);
            }

            // haveParticipantWithScreenSharingFeature calculation:
            const { features = {} } = participant;

            // Currently we use only PARTICIPANT_UPDATED to set a feature to enabled and we never disable it.
            if (String(features['screen-sharing']) === 'true') {
                state.haveParticipantWithScreenSharingFeature = true;
            }
        }

        return {
            ...state
        };
    }
    case PARTICIPANT_JOINED: {
        const participant = _participantJoined(action);
        const { id, isFakeParticipant, name, pinned } = participant;
        const { pinnedParticipant, dominantSpeaker } = state;

        if (pinned) {
            if (pinnedParticipant) {
                _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
            }

            state.pinnedParticipant = id;
        }

        if (participant.dominantSpeaker) {
            if (dominantSpeaker) {
                _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
            }
            state.dominantSpeaker = id;
        }

        const isModerator = isParticipantModerator(participant);
        const { local, remote } = state;

        if (state.everyoneIsModerator && !isModerator) {
            state.everyoneIsModerator = false;
        } else if (!local && remote.size === 0 && isModerator) {
            state.everyoneIsModerator = true;
        }

        if (participant.local) {
            return {
                ...state,
                local: participant
            };
        }

        state.remote.set(id, participant);

        // Insert the new participant.
        const displayName = _getDisplayName(name);
        const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);

        sortedRemoteParticipants.push([ id, displayName ]);
        sortedRemoteParticipants.sort((a, b) => a[1].localeCompare(b[1]));

        // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
        state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);

        if (isFakeParticipant) {
            state.fakeParticipants.set(id, participant);
        }

        return { ...state };

    }
    case PARTICIPANT_LEFT: {
        // XXX A remote participant is uniquely identified by their id in a
        // specific JitsiConference instance. The local participant is uniquely
        // identified by the very fact that there is only one local participant
        // (and the fact that the local participant "joins" at the beginning of
        // the app and "leaves" at the end of the app).
        const { conference, id } = action.participant;
        const { fakeParticipants, remote, local, dominantSpeaker, pinnedParticipant } = state;
        let oldParticipant = remote.get(id);

        if (oldParticipant && oldParticipant.conference === conference) {
            remote.delete(id);
        } else if (local?.id === id) {
            oldParticipant = state.local;
            delete state.local;
        } else {
            // no participant found
            return state;
        }

        state.sortedRemoteParticipants.delete(id);

        if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
            state.everyoneIsModerator = _isEveryoneModerator(state);
        }

        const { features = {} } = oldParticipant || {};

        if (state.haveParticipantWithScreenSharingFeature && String(features['screen-sharing']) === 'true') {
            const { features: localFeatures = {} } = state.local || {};

            if (String(localFeatures['screen-sharing']) !== 'true') {
                state.haveParticipantWithScreenSharingFeature = false;

                // eslint-disable-next-line no-unused-vars
                for (const [ key, participant ] of state.remote) {
                    const { features: f = {} } = participant;

                    if (String(f['screen-sharing']) === 'true') {
                        state.haveParticipantWithScreenSharingFeature = true;
                        break;
                    }
                }
            }

        }

        if (dominantSpeaker === id) {
            state.dominantSpeaker = undefined;
        }

        // Remove the participant from the list of speakers.
        state.speakersList.has(id) && state.speakersList.delete(id);

        if (pinnedParticipant === id) {
            state.pinnedParticipant = undefined;
        }

        if (fakeParticipants.has(id)) {
            fakeParticipants.delete(id);
        }

        return { ...state };
    }
    case RAISE_HAND_UPDATED: {
        return {
            ...state,
            raisedHandsQueue: action.queue
        };
    }
    case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED: {
        const { participantIds } = action;
        const sortedSharesList = [];

        for (const participant of participantIds) {
            const remoteParticipant = state.remote.get(participant);

            if (remoteParticipant) {
                const displayName = _getDisplayName(remoteParticipant.name);

                sortedSharesList.push([ participant, displayName ]);
            }
        }

        // Keep the remote screen share list sorted alphabetically.
        sortedSharesList.length && sortedSharesList.sort((a, b) => a[1].localeCompare(b[1]));
        state.sortedRemoteScreenshares = new Map(sortedSharesList);

        return { ...state };
    }
    }

    return state;
});

/**
 * Returns the participant's display name, default string if display name is not set on the participant.
 *
 * @param {string} name - The display name of the participant.
 * @returns {string}
 */
function _getDisplayName(name) {
    return name
        ?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
}

/**
 * Loops trough the participants in the state in order to check if all participants are moderators.
 *
 * @param {Object} state - The local participant redux state.
 * @returns {boolean}
 */
function _isEveryoneModerator(state) {
    if (isParticipantModerator(state.local)) {
        // eslint-disable-next-line no-unused-vars
        for (const [ k, p ] of state.remote) {
            if (!isParticipantModerator(p)) {
                return false;
            }
        }

        return true;
    }

    return false;
}

/**
 * Reducer function for a single participant.
 *
 * @param {Participant|undefined} state - Participant to be modified.
 * @param {Object} action - Action object.
 * @param {string} action.type - Type of action.
 * @param {Participant} action.participant - Information about participant to be
 * added/modified.
 * @param {JitsiConference} action.conference - Conference instance.
 * @private
 * @returns {Participant}
 */
function _participant(state: Object = {}, action) {
    switch (action.type) {
    case SET_LOADABLE_AVATAR_URL:
    case PARTICIPANT_UPDATED: {
        const { participant } = action; // eslint-disable-line no-shadow

        const newState = { ...state };

        for (const key in participant) {
            if (participant.hasOwnProperty(key)
                    && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
                        === -1) {
                newState[key] = participant[key];
            }
        }

        return newState;
    }
    }

    return state;
}

/**
 * Reduces a specific redux action of type {@link PARTICIPANT_JOINED} in the
 * feature base/participants.
 *
 * @param {Action} action - The redux action of type {@code PARTICIPANT_JOINED}
 * to reduce.
 * @private
 * @returns {Object} The new participant derived from the payload of the
 * specified {@code action} to be added into the redux state of the feature
 * base/participants after the reduction of the specified
 * {@code action}.
 */
function _participantJoined({ participant }) {
    const {
        avatarURL,
        botType,
        connectionStatus,
        dominantSpeaker,
        email,
        isFakeParticipant,
        isReplacing,
        isJigasi,
        loadableAvatarUrl,
        local,
        name,
        pinned,
        presence,
        role
    } = participant;
    let { conference, id } = participant;

    if (local) {
        // conference
        //
        // XXX The local participant is not identified in association with a
        // JitsiConference because it is identified by the very fact that it is
        // the local participant.
        conference = undefined;

        // id
        id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
    }

    return {
        avatarURL,
        botType,
        conference,
        connectionStatus,
        dominantSpeaker: dominantSpeaker || false,
        email,
        id,
        isFakeParticipant,
        isReplacing,
        isJigasi,
        loadableAvatarUrl,
        local: local || false,
        name,
        pinned: pinned || false,
        presence,
        role: role || PARTICIPANT_ROLE.NONE
    };
}

/**
 * Updates a specific property for a participant.
 *
 * @param {State} state - The redux state.
 * @param {string} id - The ID of the participant.
 * @param {string} property - The property to update.
 * @param {*} value - The new value.
 * @returns {boolean} - True if a participant was updated and false otherwise.
 */
function _updateParticipantProperty(state, id, property, value) {
    const { remote, local } = state;

    if (remote.has(id)) {
        remote.set(id, set(remote.get(id), property, value));

        return true;
    } else if (local?.id === id) {
        state.local = set(local, property, value);

        return true;
    }

    return false;
}
<commit_msg>fix(raise-hand): Remove participant left from raised hand queue<commit_after>// @flow

import { SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED } from '../../video-layout/actionTypes';
import { ReducerRegistry, set } from '../redux';

import {
    DOMINANT_SPEAKER_CHANGED,
    PARTICIPANT_ID_CHANGED,
    PARTICIPANT_JOINED,
    PARTICIPANT_LEFT,
    PARTICIPANT_UPDATED,
    PIN_PARTICIPANT,
    RAISE_HAND_UPDATED,
    SET_LOADABLE_AVATAR_URL
} from './actionTypes';
import { LOCAL_PARTICIPANT_DEFAULT_ID, PARTICIPANT_ROLE } from './constants';
import { isParticipantModerator } from './functions';

declare var interfaceConfig: Object;

/**
 * Participant object.
 * @typedef {Object} Participant
 * @property {string} id - Participant ID.
 * @property {string} name - Participant name.
 * @property {string} avatar - Path to participant avatar if any.
 * @property {string} role - Participant role.
 * @property {boolean} local - If true, participant is local.
 * @property {boolean} pinned - If true, participant is currently a
 * "PINNED_ENDPOINT".
 * @property {boolean} dominantSpeaker - If this participant is the dominant
 * speaker in the (associated) conference, {@code true}; otherwise,
 * {@code false}.
 * @property {string} email - Participant email.
 */

/**
 * The participant properties which cannot be updated through
 * {@link PARTICIPANT_UPDATED}. They either identify the participant or can only
 * be modified through property-dedicated actions.
 *
 * @type {string[]}
 */
const PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE = [

    // The following properties identify the participant:
    'conference',
    'id',
    'local',

    // The following properties can only be modified through property-dedicated
    // actions:
    'dominantSpeaker',
    'pinned'
];

const DEFAULT_STATE = {
    dominantSpeaker: undefined,
    everyoneIsModerator: false,
    fakeParticipants: new Map(),
    haveParticipantWithScreenSharingFeature: false,
    local: undefined,
    pinnedParticipant: undefined,
    raisedHandsQueue: [],
    remote: new Map(),
    sortedRemoteParticipants: new Map(),
    sortedRemoteScreenshares: new Map(),
    speakersList: new Map()
};

/**
 * Listen for actions which add, remove, or update the set of participants in
 * the conference.
 *
 * @param {Participant[]} state - List of participants to be modified.
 * @param {Object} action - Action object.
 * @param {string} action.type - Type of action.
 * @param {Participant} action.participant - Information about participant to be
 * added/removed/modified.
 * @returns {Participant[]}
 */
ReducerRegistry.register('features/base/participants', (state = DEFAULT_STATE, action) => {
    switch (action.type) {
    case PARTICIPANT_ID_CHANGED: {
        const { local } = state;

        if (local) {
            state.local = {
                ...local,
                id: action.newValue
            };

            return {
                ...state
            };
        }

        return state;
    }
    case DOMINANT_SPEAKER_CHANGED: {
        const { participant } = action;
        const { id, previousSpeakers = [] } = participant;
        const { dominantSpeaker, local } = state;
        const newSpeakers = [ id, ...previousSpeakers ];
        const sortedSpeakersList = [];

        for (const speaker of newSpeakers) {
            if (speaker !== local?.id) {
                const remoteParticipant = state.remote.get(speaker);

                remoteParticipant && sortedSpeakersList.push([ speaker, _getDisplayName(remoteParticipant.name) ]);
            }
        }

        // Keep the remote speaker list sorted alphabetically.
        sortedSpeakersList.sort((a, b) => a[1].localeCompare(b[1]));

        // Only one dominant speaker is allowed.
        if (dominantSpeaker) {
            _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
        }

        if (_updateParticipantProperty(state, id, 'dominantSpeaker', true)) {
            return {
                ...state,
                dominantSpeaker: id,
                speakersList: new Map(sortedSpeakersList)
            };
        }

        delete state.dominantSpeaker;

        return {
            ...state
        };
    }
    case PIN_PARTICIPANT: {
        const { participant } = action;
        const { id } = participant;
        const { pinnedParticipant } = state;

        // Only one pinned participant is allowed.
        if (pinnedParticipant) {
            _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
        }

        if (_updateParticipantProperty(state, id, 'pinned', true)) {
            return {
                ...state,
                pinnedParticipant: id
            };
        }

        delete state.pinnedParticipant;

        return {
            ...state
        };
    }
    case SET_LOADABLE_AVATAR_URL:
    case PARTICIPANT_UPDATED: {
        const { participant } = action;
        let { id } = participant;
        const { local } = participant;

        if (!id && local) {
            id = LOCAL_PARTICIPANT_DEFAULT_ID;
        }

        let newParticipant;

        if (state.remote.has(id)) {
            newParticipant = _participant(state.remote.get(id), action);
            state.remote.set(id, newParticipant);
        } else if (id === state.local?.id) {
            newParticipant = state.local = _participant(state.local, action);
        }

        if (newParticipant) {

            // everyoneIsModerator calculation:
            const isModerator = isParticipantModerator(newParticipant);

            if (state.everyoneIsModerator && !isModerator) {
                state.everyoneIsModerator = false;
            } else if (!state.everyoneIsModerator && isModerator) {
                state.everyoneIsModerator = _isEveryoneModerator(state);
            }

            // haveParticipantWithScreenSharingFeature calculation:
            const { features = {} } = participant;

            // Currently we use only PARTICIPANT_UPDATED to set a feature to enabled and we never disable it.
            if (String(features['screen-sharing']) === 'true') {
                state.haveParticipantWithScreenSharingFeature = true;
            }
        }

        return {
            ...state
        };
    }
    case PARTICIPANT_JOINED: {
        const participant = _participantJoined(action);
        const { id, isFakeParticipant, name, pinned } = participant;
        const { pinnedParticipant, dominantSpeaker } = state;

        if (pinned) {
            if (pinnedParticipant) {
                _updateParticipantProperty(state, pinnedParticipant, 'pinned', false);
            }

            state.pinnedParticipant = id;
        }

        if (participant.dominantSpeaker) {
            if (dominantSpeaker) {
                _updateParticipantProperty(state, dominantSpeaker, 'dominantSpeaker', false);
            }
            state.dominantSpeaker = id;
        }

        const isModerator = isParticipantModerator(participant);
        const { local, remote } = state;

        if (state.everyoneIsModerator && !isModerator) {
            state.everyoneIsModerator = false;
        } else if (!local && remote.size === 0 && isModerator) {
            state.everyoneIsModerator = true;
        }

        if (participant.local) {
            return {
                ...state,
                local: participant
            };
        }

        state.remote.set(id, participant);

        // Insert the new participant.
        const displayName = _getDisplayName(name);
        const sortedRemoteParticipants = Array.from(state.sortedRemoteParticipants);

        sortedRemoteParticipants.push([ id, displayName ]);
        sortedRemoteParticipants.sort((a, b) => a[1].localeCompare(b[1]));

        // The sort order of participants is preserved since Map remembers the original insertion order of the keys.
        state.sortedRemoteParticipants = new Map(sortedRemoteParticipants);

        if (isFakeParticipant) {
            state.fakeParticipants.set(id, participant);
        }

        return { ...state };

    }
    case PARTICIPANT_LEFT: {
        // XXX A remote participant is uniquely identified by their id in a
        // specific JitsiConference instance. The local participant is uniquely
        // identified by the very fact that there is only one local participant
        // (and the fact that the local participant "joins" at the beginning of
        // the app and "leaves" at the end of the app).
        const { conference, id } = action.participant;
        const { fakeParticipants, remote, local, dominantSpeaker, pinnedParticipant } = state;
        let oldParticipant = remote.get(id);

        if (oldParticipant && oldParticipant.conference === conference) {
            remote.delete(id);
        } else if (local?.id === id) {
            oldParticipant = state.local;
            delete state.local;
        } else {
            // no participant found
            return state;
        }

        state.sortedRemoteParticipants.delete(id);
        state.raisedHandsQueue = state.raisedHandsQueue.filter(pid => pid.id !== id);

        if (!state.everyoneIsModerator && !isParticipantModerator(oldParticipant)) {
            state.everyoneIsModerator = _isEveryoneModerator(state);
        }

        const { features = {} } = oldParticipant || {};

        if (state.haveParticipantWithScreenSharingFeature && String(features['screen-sharing']) === 'true') {
            const { features: localFeatures = {} } = state.local || {};

            if (String(localFeatures['screen-sharing']) !== 'true') {
                state.haveParticipantWithScreenSharingFeature = false;

                // eslint-disable-next-line no-unused-vars
                for (const [ key, participant ] of state.remote) {
                    const { features: f = {} } = participant;

                    if (String(f['screen-sharing']) === 'true') {
                        state.haveParticipantWithScreenSharingFeature = true;
                        break;
                    }
                }
            }

        }

        if (dominantSpeaker === id) {
            state.dominantSpeaker = undefined;
        }

        // Remove the participant from the list of speakers.
        state.speakersList.has(id) && state.speakersList.delete(id);

        if (pinnedParticipant === id) {
            state.pinnedParticipant = undefined;
        }

        if (fakeParticipants.has(id)) {
            fakeParticipants.delete(id);
        }

        return { ...state };
    }
    case RAISE_HAND_UPDATED: {
        return {
            ...state,
            raisedHandsQueue: action.queue
        };
    }
    case SCREEN_SHARE_REMOTE_PARTICIPANTS_UPDATED: {
        const { participantIds } = action;
        const sortedSharesList = [];

        for (const participant of participantIds) {
            const remoteParticipant = state.remote.get(participant);

            if (remoteParticipant) {
                const displayName = _getDisplayName(remoteParticipant.name);

                sortedSharesList.push([ participant, displayName ]);
            }
        }

        // Keep the remote screen share list sorted alphabetically.
        sortedSharesList.length && sortedSharesList.sort((a, b) => a[1].localeCompare(b[1]));
        state.sortedRemoteScreenshares = new Map(sortedSharesList);

        return { ...state };
    }
    }

    return state;
});

/**
 * Returns the participant's display name, default string if display name is not set on the participant.
 *
 * @param {string} name - The display name of the participant.
 * @returns {string}
 */
function _getDisplayName(name) {
    return name
        ?? (typeof interfaceConfig === 'object' ? interfaceConfig.DEFAULT_REMOTE_DISPLAY_NAME : 'Fellow Jitser');
}

/**
 * Loops trough the participants in the state in order to check if all participants are moderators.
 *
 * @param {Object} state - The local participant redux state.
 * @returns {boolean}
 */
function _isEveryoneModerator(state) {
    if (isParticipantModerator(state.local)) {
        // eslint-disable-next-line no-unused-vars
        for (const [ k, p ] of state.remote) {
            if (!isParticipantModerator(p)) {
                return false;
            }
        }

        return true;
    }

    return false;
}

/**
 * Reducer function for a single participant.
 *
 * @param {Participant|undefined} state - Participant to be modified.
 * @param {Object} action - Action object.
 * @param {string} action.type - Type of action.
 * @param {Participant} action.participant - Information about participant to be
 * added/modified.
 * @param {JitsiConference} action.conference - Conference instance.
 * @private
 * @returns {Participant}
 */
function _participant(state: Object = {}, action) {
    switch (action.type) {
    case SET_LOADABLE_AVATAR_URL:
    case PARTICIPANT_UPDATED: {
        const { participant } = action; // eslint-disable-line no-shadow

        const newState = { ...state };

        for (const key in participant) {
            if (participant.hasOwnProperty(key)
                    && PARTICIPANT_PROPS_TO_OMIT_WHEN_UPDATE.indexOf(key)
                        === -1) {
                newState[key] = participant[key];
            }
        }

        return newState;
    }
    }

    return state;
}

/**
 * Reduces a specific redux action of type {@link PARTICIPANT_JOINED} in the
 * feature base/participants.
 *
 * @param {Action} action - The redux action of type {@code PARTICIPANT_JOINED}
 * to reduce.
 * @private
 * @returns {Object} The new participant derived from the payload of the
 * specified {@code action} to be added into the redux state of the feature
 * base/participants after the reduction of the specified
 * {@code action}.
 */
function _participantJoined({ participant }) {
    const {
        avatarURL,
        botType,
        connectionStatus,
        dominantSpeaker,
        email,
        isFakeParticipant,
        isReplacing,
        isJigasi,
        loadableAvatarUrl,
        local,
        name,
        pinned,
        presence,
        role
    } = participant;
    let { conference, id } = participant;

    if (local) {
        // conference
        //
        // XXX The local participant is not identified in association with a
        // JitsiConference because it is identified by the very fact that it is
        // the local participant.
        conference = undefined;

        // id
        id || (id = LOCAL_PARTICIPANT_DEFAULT_ID);
    }

    return {
        avatarURL,
        botType,
        conference,
        connectionStatus,
        dominantSpeaker: dominantSpeaker || false,
        email,
        id,
        isFakeParticipant,
        isReplacing,
        isJigasi,
        loadableAvatarUrl,
        local: local || false,
        name,
        pinned: pinned || false,
        presence,
        role: role || PARTICIPANT_ROLE.NONE
    };
}

/**
 * Updates a specific property for a participant.
 *
 * @param {State} state - The redux state.
 * @param {string} id - The ID of the participant.
 * @param {string} property - The property to update.
 * @param {*} value - The new value.
 * @returns {boolean} - True if a participant was updated and false otherwise.
 */
function _updateParticipantProperty(state, id, property, value) {
    const { remote, local } = state;

    if (remote.has(id)) {
        remote.set(id, set(remote.get(id), property, value));

        return true;
    } else if (local?.id === id) {
        state.local = set(local, property, value);

        return true;
    }

    return false;
}
<|endoftext|>


<commit_before>'use strict';

function send_message(recipient, message_json) {
  $.ajax("action.php",
         {type: 'POST',
          data: {action: 'message.send',
                 recipient: recipient,
                 message: JSON.stringify(message_json)}});
};

function MessagePoller(ms, recipient, on_message) {
  let interval;

  let poller = this;

  this.retrieve_messages = function() {
    $.ajax("action.php",
           {type: 'POST',
            data: {action: 'message.retrieve',
                   recipient: recipient},
            success: function(data) {
              var msgs = data.messages;
              for (var i = 0; i < msgs.length; ++i) {
                on_message(mesgs[i]);
              }
            }
           });
  };

  this.set_polling_pace = function(ms) {
    if (interval) {
      clearInterval(interval);
    }
    interval = setInterval(function() { poller.retrieve_messages(); }, ms);
  };

  this.set_polling_pace(ms);
}
<commit_msg>Bug fix for typo in message-poller that prevents operation of remote camera.<commit_after>'use strict';

function send_message(recipient, message_json) {
  $.ajax("action.php",
         {type: 'POST',
          data: {action: 'message.send',
                 recipient: recipient,
                 message: JSON.stringify(message_json)}});
};

function MessagePoller(ms, recipient, on_message) {
  let interval;

  let poller = this;

  this.retrieve_messages = function() {
    $.ajax("action.php",
           {type: 'POST',
            data: {action: 'message.retrieve',
                   recipient: recipient},
            success: function(data) {
              var msgs = data.messages;
              for (var i = 0; i < msgs.length; ++i) {
                on_message(msgs[i]);
              }
            }
           });
  };

  this.set_polling_pace = function(ms) {
    if (interval) {
      clearInterval(interval);
    }
    interval = setInterval(function() { poller.retrieve_messages(); }, ms);
  };

  this.set_polling_pace(ms);
}
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider as MuiThemeProvider } from '@material-ui/styles';
import { createMuiTheme, darken } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { blue, pink } from '@material-ui/core/colors';
import { getCookie } from 'docs/src/modules/utils/helpers';
import { darkTheme, setPrismTheme } from 'docs/src/modules/components/prism';
import deepmerge from 'deepmerge';

export const themeColor = blue[700];

const themeInitialOptions = {
  dense: false,
  direction: 'ltr',
  paletteColors: {},
  spacing: 8, // spacing unit
};

/**
 * @typedef {import('@material-ui/core/src/styles/createMuiTheme').ThemeOptions} ThemeOptions
 *
 *
 * @param {ThemeOptions} themeOptions
 * @returns {ThemeOptions}
 */
function usingHighDensity(themeOptions) {
  return deepmerge(themeOptions, {
    props: {
      MuiButton: {
        size: 'small',
      },
      MuiFilledInput: {
        margin: 'dense',
      },
      MuiFormControl: {
        margin: 'dense',
      },
      MuiFormHelperText: {
        margin: 'dense',
      },
      MuiIconButton: {
        size: 'small',
      },
      MuiInputBase: {
        margin: 'dense',
      },
      MuiInputLabel: {
        margin: 'dense',
      },
      MuiListItem: {
        dense: true,
      },
      MuiOutlinedInput: {
        margin: 'dense',
      },
      MuiFab: {
        size: 'small',
      },
      MuiTable: {
        size: 'small',
      },
      MuiTextField: {
        margin: 'dense',
      },
      MuiToolbar: {
        variant: 'dense',
      },
    },
    overrides: {
      MuiIconButton: {
        sizeSmall: {
          // minimal touch target hit spacing
          marginLeft: 4,
          marginRight: 4,
          padding: 12,
        },
      },
    },
  });
}

function usingIdentity(themeOptions) {
  return themeOptions;
}

export const DispatchContext = React.createContext(() => {
  throw new Error('Forgot to wrap component in ThemeContext.Provider');
});

const useEnhancedEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;

export function ThemeProvider(props) {
  const { children } = props;

  const [themeOptions, dispatch] = React.useReducer((state, action) => {
    switch (action.type) {
      case 'SET_SPACING':
        return {
          ...state,
          spacing: action.payload,
        };
      case 'INCREASE_SPACING': {
        return {
          ...state,
          spacing: state.spacing + 1,
        };
      }
      case 'DECREASE_SPACING': {
        return {
          ...state,
          spacing: state.spacing - 1,
        };
      }
      case 'SET_DENSE':
        return {
          ...state,
          dense: action.payload,
        };
      case 'RESET_DENSITY':
        return {
          ...state,
          dense: themeInitialOptions.dense,
          spacing: themeInitialOptions.spacing,
        };
      case 'CHANGE':
        return {
          ...state,
          paletteType: action.payload.paletteType || state.paletteType,
          direction: action.payload.direction || state.direction,
          paletteColors: action.payload.paletteColors || state.paletteColors,
        };
      default:
        throw new Error(`Unrecognized type ${action.type}`);
    }
  }, themeInitialOptions);

  const prefersDarkMode = useMediaQuery('@media (prefers-color-scheme: dark)');
  const preferredType = prefersDarkMode ? 'dark' : 'light';
  const { dense, direction, paletteColors, paletteType = preferredType, spacing } = themeOptions;

  React.useEffect(() => {
    setPrismTheme(darkTheme);
  }, []);

  React.useEffect(() => {
    if (process.browser) {
      const nextPaletteColors = JSON.parse(getCookie('paletteColors') || 'null');
      const nextPaletteType = getCookie('paletteType');

      dispatch({
        type: 'CHANGE',
        payload: { paletteColors: nextPaletteColors, paletteType: nextPaletteType },
      });
    }
  }, []);

  // persist paletteType
  React.useEffect(() => {
    document.cookie = `paletteType=${paletteType};path=/;max-age=31536000`;
  }, [paletteType]);

  useEnhancedEffect(() => {
    document.body.dir = direction;
  }, [direction]);

  const theme = React.useMemo(() => {
    const themeDecorator = dense ? usingHighDensity : usingIdentity;
    const nextTheme = createMuiTheme(
      themeDecorator({
        direction,
        nprogress: {
          color: paletteType === 'light' ? '#000' : '#fff',
        },
        palette: {
          primary: {
            main: paletteType === 'light' ? blue[700] : blue[200],
          },
          secondary: {
            main: paletteType === 'light' ? darken(pink.A400, 0.1) : pink[200],
          },
          type: paletteType,
          background: {
            default: paletteType === 'light' ? '#fff' : '#121212',
          },
          ...paletteColors,
        },
        spacing,
      }),
    );

    nextTheme.palette.background.level2 =
      paletteType === 'light' ? nextTheme.palette.grey[100] : '#333';

    nextTheme.palette.background.level1 =
      paletteType === 'light' ? '#fff' : nextTheme.palette.grey[900];

    return nextTheme;
  }, [dense, direction, paletteColors, paletteType, spacing]);

  React.useEffect(() => {
    // Expose the theme as a global variable so people can play with it.
    if (process.browser) {
      window.theme = theme;
    }
  }, [theme]);

  return (
    <MuiThemeProvider theme={theme}>
      <DispatchContext.Provider value={dispatch}>{children}</DispatchContext.Provider>
    </MuiThemeProvider>
  );
}

ThemeProvider.propTypes = {
  children: PropTypes.node,
};

/**
 * @returns {(nextOptions: Partial<typeof themeInitialOptions>) => void}
 */
export function useChangeTheme() {
  const dispatch = React.useContext(DispatchContext);
  return React.useCallback(options => dispatch({ type: 'CHANGE', payload: options }), [dispatch]);
}
<commit_msg>[docs] Fix reset colors crashing app (#16750)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import { ThemeProvider as MuiThemeProvider } from '@material-ui/styles';
import { createMuiTheme, darken } from '@material-ui/core/styles';
import useMediaQuery from '@material-ui/core/useMediaQuery';
import { blue, pink } from '@material-ui/core/colors';
import { getCookie } from 'docs/src/modules/utils/helpers';
import { darkTheme, setPrismTheme } from 'docs/src/modules/components/prism';
import deepmerge from 'deepmerge';

export const themeColor = blue[700];

const themeInitialOptions = {
  dense: false,
  direction: 'ltr',
  paletteColors: {},
  spacing: 8, // spacing unit
};

/**
 * @typedef {import('@material-ui/core/src/styles/createMuiTheme').ThemeOptions} ThemeOptions
 *
 *
 * @param {ThemeOptions} themeOptions
 * @returns {ThemeOptions}
 */
function usingHighDensity(themeOptions) {
  return deepmerge(themeOptions, {
    props: {
      MuiButton: {
        size: 'small',
      },
      MuiFilledInput: {
        margin: 'dense',
      },
      MuiFormControl: {
        margin: 'dense',
      },
      MuiFormHelperText: {
        margin: 'dense',
      },
      MuiIconButton: {
        size: 'small',
      },
      MuiInputBase: {
        margin: 'dense',
      },
      MuiInputLabel: {
        margin: 'dense',
      },
      MuiListItem: {
        dense: true,
      },
      MuiOutlinedInput: {
        margin: 'dense',
      },
      MuiFab: {
        size: 'small',
      },
      MuiTable: {
        size: 'small',
      },
      MuiTextField: {
        margin: 'dense',
      },
      MuiToolbar: {
        variant: 'dense',
      },
    },
    overrides: {
      MuiIconButton: {
        sizeSmall: {
          // minimal touch target hit spacing
          marginLeft: 4,
          marginRight: 4,
          padding: 12,
        },
      },
    },
  });
}

function usingIdentity(themeOptions) {
  return themeOptions;
}

export const DispatchContext = React.createContext(() => {
  throw new Error('Forgot to wrap component in ThemeContext.Provider');
});

const useEnhancedEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;

export function ThemeProvider(props) {
  const { children } = props;

  const [themeOptions, dispatch] = React.useReducer((state, action) => {
    switch (action.type) {
      case 'SET_SPACING':
        return {
          ...state,
          spacing: action.payload,
        };
      case 'INCREASE_SPACING': {
        return {
          ...state,
          spacing: state.spacing + 1,
        };
      }
      case 'DECREASE_SPACING': {
        return {
          ...state,
          spacing: state.spacing - 1,
        };
      }
      case 'SET_DENSE':
        return {
          ...state,
          dense: action.payload,
        };
      case 'RESET_DENSITY':
        return {
          ...state,
          dense: themeInitialOptions.dense,
          spacing: themeInitialOptions.spacing,
        };
      case 'RESET_COLORS':
        return {
          ...state,
          paletteColors: themeInitialOptions.paletteColors,
        };
      case 'CHANGE':
        return {
          ...state,
          paletteType: action.payload.paletteType || state.paletteType,
          direction: action.payload.direction || state.direction,
          paletteColors: action.payload.paletteColors || state.paletteColors,
        };
      default:
        throw new Error(`Unrecognized type ${action.type}`);
    }
  }, themeInitialOptions);

  const prefersDarkMode = useMediaQuery('@media (prefers-color-scheme: dark)');
  const preferredType = prefersDarkMode ? 'dark' : 'light';
  const { dense, direction, paletteColors, paletteType = preferredType, spacing } = themeOptions;

  React.useEffect(() => {
    setPrismTheme(darkTheme);
  }, []);

  React.useEffect(() => {
    if (process.browser) {
      const nextPaletteColors = JSON.parse(getCookie('paletteColors') || 'null');
      const nextPaletteType = getCookie('paletteType');

      dispatch({
        type: 'CHANGE',
        payload: { paletteColors: nextPaletteColors, paletteType: nextPaletteType },
      });
    }
  }, []);

  // persist paletteType
  React.useEffect(() => {
    document.cookie = `paletteType=${paletteType};path=/;max-age=31536000`;
  }, [paletteType]);

  useEnhancedEffect(() => {
    document.body.dir = direction;
  }, [direction]);

  const theme = React.useMemo(() => {
    const themeDecorator = dense ? usingHighDensity : usingIdentity;
    const nextTheme = createMuiTheme(
      themeDecorator({
        direction,
        nprogress: {
          color: paletteType === 'light' ? '#000' : '#fff',
        },
        palette: {
          primary: {
            main: paletteType === 'light' ? blue[700] : blue[200],
          },
          secondary: {
            main: paletteType === 'light' ? darken(pink.A400, 0.1) : pink[200],
          },
          type: paletteType,
          background: {
            default: paletteType === 'light' ? '#fff' : '#121212',
          },
          ...paletteColors,
        },
        spacing,
      }),
    );

    nextTheme.palette.background.level2 =
      paletteType === 'light' ? nextTheme.palette.grey[100] : '#333';

    nextTheme.palette.background.level1 =
      paletteType === 'light' ? '#fff' : nextTheme.palette.grey[900];

    return nextTheme;
  }, [dense, direction, paletteColors, paletteType, spacing]);

  React.useEffect(() => {
    // Expose the theme as a global variable so people can play with it.
    if (process.browser) {
      window.theme = theme;
    }
  }, [theme]);

  return (
    <MuiThemeProvider theme={theme}>
      <DispatchContext.Provider value={dispatch}>{children}</DispatchContext.Provider>
    </MuiThemeProvider>
  );
}

ThemeProvider.propTypes = {
  children: PropTypes.node,
};

/**
 * @returns {(nextOptions: Partial<typeof themeInitialOptions>) => void}
 */
export function useChangeTheme() {
  const dispatch = React.useContext(DispatchContext);
  return React.useCallback(options => dispatch({ type: 'CHANGE', payload: options }), [dispatch]);
}
<|endoftext|>


<commit_before>'use strict';

class Business {
  doTheJob(data, next) {
    setTimeout(() => {
      console.log('alpha-worker : ', data);
      if (data.size > 5000 ) {
        next(new Error("I don't want yours bigfile, dude !"));
      } else {
        next()
      }
      next();
    }, Math.floor(Math.random() * 80 + 20));
  }
}

module.exports = new Business();<commit_msg>Shut it alpha-worker<commit_after>'use strict';

class Business {
  doTheJob(data, next) {
    setTimeout(() => {
      // console.log('alpha-worker : ', data);
      if (data.size > 5000 ) {
        next(new Error("I don't want yours bigfile, dude !"));
      } else {
        next()
      }
      next();
    }, Math.floor(Math.random() * 80 + 20));
  }
}

module.exports = new Business();<|endoftext|>


<commit_before>/*Copyright 2019 Bang & Olufsen A/S
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.*/

// HIFIBERRY SOFTWARE UPDATE FOR BEOCREATE

var exec = require("child_process").exec;
var spawn = require("child_process").spawn;
var fs = require("fs");

var debug = beo.debug;
var version = require("./package.json").version;

var defaultSettings = {
	autoCheck: true,
};
var settings = JSON.parse(JSON.stringify(defaultSettings));

beo.bus.on('general', function(event) {

	if (event.header == "startup") {

		if (settings.autoCheck) {
			startAutoCheckTimeout();
		}

	}

	if (event.header == "activatedExtension") {
		if (event.content.extension == "software-update") {
			checkForUpdate();
			autoUpdateMode();
		}

		if (event.content == "general-settings") {
			if (newVersion) {
				beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
			} else {
				beo.sendToUI("software-update", {header: "badge"});
			}
		}
	}

	if (event.header == "connected") {
		if (newVersion) {
			beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
		}
	}
});

beo.bus.on('software-update', function(event) {

	if (event.header == "settings") {
		if (event.content.settings) {
			settings = Object.assign(settings, event.content.settings);
		}
	}

	if (event.header == "autoUpdateMode") {
		if (event.content.mode != undefined) {
			autoUpdateMode(event.content.mode);
		}
	}

	if (event.header == "install") {
		installUpdate();
	}

});

var lastChecked = 0;
var newVersion = null;
var releaseNotes = "";

function checkForUpdate(forceCheck) {
	checkTime = new Date().getTime();
	if (checkTime - lastChecked > 300000 || forceCheck) {
		exec("/opt/hifiberry/bin/update --latest --check", function(error, stdout, stderr) {
			lastChecked = checkTime;
			updateLines = stdout.trim().split("\n");
			newVersion = updateLines[0];
			if (newVersion) {
				if (debug) console.log("Software update is available – release "+newVersion+".");
				updateLines.splice(0, 1);
				releaseNotes = updateLines.join("\n").trim();
				beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
			} else {
				newVersion = null;
				releaseNotes = "";
				if (debug) console.log("Product appears to be up to date.");
				beo.sendToUI("software-update", {header: "upToDate"});
			}
		});
	} else {
		if (debug) console.log("Checked for update less than 5 minutes ago, sending cached info.");
		if (newVersion) {
			beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
		} else {
			beo.sendToUI("software-update", {header: "upToDate"});
		}
	}
	if (settings.autoCheck) startAutoCheckTimeout();
}

var updateInProgress = false;
var updatePhase = 0;
var previousProgress = -5;
function installUpdate() {
	if (!updateInProgress) {
		updateInProgress = true;
		if (beo.developerMode) {
			if (debug) console.log("Starting software update simulation.");
			updateProcess = spawn("/opt/hifiberry/bin/update", ["--simulate", "--latest"]);
		} else {
			if (debug) console.log("Starting software update.");
			updateProcess = spawn("/opt/hifiberry/bin/update", ["--latest"]);
		}
		//updateProcess = spawn("curl", ["https://www.hifiberry.com/images/updater-20191030-pi3.tar.gz", "-o", "updater.tar.gz", "--progress-bar"], {cwd: "/data"});

		updateProcess.stdout.on('data', function (data) {
			//console.log('stdout: ' + data.toString());
			data = data.toString();
			switch (updatePhase) {
				case 0: // Download starting.
					if (data.indexOf("downloading http") != -1) {
						updatePhase = 1;
						if (debug) console.log("Downloading update...");
					}
					break;
				case 1:
					if (data.indexOf("Could not download updater") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updateError", content: {reason: "downloadError"}});
						console.error("Update download was unsuccessful.");
					}
					if (data.indexOf("unmounting") != -1) {
						updatePhase = 2;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 50, phase: "extractingFirmware"}});
						if (debug) console.log("Extracting firmware...");
					}
					break;
				case 2:
					if (data.indexOf("mounting") != -1) {
						updatePhase = 3;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 70, phase: "resizing"}});
						if (debug) console.log("Resizing file system...");
					}
					break;
				case 3:
					if (data.indexOf("extracting new kernel") != -1) {
						updatePhase = 4;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 75, phase: "extractingKernel"}});
						if (debug) console.log("Extracting new kernel...");
					}
					break;
				case 4:
					if (data.indexOf("migrating") != -1) {
						updatePhase = 5;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 85, phase: "copyingFiles"}});
						if (debug) console.log("Copying files from current installation to new installation...");
					}
					break;
				case 5:
					if (data.indexOf("switching root file system") != -1) {
						updatePhase = 6;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 95, phase: "finalising"}});
						if (debug) console.log("Switching root file system...");
					}
					if (data.indexOf("not switching to new version") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "doneSimulation"}});
						if (debug) console.log("Update simulation complete.");
					}
					break;
				case 6:
					if (data.indexOf("removing") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "done"}});
						if (debug) console.log("Removing updater and restarting...");
					}
					break;
			}
		});

		updateProcess.stderr.on('data', function (data) {
			data = data.toString();
			if (updatePhase == 1) { // Updates from curl.
				progressIndex = data.indexOf("%");
				if (progressIndex != -1) {
					percentage = parseFloat(data.substr(progressIndex-5, 5));
					if (percentage - previousProgress >= 5) {
						beo.sendToUI("software-update", {header: "updating", content: {progress: Math.round(percentage/2), phase: "download"}});
						previousProgress = percentage;
					}
				}
			}

		});

		updateProcess.on('exit', function (code) {
			updateInProgress = false;
			console.log('Software updater process exited (code '+code.toString()+").");
		});
	}
}

var autoCheckTimeout;
function startAutoCheckTimeout() {
	clearTimeout(autoCheckTimeout);
	autoCheckTimeout = setTimeout(function() {
		checkForUpdate();
	}, 86400000) // Check once per day.
}

function autoUpdateMode(mode) {
	if (mode) {
		switch (mode) {
			case "critical":
			case "stable":
			case "latest":
			case "experimental":
				fs.writeFileSync("/etc/update.release", mode);
				break;
			case false:
				fs.writeFileSync("/etc/update.release", "off");
				break;
		}
	} else {
		if (fs.existsSync("/etc/update.release")) {
			modeRead = fs.readFileSync(path, "utf8").trim();
			switch (modeRead) {
				case "critical":
				case "stable":
				case "latest":
				case "experimental":
					mode = modeRead;
					break;
				default:
					mode = false;
					break;
			}
		} else {
			mode = false;
		}
	}
	beo.sendToUI("software-update", {header: "autoUpdateMode", content: {mode: mode}});
}

module.exports = {
	version: version
};

<commit_msg>Fix path when reading auto-update mode<commit_after>/*Copyright 2019 Bang & Olufsen A/S
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.*/

// HIFIBERRY SOFTWARE UPDATE FOR BEOCREATE

var exec = require("child_process").exec;
var spawn = require("child_process").spawn;
var fs = require("fs");

var debug = beo.debug;
var version = require("./package.json").version;

var defaultSettings = {
	autoCheck: true,
};
var settings = JSON.parse(JSON.stringify(defaultSettings));

beo.bus.on('general', function(event) {

	if (event.header == "startup") {

		if (settings.autoCheck) {
			startAutoCheckTimeout();
		}

	}

	if (event.header == "activatedExtension") {
		if (event.content.extension == "software-update") {
			checkForUpdate();
			autoUpdateMode();
		}

		if (event.content == "general-settings") {
			if (newVersion) {
				beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
			} else {
				beo.sendToUI("software-update", {header: "badge"});
			}
		}
	}

	if (event.header == "connected") {
		if (newVersion) {
			beo.sendToUI("software-update", {header: "badge", content: {badge: 1}});
		}
	}
});

beo.bus.on('software-update', function(event) {

	if (event.header == "settings") {
		if (event.content.settings) {
			settings = Object.assign(settings, event.content.settings);
		}
	}

	if (event.header == "autoUpdateMode") {
		if (event.content.mode != undefined) {
			autoUpdateMode(event.content.mode);
		}
	}

	if (event.header == "install") {
		installUpdate();
	}

});

var lastChecked = 0;
var newVersion = null;
var releaseNotes = "";

function checkForUpdate(forceCheck) {
	checkTime = new Date().getTime();
	if (checkTime - lastChecked > 300000 || forceCheck) {
		exec("/opt/hifiberry/bin/update --latest --check", function(error, stdout, stderr) {
			lastChecked = checkTime;
			updateLines = stdout.trim().split("\n");
			newVersion = updateLines[0];
			if (newVersion) {
				if (debug) console.log("Software update is available – release "+newVersion+".");
				updateLines.splice(0, 1);
				releaseNotes = updateLines.join("\n").trim();
				beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
			} else {
				newVersion = null;
				releaseNotes = "";
				if (debug) console.log("Product appears to be up to date.");
				beo.sendToUI("software-update", {header: "upToDate"});
			}
		});
	} else {
		if (debug) console.log("Checked for update less than 5 minutes ago, sending cached info.");
		if (newVersion) {
			beo.sendToUI("software-update", {header: "updateAvailable", content: {version: newVersion, releaseNotes: releaseNotes}});
		} else {
			beo.sendToUI("software-update", {header: "upToDate"});
		}
	}
	if (settings.autoCheck) startAutoCheckTimeout();
}

var updateInProgress = false;
var updatePhase = 0;
var previousProgress = -5;
function installUpdate() {
	if (!updateInProgress) {
		updateInProgress = true;
		if (beo.developerMode) {
			if (debug) console.log("Starting software update simulation.");
			updateProcess = spawn("/opt/hifiberry/bin/update", ["--simulate", "--latest"]);
		} else {
			if (debug) console.log("Starting software update.");
			updateProcess = spawn("/opt/hifiberry/bin/update", ["--latest"]);
		}
		//updateProcess = spawn("curl", ["https://www.hifiberry.com/images/updater-20191030-pi3.tar.gz", "-o", "updater.tar.gz", "--progress-bar"], {cwd: "/data"});

		updateProcess.stdout.on('data', function (data) {
			//console.log('stdout: ' + data.toString());
			data = data.toString();
			switch (updatePhase) {
				case 0: // Download starting.
					if (data.indexOf("downloading http") != -1) {
						updatePhase = 1;
						if (debug) console.log("Downloading update...");
					}
					break;
				case 1:
					if (data.indexOf("Could not download updater") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updateError", content: {reason: "downloadError"}});
						console.error("Update download was unsuccessful.");
					}
					if (data.indexOf("unmounting") != -1) {
						updatePhase = 2;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 50, phase: "extractingFirmware"}});
						if (debug) console.log("Extracting firmware...");
					}
					break;
				case 2:
					if (data.indexOf("mounting") != -1) {
						updatePhase = 3;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 70, phase: "resizing"}});
						if (debug) console.log("Resizing file system...");
					}
					break;
				case 3:
					if (data.indexOf("extracting new kernel") != -1) {
						updatePhase = 4;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 75, phase: "extractingKernel"}});
						if (debug) console.log("Extracting new kernel...");
					}
					break;
				case 4:
					if (data.indexOf("migrating") != -1) {
						updatePhase = 5;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 85, phase: "copyingFiles"}});
						if (debug) console.log("Copying files from current installation to new installation...");
					}
					break;
				case 5:
					if (data.indexOf("switching root file system") != -1) {
						updatePhase = 6;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 95, phase: "finalising"}});
						if (debug) console.log("Switching root file system...");
					}
					if (data.indexOf("not switching to new version") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "doneSimulation"}});
						if (debug) console.log("Update simulation complete.");
					}
					break;
				case 6:
					if (data.indexOf("removing") != -1) {
						updatePhase = 0;
						beo.sendToUI("software-update", {header: "updating", content: {progress: 100, phase: "done"}});
						if (debug) console.log("Removing updater and restarting...");
					}
					break;
			}
		});

		updateProcess.stderr.on('data', function (data) {
			data = data.toString();
			if (updatePhase == 1) { // Updates from curl.
				progressIndex = data.indexOf("%");
				if (progressIndex != -1) {
					percentage = parseFloat(data.substr(progressIndex-5, 5));
					if (percentage - previousProgress >= 5) {
						beo.sendToUI("software-update", {header: "updating", content: {progress: Math.round(percentage/2), phase: "download"}});
						previousProgress = percentage;
					}
				}
			}

		});

		updateProcess.on('exit', function (code) {
			updateInProgress = false;
			console.log('Software updater process exited (code '+code.toString()+").");
		});
	}
}

var autoCheckTimeout;
function startAutoCheckTimeout() {
	clearTimeout(autoCheckTimeout);
	autoCheckTimeout = setTimeout(function() {
		checkForUpdate();
	}, 86400000) // Check once per day.
}

function autoUpdateMode(mode) {
	if (mode) {
		switch (mode) {
			case "critical":
			case "stable":
			case "latest":
			case "experimental":
				fs.writeFileSync("/etc/update.release", mode);
				break;
			case false:
				fs.writeFileSync("/etc/update.release", "off");
				break;
		}
	} else {
		if (fs.existsSync("/etc/update.release")) {
			modeRead = fs.readFileSync("/etc/update.release", "utf8").trim();
			switch (modeRead) {
				case "critical":
				case "stable":
				case "latest":
				case "experimental":
					mode = modeRead;
					break;
				default:
					mode = false;
					break;
			}
		} else {
			mode = false;
		}
	}
	beo.sendToUI("software-update", {header: "autoUpdateMode", content: {mode: mode}});
}

module.exports = {
	version: version
};

<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box from '../box';
import cx from 'classnames';
import theme from './theme.css';

class Bullet extends PureComponent {
  static propTypes = {
    className: PropTypes.string,
    color: PropTypes.oneOf(['neutral', 'mint', 'aqua', 'violet', 'teal', 'gold', 'ruby']),
    size: PropTypes.oneOf(['small', 'medium']),
  };

  static defaultProps = {
    color: 'neutral',
    size: 'medium',
  };

  render() {
    const { className, color, size, ...others } = this.props;
    const classNames = cx(theme['bullet'], theme[color], theme[size], className);

    return <Box data-teamleader-ui="bullet" className={classNames} element="span" {...others} />;
  }
}

export default Bullet;
<commit_msg>Bullet: add propType descriptions<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import Box from '../box';
import cx from 'classnames';
import theme from './theme.css';

class Bullet extends PureComponent {
  static propTypes = {
    /** A class name for the wrapper to give custom styles. */
    className: PropTypes.string,
    /** The color of the bullet. */
    color: PropTypes.oneOf(['neutral', 'mint', 'aqua', 'violet', 'teal', 'gold', 'ruby']),
    /** The size of the bullet. */
    size: PropTypes.oneOf(['small', 'medium']),
  };

  static defaultProps = {
    color: 'neutral',
    size: 'medium',
  };

  render() {
    const { className, color, size, ...others } = this.props;
    const classNames = cx(theme['bullet'], theme[color], theme[size], className);

    return <Box data-teamleader-ui="bullet" className={classNames} element="span" {...others} />;
  }
}

export default Bullet;
<|endoftext|>


<commit_before>define(['ol', 'cesiumjs'],

    function (ol, Cesium) {

        var viewer;
        var hs_map;

        var me = {
            /**
            * @ngdoc method
            * @name hs.cesium.service#calcResolutionForDistance
            * @private
            * @description Calculates the resolution for a given distance from the ground and latitude
            */
            calcResolutionForDistance: function (distance, latitude) {
                // See the reverse calculation (calcDistanceForResolution_) for details
                const canvas = viewer.scene.canvas;
                const fovy = viewer.camera.frustum.fovy;
                const metersPerUnit = ol.proj.get('EPSG:3857').getMetersPerUnit();

                const visibleMeters = 2 * distance * Math.tan(fovy / 2);
                const relativeCircumference = Math.cos(Math.abs(latitude));
                const visibleMapUnits = visibleMeters / metersPerUnit / relativeCircumference;
                const resolution = visibleMapUnits / canvas.clientHeight;
                return resolution;
            },

            /**
             * @ngdoc method
             * @name hs.cesium.service#getCameraCenterInLngLat
             * @private
             * @description Gets the position the camera is pointing to in lon/lat coordinates and resolution as the third array element
             */
            getCameraCenterInLngLat: function () {
                var ray = viewer.camera.getPickRay(new Cesium.Cartesian2(viewer.canvas.width / 2, viewer.canvas.height / 2));
                var positionCartesian3 = viewer.scene.globe.pick(ray, viewer.scene);
                if (positionCartesian3) {
                    var positionCartographic = Cesium.Cartographic.fromCartesian(positionCartesian3);
                    var lngDeg = Cesium.Math.toDegrees(positionCartographic.longitude);
                    var latDeg = Cesium.Math.toDegrees(positionCartographic.latitude);
                    position = [lngDeg, latDeg, me.calcResolutionForDistance(Cesium.Cartographic.fromCartesian(viewer.camera.position).height - positionCartographic.height, latDeg)];
                    return position;
                } else return null;
            },

            getViewportPolygon: function(){

                function cornerToDegrees(d) {
                    return [Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(d).longitude),
                    Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(d).latitude)];
                }

                var center = [Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(viewer.camera.position).longitude), Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(viewer.camera.position).latitude)];
                var top_left = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(0, 0), new Cesium.Cartesian2(viewer.canvas.width, viewer.canvas.height)));
                var top_right = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(viewer.canvas.width, 0), new Cesium.Cartesian2(0, viewer.canvas.height)));
                var bot_left = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(viewer.canvas.width, viewer.canvas.height), new Cesium.Cartesian2(0, 0)));
                var bot_right = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(0, viewer.canvas.height), new Cesium.Cartesian2(viewer.canvas.width, 0)));

                function clamp(p) {
                    if (Math.abs(p[0] - center[0]) > 0.05)
                        p[0] = center[0] + (p[0] - center[0]) * (0.05 / Math.abs(p[0] - center[0]));
                    if (Math.abs(p[1] - center[1]) > 0.05)
                        p[1] = center[1] + (p[1] - center[1]) * (0.05 / Math.abs(p[1] - center[1]));
                    return p
                }
                top_left = clamp(top_left);
                top_right = clamp(top_right);
                bot_left = clamp(bot_left);
                bot_right = clamp(bot_right);
                return [top_left, top_right, bot_left, bot_right];
            },

            getCornerCoord: function (startCoordinates, endCoordinates) {

                var coordinate = viewer.scene.camera.pickEllipsoid(startCoordinates, this.ellipsoid);

                // Translate coordinates
                var x1 = startCoordinates.x;
                var y1 = startCoordinates.y;
                var x2 = endCoordinates.x;
                var y2 = endCoordinates.y;
                // Define differences and error check
                var dx = Math.abs(x2 - x1);
                var dy = Math.abs(y2 - y1);
                var sx = (x1 < x2) ? 1 : -1;
                var sy = (y1 < y2) ? 1 : -1;
                var err = dx - dy;

                coordinate = viewer.scene.camera.pickEllipsoid({ x: x1, y: y1 }, this.ellipsoid);
                if (coordinate) {
                    return coordinate;
                }

                // Main loop
                while (!((x1 == x2) && (y1 == y2))) {
                    var e2 = err << 1;
                    if (e2 > -dy) {
                        err -= dy;
                        x1 += sx;
                    }
                    if (e2 < dx) {
                        err += dx;
                        y1 += sy;
                    }

                    coordinate = viewer.scene.camera.pickEllipsoid({ x: x1, y: y1 }, this.ellipsoid);
                    if (coordinate) {
                        return coordinate;
                    }
                }
                return;
            },

            setExtentEqualToOlExtent: function (view) {
                var ol_ext = view.calculateExtent(hs_map.map.getSize());
                var trans_ext = ol.proj.transformExtent(ol_ext, view.getProjection(), 'EPSG:4326');
                viewer.camera.setView({
                    destination: Cesium.Rectangle.fromDegrees(trans_ext[0], trans_ext[1], trans_ext[2], trans_ext[3])
                });

                var ray = viewer.camera.getPickRay(new Cesium.Cartesian2(viewer.canvas.width / 2, viewer.canvas.height / 2));
                var positionCartesian3 = viewer.scene.globe.pick(ray, viewer.scene);
                if (positionCartesian3) {
                    /*
                    var instance = new Cesium.GeometryInstance({
                        geometry : new Cesium.RectangleGeometry({
                            rectangle : Cesium.Rectangle.fromDegrees(trans_ext[0], trans_ext[1], trans_ext[2], trans_ext[3]),
                            height: Cesium.Ellipsoid.WGS84.cartesianToCartographic(positionCartesian3).height,
                            vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
                        })
                    });

                    viewer.scene.primitives.removeAll();
                    viewer.scene.primitives.add(new Cesium.Primitive({
                        geometryInstances : instance,
                        appearance : new Cesium.EllipsoidSurfaceAppearance({aboveGround: true})
                    })); */
                    viewer.camera.moveBackward(Cesium.Ellipsoid.WGS84.cartesianToCartographic(positionCartesian3).height);
                }
            },

            /**
             * @ngdoc method
             * @name hs.cesium.service#calcDistanceForResolution
             * @private
             * @deprecated
             * @description Calculates the distance from the ground based on resolution and latitude
             */
            calcDistanceForResolution: function (resolution, latitude) {
                const canvas = viewer.scene.canvas;
                const fovy = viewer.camera.frustum.fovy;
                const metersPerUnit = hs_map.map.getView().getProjection().getMetersPerUnit();

                // number of "map units" visible in 2D (vertically)
                const visibleMapUnits = resolution * canvas.clientHeight;

                // The metersPerUnit does not take latitude into account, but it should
                // be lower with increasing latitude -- we have to compensate.
                // In 3D it is not possible to maintain the resolution at more than one point,
                // so it only makes sense to use the latitude of the "target" point.
                const relativeCircumference = Math.cos(Math.abs(latitude));

                // how many meters should be visible in 3D
                const visibleMeters = visibleMapUnits * metersPerUnit * relativeCircumference;

                // distance required to view the calculated length in meters
                //
                //  fovy/2
                //    |\
                //  x | \
                //    |--\
                // visibleMeters/2
                const requiredDistance = (visibleMeters / 2) / Math.tan(fovy / 2);

                // NOTE: This calculation is not absolutely precise, because metersPerUnit
                // is a great simplification. It does not take ellipsoid/terrain into account.

                return requiredDistance;
            },

            init: function (_viewer, _hs_map) {
                viewer = _viewer;
                hs_map = _hs_map;
            }

        };
        return me
    }
)<commit_msg>Increased max distance for pois<commit_after>define(['ol', 'cesiumjs'],

    function (ol, Cesium) {

        var viewer;
        var hs_map;

        var me = {
            /**
            * @ngdoc method
            * @name hs.cesium.service#calcResolutionForDistance
            * @private
            * @description Calculates the resolution for a given distance from the ground and latitude
            */
            calcResolutionForDistance: function (distance, latitude) {
                // See the reverse calculation (calcDistanceForResolution_) for details
                const canvas = viewer.scene.canvas;
                const fovy = viewer.camera.frustum.fovy;
                const metersPerUnit = ol.proj.get('EPSG:3857').getMetersPerUnit();

                const visibleMeters = 2 * distance * Math.tan(fovy / 2);
                const relativeCircumference = Math.cos(Math.abs(latitude));
                const visibleMapUnits = visibleMeters / metersPerUnit / relativeCircumference;
                const resolution = visibleMapUnits / canvas.clientHeight;
                return resolution;
            },

            /**
             * @ngdoc method
             * @name hs.cesium.service#getCameraCenterInLngLat
             * @private
             * @description Gets the position the camera is pointing to in lon/lat coordinates and resolution as the third array element
             */
            getCameraCenterInLngLat: function () {
                var ray = viewer.camera.getPickRay(new Cesium.Cartesian2(viewer.canvas.width / 2, viewer.canvas.height / 2));
                var positionCartesian3 = viewer.scene.globe.pick(ray, viewer.scene);
                if (positionCartesian3) {
                    var positionCartographic = Cesium.Cartographic.fromCartesian(positionCartesian3);
                    var lngDeg = Cesium.Math.toDegrees(positionCartographic.longitude);
                    var latDeg = Cesium.Math.toDegrees(positionCartographic.latitude);
                    position = [lngDeg, latDeg, me.calcResolutionForDistance(Cesium.Cartographic.fromCartesian(viewer.camera.position).height - positionCartographic.height, latDeg)];
                    return position;
                } else return null;
            },

            getViewportPolygon: function(){

                function cornerToDegrees(d) {
                    return [Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(d).longitude),
                    Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(d).latitude)];
                }

                var center = [Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(viewer.camera.position).longitude), Cesium.Math.toDegrees(viewer.scene.globe.ellipsoid.cartesianToCartographic(viewer.camera.position).latitude)];
                var top_left = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(0, 0), new Cesium.Cartesian2(viewer.canvas.width, viewer.canvas.height)));
                var top_right = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(viewer.canvas.width, 0), new Cesium.Cartesian2(0, viewer.canvas.height)));
                var bot_left = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(viewer.canvas.width, viewer.canvas.height), new Cesium.Cartesian2(0, 0)));
                var bot_right = cornerToDegrees(me.getCornerCoord(new Cesium.Cartesian2(0, viewer.canvas.height), new Cesium.Cartesian2(viewer.canvas.width, 0)));

                function clamp(p) {
                    var max_dist = 0.10;
                    if (Math.abs(p[0] - center[0]) > max_dist)
                        p[0] = center[0] + (p[0] - center[0]) * (max_dist / Math.abs(p[0] - center[0]));
                    if (Math.abs(p[1] - center[1]) > max_dist)
                        p[1] = center[1] + (p[1] - center[1]) * (max_dist / Math.abs(p[1] - center[1]));
                    return p
                }
                top_left = clamp(top_left);
                top_right = clamp(top_right);
                bot_left = clamp(bot_left);
                bot_right = clamp(bot_right);
                return [top_left, top_right, bot_left, bot_right];
            },

            getCornerCoord: function (startCoordinates, endCoordinates) {

                var coordinate = viewer.scene.camera.pickEllipsoid(startCoordinates, this.ellipsoid);

                // Translate coordinates
                var x1 = startCoordinates.x;
                var y1 = startCoordinates.y;
                var x2 = endCoordinates.x;
                var y2 = endCoordinates.y;
                // Define differences and error check
                var dx = Math.abs(x2 - x1);
                var dy = Math.abs(y2 - y1);
                var sx = (x1 < x2) ? 1 : -1;
                var sy = (y1 < y2) ? 1 : -1;
                var err = dx - dy;

                coordinate = viewer.scene.camera.pickEllipsoid({ x: x1, y: y1 }, this.ellipsoid);
                if (coordinate) {
                    return coordinate;
                }

                // Main loop
                while (!((x1 == x2) && (y1 == y2))) {
                    var e2 = err << 1;
                    if (e2 > -dy) {
                        err -= dy;
                        x1 += sx;
                    }
                    if (e2 < dx) {
                        err += dx;
                        y1 += sy;
                    }

                    coordinate = viewer.scene.camera.pickEllipsoid({ x: x1, y: y1 }, this.ellipsoid);
                    if (coordinate) {
                        return coordinate;
                    }
                }
                return;
            },

            setExtentEqualToOlExtent: function (view) {
                var ol_ext = view.calculateExtent(hs_map.map.getSize());
                var trans_ext = ol.proj.transformExtent(ol_ext, view.getProjection(), 'EPSG:4326');
                viewer.camera.setView({
                    destination: Cesium.Rectangle.fromDegrees(trans_ext[0], trans_ext[1], trans_ext[2], trans_ext[3])
                });

                var ray = viewer.camera.getPickRay(new Cesium.Cartesian2(viewer.canvas.width / 2, viewer.canvas.height / 2));
                var positionCartesian3 = viewer.scene.globe.pick(ray, viewer.scene);
                if (positionCartesian3) {
                    /*
                    var instance = new Cesium.GeometryInstance({
                        geometry : new Cesium.RectangleGeometry({
                            rectangle : Cesium.Rectangle.fromDegrees(trans_ext[0], trans_ext[1], trans_ext[2], trans_ext[3]),
                            height: Cesium.Ellipsoid.WGS84.cartesianToCartographic(positionCartesian3).height,
                            vertexFormat : Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT
                        })
                    });

                    viewer.scene.primitives.removeAll();
                    viewer.scene.primitives.add(new Cesium.Primitive({
                        geometryInstances : instance,
                        appearance : new Cesium.EllipsoidSurfaceAppearance({aboveGround: true})
                    })); */
                    viewer.camera.moveBackward(Cesium.Ellipsoid.WGS84.cartesianToCartographic(positionCartesian3).height);
                }
            },

            /**
             * @ngdoc method
             * @name hs.cesium.service#calcDistanceForResolution
             * @private
             * @deprecated
             * @description Calculates the distance from the ground based on resolution and latitude
             */
            calcDistanceForResolution: function (resolution, latitude) {
                const canvas = viewer.scene.canvas;
                const fovy = viewer.camera.frustum.fovy;
                const metersPerUnit = hs_map.map.getView().getProjection().getMetersPerUnit();

                // number of "map units" visible in 2D (vertically)
                const visibleMapUnits = resolution * canvas.clientHeight;

                // The metersPerUnit does not take latitude into account, but it should
                // be lower with increasing latitude -- we have to compensate.
                // In 3D it is not possible to maintain the resolution at more than one point,
                // so it only makes sense to use the latitude of the "target" point.
                const relativeCircumference = Math.cos(Math.abs(latitude));

                // how many meters should be visible in 3D
                const visibleMeters = visibleMapUnits * metersPerUnit * relativeCircumference;

                // distance required to view the calculated length in meters
                //
                //  fovy/2
                //    |\
                //  x | \
                //    |--\
                // visibleMeters/2
                const requiredDistance = (visibleMeters / 2) / Math.tan(fovy / 2);

                // NOTE: This calculation is not absolutely precise, because metersPerUnit
                // is a great simplification. It does not take ellipsoid/terrain into account.

                return requiredDistance;
            },

            init: function (_viewer, _hs_map) {
                viewer = _viewer;
                hs_map = _hs_map;
            }

        };
        return me
    }
)<|endoftext|>


<commit_before>// 基础配置
(function(that) {
    var myStage = new createjs.Stage('game');
    that.config = {
        mapSize: 10,
        size: parseInt(myStage.canvas.width / 15),
        randomCount: 3,
        canvasElm: 'game',
        colorList: ['#67C2E4', '#7EDEC2', '#E17167', '#FEC54C', '#E77B91', '#B1E385', '#EEA46A', '#8091D4'],
        stage: myStage,
        gap: 2.5,
        bgColor: '#EBEBEB',
        radius: 4,
        scaleDown: 0.7,
        scaleUp: 1.1,
        moveUp: 70,
        fontSize: 20,
        shapes: {
            point1: '[{"x": 2, "y": 4}]',
            verticalLine2: '[{"x": 2, "y": 3}, {"x": 2,"y": 4}]',
            horizontalLine2: '[{"x": 2, "y": 4}, {"x": 3,"y": 4}]',

            leftTopL3: '[{"x": 1, "y": 4}, {"x": 1, "y": 3}, {"x": 2, "y": 3}]',
            leftBottomL3: '[{"x": 2, "y": 4}, {"x": 1, "y": 4}, {"x": 1, "y": 3}]',
            rightTopL3: '[{"x": 1, "y": 3}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
            rightBottomL3: '[{"x": 1, "y": 4}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
            verticalLine3: '[{"x": 2, "y": 2}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
            horizontalLine3: '[{"x": 2, "y": 4}, {"x": 1, "y": 4}, {"x": 3, "y": 4}]',

            square4: '[{"x": 1, "y": 4}, {"x": 1, "y": 3}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
            verticalLine4: '[{"x": 2, "y": 2}, {"x": 2, "y": 3}, {"x": 2, "y": 4}, {"x": 2, "y": 1}]',
            horizontalLine4: '[{"x": 2, "y": 4}, {"x": 1, "y": 4}, {"x": 3, "y": 4}, {"x": 0, "y": 4}]',

            verticalLine5: '[{"x": 2, "y": 0}, {"x": 2, "y": 1}, {"x": 2, "y": 2}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
            horizontalLine5: '[{"x": 0, "y": 4}, {"x": 1, "y": 4}, {"x": 2, "y": 4}, {"x": 3, "y": 4}, {"x": 4, "y": 4}]',

            leftTopL5: '[{"x": 1, "y": 2}, {"x": 1, "y": 3}, {"x": 1, "y": 4}, {"x": 2, "y": 2}, {"x": 3, "y": 2}]',
            leftBottomL5: '[{"x": 1, "y": 4}, {"x": 1, "y": 3}, {"x": 1, "y": 2}, {"x": 2, "y": 4}, {"x": 3, "y": 4}]',
            rightTopL5: '[{"x": 3, "y": 2}, {"x": 3, "y": 3}, {"x": 3, "y": 4}, {"x": 2, "y": 2}, {"x": 1, "y": 2}]',
            rightBottomL5: '[{"x": 3, "y": 4}, {"x": 3, "y": 3}, {"x": 3, "y": 2}, {"x": 2, "y": 4}, {"x": 1, "y": 4}]',

            square9: '[{"x": 2, "y": 3}, {"x": 1, "y": 2}, {"x": 2, "y": 2}, {"x": 3, "y": 2}, {"x": 1, "y": 3}, {"x": 3, "y": 3}, {"x": 1, "y": 4}, {"x": 2, "y": 4}, {"x": 3, "y": 4}]'
        }
    };
})(window);
createjs.Touch.enable(config.stage);
createjs.Ticker.setFPS(75);
createjs.Ticker.addEventListener('tick', config.stage);<commit_msg>[UPD]模块化config<commit_after>var stage = new createjs.Stage('game');

// 基础配置
module.exports = {
    mapSize: 10,
    size: parseInt(stage.canvas.width / 15),
    randomCount: 3,
    canvasElm: 'game',
    colorList: ['#67C2E4', '#7EDEC2', '#E17167', '#FEC54C', '#E77B91', '#B1E385', '#EEA46A', '#8091D4'],
    stage: stage,
    gap: 2.5,
    bgColor: '#EBEBEB',
    radius: 4,
    scaleDown: 0.7,
    scaleUp: 1.1,
    moveUp: 70,
    fontSize: 20,
    shapes: {
        point1: '[{"x": 2, "y": 4}]',
        verticalLine2: '[{"x": 2, "y": 3}, {"x": 2,"y": 4}]',
        horizontalLine2: '[{"x": 2, "y": 4}, {"x": 3,"y": 4}]',

        leftTopL3: '[{"x": 1, "y": 4}, {"x": 1, "y": 3}, {"x": 2, "y": 3}]',
        leftBottomL3: '[{"x": 2, "y": 4}, {"x": 1, "y": 4}, {"x": 1, "y": 3}]',
        rightTopL3: '[{"x": 1, "y": 3}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
        rightBottomL3: '[{"x": 1, "y": 4}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
        verticalLine3: '[{"x": 2, "y": 2}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
        horizontalLine3: '[{"x": 2, "y": 4}, {"x": 1, "y": 4}, {"x": 3, "y": 4}]',

        square4: '[{"x": 1, "y": 4}, {"x": 1, "y": 3}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
        verticalLine4: '[{"x": 2, "y": 2}, {"x": 2, "y": 3}, {"x": 2, "y": 4}, {"x": 2, "y": 1}]',
        horizontalLine4: '[{"x": 2, "y": 4}, {"x": 1, "y": 4}, {"x": 3, "y": 4}, {"x": 0, "y": 4}]',

        verticalLine5: '[{"x": 2, "y": 0}, {"x": 2, "y": 1}, {"x": 2, "y": 2}, {"x": 2, "y": 3}, {"x": 2, "y": 4}]',
        horizontalLine5: '[{"x": 0, "y": 4}, {"x": 1, "y": 4}, {"x": 2, "y": 4}, {"x": 3, "y": 4}, {"x": 4, "y": 4}]',

        leftTopL5: '[{"x": 1, "y": 2}, {"x": 1, "y": 3}, {"x": 1, "y": 4}, {"x": 2, "y": 2}, {"x": 3, "y": 2}]',
        leftBottomL5: '[{"x": 1, "y": 4}, {"x": 1, "y": 3}, {"x": 1, "y": 2}, {"x": 2, "y": 4}, {"x": 3, "y": 4}]',
        rightTopL5: '[{"x": 3, "y": 2}, {"x": 3, "y": 3}, {"x": 3, "y": 4}, {"x": 2, "y": 2}, {"x": 1, "y": 2}]',
        rightBottomL5: '[{"x": 3, "y": 4}, {"x": 3, "y": 3}, {"x": 3, "y": 2}, {"x": 2, "y": 4}, {"x": 1, "y": 4}]',

        square9: '[{"x": 2, "y": 3}, {"x": 1, "y": 2}, {"x": 2, "y": 2}, {"x": 3, "y": 2}, {"x": 1, "y": 3}, {"x": 3, "y": 3}, {"x": 1, "y": 4}, {"x": 2, "y": 4}, {"x": 3, "y": 4}]'
    }
};<|endoftext|>


<commit_before>(function () {
    var Ext;

    Ext = window.Ext4 || window.Ext;

    Ext.define('Rally.apps.roadmapplanningboard.PlanningBoardColumn', {
        extend: 'Rally.ui.cardboard.Column',
        alias: 'widget.planningboardcolumn',
        mixins: {
            maskable: 'Rally.ui.mask.Maskable'
        },
        requires: ['Rally.apps.roadmapplanningboard.plugin.OrcaColumnDropController', 'Rally.apps.roadmapplanningboard.OrcaColumnDropTarget'],
        config: {
            stores: [],
            lowestPIType: undefined,
            dropControllerConfig: {
                ptype: 'orcacolumndropcontroller'
            },
            cardConfig: {
                showIconsAndHighlightBorder: true,
                showPlusIcon: false,
                showColorIcon: true,
                showGearIcon: false,
                showReadyIcon: false,
                showBlockedIcon: false
            }
        },

        initComponent: function () {
            this.callParent(arguments);
            return this.on('beforerender', function () {
                var cls;

                cls = 'planning-column';
                this.getContentCell().addCls(cls);
                return this.getColumnHeaderCell().addCls(cls);
            }, this, {
                single: true
            });
        },

        getStores: function () {

            if (!this.planRecord || !this.planRecord.data.features.length) {
                return [];
            }

            var filters = [];

            _.each(this.planRecord.data.features, function(feature) {
                filters.push({
                    property: 'ObjectID',
                    operator: '=',
                    value: feature.id
                });
            });

            return [
                Ext.create('Rally.data.WsapiDataStore', {
                    model: this.lowestPIType,
                    autoLoad: true,
                    fetch: ['Value','FormattedID', 'Owner','Name', 'PreliminaryEstimate', 'DisplayColor'],
                    filters: Rally.data.QueryFilter.or(filters)
                })
            ];
        },

        isMatchingRecord: function () {
            return true;
        },

        _getProgressBarHtml: function () {
            return '

';
        },

        findCardInfo: function (searchCriteria, includeHiddenCards) {
            var card, index, _i, _len, _ref;

            searchCriteria = searchCriteria.get && searchCriteria.get('ObjectID') ? searchCriteria.get('ObjectID') : searchCriteria;
            _ref = this.getCards(includeHiddenCards);
            for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) {
                card = _ref[index];
                if (card.getRecord().get('ObjectID') === searchCriteria || card.getEl() === searchCriteria || card.getEl() === Ext.get(searchCriteria)) {
                    return {
                        record: card.getRecord(),
                        index: index,
                        card: card
                    };
                }
            }
            return null;
        },

        destroy: function () {
            var plugin, _i, _len, _ref;

            _ref = this.plugins;
            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                plugin = _ref[_i];
                if (plugin !== null) {
                    plugin.destroy();
                }
            }
            return this.callParent(arguments);
        }
    });

}).call(this);
<commit_msg>S53237:turned gear menu on which gives us edit which seems to work.  Also, gives us copy and basic delete.  Those may need work.<commit_after>(function () {
    var Ext;

    Ext = window.Ext4 || window.Ext;

    Ext.define('Rally.apps.roadmapplanningboard.PlanningBoardColumn', {
        extend: 'Rally.ui.cardboard.Column',
        alias: 'widget.planningboardcolumn',
        mixins: {
            maskable: 'Rally.ui.mask.Maskable'
        },
        requires: ['Rally.apps.roadmapplanningboard.plugin.OrcaColumnDropController', 'Rally.apps.roadmapplanningboard.OrcaColumnDropTarget'],
        config: {
            stores: [],
            lowestPIType: undefined,
            dropControllerConfig: {
                ptype: 'orcacolumndropcontroller'
            },
            cardConfig: {
                showIconsAndHighlightBorder: true,
                showPlusIcon: false,
                showColorIcon: true,
                showGearIcon: true,
                showReadyIcon: false,
                showBlockedIcon: false
            }
        },

        initComponent: function () {
            this.callParent(arguments);
            return this.on('beforerender', function () {
                var cls;

                cls = 'planning-column';
                this.getContentCell().addCls(cls);
                return this.getColumnHeaderCell().addCls(cls);
            }, this, {
                single: true
            });
        },

        getStores: function () {

            if (!this.planRecord || !this.planRecord.data.features.length) {
                return [];
            }

            var filters = [];

            _.each(this.planRecord.data.features, function(feature) {
                filters.push({
                    property: 'ObjectID',
                    operator: '=',
                    value: feature.id
                });
            });

            return [
                Ext.create('Rally.data.WsapiDataStore', {
                    model: this.lowestPIType,
                    autoLoad: true,
                    fetch: ['Value','FormattedID', 'Owner','Name', 'PreliminaryEstimate', 'DisplayColor'],
                    filters: Rally.data.QueryFilter.or(filters)
                })
            ];
        },

        isMatchingRecord: function () {
            return true;
        },

        _getProgressBarHtml: function () {
            return '

';
        },

        findCardInfo: function (searchCriteria, includeHiddenCards) {
            var card, index, _i, _len, _ref;

            searchCriteria = searchCriteria.get && searchCriteria.get('ObjectID') ? searchCriteria.get('ObjectID') : searchCriteria;
            _ref = this.getCards(includeHiddenCards);
            for (index = _i = 0, _len = _ref.length; _i < _len; index = ++_i) {
                card = _ref[index];
                if (card.getRecord().get('ObjectID') === searchCriteria || card.getEl() === searchCriteria || card.getEl() === Ext.get(searchCriteria)) {
                    return {
                        record: card.getRecord(),
                        index: index,
                        card: card
                    };
                }
            }
            return null;
        },

        destroy: function () {
            var plugin, _i, _len, _ref;

            _ref = this.plugins;
            for (_i = 0, _len = _ref.length; _i < _len; _i++) {
                plugin = _ref[_i];
                if (plugin !== null) {
                    plugin.destroy();
                }
            }
            return this.callParent(arguments);
        }
    });

}).call(this);
<|endoftext|>


<commit_before>
// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redex/LICENSE

import express from 'express';

import ExpressResponses from '../lib/ExpressResponses';

export function create(config, logger, context) {

   let app, server;
   let state = { config };

   function getReport() {
      let report = {};
      Object.keys(context.stores).forEach(name => {
         try {
            report[name] = context.stores[name].state;
         } catch (err) {
            logger.warn('getReport store', name, err);
         }
      });
      Object.keys(context.components).forEach(name => {
         try {
            report[name] = context.components[name].state;
         } catch (err) {
            logger.warn('getReport store', name, err);
         }
      });
      return report;
   }

   const those = {
      get state() {
         return { state };
      },
      async start() {
         app = express();
         app.get(config.location, async (req, res) => {
            res.json(getReport());
         });
         server = app.listen(config.port);
         state.hostname = context.stores.environment.hostname;
         logger.info('listening', state);
      },
      async end() {
         if (server) {
            server.close();
            logger.info('end');
         } else {
            logger.warn('end');
         }
      },
   };

   return those;
}
<commit_msg>update<commit_after>
// Copyright (c) 2015, Evan Summers (twitter.com/evanxsummers)
// ISC license, see http://github.com/evanx/redex/LICENSE

import express from 'express';

import ExpressResponses from '../lib/ExpressResponses';

export function create(config, logger, context) {

   let app, server;
   let state = { config };

   function getReport() {
      let report = {};
      Object.keys(context.stores).forEach(name => {
         try {
            report[name] = context.stores[name].state;
         } catch (err) {
            logger.warn('getReport store', name, err);
         }
      });
      Object.keys(context.components).forEach(name => {
         try {
            report[name] = context.components[name].state;
         } catch (err) {
            logger.warn('getReport store', name, err);
         }
      });
      return report;
   }

   const those = {
      get state() {
         return { state };
      },
      async start() {
         app = express();
         app.get(config.location, async (req, res) => {
            logger.debug('req', req.url, getReport());
            res.json(getReport());
         });
         app.get('/', async (req, res) => {
            logger.debug('req', req.url, getReport());
            res.json(getReport());
         });
         server = app.listen(config.port);
         state.hostname = context.stores.environment.hostname;
         logger.info('listening', state);
      },
      async end() {
         if (server) {
            server.close();
            logger.info('end');
         } else {
            logger.warn('end');
         }
      },
   };

   return those;
}
<|endoftext|>