<commit_before>/**
 * (c) 2016 Tieto Finland Oy
 * Licensed under the MIT license.
 */
'use strict';

/**
* @ngdoc function
* @name dashboard.controller:meetingStatusCtrl
* @description
* # meetingStatusCtrl
* Controller of the dashboard
*/
angular.module('dashboard')
    .controller('meetingStatusCtrl', ['$log', '$scope', '$rootScope', '$stateParams', '$state', 'CONST', 'StorageSrv', 'ENV', 'AhjoMeetingSrv', '$timeout', 'Utils', function ($log, $scope, $rootScope, $stateParams, $state, CONST, StorageSrv, ENV, AhjoMeetingSrv, $timeout, Utils) {
        $log.debug("meetingStatusCtrl: CONTROLLER");
        var self = this;
        $rootScope.menu = $stateParams.menu;
        self.title = 'MOBILE TITLE';
        self.uiName = null;
        self.uiRole = 'Päätösvaltainen osallistuja'; // todo: update dynamically
        self.meeting = null;
        self.chairman = false;
        self.loading = true;
        self.hasUnsavedData = false;
        self.parallelModeActive = false;
        self.unsavedConfig = { title: 'STR_CONFIRM', text: 'STR_WARNING_UNSAVED', yes: 'STR_CONTINUE' };
        var meetingItem = StorageSrv.getKey(CONST.KEY.MEETING_ITEM);
        self.isMobile = $rootScope.isMobile;
        var pollingTimer = null;
        var lastEventId = null;
        var selectedTopicGuid = null;

        for (var i = 0; i < ENV.SupportedRoles.length; i++) {
            if (ENV.SupportedRoles[i].RoleID === CONST.MTGROLE.CHAIRMAN) {
                self.chairman = true;
            }
        }

        function meetingStatusChanged(event) {
            $log.debug("meetingStatusCtrl: meetingStatusChanged");
            if (angular.isObject(event) && angular.isObject(self.meeting)) {
                self.meeting.meetingStatus = event.meetingStateType;
            }
        }

        function topicStatusChanged(event) {
            $log.debug("meetingStatusCtrl: topicStatusChanged");
            if (angular.isObject(event) && angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {
                for (var i = 0; i < self.meeting.topicList.length; i++) {
                    var topic = self.meeting.topicList[i];
                    if (angular.isObject(topic) && angular.equals(topic.topicGuid, event.topicID)) {
                        topic.topicStatus = event.topicStateType;
                    }
                }
            }
        }

        function proposalsStatusChanged(events) {
            $log.debug("meetingStatusCtrl: proposalsStatusChanged");
            if (angular.isArray(events) && angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {

                var changedTopicGuidArray = [];
                angular.forEach(events, function (topic) {
                    if (angular.isObject(topic) && angular.isObject(topic.proposal) && angular.isString(topic.proposal.topicGuid)) {
                        changedTopicGuidArray.push(topic.proposal.topicGuid);
                    }
                }, changedTopicGuidArray);

                for (var j = 0; j < self.meeting.topicList.length; j++) {
                    var topic = self.meeting.topicList[j];
                    if (angular.isObject(topic)) {
                        topic.isModified = (changedTopicGuidArray.indexOf(topic.topicGuid) > CONST.NOTFOUND);
                        if (!self.isMobile && angular.equals(topic.topicGuid, selectedTopicGuid)) {
                            StorageSrv.setKey(CONST.KEY.TOPIC, angular.copy(topic));
                        }
                    }
                }
            }
        }

        function topicEdited(event, mtgGuid, mtg) {
            if (!angular.isObject(event) || !angular.isObject(event.topic) || !angular.isObject(mtg) || !mtgGuid || !angular.isArray(mtg.topicList)) {
                $log.error("meetingStatusCtrl.topicEdited: ignored, bad args");
                return;
            }

            if (!angular.equals(event.meetingID, mtgGuid)) {
                $log.debug("meetingStatusCtrl.topicEdited: ignored, not this meeting, event.meetingID=" + event.meetingID + " meeting=" + mtg.meetingGuid);
                return;
            }

            var eTopic = event.topic;
            $log.debug("meetingStatusCtrl: topicEdited, topicGuid=" + eTopic.topicGuid + " sequencenumber=" + eTopic.sequencenumber + " mtgGuid=" + mtgGuid);

            var matchInd;
            for (var i = 0; !angular.isDefined(matchInd) && i < mtg.topicList.length; i++) {
                if (angular.equals(eTopic.topicGuid, mtg.topicList[i].topicGuid)) {
                    matchInd = i;
                    $log.debug("meetingStatusCtrl: merging, topicGuid exists at matchInd=" + matchInd);
                    angular.merge(mtg.topicList[matchInd], eTopic);
                }
            }

            if (!angular.isDefined(matchInd)) {
                $log.debug("meetingStatusCtrl: adding a new topic sequenceNumber=" + eTopic.sequencenumber + " topicList.length=" + mtg.topicList.length);
                var ind = eTopic.sequencenumber - 1;
                if ((ind >= 0) && (ind < mtg.topicList.length)) {
                    mtg.topicList.splice(ind, 0, eTopic);
                } else {
                    $log.error("meetingStatusCtrl: ignored, bad sequencenumber");
                }
            }

        }

        function getEvents() {
            if (lastEventId && meetingItem.meetingGuid) {
                var proposalEvents = [];
                var deleteEvents = [];
                AhjoMeetingSrv.getEvents(lastEventId, meetingItem.meetingGuid).then(function (response) {
                    if (angular.isArray(response)) {
                        response.forEach(function (event) {
                            $log.debug("meetingStatusCtrl: getEvents then: " + event.typeName);
                            switch (event.typeName) {
                                case CONST.MTGEVENT.LASTEVENTID:
                                    lastEventId = event.lastEventId;
                                    break;
                                case CONST.MTGEVENT.REMARKPUBLISHED:
                                case CONST.MTGEVENT.REMARKUPDATED:
                                    if (angular.isObject(event) && angular.isObject(event.proposal) && event.proposal.isOwnProposal !== true) {
                                        proposalEvents.push(event);
                                    }
                                    break;
                                case CONST.MTGEVENT.REMARKDELETED:
                                    if (angular.isObject(event) && angular.isString(event.deletedProposal)) {
                                        deleteEvents.push(event);
                                    }
                                    break;
                                case CONST.MTGEVENT.MEETINGSTATECHANGED:
                                    meetingStatusChanged(event);
                                    break;
                                case CONST.MTGEVENT.TOPICSTATECHANGED:
                                    topicStatusChanged(event);
                                    break;
                                case CONST.MTGEVENT.TOPICEDITED:
                                    topicEdited(event, meetingItem.meetingGuid, self.meeting);
                                    break;
                                default:
                                    $log.error("meetingStatusCtrl: unsupported typeName: " + event.typeName);
                                    break;
                            }
                        }, this);
                    }
                }, function (error) {
                    $log.error("meetingStatusCtrl: getEvents error: " + JSON.stringify(error));
                }).finally(function () {
                    $timeout.cancel(pollingTimer);
                    pollingTimer = $timeout(function () {
                        getEvents();
                    }, CONST.POLLINGTIMEOUT);

                    if (proposalEvents.length) {
                        var events = angular.copy(StorageSrv.getKey(CONST.KEY.PROPOSAL_EVENT_ARRAY));
                        if (angular.isArray(events)) {
                            var concated = events.concat(proposalEvents);
                            StorageSrv.setKey(CONST.KEY.PROPOSAL_EVENT_ARRAY, concated);
                        }
                    }
                    if (deleteEvents.length) {
                        $rootScope.$emit(CONST.PROPOSALDELETED, { deleted: deleteEvents });
                    }
                });
            }
            else {
                $log.error("meetingStatusCtrl: getEvents invalid parameter:");
            }
        }

        function getMeeting(meetingItem) {
            $log.debug("meetingStatusCtrl.getMeeting");
            self.uiName = meetingItem.agencyName + ' ' + meetingItem.name;
            selectedTopicGuid = null;
            StorageSrv.deleteKey(CONST.KEY.TOPIC);
            $timeout.cancel(pollingTimer);
            pollingTimer = null;
            AhjoMeetingSrv.getMeeting(meetingItem.meetingGuid).then(function (response) {
                $log.debug("meetingStatusCtrl.getMeeting: done");
                if (angular.isObject(response) && angular.isArray(response.objects) && response.objects.length) {
                    self.meeting = response.objects[0];
                    if (angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {

                        angular.forEach(self.meeting.topicList, function (topic) {
                            if (angular.isObject(topic)) {
                                topic.userPersonGuid = self.meeting.userPersonGuid;
                                topic.isCityCouncil = self.meeting.isCityCouncil;

                                if (!selectedTopicGuid && topic.topicGuid) {
                                    selectedTopicGuid = topic.topicGuid;
                                    if (!self.isMobile) {
                                        StorageSrv.setKey(CONST.KEY.TOPIC, topic);
                                    }
                                }
                            }
                        }, this);

                        lastEventId = self.meeting.lastEventId;
                        $timeout.cancel(pollingTimer);
                        pollingTimer = $timeout(function () {
                            getEvents();
                        }, CONST.POLLINGTIMEOUT);

                        self.uiName = self.meeting.meetingTitle;
                    }
                }
            }, function (error) {
                $log.error("meetingStatusCtrl: getMeeting error: " + JSON.stringify(error));
                self.error = error;
            }).finally(function () {
                self.loading = false;
            });
        }

        self.goHome = function () {
            $state.go(CONST.APPSTATE.HOME, { menu: CONST.MENU.CLOSED });
        };

        self.topicSelected = function (topic) {
            if (angular.isObject(topic)) {
                $log.debug("meetingStatusCtrl.topicSelected: publicity=" + topic.publicity);
                topic.userPersonGuid = self.meeting.userPersonGuid;
                topic.isCityCouncil = self.meeting.isCityCouncil;
                selectedTopicGuid = topic.topicGuid;
                StorageSrv.setKey(CONST.KEY.TOPIC, angular.copy(topic));
                if (self.isMobile) {
                    $state.go(CONST.APPSTATE.MEETINGDETAILS, {});
                }
                self.hasUnsavedData = false;
            }
            else {
                $log.error("meetingStatusCtrl: topicSelected: invalid parameter");
            }
        };

        self.isSelected = function (topic) {
            return (angular.isObject(topic) && topic.topicGuid === selectedTopicGuid);
        };

        self.isTopicPublic = function (topic) {
            return (angular.isObject(topic) && topic.publicity === CONST.PUBLICITY.PUBLIC);
        };

        self.statusIcon = function (topic) {
            if (angular.isObject(topic) && topic.topicStatus) {
                for (var item in CONST.TOPICSTATUS) {
                    if (CONST.TOPICSTATUS.hasOwnProperty(item) && topic.topicStatus === CONST.TOPICSTATUS[item].value) {
                        return CONST.TOPICSTATUS[item].iconPath;
                    }
                }
            }
            return null;
        };

        self.topicStatusText = function (topic) {
            if (angular.isObject(topic) && topic.topicStatus) {
                for (var item in CONST.TOPICSTATUS) {
                    if (CONST.TOPICSTATUS.hasOwnProperty(item) && topic.topicStatus === CONST.TOPICSTATUS[item].value) {
                        return CONST.TOPICSTATUS[item].stringId;
                    }
                }
            }
            return null;
        };

        self.stringId = function (meeting) {
            for (var item in CONST.MTGSTATUS) {
                if (CONST.MTGSTATUS.hasOwnProperty(item)) {
                    if (angular.isObject(meeting) && meeting.meetingStatus === CONST.MTGSTATUS[item].value) {
                        return CONST.MTGSTATUS[item].stringId;
                    }
                }
            }
            return 'STR_TOPIC_UNKNOWN';
        };

        self.mtgStatusClass = function (meeting) {
            if (angular.isObject(meeting)) {
                var s = Utils.objWithVal(CONST.MTGSTATUS, CONST.KEY.VALUE, meeting.meetingStatus);
                return s ? s.badgeClass : 'db-badge-red';
            }
            return 'db-badge-red';
        };

        self.toggleParallelMode = function () {
            $rootScope.parallelMode = $rootScope.parallelMode ? false : true;
        };

        $scope.$watch(function () {
            return StorageSrv.getKey(CONST.KEY.PROPOSAL_EVENT_ARRAY);
        }, function (events, oldEvents) {
            if (!angular.equals(events, oldEvents)) {
                proposalsStatusChanged(events);
            }
        });

        var unsavedWatcher = $rootScope.$on(CONST.UNSAVEMEETINGDDATA, function (event, hasUnsaved) {
            self.hasUnsavedData = hasUnsaved ? true : false;
        });

        var modeWatcher = $rootScope.$on(CONST.MEETINGPARALLELMODE, function (event, active) {
            self.parallelModeActive = active ? true : false;
        });

        $scope.$on('$destroy', unsavedWatcher);
        $scope.$on('$destroy', modeWatcher);

        $scope.$on('$destroy', function () {
            $log.debug("meetingStatusCtrl: DESTROY");
            $timeout.cancel(pollingTimer);
        });

        if (meetingItem) {
            getMeeting(meetingItem);
        }
        else {
            $state.go(CONST.APPSTATE.HOME, { menu: CONST.MENU.CLOSED });
        }

    }]);
<commit_msg>topic status updating fixed<commit_after>/**
 * (c) 2016 Tieto Finland Oy
 * Licensed under the MIT license.
 */
'use strict';

/**
* @ngdoc function
* @name dashboard.controller:meetingStatusCtrl
* @description
* # meetingStatusCtrl
* Controller of the dashboard
*/
angular.module('dashboard')
    .controller('meetingStatusCtrl', ['$log', '$scope', '$rootScope', '$stateParams', '$state', 'CONST', 'StorageSrv', 'ENV', 'AhjoMeetingSrv', '$timeout', 'Utils', function ($log, $scope, $rootScope, $stateParams, $state, CONST, StorageSrv, ENV, AhjoMeetingSrv, $timeout, Utils) {
        $log.debug("meetingStatusCtrl: CONTROLLER");
        var self = this;
        $rootScope.menu = $stateParams.menu;
        self.title = 'MOBILE TITLE';
        self.uiName = null;
        self.uiRole = 'Päätösvaltainen osallistuja'; // todo: update dynamically
        self.meeting = null;
        self.chairman = false;
        self.loading = true;
        self.hasUnsavedData = false;
        self.parallelModeActive = false;
        self.unsavedConfig = { title: 'STR_CONFIRM', text: 'STR_WARNING_UNSAVED', yes: 'STR_CONTINUE' };
        var meetingItem = StorageSrv.getKey(CONST.KEY.MEETING_ITEM);
        self.isMobile = $rootScope.isMobile;
        var pollingTimer = null;
        var lastEventId = null;
        var selectedTopicGuid = null;

        for (var i = 0; i < ENV.SupportedRoles.length; i++) {
            if (ENV.SupportedRoles[i].RoleID === CONST.MTGROLE.CHAIRMAN) {
                self.chairman = true;
            }
        }

        function meetingStatusChanged(event) {
            $log.debug("meetingStatusCtrl: meetingStatusChanged");
            if (angular.isObject(event) && angular.isObject(self.meeting)) {
                self.meeting.meetingStatus = event.meetingStateType;
            }
        }

        function topicStatusChanged(event) {
            $log.debug("meetingStatusCtrl: topicStatusChanged");
            if (angular.isObject(event) && angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {
                for (var i = 0; i < self.meeting.topicList.length; i++) {
                    var topic = self.meeting.topicList[i];
                    if (angular.isObject(topic) && angular.equals(topic.topicGuid, event.topicID)) {
                        topic.topicStatus = event.topicStateType;
                    }
                }
            }
        }

        function proposalsStatusChanged(events) {
            $log.debug("meetingStatusCtrl: proposalsStatusChanged");
            if (angular.isArray(events) && angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {

                var changedTopicGuidArray = [];
                angular.forEach(events, function (topic) {
                    if (angular.isObject(topic) && angular.isObject(topic.proposal) && angular.isString(topic.proposal.topicGuid)) {
                        changedTopicGuidArray.push(topic.proposal.topicGuid);
                    }
                }, changedTopicGuidArray);

                for (var j = 0; j < self.meeting.topicList.length; j++) {
                    var topic = self.meeting.topicList[j];
                    if (angular.isObject(topic)) {
                        topic.isModified = (changedTopicGuidArray.indexOf(topic.topicGuid) > CONST.NOTFOUND);
                        if (!self.isMobile && angular.equals(topic.topicGuid, selectedTopicGuid)) {
                            StorageSrv.setKey(CONST.KEY.TOPIC, angular.copy(topic));
                        }
                    }
                }
            }
        }

        function topicEdited(event, mtgGuid, mtg) {
            if (!angular.isObject(event) || !angular.isObject(event.topic) || !angular.isObject(mtg) || !mtgGuid || !angular.isArray(mtg.topicList)) {
                $log.error("meetingStatusCtrl.topicEdited: ignored, bad args");
                return;
            }

            if (!angular.equals(event.meetingID, mtgGuid)) {
                $log.debug("meetingStatusCtrl.topicEdited: ignored, not this meeting, event.meetingID=" + event.meetingID + " meeting=" + mtg.meetingGuid);
                return;
            }

            var eTopic = event.topic;
            $log.debug("meetingStatusCtrl: topicEdited, topicGuid=" + eTopic.topicGuid + " sequencenumber=" + eTopic.sequencenumber + " mtgGuid=" + mtgGuid);

            var matchInd;
            for (var i = 0; !angular.isDefined(matchInd) && i < mtg.topicList.length; i++) {
                if (angular.equals(eTopic.topicGuid, mtg.topicList[i].topicGuid)) {
                    matchInd = i;
                    $log.debug("meetingStatusCtrl: merging, topicGuid exists at matchInd=" + matchInd);
                    angular.merge(mtg.topicList[matchInd], eTopic);
                }
            }

            if (!angular.isDefined(matchInd)) {
                $log.debug("meetingStatusCtrl: adding a new topic sequenceNumber=" + eTopic.sequencenumber + " topicList.length=" + mtg.topicList.length);
                var ind = eTopic.sequencenumber - 1;
                if ((ind >= 0) && (ind < mtg.topicList.length)) {
                    mtg.topicList.splice(ind, 0, eTopic);
                } else {
                    $log.error("meetingStatusCtrl: ignored, bad sequencenumber");
                }
            }

        }

        function getEvents() {
            if (lastEventId && meetingItem.meetingGuid) {
                var proposalEvents = [];
                var deleteEvents = [];
                AhjoMeetingSrv.getEvents(lastEventId, meetingItem.meetingGuid).then(function (response) {
                    if (angular.isArray(response)) {
                        response.forEach(function (event) {
                            $log.debug("meetingStatusCtrl: getEvents then: " + event.typeName);
                            switch (event.typeName) {
                                case CONST.MTGEVENT.LASTEVENTID:
                                    lastEventId = event.lastEventId;
                                    break;
                                case CONST.MTGEVENT.REMARKPUBLISHED:
                                case CONST.MTGEVENT.REMARKUPDATED:
                                    if (angular.isObject(event) && angular.isObject(event.proposal) && event.proposal.isOwnProposal !== true) {
                                        proposalEvents.push(event);
                                    }
                                    break;
                                case CONST.MTGEVENT.REMARKDELETED:
                                    if (angular.isObject(event) && angular.isString(event.deletedProposal)) {
                                        deleteEvents.push(event);
                                    }
                                    break;
                                case CONST.MTGEVENT.MEETINGSTATECHANGED:
                                    meetingStatusChanged(event);
                                    break;
                                case CONST.MTGEVENT.TOPICSTATECHANGED:
                                    topicStatusChanged(event);
                                    break;
                                case CONST.MTGEVENT.TOPICEDITED:
                                    topicEdited(event, meetingItem.meetingGuid, self.meeting);
                                    break;
                                default:
                                    $log.error("meetingStatusCtrl: unsupported typeName: " + event.typeName);
                                    break;
                            }
                        }, this);
                    }
                }, function (error) {
                    $log.error("meetingStatusCtrl: getEvents error: " + JSON.stringify(error));
                }).finally(function () {
                    $timeout.cancel(pollingTimer);
                    pollingTimer = $timeout(function () {
                        getEvents();
                    }, CONST.POLLINGTIMEOUT);

                    if (proposalEvents.length) {
                        var concated = proposalEvents;

                        if (angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {
                            angular.forEach(concated, function (e) {
                                angular.forEach(self.meeting.topicList, function (t) {
                                    if (angular.isObject(e) && angular.isObject(t) && angular.equals(e.proposal.topicGuid, t.topicGuid)) {
                                        t.includePublishedRemark = true;
                                    }
                                }, this);
                            }, this);
                        }

                        var events = angular.copy(StorageSrv.getKey(CONST.KEY.PROPOSAL_EVENT_ARRAY));
                        if (angular.isArray(events)) {
                            concated = events.concat(proposalEvents);
                        }
                        StorageSrv.setKey(CONST.KEY.PROPOSAL_EVENT_ARRAY, concated);
                    }
                    if (deleteEvents.length) {
                        $rootScope.$emit(CONST.PROPOSALDELETED, { deleted: deleteEvents });
                    }
                });
            }
            else {
                $log.error("meetingStatusCtrl: getEvents invalid parameter:");
            }
        }

        function getMeeting(meetingItem) {
            $log.debug("meetingStatusCtrl.getMeeting");
            self.uiName = meetingItem.agencyName + ' ' + meetingItem.name;
            selectedTopicGuid = null;
            StorageSrv.deleteKey(CONST.KEY.TOPIC);
            $timeout.cancel(pollingTimer);
            pollingTimer = null;
            AhjoMeetingSrv.getMeeting(meetingItem.meetingGuid).then(function (response) {
                $log.debug("meetingStatusCtrl.getMeeting: done");
                if (angular.isObject(response) && angular.isArray(response.objects) && response.objects.length) {
                    self.meeting = response.objects[0];
                    if (angular.isObject(self.meeting) && angular.isArray(self.meeting.topicList)) {

                        angular.forEach(self.meeting.topicList, function (topic) {
                            if (angular.isObject(topic)) {
                                topic.userPersonGuid = self.meeting.userPersonGuid;
                                topic.isCityCouncil = self.meeting.isCityCouncil;

                                if (!selectedTopicGuid && topic.topicGuid) {
                                    selectedTopicGuid = topic.topicGuid;
                                    if (!self.isMobile) {
                                        StorageSrv.setKey(CONST.KEY.TOPIC, topic);
                                    }
                                }
                            }
                        }, this);

                        lastEventId = self.meeting.lastEventId;
                        $timeout.cancel(pollingTimer);
                        pollingTimer = $timeout(function () {
                            getEvents();
                        }, CONST.POLLINGTIMEOUT);

                        self.uiName = self.meeting.meetingTitle;
                    }
                }
            }, function (error) {
                $log.error("meetingStatusCtrl: getMeeting error: " + JSON.stringify(error));
                self.error = error;
            }).finally(function () {
                self.loading = false;
            });
        }

        self.goHome = function () {
            $state.go(CONST.APPSTATE.HOME, { menu: CONST.MENU.CLOSED });
        };

        self.topicSelected = function (topic) {
            if (angular.isObject(topic)) {
                $log.debug("meetingStatusCtrl.topicSelected: publicity=" + topic.publicity);
                topic.userPersonGuid = self.meeting.userPersonGuid;
                topic.isCityCouncil = self.meeting.isCityCouncil;
                selectedTopicGuid = topic.topicGuid;
                StorageSrv.setKey(CONST.KEY.TOPIC, angular.copy(topic));
                if (self.isMobile) {
                    $state.go(CONST.APPSTATE.MEETINGDETAILS, {});
                }
                self.hasUnsavedData = false;
            }
            else {
                $log.error("meetingStatusCtrl: topicSelected: invalid parameter");
            }
        };

        self.isSelected = function (topic) {
            return (angular.isObject(topic) && topic.topicGuid === selectedTopicGuid);
        };

        self.isTopicPublic = function (topic) {
            return (angular.isObject(topic) && topic.publicity === CONST.PUBLICITY.PUBLIC);
        };

        self.statusIcon = function (topic) {
            if (angular.isObject(topic) && topic.topicStatus) {
                for (var item in CONST.TOPICSTATUS) {
                    if (CONST.TOPICSTATUS.hasOwnProperty(item) && topic.topicStatus === CONST.TOPICSTATUS[item].value) {
                        return CONST.TOPICSTATUS[item].iconPath;
                    }
                }
            }
            return null;
        };

        self.topicStatusText = function (topic) {
            if (angular.isObject(topic) && topic.topicStatus) {
                for (var item in CONST.TOPICSTATUS) {
                    if (CONST.TOPICSTATUS.hasOwnProperty(item) && topic.topicStatus === CONST.TOPICSTATUS[item].value) {
                        return CONST.TOPICSTATUS[item].stringId;
                    }
                }
            }
            return null;
        };

        self.stringId = function (meeting) {
            for (var item in CONST.MTGSTATUS) {
                if (CONST.MTGSTATUS.hasOwnProperty(item)) {
                    if (angular.isObject(meeting) && meeting.meetingStatus === CONST.MTGSTATUS[item].value) {
                        return CONST.MTGSTATUS[item].stringId;
                    }
                }
            }
            return 'STR_TOPIC_UNKNOWN';
        };

        self.mtgStatusClass = function (meeting) {
            if (angular.isObject(meeting)) {
                var s = Utils.objWithVal(CONST.MTGSTATUS, CONST.KEY.VALUE, meeting.meetingStatus);
                return s ? s.badgeClass : 'db-badge-red';
            }
            return 'db-badge-red';
        };

        self.toggleParallelMode = function () {
            $rootScope.parallelMode = $rootScope.parallelMode ? false : true;
        };

        $scope.$watch(function () {
            return StorageSrv.getKey(CONST.KEY.PROPOSAL_EVENT_ARRAY);
        }, function (events, oldEvents) {
            if (!angular.equals(events, oldEvents)) {
                proposalsStatusChanged(events);
            }
        });

        var unsavedWatcher = $rootScope.$on(CONST.UNSAVEMEETINGDDATA, function (event, hasUnsaved) {
            self.hasUnsavedData = hasUnsaved ? true : false;
        });

        var modeWatcher = $rootScope.$on(CONST.MEETINGPARALLELMODE, function (event, active) {
            self.parallelModeActive = active ? true : false;
        });

        $scope.$on('$destroy', unsavedWatcher);
        $scope.$on('$destroy', modeWatcher);

        $scope.$on('$destroy', function () {
            $log.debug("meetingStatusCtrl: DESTROY");
            $timeout.cancel(pollingTimer);
        });

        if (meetingItem) {
            getMeeting(meetingItem);
        }
        else {
            $state.go(CONST.APPSTATE.HOME, { menu: CONST.MENU.CLOSED });
        }

    }]);
<|endoftext|>


<commit_before>//====================================================================================================================
// Webble World
// [IntelligentPad system for the web]
// Copyright (c) 2010 Micke Nicander Kuwahara, Giannis Georgalis, Yuzuru Tanaka in Meme Media R&D Group of Hokkaido University
// v3.0 (2013), v3.1(2015)
//
// Project Leader & Lead Meme Media Architect: Yuzuru Tanaka
// Webble System Lead Architect & Developer: Micke Nicander Kuwahara
// Server Side Developer: Giannis Georgalis
// Additional Support: Jonas Sjöbergh
//
// This file is part of Webble World (c).
// ******************************************************************************************
// Webble World is licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ******************************************************************************************
// The use of the word "Webble" or "webble" for the loadable meme media objects are limited
// only to objects that actually loads in this original Webble World Platform. Modifications
// of the meme media object code which leads to breaking of compatibility with the original
// Webble World platform, may no longer be referred to as a "Webble" or "webble".
// ******************************************************************************************
//====================================================================================================================
'use strict';

//====================================================================================================================
// ABOUT WEBBLE WORLD FORM CONTROLLER
// This controls the Platforms About form
//====================================================================================================================
ww3Controllers.controller('AboutWebbleSheetCtrl', function ($scope, $modalInstance, $log, wblData, gettext, dbService, Enum) {

    //=== PROPERTIES ================================================================

    $scope.formData = wblData;
	var wblDefMetaData = $scope.formData.wblPlatformScope.getWebbleDefsMetaDataMemory();

    $scope.tooltip = {
        displayname: gettext("This is the currently chosen display name for this Webble Instance, may be changed in properties"),
        instanceid: gettext("This is the session instance id for this particular Webble"),
        defid: gettext("This is the name of the Webble definition this Webble was created from"),
        author: gettext("This is the user name of the author for this Webble definition"),
        description: gettext("This is the description of this Webble Definition"),
        keywords: gettext("This is the keywords selected for this Webble Definition"),
        templateid: gettext("This is the name of the template this Webble is created from. If you click the 'Create Sandbox Copy' button you create an identical copy of this Webble template (HTML5 code) and place it in your sandbox template editor for developing your own version based on this."),
        templaterevision: gettext("This is the revision number of the template being used")
    };

	$scope.textParts = {
		starRatingTxt: [
			gettext("Terrible"),
			gettext("Very Bad"),
			gettext("Bad"),
			gettext("Could be better"),
			gettext("Fairly Ok"),
			gettext("Good"),
			gettext("Very Good"),
			gettext("Great!"),
			gettext("Amazing!"),
			gettext("Masterpiece!")
		],
		rateTxtVoters: gettext("votes")
	};

    //=== EVENT HANDLERS =====================================================================

    //*****************************************************************************************************************
    //=== PRIVATE FUNCTIONS ===========================================================================================
    //*****************************************************************************************************************

    //*****************************************************************************************************************
    //=== PUBLIC FUNCTIONS ============================================================================================
    //*****************************************************************************************************************

    //========================================================================================
    // Adjust Tooltip Placement By Device Width
    // the placement of the tooltip is by default at the bottom, but with smaller devices in
    // some rare cases that should be set to right instead.
    //========================================================================================
    $scope.adjustTooltipPlacementByDeviceWidth = function(){
        if($(document).width() < 410){
            return 'right';
        }
        else{
            return 'left';
        }
    };
    //========================================================================================

	//========================================================================================
	// Get Star Img
	// Returns a lit star or an off star depending on the star index and the rate value of
	// the Webble.
	//========================================================================================
	$scope.getStarImg = function(rate, index){
		if(index < rate ){
			return '../../images/starOn.png';
		}
		else{
			return '../../images/starOff.png';
		}
	};
	//========================================================================================

	//========================================================================================
	// Rate This
	// Opens a modal window which lets the user rate the chosen Webble and comment on it.
	//========================================================================================
	$scope.rateThis = function(wbl){
		$scope.formData.wblPlatformScope.openForm(Enum.aopForms.rateWbl, {wblDefId: wbl.defid, wblDefName: wbl.displayname}, function(done){
			if(done != null){
				wbl['rateShow'] = false;
				dbService.getWebbleDef(wbl.defid).then(function(data) {
					$scope.formData.rating = data.rating;
					$scope.formData.ratingCount = data.rating_count;
				},function(eMsg){
					$log.log("Rating data not available from server: " + eMsg);
				});
			}
		});
	};
	//========================================================================================

	//========================================================================================
	// View Comments
	// Opens a modal window which lets the user read all other users rating and comments.
	//========================================================================================
	$scope.viewComments = function(wbl){
		$scope.formData.wblPlatformScope.openForm(Enum.aopForms.viewWblRatingAndComments, {wblDefId: wbl.defid, wblDefName: wbl.displayname}, null);
	};
	//========================================================================================

	//========================================================================================
	// Get Rate Text
	// Returns a the text that comes with the selected rating.
	//========================================================================================
	$scope.getRateText = function(theRate){
		theRate = Math.ceil(parseInt(theRate));
		if(theRate == 0){
			return gettext("Unrated");
		}
		else{
			return $scope.textParts.starRatingTxt[theRate - 1];
		}
	};
	//========================================================================================

    //========================================================================================
    // Make Copy
    // Makes a template copy for the selected Webble and puts in the users sandbox.
    //========================================================================================
    $scope.makeCopy = function (templateId) {
        dbService.copyPublishedTemplateAsMyUnpublishedTemplate(templateId).then(function(data){
            $scope.formData.wblPlatformScope.showQIM(gettext("Template code copied to your Template Editor"), undefined, undefined, undefined, '#c7fcff');
        },function(eMsg){
            $scope.errorMsg = eMsg;
        });
    };
    //========================================================================================

    //========================================================================================
    // Close
    // Closes the modal form and send the resulting content back to the creator
    //========================================================================================
    $scope.close = function (result) {
        $modalInstance.close(null);
    };
    //========================================================================================

    //******************************************************************************************************************
    //=== CTRL MAIN CODE ===============================================================================================
    //******************************************************************************************************************
	$scope.formData['socialMediaUrl'] = 'https://wws.meme.hokudai.ac.jp/#app?webble=' + $scope.formData.defid;
	$scope.formData['socialMediaModelName'] = 'Cool Webble, ' + $scope.formData.displayname + ', found in Webble World. Check it out!';
	$scope.formData['rateShow'] = true;

	if(($scope.formData.rating == 0 && $scope.formData.ratingCount == 0) || $scope.formData.image == "images/notFound.png"){
		dbService.getWebbleDef($scope.formData.defid).then(function(data) {
			if($scope.formData.rating == 0 && $scope.formData.ratingCount == 0){
				$scope.formData.rating = data.rating;
				$scope.formData.ratingCount = data.rating_count;
			}
			if($scope.formData.image == "images/notFound.png" && data.webble.image != "" && data.webble.image != "images/notFound.png"){
				$scope.formData.image = data.webble.image;
			}
			else{
				$scope.formData.image = "images/icons/No_Image_Available.png";
			}
		},function(eMsg){
			$log.log("Rating data not available from server: " + eMsg);
		});
	}

});
//======================================================================================================================
<commit_msg>image display bug that showed no image at all in webble about form... easy fix<commit_after>//====================================================================================================================
// Webble World
// [IntelligentPad system for the web]
// Copyright (c) 2010 Micke Nicander Kuwahara, Giannis Georgalis, Yuzuru Tanaka in Meme Media R&D Group of Hokkaido University
// v3.0 (2013), v3.1(2015)
//
// Project Leader & Lead Meme Media Architect: Yuzuru Tanaka
// Webble System Lead Architect & Developer: Micke Nicander Kuwahara
// Server Side Developer: Giannis Georgalis
// Additional Support: Jonas Sjöbergh
//
// This file is part of Webble World (c).
// ******************************************************************************************
// Webble World is licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ******************************************************************************************
// The use of the word "Webble" or "webble" for the loadable meme media objects are limited
// only to objects that actually loads in this original Webble World Platform. Modifications
// of the meme media object code which leads to breaking of compatibility with the original
// Webble World platform, may no longer be referred to as a "Webble" or "webble".
// ******************************************************************************************
//====================================================================================================================
'use strict';

//====================================================================================================================
// ABOUT WEBBLE WORLD FORM CONTROLLER
// This controls the Platforms About form
//====================================================================================================================
ww3Controllers.controller('AboutWebbleSheetCtrl', function ($scope, $modalInstance, $log, wblData, gettext, dbService, Enum) {

    //=== PROPERTIES ================================================================

    $scope.formData = wblData;
	var wblDefMetaData = $scope.formData.wblPlatformScope.getWebbleDefsMetaDataMemory();

    $scope.tooltip = {
        displayname: gettext("This is the currently chosen display name for this Webble Instance, may be changed in properties"),
        instanceid: gettext("This is the session instance id for this particular Webble"),
        defid: gettext("This is the name of the Webble definition this Webble was created from"),
        author: gettext("This is the user name of the author for this Webble definition"),
        description: gettext("This is the description of this Webble Definition"),
        keywords: gettext("This is the keywords selected for this Webble Definition"),
        templateid: gettext("This is the name of the template this Webble is created from. If you click the 'Create Sandbox Copy' button you create an identical copy of this Webble template (HTML5 code) and place it in your sandbox template editor for developing your own version based on this."),
        templaterevision: gettext("This is the revision number of the template being used")
    };

	$scope.textParts = {
		starRatingTxt: [
			gettext("Terrible"),
			gettext("Very Bad"),
			gettext("Bad"),
			gettext("Could be better"),
			gettext("Fairly Ok"),
			gettext("Good"),
			gettext("Very Good"),
			gettext("Great!"),
			gettext("Amazing!"),
			gettext("Masterpiece!")
		],
		rateTxtVoters: gettext("votes")
	};

    //=== EVENT HANDLERS =====================================================================

    //*****************************************************************************************************************
    //=== PRIVATE FUNCTIONS ===========================================================================================
    //*****************************************************************************************************************

    //*****************************************************************************************************************
    //=== PUBLIC FUNCTIONS ============================================================================================
    //*****************************************************************************************************************

    //========================================================================================
    // Adjust Tooltip Placement By Device Width
    // the placement of the tooltip is by default at the bottom, but with smaller devices in
    // some rare cases that should be set to right instead.
    //========================================================================================
    $scope.adjustTooltipPlacementByDeviceWidth = function(){
        if($(document).width() < 410){
            return 'right';
        }
        else{
            return 'left';
        }
    };
    //========================================================================================

	//========================================================================================
	// Get Star Img
	// Returns a lit star or an off star depending on the star index and the rate value of
	// the Webble.
	//========================================================================================
	$scope.getStarImg = function(rate, index){
		if(index < rate ){
			return '../../images/starOn.png';
		}
		else{
			return '../../images/starOff.png';
		}
	};
	//========================================================================================

	//========================================================================================
	// Rate This
	// Opens a modal window which lets the user rate the chosen Webble and comment on it.
	//========================================================================================
	$scope.rateThis = function(wbl){
		$scope.formData.wblPlatformScope.openForm(Enum.aopForms.rateWbl, {wblDefId: wbl.defid, wblDefName: wbl.displayname}, function(done){
			if(done != null){
				wbl['rateShow'] = false;
				dbService.getWebbleDef(wbl.defid).then(function(data) {
					$scope.formData.rating = data.rating;
					$scope.formData.ratingCount = data.rating_count;
				},function(eMsg){
					$log.log("Rating data not available from server: " + eMsg);
				});
			}
		});
	};
	//========================================================================================

	//========================================================================================
	// View Comments
	// Opens a modal window which lets the user read all other users rating and comments.
	//========================================================================================
	$scope.viewComments = function(wbl){
		$scope.formData.wblPlatformScope.openForm(Enum.aopForms.viewWblRatingAndComments, {wblDefId: wbl.defid, wblDefName: wbl.displayname}, null);
	};
	//========================================================================================

	//========================================================================================
	// Get Rate Text
	// Returns a the text that comes with the selected rating.
	//========================================================================================
	$scope.getRateText = function(theRate){
		theRate = Math.ceil(parseInt(theRate));
		if(theRate == 0){
			return gettext("Unrated");
		}
		else{
			return $scope.textParts.starRatingTxt[theRate - 1];
		}
	};
	//========================================================================================

    //========================================================================================
    // Make Copy
    // Makes a template copy for the selected Webble and puts in the users sandbox.
    //========================================================================================
    $scope.makeCopy = function (templateId) {
        dbService.copyPublishedTemplateAsMyUnpublishedTemplate(templateId).then(function(data){
            $scope.formData.wblPlatformScope.showQIM(gettext("Template code copied to your Template Editor"), undefined, undefined, undefined, '#c7fcff');
        },function(eMsg){
            $scope.errorMsg = eMsg;
        });
    };
    //========================================================================================

    //========================================================================================
    // Close
    // Closes the modal form and send the resulting content back to the creator
    //========================================================================================
    $scope.close = function (result) {
        $modalInstance.close(null);
    };
    //========================================================================================

    //******************************************************************************************************************
    //=== CTRL MAIN CODE ===============================================================================================
    //******************************************************************************************************************
	$scope.formData['socialMediaUrl'] = 'https://wws.meme.hokudai.ac.jp/#app?webble=' + $scope.formData.defid;
	$scope.formData['socialMediaModelName'] = 'Cool Webble, ' + $scope.formData.displayname + ', found in Webble World. Check it out!';
	$scope.formData['rateShow'] = true;

	if(($scope.formData.rating == 0 && $scope.formData.ratingCount == 0) || ($scope.formData.image == "images/notFound.png" || $scope.formData.image == "")){
		dbService.getWebbleDef($scope.formData.defid).then(function(data) {
			if($scope.formData.rating == 0 && $scope.formData.ratingCount == 0){
				$scope.formData.rating = data.rating;
				$scope.formData.ratingCount = data.rating_count;
			}
			if($scope.formData.image == "images/notFound.png" || $scope.formData.image == ""){
				if(data.webble.image != "" && data.webble.image != "images/notFound.png"){
					$scope.formData.image = data.webble.image;
				}
				else{
					$scope.formData.image = "images/icons/No_Image_Available.png";
				}
			}
		},function(eMsg){
			$log.log("Rating data not available from server: " + eMsg);
		});
	}

});
//======================================================================================================================
<|endoftext|>


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

  angular.module('dimApp')
    .directive('dimSearchFilter', SearchFilter);

  SearchFilter.$inject = [];

  function SearchFilter() {
    return {
      controller: SearchFilterCtrl,
      controllerAs: 'vm',
      bindToController: true,
      restrict: 'A',
      template: [
        '<input id="filter-input" placeholder="filter items or is:arc" type="search" name="filter" ng-model="vm.search.query" ng-model-options="{ debounce: 500 }" ng-trim="true" ng-change="vm.filter()">'
      ].join('')
    };
  }

  SearchFilterCtrl.$inject = ['$scope', 'dimStoreService', '$timeout', '$interval'];

  function SearchFilterCtrl($scope, dimStoreService, $timeout, $interval) {
    var vm = this;
    var filterInputSelector = '#filter-input';
    var _duplicates = null; // Holds a map from item hash to count of occurrances of that hash

    vm.search = {
      'query': ""
    };

    $scope.$on('dim-stores-updated', function(arg) {
      _duplicates = null;
      vm.filter();
    });

    $scope.$on('dim-focus-filter-input', function(arg) {
      vm.focusFilterInput();
    });

    $scope.$on('dim-escape-filter-input', function(arg) {
      vm.blurFilterInputIfEmpty();
      vm.clearFilter();
    });

    $scope.$on('dim-clear-filter-input', function(arg) {
      vm.clearFilter();
    });

    $scope.$on('dim-active-platform-updated', function(event, args) {
      $scope.filterTimer = $interval(function () {
        // Wait until the interface has finished loading.
        if (!$scope.loadingTracker.active() && !$scope.isUserInactive()) {
          vm.filter();
          $interval.cancel($scope.filterTimer);
        }
      }, 300);
    });

    vm.blurFilterInputIfEmpty = function () {
      if (vm.search.query === "") {
        vm.blurFilterInput();
      }
    };

    vm.focusFilterInput = function () {
      $(filterInputSelector).focus();
    };

    vm.blurFilterInput = function () {
      $(filterInputSelector).blur();
    };

    vm.clearFilter = function () {
      vm.search.query = "";
      vm.filter();
    };

    vm.filter = function() {
      var filterValue = (vm.search.query) ? vm.search.query.toLowerCase() : '';
      var searchTerms = filterValue.split(" ");
      var filter, predicate = '';
      var filterFn;
      var filters = [];

      function addPredicate(predicate, filter){
        filters.push({predicate: predicate, value: filter});
      }

      _.each(searchTerms, function(term){
        if(term.indexOf('is:') >=0) {
          filter = term.replace('is:', '');
          if(_cachedFilters[filter]) {
            predicate = _cachedFilters[filter];
            addPredicate(predicate, filter);
          } else {
            for(var key in filterTrans) {
              if(filterTrans.hasOwnProperty(key) && !!~filterTrans[key].indexOf(filter)) {
                predicate = key;
                _cachedFilters[filter] = key;
                addPredicate(predicate, filter);
                break;
              }
            }
          }
        } else if(term.indexOf('light:') >= 0 || term.indexOf('level:') >= 0) {
          filter = term.replace('light:', '').replace('level:', '');
          addPredicate("light", filter);
        } else {
          addPredicate("keyword", term);
        }
      });

      filterFn = function(item) {
        var checks = 0;
        _.each(filters, function(filter){
          filterFns[filter.predicate](filter.value, item) ? checks++ : null;
        });
        return checks === filters.length;
      };

      _.each(dimStoreService.getStores(), function(store) {
        _.chain(store.items)
          .each(function(item) {
              filters.length > 0 ? item.visible = filterFn(item) : item.visible = true;
          });
      });

      $timeout(dimStoreService.setHeights, 32);
    };

    /**
     * Filter translation sets. Left-hand is the filter to run from filterFns, right side are possible filterResult
     * values that will set the left-hand to the "match."
     */
    var filterTrans = {
      'dmg':          ['arc', 'solar', 'void', 'kinetic'],
      'type':         ['primary', 'special', 'heavy', 'helmet', 'leg', 'gauntlets', 'chest', 'class', 'classitem'],
      'tier':         ['common', 'uncommon', 'rare', 'legendary', 'exotic'],
      'incomplete':   ['incomplete'],
      'complete':     ['complete'],
      'xpcomplete':   ['xpcomplete'],
      'xpincomplete': ['xpincomplete'],
      'upgraded':     ['upgraded'],
      'classType':    ['titan', 'hunter', 'warlock'],
      'dupe':         ['dupe', 'duplicate'],
      'unascended':   ['unascended', 'unassended', 'unasscended'],
      'ascended':     ['ascended', 'assended', 'asscended'],
      'locked':       ['locked'],
      'unlocked':     ['unlocked'],
      'stackable':    ['stackable'],
      'weaponClass':  ["pulserifle", "scoutrifle", "handcannon", "autorifle", "primaryweaponengram", "sniperrifle", "shotgun", "fusionrifle", "specialweaponengram", "rocketlauncher", "machinegun", "heavyweaponengram", "sidearm"]
    };

    // Cache for searches against filterTrans. Somewhat noticebly speeds up the lookup on my older Mac, YMMV. Helps
    // make the for(...) loop for filterTrans a little more bearable for the readability tradeoff.
    var _cachedFilters = {};

    /**
     * Filter groups keyed by type check. Key is what the user will search for, e.g.
     * is:complete
     *
     * Value is the checking function
     * @param {String} predicate The predicate - for example, is:arc gets the 'elemental' filter function, with predicate='arc'
     * @param {Object} item The item to test against.
     * @return {Boolean} Returns false for a match, true for a non-match (@TODO make this less confusing)
     */
    var filterFns = {
      'dmg': function(predicate, item) {
        return (item.dmg === predicate);
      },
      'type': function(predicate, item) {
        return (item.type.toLowerCase() === predicate);
      },
      'tier': function(predicate, item) {
        return (item.tier.toLowerCase() === predicate);
      },
      // @TODO This logic breaks my brain, I can't really reverse it tonight so just not(!)'ing it for now...cheap way
      // out I know. This applies to incomplete, complete, and upgraded
      'incomplete': function(predicate, item) {
        return !((item.complete !== true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class')) || ((item.xpComplete && item.hasXP) || (!item.hasXP)));
      },
      'complete': function(predicate, item) {
        return !(item.complete === false) || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class');
      },
      'upgraded': function(predicate, item) {
        return ((item.complete === true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class')) || ((!item.xpComplete && item.hasXP) || (!item.hasXP)));
      },
      'xpincomplete': function(predicate, item) {
        return item.hasXP && !item.xpComplete;
      },
      'xpcomplete': function(predicate, item) {
        return !item.hasXP || item.xpComplete;
      },
      'ascended': function(predicate, item) {
        return item.hasAscendNode && item.ascended;
      },
      'unascended': function(predicate, item) {
        return !filterFns.ascended(predicate, item);
      },
      'unlocked': function(predicate, item) {
        return item.lockable && !item.locked;
      },
      'locked': function(predicate, item) {
        return !filterFns.unlocked(predicate, item);
      },
      'dupe': function(predicate, item) {
        if (_duplicates === null) {
          _duplicates = _.chain(dimStoreService.getStores())
            .pluck('items')
            .flatten()
            .countBy('hash')
            .value();
        }

        return _duplicates[item.hash] > 1;
      },
      'classType': function(predicate, item) {
        var value;

        switch (predicate) {
          case 'titan':
            value = 0;
            break;
          case 'hunter':
            value = 1;
            break;
          case 'warlock':
            value = 2;
            break;
        }

        return (item.classType == value);
      },
      'stackable': function(predicate, item) {
        return item.maxStackSize > 1;
      },
      'weaponClass': function(predicate, item) {
        return predicate.toLowerCase().replace(/\s/g, '') == item.weaponClass;
      },
      'keyword': function(predicate, item){
        return !!~item.name.toLowerCase().indexOf(predicate);
      },
      'light': function(predicate, item){
        if (predicate.length === 0 || item.primStat == undefined || !item.equipment) return false;

        var operands = ['<=','>=','=','>','<'];
        var operand = 'none';
        var result = false;

        operands.forEach(function(element) {
          if (predicate.substring(0,element.length) === element) {
            operand = element;
            predicate = predicate.substring(element.length);
            return false;
          }
        }, this);

        switch (operand) {
          case 'none':
            result = (item.primStat.value == predicate)
            break;
          case '=':
            result = (item.primStat.value == predicate)
            break;
          case '<':
            result = (item.primStat.value < predicate)
            break;
          case '<=':
            result = (item.primStat.value <= predicate)
            break;
          case '>':
            result = (item.primStat.value > predicate)
            break;
          case '>=':
            result = (item.primStat.value >= predicate)
            break;
        }
        return result;
      }
    };
  }
})();
<commit_msg>Removed check on item type for light filter.<commit_after>(function() {
  'use strict';

  angular.module('dimApp')
    .directive('dimSearchFilter', SearchFilter);

  SearchFilter.$inject = [];

  function SearchFilter() {
    return {
      controller: SearchFilterCtrl,
      controllerAs: 'vm',
      bindToController: true,
      restrict: 'A',
      template: [
        '<input id="filter-input" placeholder="filter items or is:arc" type="search" name="filter" ng-model="vm.search.query" ng-model-options="{ debounce: 500 }" ng-trim="true" ng-change="vm.filter()">'
      ].join('')
    };
  }

  SearchFilterCtrl.$inject = ['$scope', 'dimStoreService', '$timeout', '$interval'];

  function SearchFilterCtrl($scope, dimStoreService, $timeout, $interval) {
    var vm = this;
    var filterInputSelector = '#filter-input';
    var _duplicates = null; // Holds a map from item hash to count of occurrances of that hash

    vm.search = {
      'query': ""
    };

    $scope.$on('dim-stores-updated', function(arg) {
      _duplicates = null;
      vm.filter();
    });

    $scope.$on('dim-focus-filter-input', function(arg) {
      vm.focusFilterInput();
    });

    $scope.$on('dim-escape-filter-input', function(arg) {
      vm.blurFilterInputIfEmpty();
      vm.clearFilter();
    });

    $scope.$on('dim-clear-filter-input', function(arg) {
      vm.clearFilter();
    });

    $scope.$on('dim-active-platform-updated', function(event, args) {
      $scope.filterTimer = $interval(function () {
        // Wait until the interface has finished loading.
        if (!$scope.loadingTracker.active() && !$scope.isUserInactive()) {
          vm.filter();
          $interval.cancel($scope.filterTimer);
        }
      }, 300);
    });

    vm.blurFilterInputIfEmpty = function () {
      if (vm.search.query === "") {
        vm.blurFilterInput();
      }
    };

    vm.focusFilterInput = function () {
      $(filterInputSelector).focus();
    };

    vm.blurFilterInput = function () {
      $(filterInputSelector).blur();
    };

    vm.clearFilter = function () {
      vm.search.query = "";
      vm.filter();
    };

    vm.filter = function() {
      var filterValue = (vm.search.query) ? vm.search.query.toLowerCase() : '';
      var searchTerms = filterValue.split(" ");
      var filter, predicate = '';
      var filterFn;
      var filters = [];

      function addPredicate(predicate, filter){
        filters.push({predicate: predicate, value: filter});
      }

      _.each(searchTerms, function(term){
        if(term.indexOf('is:') >=0) {
          filter = term.replace('is:', '');
          if(_cachedFilters[filter]) {
            predicate = _cachedFilters[filter];
            addPredicate(predicate, filter);
          } else {
            for(var key in filterTrans) {
              if(filterTrans.hasOwnProperty(key) && !!~filterTrans[key].indexOf(filter)) {
                predicate = key;
                _cachedFilters[filter] = key;
                addPredicate(predicate, filter);
                break;
              }
            }
          }
        } else if(term.indexOf('light:') >= 0 || term.indexOf('level:') >= 0) {
          filter = term.replace('light:', '').replace('level:', '');
          addPredicate("light", filter);
        } else {
          addPredicate("keyword", term);
        }
      });

      filterFn = function(item) {
        var checks = 0;
        _.each(filters, function(filter){
          filterFns[filter.predicate](filter.value, item) ? checks++ : null;
        });
        return checks === filters.length;
      };

      _.each(dimStoreService.getStores(), function(store) {
        _.chain(store.items)
          .each(function(item) {
              filters.length > 0 ? item.visible = filterFn(item) : item.visible = true;
          });
      });

      $timeout(dimStoreService.setHeights, 32);
    };

    /**
     * Filter translation sets. Left-hand is the filter to run from filterFns, right side are possible filterResult
     * values that will set the left-hand to the "match."
     */
    var filterTrans = {
      'dmg':          ['arc', 'solar', 'void', 'kinetic'],
      'type':         ['primary', 'special', 'heavy', 'helmet', 'leg', 'gauntlets', 'chest', 'class', 'classitem'],
      'tier':         ['common', 'uncommon', 'rare', 'legendary', 'exotic'],
      'incomplete':   ['incomplete'],
      'complete':     ['complete'],
      'xpcomplete':   ['xpcomplete'],
      'xpincomplete': ['xpincomplete'],
      'upgraded':     ['upgraded'],
      'classType':    ['titan', 'hunter', 'warlock'],
      'dupe':         ['dupe', 'duplicate'],
      'unascended':   ['unascended', 'unassended', 'unasscended'],
      'ascended':     ['ascended', 'assended', 'asscended'],
      'locked':       ['locked'],
      'unlocked':     ['unlocked'],
      'stackable':    ['stackable'],
      'weaponClass':  ["pulserifle", "scoutrifle", "handcannon", "autorifle", "primaryweaponengram", "sniperrifle", "shotgun", "fusionrifle", "specialweaponengram", "rocketlauncher", "machinegun", "heavyweaponengram", "sidearm"]
    };

    // Cache for searches against filterTrans. Somewhat noticebly speeds up the lookup on my older Mac, YMMV. Helps
    // make the for(...) loop for filterTrans a little more bearable for the readability tradeoff.
    var _cachedFilters = {};

    /**
     * Filter groups keyed by type check. Key is what the user will search for, e.g.
     * is:complete
     *
     * Value is the checking function
     * @param {String} predicate The predicate - for example, is:arc gets the 'elemental' filter function, with predicate='arc'
     * @param {Object} item The item to test against.
     * @return {Boolean} Returns false for a match, true for a non-match (@TODO make this less confusing)
     */
    var filterFns = {
      'dmg': function(predicate, item) {
        return (item.dmg === predicate);
      },
      'type': function(predicate, item) {
        return (item.type.toLowerCase() === predicate);
      },
      'tier': function(predicate, item) {
        return (item.tier.toLowerCase() === predicate);
      },
      // @TODO This logic breaks my brain, I can't really reverse it tonight so just not(!)'ing it for now...cheap way
      // out I know. This applies to incomplete, complete, and upgraded
      'incomplete': function(predicate, item) {
        return !((item.complete !== true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class')) || ((item.xpComplete && item.hasXP) || (!item.hasXP)));
      },
      'complete': function(predicate, item) {
        return !(item.complete === false) || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class');
      },
      'upgraded': function(predicate, item) {
        return ((item.complete === true || (!item.primStat && item.type !== 'Class') || item.type === 'Vehicle' || (item.tier === 'Common' && item.type !== 'Class')) || ((!item.xpComplete && item.hasXP) || (!item.hasXP)));
      },
      'xpincomplete': function(predicate, item) {
        return item.hasXP && !item.xpComplete;
      },
      'xpcomplete': function(predicate, item) {
        return !item.hasXP || item.xpComplete;
      },
      'ascended': function(predicate, item) {
        return item.hasAscendNode && item.ascended;
      },
      'unascended': function(predicate, item) {
        return !filterFns.ascended(predicate, item);
      },
      'unlocked': function(predicate, item) {
        return item.lockable && !item.locked;
      },
      'locked': function(predicate, item) {
        return !filterFns.unlocked(predicate, item);
      },
      'dupe': function(predicate, item) {
        if (_duplicates === null) {
          _duplicates = _.chain(dimStoreService.getStores())
            .pluck('items')
            .flatten()
            .countBy('hash')
            .value();
        }

        return _duplicates[item.hash] > 1;
      },
      'classType': function(predicate, item) {
        var value;

        switch (predicate) {
          case 'titan':
            value = 0;
            break;
          case 'hunter':
            value = 1;
            break;
          case 'warlock':
            value = 2;
            break;
        }

        return (item.classType == value);
      },
      'stackable': function(predicate, item) {
        return item.maxStackSize > 1;
      },
      'weaponClass': function(predicate, item) {
        return predicate.toLowerCase().replace(/\s/g, '') == item.weaponClass;
      },
      'keyword': function(predicate, item){
        return !!~item.name.toLowerCase().indexOf(predicate);
      },
      'light': function(predicate, item){
        if (predicate.length === 0 || item.primStat == undefined) return false;

        var operands = ['<=','>=','=','>','<'];
        var operand = 'none';
        var result = false;

        operands.forEach(function(element) {
          if (predicate.substring(0,element.length) === element) {
            operand = element;
            predicate = predicate.substring(element.length);
            return false;
          }
        }, this);

        switch (operand) {
          case 'none':
            result = (item.primStat.value == predicate)
            break;
          case '=':
            result = (item.primStat.value == predicate)
            break;
          case '<':
            result = (item.primStat.value < predicate)
            break;
          case '<=':
            result = (item.primStat.value <= predicate)
            break;
          case '>':
            result = (item.primStat.value > predicate)
            break;
          case '>=':
            result = (item.primStat.value >= predicate)
            break;
        }
        return result;
      }
    };
  }
})();
<|endoftext|>


<commit_before>'use strict';

/**
 * @module portal-controllers
 */

/**
 * Provides route actions for all requests relative to statistics.
 *
 * @class StatisticsController
 */

const videoCache = process.require('/app/server/serverCache/VideoCache');

/**
 * Verify that a user has not seen the video more than one time during the video duration
 */
const userCanIncrease = (sesionID, entity, callback) => {
  videoCache.getVideoViewByUser(sesionID, entity.id, (error, value) => {

    if (error || value) {
      callback(false);
      return;
    }

    const duration = entity.metadata && entity.metadata.duration ? entity.metadata.duration : 7200;

    // user has not view this video
    // This user will not be allowed to increase view number during video duration or at least 2hours
    videoCache.setVideoViewByUser(sesionID, entity.id, duration);
    callback(true);
  });
};

/**
 * Increase video count when needed
 */
const increaseViews = (video, sesionID, callback) => {
  userCanIncrease(sesionID, video, (canIncrease) => {
    if (canIncrease)
      videoCache.addVideoView(video.id);
    callback();
  });
};

/**
 * Displays video player template.
 *
 * Checks first if the video id is valid and if the video is published
 * before returning the template.
 *
 * @method displayVideoAction
 * @static
 */
module.exports.statisticsAction = (request, response, next) => {

  switch (request.params.entity) {
    case 'video':
      switch (request.params.type) {
        case 'views':
          videoCache.getVideo(request.params.id, (error, entity) => {
            const video = entity.video;
            if (error) {
              next(error);
            } else if (video.private && !request.isAuthenticated())
              next();
            else
              increaseViews(video, request.sessionID, () => {
                response.send();
              });
          });
          break;
        default:
          next();
      }
      break;
    default:
      next();
  }
};
<commit_msg>Debug entity statistics callback<commit_after>'use strict';

/**
 * @module portal-controllers
 */

/**
 * Provides route actions for all requests relative to statistics.
 *
 * @class StatisticsController
 */

const videoCache = process.require('/app/server/serverCache/VideoCache');

/**
 * Verify that a user has not seen the video more than one time during the video duration
 */
const userCanIncrease = (sesionID, entity, callback) => {
  videoCache.getVideoViewByUser(sesionID, entity.id, (error, value) => {

    if (error || value) {
      callback(false);
      return;
    }

    const duration = entity.metadata && entity.metadata.duration ? entity.metadata.duration : 7200;

    // user has not view this video
    // This user will not be allowed to increase view number during video duration or at least 2hours
    videoCache.setVideoViewByUser(sesionID, entity.id, duration);
    callback(true);
  });
};

/**
 * Increase video count when needed
 */
const increaseViews = (video, sesionID, callback) => {
  userCanIncrease(sesionID, video, (canIncrease) => {
    if (canIncrease)
      videoCache.addVideoView(video.id);
    callback();
  });
};

/**
 * Displays video player template.
 *
 * Checks first if the video id is valid and if the video is published
 * before returning the template.
 *
 * @method displayVideoAction
 * @static
 */
module.exports.statisticsAction = (request, response, next) => {

  switch (request.params.entity) {
    case 'video':
      switch (request.params.type) {
        case 'views':
          videoCache.getVideo(request.params.id, (error, entity) => {
            const video = entity.entity;
            if (error) {
              next(error);
            } else if (video.private && !request.isAuthenticated())
              next();
            else
              increaseViews(video, request.sessionID, () => {
                response.send();
              });
          });
          break;
        default:
          next();
      }
      break;
    default:
      next();
  }
};
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import {Row, Col, FormGroup, FormControl, ControlLabel, Table, ListGroup, ListGroupItem, Button, ButtonToolbar} from 'react-bootstrap';
import Dropzone from 'react-dropzone';
import Utils from './utils/Functions';

import Attachment from './models/Attachment';
import SamplesFetcher from './fetchers/SamplesFetcher';
import AttachmentFetcher from './fetchers/AttachmentFetcher';
import Container from './models/Container';

export default class ContainerDataset extends Component {
  constructor(props) {
    super();
    let dataset_container = Object.assign({}, props.dataset_container);
    this.state = {
      dataset_container: dataset_container
    };
  }

  componentDidMount() {
    this.createAttachmentPreviews(this.state.dataset_container);
  }

  createAttachmentPreviews(dataset_container) {
    const { attachments } = dataset_container;
    let updatedAttachments = attachments.map((attachment) => {
      return attachment.thumb ? AttachmentFetcher.fetchThumbnail({id: attachment.id}).then((result) => {
        if(result != null) {
          attachment.preview = `data:image/png;base64,${result}`;
        }
        return attachment;
      }) : attachment;
    });

    Promise.all(updatedAttachments).then((attachments) => {
      dataset_container.attachments = attachments;

      this.setState({
        dataset_container: dataset_container
      });
    });
  }

 handleInputChange(type, event) {
    const {dataset_container} = this.state;
    const {value} = event.target;
    switch(type) {
      case 'name':
        dataset_container.name = value;
        break;
      case 'instrument':
        dataset_container.extended_metadata['instrument'] = value;
        break;
      case 'description':
        dataset_container.description = value;
        break;
    }
    this.setState({dataset_container});
  }

  handleFileDrop(files) {
    const {dataset_container} = this.state;

    let attachments = files.map(f => Attachment.fromFile(f))
    dataset_container.attachments = dataset_container.attachments.concat(attachments)
    let attachment_list = dataset_container.attachments
    let attach_name = attachment_list[attachment_list.length - 1].filename
    attach_name = attach_name.slice(0, -4)
    dataset_container.name = attach_name

    this.setState({dataset_container});
  }

  handleAttachmentDownload(attachment) {
      Utils.downloadFile({contents: `/api/v1/attachments/${attachment.id}`, name: attachment.filename});
  }

  handleAttachmentRemove(attachment) {
    const {dataset_container} = this.state;
    const index = dataset_container.attachments.indexOf(attachment);

    dataset_container.attachments[index].is_deleted = true;
    this.setState({dataset_container});
  }

  handleUndo(attachment) {
    const {dataset_container} = this.state;
    const index = dataset_container.attachments.indexOf(attachment);

    dataset_container.attachments[index].is_deleted = false;
    this.setState({dataset_container});
  }

  handleSave() {
    const {dataset_container} = this.state;
    const {onChange, onModalHide} = this.props;
    onChange(dataset_container);
    onModalHide();
  }

  listGroupItem(attachment){
    if(attachment.is_deleted){
      return(

              {attachment.filename}

            <Button bsSize="xsmall" bsStyle="danger" onClick={() => this.handleUndo(attachment)}>

            </Button>

      );
    }else{
      return(

               this.handleAttachmentDownload(attachment)} style={{cursor: 'pointer'}}>{attachment.filename}

              {this.removeAttachmentButton(attachment)}

      );
    }
  }

  attachments() {
    const {dataset_container} = this.state;
    if(dataset_container.attachments && dataset_container.attachments.length > 0) {
      return (
        <ListGroup>
        {dataset_container.attachments.map(attachment => {
          return (
            <ListGroupItem key={attachment.id}>
              {this.listGroupItem(attachment)}
            </ListGroupItem>
          )
        })}
        </ListGroup>
      )
    } else {
      return (

          There are currently no Datasets.

      )
    }
  }

  removeAttachmentButton(attachment) {
    const {readOnly} = this.props;
    if(!readOnly) {
      return (
        <Button bsSize="xsmall" bsStyle="danger" onClick={() => this.handleAttachmentRemove(attachment)}>

        </Button>
      );
    }
  }

  dropzone() {
    const {readOnly} = this.props;
    if(!readOnly) {
      return (
        <Dropzone
          onDrop={files => this.handleFileDrop(files)}
          style={{height: 50, width: '100%', border: '3px dashed lightgray'}}
          >

            Drop Files, or Click to Select.

        </Dropzone>
      );
    }
  }

  render() {
    const {dataset_container} = this.state;
    const {readOnly, onModalHide} = this.props;
    return (
      <Row>
        <Col md={6} style={{paddingRight: 0}}>
          <Col md={12} style={{padding: 0}}>
            <FormGroup controlId="datasetName">
              <ControlLabel>Name</ControlLabel>
              <FormControl
                type="text"
                value={dataset_container.name || ''}
                disabled={readOnly}
                onChange={event => this.handleInputChange('name', event)}
                />
            </FormGroup>

          </Col>
          <Col md={12} style={{padding: 0}}>
          <FormGroup controlId="datasetInstrument">
            <ControlLabel>Instrument</ControlLabel>
              <FormControl
                type="text"
                value={dataset_container.extended_metadata['instrument'] || ''}
                disabled={readOnly}
                onChange={event => this.handleInputChange('instrument', event)}
              />
            </FormGroup>
          </Col>
          <Col md={12} style={{padding: 0}}>
            <FormGroup controlId="datasetDescription">
              <ControlLabel>Description</ControlLabel>
              <FormControl
                componentClass="textarea"
                value={dataset_container.description || ''}
                disabled={readOnly}
                onChange={event => this.handleInputChange('description', event)}
                style={{minHeight: 100}}
              />
            </FormGroup>
          </Col>
        </Col>
        <Col md={6}>
          Attachments
          {this.attachments()}
          {this.dropzone()}
        </Col>
        <Col md={12}>
          <ButtonToolbar>
            <Button bsStyle="primary" onClick={() => onModalHide()}>Close</Button>
            <Button bsStyle="warning" onClick={() => this.handleSave()}>Save</Button>
          </ButtonToolbar>
        </Col>
      </Row>
    );
  }
}
<commit_msg>Dataset renaming on first attachment<commit_after>import React, {Component} from 'react';
import {Row, Col, FormGroup, FormControl, ControlLabel, Table, ListGroup, ListGroupItem, Button, ButtonToolbar} from 'react-bootstrap';
import Dropzone from 'react-dropzone';
import Utils from './utils/Functions';

import Attachment from './models/Attachment';
import SamplesFetcher from './fetchers/SamplesFetcher';
import AttachmentFetcher from './fetchers/AttachmentFetcher';
import Container from './models/Container';

export default class ContainerDataset extends Component {
  constructor(props) {
    super();
    let dataset_container = Object.assign({}, props.dataset_container);
    this.state = {
      dataset_container: dataset_container
    };
  }

  componentDidMount() {
    this.createAttachmentPreviews(this.state.dataset_container);
  }

  createAttachmentPreviews(dataset_container) {
    const { attachments } = dataset_container;
    let updatedAttachments = attachments.map((attachment) => {
      return attachment.thumb ? AttachmentFetcher.fetchThumbnail({id: attachment.id}).then((result) => {
        if(result != null) {
          attachment.preview = `data:image/png;base64,${result}`;
        }
        return attachment;
      }) : attachment;
    });

    Promise.all(updatedAttachments).then((attachments) => {
      dataset_container.attachments = attachments;

      this.setState({
        dataset_container: dataset_container
      });
    });
  }

 handleInputChange(type, event) {
    const {dataset_container} = this.state;
    const {value} = event.target;
    switch(type) {
      case 'name':
        dataset_container.name = value;
        break;
      case 'instrument':
        dataset_container.extended_metadata['instrument'] = value;
        break;
      case 'description':
        dataset_container.description = value;
        break;
    }
    this.setState({dataset_container});
  }

  handleFileDrop(files) {
    const {dataset_container} = this.state;

    let attachments = files.map(f => Attachment.fromFile(f))
    let first_attach = dataset_container.attachments.length == 0
    dataset_container.attachments = dataset_container.attachments.concat(attachments)

    if (first_attach) {
      let attachment_list = dataset_container.attachments
      let attach_name = attachment_list[attachment_list.length - 1].filename
      let splitted = attach_name.split(".")
      splitted.splice(-1, 1)
      attach_name = splitted.join(".")
      dataset_container.name = attach_name
    }

    this.setState({dataset_container});
  }

  handleAttachmentDownload(attachment) {
      Utils.downloadFile({contents: `/api/v1/attachments/${attachment.id}`, name: attachment.filename});
  }

  handleAttachmentRemove(attachment) {
    const {dataset_container} = this.state;
    const index = dataset_container.attachments.indexOf(attachment);

    dataset_container.attachments[index].is_deleted = true;
    this.setState({dataset_container});
  }

  handleUndo(attachment) {
    const {dataset_container} = this.state;
    const index = dataset_container.attachments.indexOf(attachment);

    dataset_container.attachments[index].is_deleted = false;
    this.setState({dataset_container});
  }

  handleSave() {
    const {dataset_container} = this.state;
    const {onChange, onModalHide} = this.props;
    onChange(dataset_container);
    onModalHide();
  }

  listGroupItem(attachment){
    if(attachment.is_deleted){
      return(

              {attachment.filename}

            <Button bsSize="xsmall" bsStyle="danger" onClick={() => this.handleUndo(attachment)}>

            </Button>

      );
    }else{
      return(

               this.handleAttachmentDownload(attachment)} style={{cursor: 'pointer'}}>{attachment.filename}

              {this.removeAttachmentButton(attachment)}

      );
    }
  }

  attachments() {
    const {dataset_container} = this.state;
    if(dataset_container.attachments && dataset_container.attachments.length > 0) {
      return (
        <ListGroup>
        {dataset_container.attachments.map(attachment => {
          return (
            <ListGroupItem key={attachment.id}>
              {this.listGroupItem(attachment)}
            </ListGroupItem>
          )
        })}
        </ListGroup>
      )
    } else {
      return (

          There are currently no Datasets.

      )
    }
  }

  removeAttachmentButton(attachment) {
    const {readOnly} = this.props;
    if(!readOnly) {
      return (
        <Button bsSize="xsmall" bsStyle="danger" onClick={() => this.handleAttachmentRemove(attachment)}>

        </Button>
      );
    }
  }

  dropzone() {
    const {readOnly} = this.props;
    if(!readOnly) {
      return (
        <Dropzone
          onDrop={files => this.handleFileDrop(files)}
          style={{height: 50, width: '100%', border: '3px dashed lightgray'}}
          >

            Drop Files, or Click to Select.

        </Dropzone>
      );
    }
  }

  render() {
    const {dataset_container} = this.state;
    const {readOnly, onModalHide} = this.props;
    return (
      <Row>
        <Col md={6} style={{paddingRight: 0}}>
          <Col md={12} style={{padding: 0}}>
            <FormGroup controlId="datasetName">
              <ControlLabel>Name</ControlLabel>
              <FormControl
                type="text"
                value={dataset_container.name || ''}
                disabled={readOnly}
                onChange={event => this.handleInputChange('name', event)}
                />
            </FormGroup>

          </Col>
          <Col md={12} style={{padding: 0}}>
          <FormGroup controlId="datasetInstrument">
            <ControlLabel>Instrument</ControlLabel>
              <FormControl
                type="text"
                value={dataset_container.extended_metadata['instrument'] || ''}
                disabled={readOnly}
                onChange={event => this.handleInputChange('instrument', event)}
              />
            </FormGroup>
          </Col>
          <Col md={12} style={{padding: 0}}>
            <FormGroup controlId="datasetDescription">
              <ControlLabel>Description</ControlLabel>
              <FormControl
                componentClass="textarea"
                value={dataset_container.description || ''}
                disabled={readOnly}
                onChange={event => this.handleInputChange('description', event)}
                style={{minHeight: 100}}
              />
            </FormGroup>
          </Col>
        </Col>
        <Col md={6}>
          Attachments
          {this.attachments()}
          {this.dropzone()}
        </Col>
        <Col md={12}>
          <ButtonToolbar>
            <Button bsStyle="primary" onClick={() => onModalHide()}>Close</Button>
            <Button bsStyle="warning" onClick={() => this.handleSave()}>Save</Button>
          </ButtonToolbar>
        </Col>
      </Row>
    );
  }
}
<|endoftext|>