From bf2eae079ceac6b2049b15a13bcc4b43415b17d2 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Thu, 18 Aug 2016 19:35:36 -0700 Subject: [PATCH] Improved error handling and added save button --- ..._add_unique_publishedmedia_project_name.py | 14 +++-- ide/models/published_media.py | 4 +- ide/static/ide/css/ide.css | 4 +- ide/static/ide/js/live_settings_form.js | 4 +- ide/static/ide/js/published_media.js | 53 +++++++++++++------ .../ide/project/published_media.html | 9 +++- 6 files changed, 61 insertions(+), 27 deletions(-) diff --git a/ide/migrations/0051_auto__add_publishedmedia__add_unique_publishedmedia_project_name.py b/ide/migrations/0051_auto__add_publishedmedia__add_unique_publishedmedia_project_name.py index 08215bfd..80665d09 100644 --- a/ide/migrations/0051_auto__add_publishedmedia__add_unique_publishedmedia_project_name.py +++ b/ide/migrations/0051_auto__add_publishedmedia__add_unique_publishedmedia_project_name.py @@ -25,8 +25,14 @@ def forwards(self, orm): # Adding unique constraint on 'PublishedMedia', fields ['project', 'name'] db.create_unique(u'ide_publishedmedia', ['project_id', 'name']) + # Adding unique constraint on 'PublishedMedia', fields ['project', 'media_id'] + db.create_unique(u'ide_publishedmedia', ['project_id', 'media_id']) + def backwards(self, orm): + # Removing unique constraint on 'PublishedMedia', fields ['project', 'media_id'] + db.delete_unique(u'ide_publishedmedia', ['project_id', 'media_id']) + # Removing unique constraint on 'PublishedMedia', fields ['project', 'name'] db.delete_unique(u'ide_publishedmedia', ['project_id', 'name']) @@ -78,7 +84,7 @@ def backwards(self, orm): 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'builds'", 'to': "orm['ide.Project']"}), 'started': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'db_index': 'True', 'blank': 'True'}), 'state': ('django.db.models.fields.IntegerField', [], {'default': '1'}), - 'uuid': ('django.db.models.fields.CharField', [], {'default': "'8b836ddd-8bc8-4884-bdda-824481f7f05c'", 'max_length': '36'}) + 'uuid': ('django.db.models.fields.CharField', [], {'default': "'f59cc0ab-c224-4308-9401-e2c34ae88798'", 'max_length': '36'}) }, 'ide.buildsize': { 'Meta': {'object_name': 'BuildSize'}, @@ -111,7 +117,7 @@ def backwards(self, orm): 'app_modern_multi_js': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'app_platforms': ('django.db.models.fields.TextField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), 'app_short_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), - 'app_uuid': ('django.db.models.fields.CharField', [], {'default': "'488507d8-f7d8-449e-b2d9-36c05ba88d4c'", 'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'app_uuid': ('django.db.models.fields.CharField', [], {'default': "'2a0eca7f-7b2b-49dd-a5f8-ae6a482b8c4a'", 'max_length': '36', 'null': 'True', 'blank': 'True'}), 'app_version_label': ('django.db.models.fields.CharField', [], {'default': "'1.0'", 'max_length': '40', 'null': 'True', 'blank': 'True'}), 'github_branch': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), 'github_hook_build': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), @@ -129,7 +135,7 @@ def backwards(self, orm): 'sdk_version': ('django.db.models.fields.CharField', [], {'default': "'2'", 'max_length': '6'}) }, 'ide.publishedmedia': { - 'Meta': {'unique_together': "(('project', 'name'),)", 'object_name': 'PublishedMedia'}, + 'Meta': {'unique_together': "(('project', 'name'), ('project', 'media_id'))", 'object_name': 'PublishedMedia'}, 'glance': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), 'has_timeline': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), @@ -199,7 +205,7 @@ def backwards(self, orm): 'theme': ('django.db.models.fields.CharField', [], {'default': "'cloudpebble'", 'max_length': '50'}), 'use_spaces': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'user': ('django.db.models.fields.related.OneToOneField', [], {'to': u"orm['auth.User']", 'unique': 'True', 'primary_key': 'True'}), - 'whats_new': ('django.db.models.fields.PositiveIntegerField', [], {'default': '23'}) + 'whats_new': ('django.db.models.fields.PositiveIntegerField', [], {'default': '24'}) } } diff --git a/ide/models/published_media.py b/ide/models/published_media.py index 34fad657..2ab62c0a 100644 --- a/ide/models/published_media.py +++ b/ide/models/published_media.py @@ -50,8 +50,10 @@ def clean(self): raise ValidationError(_("If glance and timeline.tiny are both used, they must be identical.")) if self.has_timeline and not (self.timeline_tiny and self.timeline_small and self.timeline_large): raise ValidationError(_("If timeline icons are enabled, they must all be set.")) + if not self.glance and not self.has_timeline: + raise ValidationError(_("Glance and Timeline cannot both be unset.")) if self.media_id < 0: raise ValidationError(_("Published Media IDs cannot be negative.")) class Meta(IdeModel.Meta): - unique_together = (('project', 'name'),) + unique_together = (('project', 'name'), ('project', 'media_id')) diff --git a/ide/static/ide/css/ide.css b/ide/static/ide/css/ide.css index 88b5b7ec..47f6ad40 100644 --- a/ide/static/ide/css/ide.css +++ b/ide/static/ide/css/ide.css @@ -530,8 +530,9 @@ table.kv-table .kv-remove { .compilation-pane #run-on-phone .btn, .compilation-pane #run-on-qemu .btn, .compilation-pane .build-buttons .btn { - width: 137px; + min-width: 137px; margin: 0 4px; + wdith: initial; } .compilation-pane #run-on-phone .btn:last-of-type, @@ -684,6 +685,7 @@ table.build-results tr.pending .build-state { .media-tool-buttons button { width: initial; + min-width: 130px; margin-right: 10px; } diff --git a/ide/static/ide/js/live_settings_form.js b/ide/static/ide/js/live_settings_form.js index 1d972277..56908c05 100644 --- a/ide/static/ide/js/live_settings_form.js +++ b/ide/static/ide/js/live_settings_form.js @@ -234,8 +234,8 @@ function make_live_settings_form(options) { init: function() { init(); }, - save: function(element) { - return save(element); + save: function(element, event) { + return save(element, event); } }; } diff --git a/ide/static/ide/js/published_media.js b/ide/static/ide/js/published_media.js index 8ba0fbc0..95c5afb3 100644 --- a/ide/static/ide/js/published_media.js +++ b/ide/static/ide/js/published_media.js @@ -24,6 +24,7 @@ CloudPebble.PublishedMedia = (function() { var pane; this.show_error = function show_error(error) { pane.find('.alert-error').removeClass('hide').text(error); + $('#main-pane').animate({scrollTop: 0}) }; this.hide_error = function hide_error() { pane.find('.alert-error').addClass('hide'); @@ -59,7 +60,7 @@ CloudPebble.PublishedMedia = (function() { function MediaItem() { var self = this; var deleting = false; - var item_form = media_template.find('form'); + var item_form = media_template.find('#media-items'); var item = item_template.clone().appendTo(item_form).data('item', this); var name_input = item.find('.edit-media-name'); var id_input = item.find('.edit-media-id'); @@ -226,10 +227,6 @@ CloudPebble.PublishedMedia = (function() { if (!_.every(names)) { throw new Error(gettext('Identifiers cannot be blank')); } - // Check that all IDs are unique - if (_.max(_.countBy(data, 'id')) > 1) { - throw new Error(gettext('Numeric IDs must be unique')); - } // Check that all identifiers are valid _.each(names, function(name) { if (!REGEXES.c_identifier.test(name)) { @@ -238,8 +235,7 @@ CloudPebble.PublishedMedia = (function() { }); return Ajax.Post('/ide/project/' + PROJECT_ID + '/save_published_media', { 'published_media': JSON.stringify(data) - }).then(function(result) { - // TODO: use 'result' or not? + }).then(function() { CloudPebble.ProjectInfo.published_media = data; sync_with_ycm(); return null; @@ -247,24 +243,42 @@ CloudPebble.PublishedMedia = (function() { } /** Save the whole form. If any names are incomplete or resources are invalid, it simply refuses to save without error. */ - function save_forms() { - var items = get_media_items(); + function save_forms(event) { var data = get_form_data(); + var do_cancel = event.type != 'submit'; + var items = get_media_items(); var identifiers = get_eligible_identifiers(); + function maybe_error(text) { + if (do_cancel) return {incomplete: true}; + throw new Error(text); + } // If not all items have names, cancel saving without displaying an error if (!_.every(_.pluck(data, 'name'))) { - return {incomplete: true}; + return maybe_error(gettext("Published Media must have non-empty identifiers.")) + } + + // Cancel if there are any incomplete items + if (!_.every(_.map(data, function(item) { + return !!item.timeline || !!item.glance; + }))) { + return maybe_error(gettext("Published Media items must specify glance, timeline icons, or both.")) } - // Raise an error if there are any invalid selections + + // Check that all IDs are unique + if (_.max(_.countBy(data, 'id')) > 1) { + return maybe_error(gettext("Published Media IDs must be unique.")) + } + + // Cancel (and show the 'invalid items' icon in the sidebar) if there are any invalid values var validity = _.map(items, function(item) {return item.is_valid(identifiers);}); if (!_.every(validity)) { toggle_sidebar_error(true); - return {incomplete: true}; + return maybe_error(gettext("You cannot save Published Media items with references to resuorces which do not exist.")) } - // If we successfully saved, it implies that there were no invalid references - // so we can get rid of the sidebar error notification. return save_pubished_media(data).then(function() { + // If we successfully saved, it implies that there were no invalid references + // so we can get rid of the sidebar error notification. toggle_sidebar_error(false); }); } @@ -274,12 +288,17 @@ CloudPebble.PublishedMedia = (function() { if (media_pane_setup) return false; media_pane_setup = true; - var initial_data = CloudPebble.ProjectInfo.published_media; - _.each(initial_data, function(data) { + // Set up the data + _.each(CloudPebble.ProjectInfo.published_media, function(data) { var item = new MediaItem(); item.setData(data); }); + media_template.find('form').submit(function(e) { + live_form.save(null, e); + return false; + }); + media_template.find('#add-published-media').click(function() { new MediaItem(); }); @@ -290,7 +309,7 @@ CloudPebble.PublishedMedia = (function() { error_function: alerts.show_error, on_progress_started: alerts.show_progress, on_progress_complete: alerts.hide_progress, - form: media_template.find('form') + form: media_template.find('#media-items') }); media_template.find('.media-tool-buttons').removeClass('hide'); media_template.find('.media-pane-loading').remove(); diff --git a/ide/templates/ide/project/published_media.html b/ide/templates/ide/project/published_media.html index 0de96ff2..6305df3b 100644 --- a/ide/templates/ide/project/published_media.html +++ b/ide/templates/ide/project/published_media.html @@ -2,18 +2,23 @@
-
-
+
+
+

{% trans 'Loading...' %}

+
+