From: Calin Crisan Date: Sat, 28 Sep 2013 14:47:23 +0000 (+0300) Subject: added an apply button X-Git-Url: http://www.vanbest.org/gitweb/?a=commitdiff_plain;h=2af2b90353090d8fb32fdd8d33ef717a0626c318;p=motioneye-debian added an apply button --- diff --git a/doc/todo.txt b/doc/todo.txt index 9fd65dc..c5af23a 100644 --- a/doc/todo.txt +++ b/doc/todo.txt @@ -1,6 +1,4 @@ --> add complete js validation -> group @config rules to top --> notification: multiple email addresses -> what do we do with working schedule -> browser compatibility test -> hint text next to section titles diff --git a/src/config.py b/src/config.py index 9d67eab..c5c1923 100644 --- a/src/config.py +++ b/src/config.py @@ -69,6 +69,14 @@ def set_main(data): # read the actual configuration from file lines = get_main(as_lines=True) + # preserve the threads + if 'thread' not in data: + threads = data.setdefault('thread', []) + for line in lines: + match = re.match('^\s*thread\s+([a-zA-Z0-9.\-]+)', line) + if match: + threads.append(match.groups()[0]) + # write the configuration to file logging.debug('writing main config to %(path)s...' % {'path': _MAIN_CONFIG_FILE_PATH}) @@ -186,11 +194,7 @@ def set_camera(camera_id, data): elif not data['@enabled']: threads = [t for t in threads if t != config_file_name] - if len(threads): - main_config['thread'] = threads - - elif 'thread' in main_config: - del main_config['thread'] + main_config['thread'] = threads set_main(main_config) @@ -274,11 +278,7 @@ def rem_camera(camera_id): threads = main_config.setdefault('thread', []) threads = [t for t in threads if t != camera_config_name] - if len(threads): - main_config['thread'] = threads - - elif 'thread' in main_config: - del main_config['thread'] + main_config['thread'] = threads set_main(main_config) diff --git a/src/handlers.py b/src/handlers.py index b509729..2278593 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -69,8 +69,8 @@ class ConfigHandler(BaseHandler): else: logging.debug('getting main config') - # TODO _main_dict_to_ui - self.finish_json(config.get_main()) + ui_config = self._main_dict_to_ui(config.get_main()) + self.finish_json(ui_config) def set_config(self, camera_id): try: @@ -102,7 +102,7 @@ class ConfigHandler(BaseHandler): raise - # TODO _main_ui_to_dict + data = self._main_ui_to_dict(data) config.set_main(data) def list_cameras(self): @@ -130,6 +130,26 @@ class ConfigHandler(BaseHandler): logging.debug('removing camera %(id)s' % {'id': camera_id}) config.rem_camera(camera_id) + + def _main_ui_to_dict(self, ui): + return { + '@enabled': ui.get('enabled', True), + '@show_advanced': ui.get('show_advanced', False), + '@admin_username': ui.get('admin_username', ''), + '@admin_password': ui.get('admin_password', ''), + '@normal_username': ui.get('normal_username', ''), + '@normal_password': ui.get('normal_password', '') + } + + def _main_dict_to_ui(self, data): + return { + 'enabled': data.get('@enabled', True), + 'show_advanced': data.get('@show_advanced', False), + 'admin_username': data.get('@admin_username', ''), + 'admin_password': data.get('@admin_password', ''), + 'normal_username': data.get('@normal_username', ''), + 'normal_password': data.get('@normal_password', '') + } def _camera_ui_to_dict(self, ui): video_device = ui.get('device', '') diff --git a/static/css/base-site.css b/static/css/base-site.css index ed8662c..92ffcb0 100644 --- a/static/css/base-site.css +++ b/static/css/base-site.css @@ -42,7 +42,7 @@ div.page { margin-top: 50px; padding-bottom: 20px; font-size: 1em; - transition: all 0.5s; + transition: all 0.5s linear; } div.header { @@ -56,7 +56,7 @@ div.header { } div.header-container { - transition: all 0.5s; + transition: all 0.5s linear; } div.footer { @@ -70,7 +70,7 @@ div.footer { } div.page-container { - transition: all 0.2s; + transition: all 0.2s linear; padding: 5px; } @@ -89,7 +89,7 @@ div.settings { left: 0px; width: 0px; height: 100%; - transition: all 0.2s; + transition: all 0.2s linear; overflow: auto; } @@ -110,6 +110,7 @@ div.settings.open div.settings-container { } div.settings-top-bar { + position: relative; display: inline-block; width: 40%; height: 50px; @@ -165,6 +166,37 @@ select.video-device { vertical-align: middle; font-size: 20px; width: auto; + max-width: 40%; +} + +div.apply-button { + position: relative; + display: none; + opacity: 0; + float: right; + width: 80px; + height: 30px; + line-height: 30px; + text-align: center; + margin: 10px; + color: white; + font-weight: bold; + font-size: 17px; + background-color: #FF6F00; + border-radius: 3px; + transition: all 0.1s linear; +} + +div.apply-button:HOVER { + background-color: #FF7D19; +} + +div.apply-button:ACTIVE { + background-color: #F06800; +} + +div.apply-button.progress { + background-color: #FF9340; } div.settings-top-bar.open select.video-device { diff --git a/static/css/ui.css b/static/css/ui.css index 78bb79f..97afe28 100644 --- a/static/css/ui.css +++ b/static/css/ui.css @@ -18,6 +18,16 @@ input[type=checkbox].styled { } + /* button */ + +div.button { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + cursor: pointer; +} + + /* check box */ div.check-box { diff --git a/static/img/progress.gif b/static/img/progress.gif new file mode 100644 index 0000000..12ccd9b Binary files /dev/null and b/static/img/progress.gif differ diff --git a/static/js/base-site.js b/static/js/base-site.js index 58e55d0..378a4cf 100644 --- a/static/js/base-site.js +++ b/static/js/base-site.js @@ -1,6 +1,5 @@ - -var noPushLock = 0; +var pushConfigs = {}; /* Ajax */ @@ -66,6 +65,23 @@ function initUI() { makeSlider($('#frameChangeThresholdSlider'), 0, 10000, 0, null, 3, 0, 'px'); makeSlider($('#noiseLevelSlider'), 0, 100, 0, null, 5, 0, '%'); + /* text validators */ + makeTextValidator($('#adminUsernameEntry'), true); + makeTextValidator($('#adminPasswordEntry'), true); + makeTextValidator($('#normalUsernameEntry'), true); + makeTextValidator($('#normalPasswordEntry'), true); + makeTextValidator($('#deviceNameEntry'), true); + makeTextValidator($('#networkServerEntry'), true); + makeTextValidator($('#networkShareNameEntry'), true); + makeTextValidator($('#networkUsernameEntry'), false); + makeTextValidator($('#networkPasswordEntry'), false); + makeTextValidator($('#rootDirectoryEntry'), true); + makeTextValidator($('#leftTextEntry'), true); + makeTextValidator($('#rightTextEntry'), true); + makeTextValidator($('#imageFileNameEntry'), true); + makeTextValidator($('#movieFileNameEntry'), true); + makeTextValidator($('#emailAddressesEntry'), true); + /* number validators */ makeNumberValidator($('#streamingPortEntry'), 1024, 65535, false, false, true); makeNumberValidator($('#snapshotIntervalEntry'), 1, 86400, false, false, true); @@ -90,21 +106,21 @@ function initUI() { makeTimeValidator($('#sundayTo')); /* ui elements that enable/disable other ui elements */ - $('#motionEyeSwitch').change(updateConfigUI); - $('#showAdvancedSwitch').change(updateConfigUI); - $('#storageDeviceSelect').change(updateConfigUI); - $('#autoBrightnessSwitch').change(updateConfigUI); - $('#leftTextSelect').change(updateConfigUI); - $('#rightTextSelect').change(updateConfigUI); - $('#captureModeSelect').change(updateConfigUI); - $('#autoNoiseDetectSwitch').change(updateConfigUI); - $('#videoDeviceSwitch').change(updateConfigUI); - $('#textOverlaySwitch').change(updateConfigUI); - $('#videoStreamingSwitch').change(updateConfigUI); - $('#stillImagesSwitch').change(updateConfigUI); - $('#motionMoviesSwitch').change(updateConfigUI); - $('#motionNotificationsSwitch').change(updateConfigUI); - $('#workingScheduleSwitch').change(updateConfigUI); + $('#motionEyeSwitch').change(updateConfigUi); + $('#showAdvancedSwitch').change(updateConfigUi); + $('#storageDeviceSelect').change(updateConfigUi); + $('#autoBrightnessSwitch').change(updateConfigUi); + $('#leftTextSelect').change(updateConfigUi); + $('#rightTextSelect').change(updateConfigUi); + $('#captureModeSelect').change(updateConfigUi); + $('#autoNoiseDetectSwitch').change(updateConfigUi); + $('#videoDeviceSwitch').change(updateConfigUi); + $('#textOverlaySwitch').change(updateConfigUi); + $('#videoStreamingSwitch').change(updateConfigUi); + $('#stillImagesSwitch').change(updateConfigUi); + $('#motionMoviesSwitch').change(updateConfigUi); + $('#motionNotificationsSwitch').change(updateConfigUi); + $('#workingScheduleSwitch').change(updateConfigUi); /* fetch & push handlers */ $('#videoDeviceSelect').change(fetchCameraConfig); @@ -118,11 +134,18 @@ function initUI() { 'input.motion-detection, select.motion-detection, ' + 'input.notifications, select.notifications, ' + 'input.working-schedule, select.working-schedule').change(pushCameraConfig); + + /* apply button */ + $('#applyButton').click(function () { + if ($(this).hasClass('progress')) { + return; /* in progress */ + } + + doApply(); + }); } -function updateConfigUI() { - noPushLock++; - +function updateConfigUi() { var objs = $('tr.settings-item, div.advanced-setting, table.advanced-setting, div.settings-section-title, table.settings'); function markHide() { @@ -223,7 +246,7 @@ function updateConfigUI() { }); /* re-validate all the input validators */ - $('div.settings').find('input.number-validator, input.time-validator').each(function () { + $('div.settings').find('input.text-validator, input.number-validator, input.time-validator').each(function () { this.validate(); }); @@ -238,34 +261,40 @@ function updateConfigUI() { this.selectedIndex = 0; } }); +} + +function configUiValid() { + var valid = true; + $('div.settings input, select').each(function () { + if (this.invalid) { + valid = false; + return false; + } + }); - noPushLock--; + return valid; } function mainUi2Dict() { return { - '@enabled': $('#motionEyeSwitch')[0].checked, - '@show_advanced': $('#showAdvancedSwitch')[0].checked, - '@admin_username': $('#adminUsernameEntry').val(), - '@admin_password': $('#adminPasswordEntry').val(), - '@normal_username': $('#normalUsernameEntry').val(), - '@normal_password': $('#normalPasswordEntry').val() + 'enabled': $('#motionEyeSwitch')[0].checked, + 'show_advanced': $('#showAdvancedSwitch')[0].checked, + 'admin_username': $('#adminUsernameEntry').val(), + 'admin_password': $('#adminPasswordEntry').val(), + 'normal_username': $('#normalUsernameEntry').val(), + 'normal_password': $('#normalPasswordEntry').val() }; } function dict2MainUi(dict) { - noPushLock++; - - $('#motionEyeSwitch')[0].checked = dict['@enabled']; - $('#showAdvancedSwitch')[0].checked = dict['@show_advanced']; - $('#adminUsernameEntry').val(dict['@admin_username']); - $('#adminPasswordEntry').val(dict['@admin_password']); - $('#normalUsernameEntry').val(dict['@normal_username']); - $('#normalPasswordEntry').val(dict['@normal_password']); - - updateConfigUI(); - - noPushLock--; + $('#motionEyeSwitch')[0].checked = dict['enabled']; + $('#showAdvancedSwitch')[0].checked = dict['show_advanced']; + $('#adminUsernameEntry').val(dict['admin_username']); + $('#adminPasswordEntry').val(dict['admin_password']); + $('#normalUsernameEntry').val(dict['normal_username']); + $('#normalPasswordEntry').val(dict['normal_password']); + + updateConfigUi(); } function cameraUi2Dict() { @@ -353,8 +382,6 @@ function cameraUi2Dict() { } function dict2CameraUi(dict) { - noPushLock++; - /* video device */ $('#videoDeviceSwitch')[0].checked = dict['enabled']; $('#deviceNameEntry').val(dict['name']); @@ -435,9 +462,98 @@ function dict2CameraUi(dict) { $('#sundayFrom').val(dict['sunday_from']); $('#sundayTo').val(dict['sunday_to']); - updateConfigUI(); + updateConfigUi(); +} + + + /* apply button */ + +function showApply() { + if (!$('div.settings-container').is(':visible')) { + return; /* settings panel is not open */ + } + + var applyButton = $('#applyButton'); + + applyButton.html('Apply'); + applyButton.css('display', 'inline-block'); + applyButton.animate({'opacity': '1'}, 100); + applyButton.removeClass('inactive'); +} + +function showProgress() { + if (!$('div.settings-container').is(':visible')) { + return; /* settings panel is not open */ + } + + var applyButton = $('#applyButton'); + + if (applyButton.hasClass('progress')) { + return; /* progress already visible */ + } - noPushLock--; + applyButton.html(''); + applyButton.css('display', 'inline-block'); + applyButton.animate({'opacity': '1'}, 100); + applyButton.addClass('progress'); +} + +function hideApply() { + if (!$('div.settings-container').is(':visible')) { + return; /* settings panel is not open */ + } + + var applyButton = $('#applyButton'); + + applyButton.animate({'opacity': '0'}, 200, function () { + applyButton.removeClass('progress'); + applyButton.css('display', 'none'); + }); +} + +function isProgress() { + var applyButton = $('#applyButton'); + + return applyButton.hasClass('progress'); +} + +function isApplyVisible() { + var applyButton = $('#applyButton'); + + return applyButton.is(':visible'); +} + +function doApply() { + var finishedCount = 0; + var configs = []; + + function testReady() { + if (finishedCount >= configs.length) { + hideApply(); + } + } + + for (var key in pushConfigs) { + if (pushConfigs.hasOwnProperty(key)) { + configs.push({key: key, config: pushConfigs[key]}); + } + } + + if (configs.length === 0) { + return; + } + + showProgress(); + + for (var i = 0; i < configs.length; i++) { + var config = configs[i]; + ajax('POST', '/config/' + config.key + '/set/', config.config, function () { + finishedCount++; + testReady(); + }); + } + + pushConfigs = {}; } function fetchCurrentConfig() { @@ -476,32 +592,22 @@ function fetchCameraConfig() { } function pushMainConfig() { - if (noPushLock) { - return; - } - - noPushLock++; - var mainConfig = mainUi2Dict(); - ajax('POST', '/config/main/set/', mainConfig, function () { - noPushLock--; - }); + pushConfigs['main'] = mainConfig; + if (!isApplyVisible()) { + showApply(); + } } function pushCameraConfig() { - if (noPushLock) { - return; - } - - noPushLock++; - var cameraConfig = cameraUi2Dict(); var cameraId = $('#videoDeviceSelect').val(); - - ajax('POST', '/config/' + cameraId + '/set/', cameraConfig, function () { - noPushLock--; - }); + + pushConfigs[cameraId] = cameraConfig; + if (!isApplyVisible()) { + showApply(); + } } $(document).ready(function () { @@ -516,6 +622,8 @@ $(document).ready(function () { $('div.settings').addClass('open'); $('div.page-container').addClass('stretched'); $('div.settings-top-bar').addClass('open'); + + updateConfigUi(); } }); @@ -531,6 +639,5 @@ $(document).ready(function () { }); initUI(); - updateConfigUI(); fetchCurrentConfig(); }); diff --git a/static/js/ui.js b/static/js/ui.js index f9b8747..463fbe5 100644 --- a/static/js/ui.js +++ b/static/js/ui.js @@ -223,6 +223,47 @@ function makeSlider($input, minVal, maxVal, snapMode, ticks, ticksNumber, decima return slider; } +function makeTextValidator($input, required) { + if (required == null) { + required = true; + } + + function isValid(strVal) { + if (!$input.parents('tr:eq(0)').is(':visible')) { + return true; /* an invisible element is considered always valid */ + } + + if (strVal.length === 0 && required) { + return false; + } + + return true; + } + + var msg = 'this field is required'; + + function validate() { + var strVal = $input.val(); + if (isValid(strVal)) { + $input.attr('title', ''); + $input.removeClass('error'); + $input[0].invalid = false; + } + else { + $input.attr('title', msg); + $input.addClass('error'); + $input[0].invalid = true; + } + } + + $input.keyup(validate); + $input.blur(validate); + $input.change(validate).change(); + + $input.addClass('text-validator'); + $input[0].validate = validate; +} + function makeNumberValidator($input, minVal, maxVal, floating, sign, required) { if (minVal == null) { minVal = -Infinity; @@ -241,6 +282,10 @@ function makeNumberValidator($input, minVal, maxVal, floating, sign, required) { } function isValid(strVal) { + if (!$input.parents('tr:eq(0)').is(':visible')) { + return true; /* an invisible element is considered always valid */ + } + if (strVal.length === 0 && !required) { return true; } @@ -293,14 +338,17 @@ function makeNumberValidator($input, minVal, maxVal, floating, sign, required) { if (isValid(strVal)) { $input.attr('title', ''); $input.removeClass('error'); + $input[0].invalid = false; } else { $input.attr('title', msg); $input.addClass('error'); + $input[0].invalid = true; } } $input.keyup(validate); + $input.blur(validate); $input.change(validate).change(); $input.addClass('number-validator'); @@ -315,18 +363,25 @@ function makeTimeValidator($input) { var msg = 'enter a valid time in the following format: HH:MM'; function validate() { + if (!$input.parents('tr:eq(0)').is(':visible')) { + return true; /* an invisible element is considered always valid */ + } + var strVal = $input.val(); if (isValid(strVal)) { $input.attr('title', ''); $input.removeClass('error'); + $input[0].invalid = false; } else { $input.attr('title', msg); $input.addClass('error'); + $input[0].invalid = true; } } $input.keyup(validate); + $input.blur(validate); $input.change(validate).change(); $input.timepicker({ closeOnWindowScroll: true, diff --git a/templates/base-site.html b/templates/base-site.html index 138c010..6ac6e03 100644 --- a/templates/base-site.html +++ b/templates/base-site.html @@ -17,8 +17,8 @@
- + +
Apply