From de3d582c78d6afd797da81c68ddf486b0340b57a Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Tue, 6 Oct 2015 07:58:15 -0700 Subject: [PATCH 01/60] Schema and datamigration for Per-ID target_platforms --- ...eld_resourceidentifier_target_platforms.py | 167 ++++++++++++++++ ide/migrations/0040_fix_target_platforms.py | 183 ++++++++++++++++++ ide/models/files.py | 1 + 3 files changed, 351 insertions(+) create mode 100644 ide/migrations/0039_auto__add_field_resourceidentifier_target_platforms.py create mode 100644 ide/migrations/0040_fix_target_platforms.py diff --git a/ide/migrations/0039_auto__add_field_resourceidentifier_target_platforms.py b/ide/migrations/0039_auto__add_field_resourceidentifier_target_platforms.py new file mode 100644 index 00000000..0a9218b7 --- /dev/null +++ b/ide/migrations/0039_auto__add_field_resourceidentifier_target_platforms.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'ResourceIdentifier.target_platforms' + db.add_column(u'ide_resourceidentifier', 'target_platforms', + self.gf('django.db.models.fields.CharField')(default=None, max_length=30, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'ResourceIdentifier.target_platforms' + db.delete_column(u'ide_resourceidentifier', 'target_platforms') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ide.buildresult': { + 'Meta': {'object_name': 'BuildResult'}, + 'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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': "'836953de-ebc1-4af1-a2a6-1378bf9ad883'", 'max_length': '36'}) + }, + 'ide.buildsize': { + 'Meta': {'object_name': 'BuildSize'}, + 'binary_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sizes'", 'to': "orm['ide.BuildResult']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'resource_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'worker_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.project': { + 'Meta': {'object_name': 'Project'}, + 'app_capabilities': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'app_company_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'app_is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_shown_on_communication': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_watchface': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_jshint': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'app_keys': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'app_long_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': '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': "'191723ea-64fa-4e4f-beb2-13a9ed69705b'", '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'}), + 'github_hook_uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'github_last_commit': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'github_last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'github_repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'optimisation': ('django.db.models.fields.CharField', [], {'default': "'s'", 'max_length': '1'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'project_type': ('django.db.models.fields.CharField', [], {'default': "'native'", 'max_length': '10'}), + 'sdk_version': ('django.db.models.fields.CharField', [], {'default': "'2'", 'max_length': '6'}) + }, + 'ide.resourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'ResourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_menu_icon': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'kind': ('django.db.models.fields.CharField', [], {'max_length': '9'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'resources'", 'to': "orm['ide.Project']"}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}) + }, + 'ide.resourceidentifier': { + 'Meta': {'unique_together': "(('resource_file', 'resource_id'),)", 'object_name': 'ResourceIdentifier'}, + 'character_regex': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'compatibility': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['ide.ResourceFile']"}), + 'resource_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'tracking': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.resourcevariant': { + 'Meta': {'unique_together': "(('resource_file', 'tags'),)", 'object_name': 'ResourceVariant'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_legacy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variants'", 'to': "orm['ide.ResourceFile']"}), + 'tags': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'max_length': '50', 'blank': 'True'}) + }, + 'ide.sourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'SourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'folded_lines': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_files'", 'to': "orm['ide.Project']"}), + 'target': ('django.db.models.fields.CharField', [], {'default': "'app'", 'max_length': '10'}) + }, + 'ide.templateproject': { + 'Meta': {'object_name': 'TemplateProject', '_ormbases': ['ide.Project']}, + u'project_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ide.Project']", 'unique': 'True', 'primary_key': 'True'}), + 'template_kind': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) + }, + 'ide.usergithub': { + 'Meta': {'object_name': 'UserGithub'}, + 'avatar': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'github'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['auth.User']"}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}) + }, + 'ide.usersettings': { + 'Meta': {'object_name': 'UserSettings'}, + 'accepted_terms': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'autocomplete': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'keybinds': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}), + 'tab_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '2'}), + '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': '18'}) + } + } + + complete_apps = ['ide'] \ No newline at end of file diff --git a/ide/migrations/0040_fix_target_platforms.py b/ide/migrations/0040_fix_target_platforms.py new file mode 100644 index 00000000..7f696c3f --- /dev/null +++ b/ide/migrations/0040_fix_target_platforms.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +import json + +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + + +class Migration(DataMigration): + + def forwards(self, orm): + """For each ResourceVariant, set its target_platforms to its resource_file's target_platforms""" + for resource_identifier in orm.ResourceIdentifier.objects.all(): + resource_identifier.target_platforms = resource_identifier.resource_file.target_platforms + resource_identifier.save() + + + def backwards(self, orm): + """For each ResourceFile, set it to target all of the platforms that its resource identifiers target, + or null if none of its identifiers have targets.""" + for resource_file in orm.ResourceFile.objects.all(): + platforms = None + for resource_identifier in orm.ResourceIdentifier.objects.filter(resource_file=resource_file): + id_platforms = json.loads(resource_identifier.target_platforms) + if id_platforms is not None: + if platforms is None: + platforms = set() + platforms.update(id_platforms) + if platforms is not None: + resource_file.target_platforms = json.dumps(list(platforms)) + else: + resource_file.target_platforms = json.dumps(None) + resource_file.save() + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ide.buildresult': { + 'Meta': {'object_name': 'BuildResult'}, + 'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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': "'49b98229-7956-4c53-9a5d-469662504d21'", 'max_length': '36'}) + }, + 'ide.buildsize': { + 'Meta': {'object_name': 'BuildSize'}, + 'binary_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sizes'", 'to': "orm['ide.BuildResult']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'resource_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'worker_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.project': { + 'Meta': {'object_name': 'Project'}, + 'app_capabilities': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'app_company_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'app_is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_shown_on_communication': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_watchface': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_jshint': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'app_keys': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'app_long_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': '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': "'322a65a1-a1db-4310-9917-2240b761083b'", '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'}), + 'github_hook_uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'github_last_commit': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'github_last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'github_repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'optimisation': ('django.db.models.fields.CharField', [], {'default': "'s'", 'max_length': '1'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'project_type': ('django.db.models.fields.CharField', [], {'default': "'native'", 'max_length': '10'}), + 'sdk_version': ('django.db.models.fields.CharField', [], {'default': "'2'", 'max_length': '6'}) + }, + 'ide.resourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'ResourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_menu_icon': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'kind': ('django.db.models.fields.CharField', [], {'max_length': '9'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'resources'", 'to': "orm['ide.Project']"}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}) + }, + 'ide.resourceidentifier': { + 'Meta': {'unique_together': "(('resource_file', 'resource_id'),)", 'object_name': 'ResourceIdentifier'}, + 'character_regex': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'compatibility': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['ide.ResourceFile']"}), + 'resource_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'tracking': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.resourcevariant': { + 'Meta': {'unique_together': "(('resource_file', 'tags'),)", 'object_name': 'ResourceVariant'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_legacy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variants'", 'to': "orm['ide.ResourceFile']"}), + 'tags': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'max_length': '50', 'blank': 'True'}) + }, + 'ide.sourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'SourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'folded_lines': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_files'", 'to': "orm['ide.Project']"}), + 'target': ('django.db.models.fields.CharField', [], {'default': "'app'", 'max_length': '10'}) + }, + 'ide.templateproject': { + 'Meta': {'object_name': 'TemplateProject', '_ormbases': ['ide.Project']}, + u'project_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ide.Project']", 'unique': 'True', 'primary_key': 'True'}), + 'template_kind': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) + }, + 'ide.usergithub': { + 'Meta': {'object_name': 'UserGithub'}, + 'avatar': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'github'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['auth.User']"}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}) + }, + 'ide.usersettings': { + 'Meta': {'object_name': 'UserSettings'}, + 'accepted_terms': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'autocomplete': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'keybinds': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}), + 'tab_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '2'}), + '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': '18'}) + } + } + + complete_apps = ['ide'] + symmetrical = True diff --git a/ide/models/files.py b/ide/models/files.py index db9b7e25..03429c0f 100644 --- a/ide/models/files.py +++ b/ide/models/files.py @@ -219,6 +219,7 @@ class ResourceIdentifier(IdeModel): character_regex = models.CharField(max_length=100, blank=True, null=True) tracking = models.IntegerField(blank=True, null=True) compatibility = models.CharField(max_length=10, blank=True, null=True) + target_platforms = models.CharField(max_length=30, null=True, blank=True, default=None) def save(self, *args, **kwargs): self.resource_file.project.last_modified = now() From 68bdb2428da7c9793b32ae30e4ee86469042d125 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Tue, 6 Oct 2015 08:12:40 -0700 Subject: [PATCH 02/60] Interface and API support for per-ID target_platforms --- ide/api/resource.py | 30 +- ide/static/ide/css/ide.css | 8 +- ide/static/ide/js/resources.js | 395 ++++++++++++------------ ide/tasks/archive.py | 82 ++--- ide/templates/ide/project/resource.html | 71 +++-- ide/utils/sdk.py | 4 +- 6 files changed, 297 insertions(+), 293 deletions(-) diff --git a/ide/api/resource.py b/ide/api/resource.py index 845f76b4..d4095b76 100644 --- a/ide/api/resource.py +++ b/ide/api/resource.py @@ -30,14 +30,14 @@ def create_resource(request, project_id): for r in resource_ids: regex = r['regex'] if 'regex' in r else None tracking = int(r['tracking']) if 'tracking' in r else None + target_platforms = json.dumps(r['target_platforms']) if 'target_platforms' in r else None resources.append(ResourceIdentifier.objects.create(resource_file=rf, resource_id=r['id'], - character_regex=regex, tracking=tracking)) + character_regex=regex, tracking=tracking, + target_platforms=target_platforms)) if posted_file is not None: variant = ResourceVariant.objects.create(resource_file=rf, tags=",".join(str(int(t)) for t in new_tags)) variant.save_file(posted_file, posted_file.size) - target_platforms = json.loads(request.POST.get('target_platforms', None)) - rf.target_platforms = None if target_platforms is None else json.dumps(target_platforms) rf.save() except Exception as e: @@ -55,8 +55,11 @@ def create_resource(request, project_id): "id": rf.id, "kind": rf.kind, "file_name": rf.file_name, - "target_platforms": json.loads(rf.target_platforms) if rf.target_platforms else None, - "resource_ids": [{'id': x.resource_id, 'regex': x.character_regex} for x in resources], + "resource_ids": [{ + 'id': x.resource_id, + 'regex': x.character_regex, + 'target_platforms': json.loads(x.target_platforms) if x.target_platforms else None + } for x in resources], "identifiers": [x.resource_id for x in resources], "variants": [x.get_tags() for x in rf.variants.all()], "extra": {y.resource_id: {'regex': y.character_regex, 'tracking': y.tracking} for y in rf.identifiers.all()} @@ -80,12 +83,12 @@ def resource_info(request, project_id, resource_id): return json_response({ 'resource': { - "target_platforms": json.loads(resource.target_platforms) if resource.target_platforms else None, 'resource_ids': [{ 'id': x.resource_id, 'regex': x.character_regex, 'tracking': x.tracking, - 'compatibility': x.compatibility + 'compatibility': x.compatibility, + 'target_platforms': json.loads(x.target_platforms) if x.target_platforms else None } for x in resources], 'id': resource.id, 'file_name': resource.file_name, @@ -154,7 +157,6 @@ def update_resource(request, project_id, resource_id): resource = get_object_or_404(ResourceFile, pk=resource_id, project=project) resource_ids = json.loads(request.POST['resource_ids']) file_name = request.POST.get('file_name', None) - target_platforms = json.loads(request.POST.get('target_platforms', None)) variant_tags = json.loads(request.POST.get('variants', "[]")) new_tags = json.loads(request.POST.get('new_tags', "[]")) @@ -168,7 +170,8 @@ def update_resource(request, project_id, resource_id): regex = r['regex'] if 'regex' in r else None tracking = int(r['tracking']) if 'tracking' in r else None compat = r['compatibility'] if 'compatibility' in r else None - resources.append(ResourceIdentifier.objects.create(resource_file=resource, resource_id=r['id'], character_regex=regex, tracking=tracking, compatibility=compat)) + target_platforms = json.dumps(r['target_platforms']) if 'target_platforms' in r else None + resources.append(ResourceIdentifier.objects.create(resource_file=resource, resource_id=r['id'], character_regex=regex, tracking=tracking, compatibility=compat, target_platforms=target_platforms)) # We get sent a list of (tags_before, tags_after) pairs. updated_variants = [] @@ -184,7 +187,6 @@ def update_resource(request, project_id, resource_id): variant = resource.variants.create(tags=",".join(str(int(t)) for t in new_tags)) variant.save_file(request.FILES['file'], request.FILES['file'].size) - resource.target_platforms = None if target_platforms is None else json.dumps(target_platforms) if file_name and resource.file_name != file_name: resource.file_name = file_name @@ -204,8 +206,12 @@ def update_resource(request, project_id, resource_id): "id": resource.id, "kind": resource.kind, "file_name": resource.file_name, - "resource_ids": [{'id': x.resource_id, 'regex': x.character_regex, 'compatibility': x.compatibility} for x in resources], - "target_platforms": json.loads(resource.target_platforms) if resource.target_platforms else None, + "resource_ids": [{ + 'id': x.resource_id, + 'regex': x.character_regex, + 'compatibility': x.compatibility, + 'target_platforms': json.loads(x.target_platforms) if x.target_platforms else None + } for x in resources], "identifiers": [x.resource_id for x in resources], "variants": [x.get_tags() for x in resource.variants.all()], "extra": {y.resource_id: {'regex': y.character_regex, 'tracking': y.tracking, 'compatibility': y.compatibility} for y in resource.identifiers.all()} diff --git a/ide/static/ide/css/ide.css b/ide/static/ide/css/ide.css index 85a214f2..088a7f48 100644 --- a/ide/static/ide/css/ide.css +++ b/ide/static/ide/css/ide.css @@ -1082,6 +1082,10 @@ button#add-filter { display: table; } +.edit-resource-target-platforms-enabled:not(:checked) ~ .edit-resource-targets { + display: none; +} + .pane-boxes { width: 100%; } @@ -1122,11 +1126,11 @@ button#add-filter { line-height: 40px; color: white; } -.btn-delvariant { +.btn-delete-well { float: right; width: initial; margin-bottom: 20px; - bottom: 30px; + bottom: 20px; } .image-platform-preview span { text-align: center; diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 08365281..fedfdd47 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -43,17 +43,23 @@ CloudPebble.Resources = (function() { } /** - * Get the the list of target platforms currently checked in the targetPlatforms interface, - * or null if target platforms is disabled. + * Get the the list of target platforms currently checked in all targetPlatforms interfaces in the pane, + * or null if all target platform checkboxes are disabled. * @param {jQuery|HTMLElement} pane element containing the form * @returns {Array|null} */ function get_target_platforms(pane) { pane = $(pane); - var do_target_platforms = pane.find('#edit-resource-target-platforms-enabled').is(":checked"); - return (!do_target_platforms ? null : _.filter(_.keys(PLATFORMS), function(platform) { - return pane.find('#edit-resource-target-'+platform).is(":checked"); - })); + // Return null any IDs in the pane have no targetPlatforms enabled + if (pane.find('.edit-resource-target-platforms-enabled').is(":not(:checked)")) { + return null; + } + // Otherwise, return the union of all targetPlatforms set in the pane. + else { + return _.filter(_.keys(PLATFORMS), function(platform) { + return pane.find('.edit-resource-target-'+platform).is(":checked"); + }); + } } /** @@ -276,69 +282,10 @@ CloudPebble.Resources = (function() { } // Validate the file name - if (!/^[a-zA-Z0-9_(). -]+$/.test(name)) { report_error(gettext("You must provide a valid filename. Only alphanumerics and characters in the set \"_(). -\" are allowed.")); return; } - var resources = []; - if(kind != 'font') { - // Check that the Resource ID is valid - if(CloudPebble.ProjectInfo.type != 'pebblejs') { - var resource_id = form.find('#non-font-resource-group .edit-resource-id').val(); - if(resource_id === '' || !validate_resource_id(resource_id)) { - report_error(gettext("You must provide a valid resource identifier. Use only letters, numbers and underscores.")); - return; - } - resources = [{'id': resource_id}]; - } - } else { - var resource_ids = {}; - var okay = true; - $.each(form.find('.font-resource-group-single'), function(index, value) { - value = $(value); - var resource_id = value.find('.edit-resource-id').val(); - var regex = value.find('.edit-resource-regex').val(); - var tracking = parseInt(value.find('.edit-resource-tracking').val() || '0', 10); - var compat = value.find('.font-compat-option').val() || null; - if(resource_id === '') return true; // continue - if(!validate_resource_id(resource_id)) { - report_error(gettext("Invalid resource identifier. Use only letters, numbers and underscores.")); - okay = false; - return false; - } - if(!/[0-9]+$/.test(resource_id)) { - report_error(interpolate(gettext("Font resource identifiers must end with the desired font size, e.g. %s_24"), [resource_id])); - okay = false; - return false; - } - if(!!resource_ids[resource_id]) { - report_error(gettext("You can't have multiple identical identifiers. Please remove or change one.")); - okay = false; - return false; - } - if(tracking != tracking) { - report_error(gettext("Tracking must be an integer.")); - okay = false; - return false; - } - resource_ids[resource_id] = true; - resources.push({'id': resource_id, 'regex': regex, 'tracking': tracking, 'compatibility': compat}); - }); - if(!okay) return; - if(resources.length === 0) { - report_error(gettext("You must specify at least one resource.")); - return; - } - } - - // Extract target platforms and verify that they're valid - var target_platforms = get_target_platforms(form); - var targeted_platform_tags = (target_platforms!== null ? _.pick(PLATFORMS, target_platforms) : PLATFORMS); - if (_.isEqual(target_platforms, [])) { - report_error(gettext("You cannot specifically target no platforms.")); - return; - } // Extract the tags from each variant which has changed function extract_tags(element) { @@ -357,30 +304,85 @@ CloudPebble.Resources = (function() { // Validate the tags! var new_tag_values = get_new_tag_values(form, !!file, true); - // Validate the tags: ensure that all variants are unique + // Ensure that all variant's tags are unique if (_.uniq(_.map(new_tag_values, JSON.stringify)).length != new_tag_values.length) { report_error(gettext("Each variant must have a different set of tags")); return; } - // Validate the tags: detect ambiguities and check that each targeted platform has a matching variant - for (var platform_name in targeted_platform_tags) { - try { - if (get_resource_for_platform(new_tag_values, platform_name) == null) { - report_error(interpolate(gettext("There is no variant matching the target platform '%s'."), [platform_name])); + var variant_tags = extract_tags(form.find('#edit-resource-previews .text-wrap input')); + var new_tags = extract_tags(form.find('#edit-resource-new-file .text-wrap input')); + + var resources = []; + + var resource_ids = {}; + var okay = true; + $.each(form.find('.resource-id-group-single'), function(index, value) { + value = $(value); + var resource_id = value.find('.edit-resource-id').val(); + if(resource_id === '') return true; // continue + var resource = {'id': resource_id}; + + // Check the resource ID + if(!validate_resource_id(resource_id)) { + report_error(gettext("Invalid resource identifier. Use only letters, numbers and underscores.")); + okay = false; + return false; + } + + // Extract target platforms and verify that they're valid + var target_platforms = get_target_platforms(value); + var targeted_platform_tags = (target_platforms!== null ? _.pick(PLATFORMS, target_platforms) : PLATFORMS); + if (_.isEqual(target_platforms, [])) { + report_error(gettext("You cannot specifically target no platforms.")); + okay = false; + return; + } + + // Validate the tags: detect ambiguities and check that each targeted platform has a matching variant + for (var platform_name in targeted_platform_tags) { + try { + if (get_resource_for_platform(new_tag_values, platform_name) == null) { + report_error(interpolate(gettext("There is no variant matching the target platform '%s'."), [platform_name])); + okay = false; + return false; + } + } + catch (err) { + report_error(err.description || err); + okay = false; return false; } } - catch (err) { - report_error(err.description || err); - return false; + resource.target_platforms = target_platforms; + + // Add in font-specific per-ID settings, if applicable + if (kind == 'font') { + var regex = value.find('.edit-resource-regex').val(); + var tracking = parseInt(value.find('.edit-resource-tracking').val() || '0', 10); + var compat = value.find('.font-compat-option').val() || null; + if(!/[0-9]+$/.test(resource_id)) { + report_error(interpolate(gettext("Font resource identifiers must end with the desired font size, e.g. %s_24"), [resource_id])); + okay = false; + return false; + } + if(tracking != tracking) { + report_error(gettext("Tracking must be an integer.")); + okay = false; + return false; + } + _(resource).extend({'regex': regex, 'tracking': tracking, 'compatibility': compat}) } + resource_ids[resource_id] = true; + resources.push(resource); + }); + if(!okay) return; + if(resources.length === 0) { + report_error(gettext("You must specify at least one resource.")); + return; } - var variant_tags = extract_tags(form.find('#edit-resource-previews .text-wrap input')); - var new_tags = extract_tags(form.find('#edit-resource-new-file .text-wrap input')); - var form_data = new FormData(); form_data.append("kind", kind); form_data.append("resource_ids", JSON.stringify(resources)); @@ -395,7 +397,6 @@ CloudPebble.Resources = (function() { form_data.append("file_name", name); - form_data.append("target_platforms", JSON.stringify(target_platforms)); disable_controls(); $.ajax({ @@ -417,11 +418,6 @@ CloudPebble.Resources = (function() { ga('send', 'event', 'resource', 'save'); }; - var show_resource_targets = function(parent) { - var target_platforms_checkbox = parent.find("#edit-resource-target-platforms-enabled"); - parent.find('#edit-resource-targets').toggle(target_platforms_checkbox.is(":checked")); - }; - var edit_resource = function(resource) { CloudPebble.FuzzyPrompt.SetCurrentItemName(resource.file_name); CloudPebble.Sidebar.SuspendActive(); @@ -436,7 +432,6 @@ CloudPebble.Resources = (function() { var pane = prepare_resource_pane(); var list_entry = $('#sidebar-pane-resource-' + resource.id); - var target_platforms_checkbox = pane.find("#edit-resource-target-platforms-enabled"); if(list_entry) { list_entry.addClass('active'); } @@ -445,6 +440,37 @@ CloudPebble.Resources = (function() { pane.find('#edit-resource-type').val(resource.kind).attr('disabled', 'disabled'); pane.find('#edit-resource-type').change(); + var save = function(e) { + if (e) e.preventDefault(); + process_resource_form(form, false, resource.file_name, "/ide/project/" + PROJECT_ID + "/resource/" + resource.id + "/update", function(data) { + delete project_resources[resource.file_name]; + // Update our information about the resource. + update_resource(data); + resource = project_resources[data.file_name]; + + // Set the resource's sidebar name + generate_resource_previews(resource.kind); + CloudPebble.Sidebar.SetItemName('resource', data.id, data.file_name); + + if(resource.kind == 'font') { + resource.family = null; + $.each(pane.find('.resource-id-group-single'), function(index, group) { + update_font_preview($(group)); + }); + } + + // Clear and disable the upload-file form + pane.find('#edit-resource-new-file input').val(''); + pane.find('#edit-resource-new-file textarea').textext()[0].tags().empty().core().enabled(false); + pane.find('#edit-resource-new-file').toggleClass('file-present', false); + CloudPebble.Sidebar.ClearIcon('resource-'+resource.id); + live_form.clearIcons(); + + // Only show the delete-identifiers button if there is more than one ID. + pane.find('.btn-delidentifier').toggle(resource.resource_ids.length > 1); + }); + }; + // Generate a preview. var generate_resource_previews = function(kind) { @@ -527,68 +553,58 @@ CloudPebble.Resources = (function() { } var update_font_preview = function(group) { - group.find('.font-preview').remove(); - var regex_str = group.find('.edit-resource-regex').val(); - var id_str = group.find('.edit-resource-id').val(); - var preview_regex = new RegExp(''); - try { - preview_regex = new RegExp(regex_str ? regex_str : '.', 'g'); - group.find('.font-resource-regex-group').removeClass('error').find('.help-block').text(gettext("A PCRE regular expression that restricts characters.")); - } catch(e) { - group.find('.font-resource-regex-group').addClass('error').find('.help-block').text(e); - } - var tracking = parseInt(group.find('.edit-resource-tracking').val(), 10) || 0; - - _.each(resource.variants, function(tags) { - var row = $('
'); - var preview_holder = $('
'); - $('
').appendTo(preview_holder).text(_.chain(tags).map(get_tag_data_for_id).pluck('name').value().join(', ') || gettext("No tags")); - var preview = $('
').appendTo(preview_holder); - var line = ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^& *()_+[]{}\\|;:\'"<>?`'.match(preview_regex)||[]).join(''); - var font_size = id_str.match(/[0-9]+$/)[0]; - - preview.text(line); - // Officially, a CSS pixel is defined as one pixel at 96 dpi. - // 96 / PEBBLE_PPI should thus be correct. - // We use 'transform' to work around https://bugs.webkit.org/show_bug.cgi?id=20606 - preview.css({ - 'font-family': CloudPebble.Resources.GetFontFamily(resource, tags), - 'font-size': font_size + 'px', - 'line-height': font_size + 'px', - 'letter-spacing': tracking + 'px', - 'transform': 'scale(' + (96 / PEBBLE_PPI) + ')', - 'transform-origin': '0 0', - 'display': 'inline-block', - 'border': (2 * (PEBBLE_PPI / 96)) + 'px solid #767676', - 'padding': '5px', - 'border-radius': '5px', - 'background-color': 'white', - 'color': 'black' + if (resource.kind == 'font') { + group.find('.font-preview').remove(); + var regex_str = group.find('.edit-resource-regex').val(); + var id_str = group.find('.edit-resource-id').val(); + var preview_regex = new RegExp(''); + try { + preview_regex = new RegExp(regex_str ? regex_str : '.', 'g'); + group.find('.font-resource-regex-group').removeClass('error').find('.help-block').text(gettext("A PCRE regular expression that restricts characters.")); + } catch (e) { + group.find('.font-resource-regex-group').addClass('error').find('.help-block').text(e); + } + var tracking = parseInt(group.find('.edit-resource-tracking').val(), 10) || 0; + + _.each(resource.variants, function (tags) { + var row = $('
'); + var preview_holder = $('
'); + $('
').appendTo(preview_holder).text(_.chain(tags).map(get_tag_data_for_id).pluck('name').value().join(', ') || gettext("No tags")); + var preview = $('
').appendTo(preview_holder); + var line = ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^& *()_+[]{}\\|;:\'"<>?`'.match(preview_regex) || []).join(''); + var font_size = id_str.match(/[0-9]+$/)[0]; + + preview.text(line); + // Officially, a CSS pixel is defined as one pixel at 96 dpi. + // 96 / PEBBLE_PPI should thus be correct. + // We use 'transform' to work around https://bugs.webkit.org/show_bug.cgi?id=20606 + preview.css({ + 'font-family': CloudPebble.Resources.GetFontFamily(resource, tags), + 'font-size': font_size + 'px', + 'line-height': font_size + 'px', + 'letter-spacing': tracking + 'px', + 'transform': 'scale(' + (96 / PEBBLE_PPI) + ')', + 'transform-origin': '0 0', + 'display': 'inline-block', + 'border': (2 * (PEBBLE_PPI / 96)) + 'px solid #767676', + 'padding': '5px', + 'border-radius': '5px', + 'background-color': 'white', + 'color': 'black' + }); + row.append(preview_holder); + group.append(row); }); - row.append(preview_holder); - group.append(row); - }); - }; - var has_target_platforms = _.isArray(resource["target_platforms"]); - if (has_target_platforms) { - target_platforms_checkbox.prop('checked', true); - _.each(_.keys(PLATFORMS), function(platform) { - $("#edit-resource-target-"+platform).prop('checked', _.contains(resource["target_platforms"], platform)); - }); - } - show_resource_targets(pane); - if(resource.kind != 'font') { - if(resource.resource_ids.length > 0) { - pane.find('#non-font-resource-group .edit-resource-id').val(resource.resource_ids[0].id); } - } else { - pane.find('#non-font-resource-group').addClass('hide'); - var template = pane.find('.font-resource-group-single').detach(); - var parent = $('#font-resource-group').removeClass('hide'); - $.each(resource.resource_ids, function(index, value) { - var group = template.clone(); - group.removeClass('hide').attr('id',''); - group.find('.edit-resource-id').val(value.id); + }; + + var template = pane.find('.resource-id-group-single').detach(); + var parent = $('#resource-id-group').removeClass('hide'); + $.each(resource.resource_ids, function(index, value) { + var group = template.clone(); + group.removeClass('hide').attr('id',''); + group.find('.edit-resource-id').val(value.id); + if (resource.kind == 'font') { group.find('.edit-resource-regex').val(value.regex); group.find('.edit-resource-tracking').val(value.tracking || '0'); group.find('.font-compat-option').val(value.compatibility || ""); @@ -596,20 +612,46 @@ CloudPebble.Resources = (function() { group.find('input[type=text], input[type=number]').on('input', function() { update_font_preview(group); }); - parent.append(group); + group.find('.non-font-only').remove(); + } else { + group.find('.font-only').remove(); + } + + var has_target_platforms = _.isArray(value["target_platforms"]); + if (has_target_platforms) { + var target_platforms_checkbox = group.find(".edit-resource-target-platforms-enabled"); + target_platforms_checkbox.prop('checked', true); + _.each(_.keys(PLATFORMS), function(platform) { + group.find(".edit-resource-target-"+platform).prop('checked', _.contains(value["target_platforms"], platform)); + }); + } + group.find(".resource-targets-section input").change(function() { + update_platform_labels(pane); }); - pane.find('#add-font-resource').removeClass('hide').click(function() { - var clone = parent.find('.font-resource-group-single:last').clone(false); - if(!clone.length) { - clone = template.clone().removeClass('hide').attr('id',''); - } - parent.append(clone); - clone.find('input[type=text], input[type=number]').on('input', function() { - update_font_preview(clone); + group.find('.btn-delidentifier').click(function() { + CloudPebble.Prompts.Confirm(gettext("Do you want to this resource identifier?"), gettext("This cannot be undone."), function () { + group.remove(); + CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); + save(); }); }); - } + + parent.append(group); + }); + pane.find('.btn-delidentifier').toggle(resource.resource_ids.length > 1); + pane.find('#add-resource-id').removeClass('hide').click(function() { + var clone = parent.find('.resource-id-group-single:last').clone(false); + + if(!clone.length) { + clone = template.clone().removeClass('hide').attr('id',''); + } + parent.append(clone); + clone.find('input[type=text], input[type=number]').on('input', function() { + update_font_preview(clone); + }); + CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); + }); pane.find('.image-platform-preview').toggle((resource.kind == 'png' || resource.kind == 'png-trans')); @@ -643,33 +685,7 @@ CloudPebble.Resources = (function() { CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); } }).init(); - form.submit(function(e) { - e.preventDefault(); - process_resource_form(form, false, resource.file_name, "/ide/project/" + PROJECT_ID + "/resource/" + resource.id + "/update", function(data) { - delete project_resources[resource.file_name]; - // Update our information about the resource. - update_resource(data); - resource = project_resources[data.file_name]; - - // Set the resource's sidebar name - generate_resource_previews(resource.kind); - CloudPebble.Sidebar.SetItemName('resource', data.id, data.file_name); - - if(resource.kind == 'font') { - resource.family = null; - $.each(pane.find('.font-resource-group-single'), function(index, group) { - update_font_preview($(group)); - }); - } - - // Clear and disable the upload-file form - pane.find('#edit-resource-new-file input').val(''); - pane.find('#edit-resource-new-file textarea').textext()[0].tags().empty().core().enabled(false); - pane.find('#edit-resource-new-file').toggleClass('file-present', false); - CloudPebble.Sidebar.ClearIcon('resource-'+resource.id); - live_form.clearIcons(); - }); - }); + form.submit(save); restore_pane(pane); }); @@ -681,7 +697,6 @@ CloudPebble.Resources = (function() { parent.find('.colour-resource, #resource-targets-section').hide(); } else { parent.find('.colour-resource, #resource-targets-section').show(); - show_resource_targets(parent) } if(CloudPebble.ProjectInfo.sdk_version != '3') { parent.find('.sdk3-only').hide(); @@ -765,17 +780,16 @@ CloudPebble.Resources = (function() { var prepare_resource_pane = function() { var template = resource_template.clone(); template.removeClass('hide'); - + template.find('.font-only').addClass('hide'); + template.find('.nont-font-only').removeClass('hide'); template.find('.image-platform-preview').hide(); template.find('#edit-resource-type').change(function() { if($(this).val() == 'font') { - template.find('#non-font-resource-group').addClass('hide'); - template.find('#font-resource-group').removeClass('hide'); - template.find('#add-font-resource').removeClass('hide'); + template.find('.font-only').removeClass('hide'); + template.find('.non-font-only').addClass('hide'); } else { - template.find('#font-resource-group').addClass('hide'); - template.find('#non-font-resource-group').removeClass('hide'); - template.find('#add-font-resource').addClass('hide'); + template.find('.font-only').addClass('hide'); + template.find('.non-font-only').removeClass('hide'); } }); @@ -786,9 +800,6 @@ CloudPebble.Resources = (function() { }); }); - template.find("#edit-resource-target-platforms-enabled").change(_.partial(show_resource_targets, template)); - template.find("#resource-targets-section input").change(function() {update_platform_labels(template);}); - // setTimeout is used because the textarea has to actually be visible when the textext tag editor is initialised setTimeout(function() { var textext = build_tags_editor(template, template.find("#new-resource-tags"), []); diff --git a/ide/tasks/archive.py b/ide/tasks/archive.py index cd0a4858..4a7f9846 100644 --- a/ide/tasks/archive.py +++ b/ide/tasks/archive.py @@ -190,7 +190,6 @@ def make_valid_filename(zip_entry): desired_resources = {} resources_files = {} - resource_identifiers = {} resource_variants = {} file_exists_for_root = {} @@ -230,63 +229,48 @@ def make_valid_filename(zip_entry): print "Importing file %s with root %s " % (zipitem.filename, root_file_name) if root_file_name in desired_resources: - ''' FIXME: targetPlatforms is currently stored in resourceFile, but it *should* be in - ResourceIdentifier. Until that is fixed, we cannot support multiple identifiers - linked to a single file compiling for different platforms. When the bug is fixed, - this will need to be changed. Until then, we just pick the first file on the list - of desired_resources.''' medias = desired_resources[root_file_name] - is_font = False print "Looking for variants of %s" % root_file_name - # An exception to the above warning is made for fonts, where multiple identifiers is - # already implemented in the UI. - if len(medias) > 1: - if set(r['type'] for r in medias) != {'font'}: - raise NotImplementedError("You cannot currently import a project with multiple identifiers for a single non-font file") - else: - is_font = True - resource = medias[-1] - - for resource in medias: - # Make only one resource file per base resource. - if root_file_name not in resources_files: - kind = resource['type'] - is_menu_icon = resource.get('menuIcon', False) - target_platforms = resource.get('targetPlatforms', None) - target_platforms = json.dumps(target_platforms) if target_platforms else None - resources_files[root_file_name] = ResourceFile.objects.create( - project=project, - file_name=os.path.basename(root_file_name), - kind=kind, - is_menu_icon=is_menu_icon, - target_platforms=target_platforms) - - identifier = resource['name'] - # Add all the identifiers which don't clash with existing identifiers - if not identifier in resource_identifiers: - tracking = resource.get('trackingAdjust', None) - regex = resource.get('characterRegex', None) - compatibility = resource.get('compatibility', None) - - ResourceIdentifier.objects.create( - resource_file=resources_files[root_file_name], - resource_id=identifier, - character_regex=regex, - tracking=tracking, - compatibility=compatibility - ) - resource_identifiers[identifier] = resources_files[root_file_name] - - # At the moment, only add > 1 identifier for fonts. - if not is_font: - break + # Because 'kind' and 'is_menu_icons' are properties of ResourceFile in the database, + # we just use the first one. + resource = medias[0] + # Make only one resource file per base resource. + if root_file_name not in resources_files: + kind = resource['type'] + is_menu_icon = resource.get('menuIcon', False) + resources_files[root_file_name] = ResourceFile.objects.create( + project=project, + file_name=os.path.basename(root_file_name), + kind=kind, + is_menu_icon=is_menu_icon) + + # But add a resource variant for every file print "Adding variant %s with tags [%s]" % (root_file_name, tags_string) actual_file_name = resource['file'] resource_variants[actual_file_name] = ResourceVariant.objects.create(resource_file=resources_files[root_file_name], tags=tags_string) resource_variants[actual_file_name].save_file(extracted) file_exists_for_root[root_file_name] = True + # Now add all the resource identifiers + for root_file_name in desired_resources: + for resource in desired_resources[root_file_name]: + print resource + identifier = resource['name'] + tracking = resource.get('trackingAdjust', None) + regex = resource.get('characterRegex', None) + compatibility = resource.get('compatibility', None) + target_platforms = resource.get('targetPlatforms', None) + target_platforms = json.dumps(target_platforms) if target_platforms is not None else None + ResourceIdentifier.objects.create( + resource_file=resources_files[root_file_name], + resource_id=identifier, + character_regex=regex, + tracking=tracking, + compatibility=compatibility, + target_platforms=target_platforms + ) + # Check that at least one variant of each specified resource exists. for root_file_name, loaded in file_exists_for_root.iteritems(): if not loaded: diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index 7deb6c99..fdf8ac26 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -21,13 +21,6 @@
-
- -
- - {% trans 'This is used in your code and must be a valid C identifier.' %} -
-
@@ -35,24 +28,6 @@
-
-
- - -
-
-
- {% for platform in supported_platforms %} -
- - -
- {% endfor %} -
-

{% trans "The 'targetPlatforms' option restricts all variants of a resource to a specific set of platforms." %}

-
-
-
@@ -96,7 +71,7 @@

{{ platform | capfirst }}

- +
@@ -123,28 +98,31 @@

{{ platform | capfirst }}

- +
-
-
+
+
- + {# TODO: placeholder/pattern thingybobs #} + + {% trans 'This is used in your code and must be a valid C identifier.' %}
- {% trans 'It must end with the desired font size.' %}
+ {% trans 'It must end with the desired font size.' %} +
-
+
{% trans 'A PCRE regular expression that restricts characters.' %}
-
+
{# Translators: This refers to the number of additional pixels by which to shift each character #}
@@ -152,7 +130,7 @@

{{ platform | capfirst }}

{% trans 'Number of additional pixels by which to shift each character.' %}
-
+

+ +
+
+ {% for platform in supported_platforms %} +
+ + +
+ {% endfor %} +
+

{% trans "The 'targetPlatforms' option restricts all variants of a resource to a specific set of platforms. It is an advanced option, which you should only use if you require multiple Resource IDs for different platforms." %}

+
+
+
+
@@ -184,11 +181,13 @@

{{ platform | capfirst }}

-
- +
diff --git a/ide/utils/sdk.py b/ide/utils/sdk.py index 7e4f4a21..2b041f82 100644 --- a/ide/utils/sdk.py +++ b/ide/utils/sdk.py @@ -314,8 +314,8 @@ def generate_native_resource_dict(project, resources): d['menuIcon'] = True if resource_id.compatibility is not None: d['compatibility'] = resource_id.compatibility - if project.sdk_version == '3' and resource.target_platforms: - d['targetPlatforms'] = json.loads(resource.target_platforms) + if project.sdk_version == '3' and resource_id.target_platforms: + d['targetPlatforms'] = json.loads(resource_id.target_platforms) resource_map['media'].append(d) return resource_map From 8e2053a5fbba0da55ded18cad935566874b825e9 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Tue, 6 Oct 2015 08:13:13 -0700 Subject: [PATCH 03/60] Final target_platforms schemamigration. --- ...rget_platforms__del_unique_resourceiden.py | 172 ++++++++++++++++++ ide/models/files.py | 4 - 2 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 ide/migrations/0041_auto__del_field_resourcefile_target_platforms__del_unique_resourceiden.py diff --git a/ide/migrations/0041_auto__del_field_resourcefile_target_platforms__del_unique_resourceiden.py b/ide/migrations/0041_auto__del_field_resourcefile_target_platforms__del_unique_resourceiden.py new file mode 100644 index 00000000..55cfef5b --- /dev/null +++ b/ide/migrations/0041_auto__del_field_resourcefile_target_platforms__del_unique_resourceiden.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Removing unique constraint on 'ResourceIdentifier', fields ['resource_file', 'resource_id'] + db.delete_unique(u'ide_resourceidentifier', ['resource_file_id', 'resource_id']) + + # Deleting field 'ResourceFile.target_platforms' + db.delete_column(u'ide_resourcefile', 'target_platforms') + + + def backwards(self, orm): + # Adding field 'ResourceFile.target_platforms' + db.add_column(u'ide_resourcefile', 'target_platforms', + self.gf('django.db.models.fields.CharField')(default=None, max_length=30, null=True, blank=True), + keep_default=False) + + # Adding unique constraint on 'ResourceIdentifier', fields ['resource_file', 'resource_id'] + db.create_unique(u'ide_resourceidentifier', ['resource_file_id', 'resource_id']) + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ide.buildresult': { + 'Meta': {'object_name': 'BuildResult'}, + 'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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': "'76245c80-563b-4424-b3ae-be3652761026'", 'max_length': '36'}) + }, + 'ide.buildsize': { + 'Meta': {'object_name': 'BuildSize'}, + 'binary_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sizes'", 'to': "orm['ide.BuildResult']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'resource_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'worker_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.project': { + 'Meta': {'object_name': 'Project'}, + 'app_capabilities': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'app_company_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'app_is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_shown_on_communication': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_watchface': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_jshint': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'app_keys': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'app_long_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': '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': "'0bc7e7de-adfc-4e4b-af2d-c993a88673ec'", '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'}), + 'github_hook_uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'github_last_commit': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'github_last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'github_repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'optimisation': ('django.db.models.fields.CharField', [], {'default': "'s'", 'max_length': '1'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'project_type': ('django.db.models.fields.CharField', [], {'default': "'native'", 'max_length': '10'}), + 'sdk_version': ('django.db.models.fields.CharField', [], {'default': "'2'", 'max_length': '6'}) + }, + 'ide.resourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'ResourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_menu_icon': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'kind': ('django.db.models.fields.CharField', [], {'max_length': '9'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'resources'", 'to': "orm['ide.Project']"}) + }, + 'ide.resourceidentifier': { + 'Meta': {'object_name': 'ResourceIdentifier'}, + 'character_regex': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'compatibility': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['ide.ResourceFile']"}), + 'resource_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'tracking': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.resourcevariant': { + 'Meta': {'unique_together': "(('resource_file', 'tags'),)", 'object_name': 'ResourceVariant'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_legacy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variants'", 'to': "orm['ide.ResourceFile']"}), + 'tags': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'max_length': '50', 'blank': 'True'}) + }, + 'ide.sourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'SourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'folded_lines': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_files'", 'to': "orm['ide.Project']"}), + 'target': ('django.db.models.fields.CharField', [], {'default': "'app'", 'max_length': '10'}) + }, + 'ide.templateproject': { + 'Meta': {'object_name': 'TemplateProject', '_ormbases': ['ide.Project']}, + u'project_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ide.Project']", 'unique': 'True', 'primary_key': 'True'}), + 'template_kind': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) + }, + 'ide.usergithub': { + 'Meta': {'object_name': 'UserGithub'}, + 'avatar': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'github'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['auth.User']"}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}) + }, + 'ide.usersettings': { + 'Meta': {'object_name': 'UserSettings'}, + 'accepted_terms': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'autocomplete': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'keybinds': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}), + 'tab_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '2'}), + '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': '18'}) + } + } + + complete_apps = ['ide'] \ No newline at end of file diff --git a/ide/models/files.py b/ide/models/files.py index 03429c0f..e7b1f44b 100644 --- a/ide/models/files.py +++ b/ide/models/files.py @@ -31,7 +31,6 @@ class ResourceFile(IdeModel): file_name = models.CharField(max_length=100, validators=[RegexValidator(r"^[/a-zA-Z0-9_(). -]+$")]) kind = models.CharField(max_length=9, choices=RESOURCE_KINDS) is_menu_icon = models.BooleanField(default=False) - target_platforms = models.CharField(max_length=30, null=True, blank=True, default=None) def get_best_variant(self, tags_string): try: @@ -226,9 +225,6 @@ def save(self, *args, **kwargs): self.resource_file.project.save() super(ResourceIdentifier, self).save(*args, **kwargs) - class Meta(IdeModel.Meta): - unique_together = (('resource_file', 'resource_id'),) - class SourceFile(IdeModel): project = models.ForeignKey('Project', related_name='source_files') From edeb82ffce9d63eb134cde058ca055eac734013b Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Tue, 6 Oct 2015 08:59:15 -0700 Subject: [PATCH 04/60] Shuffled the order of things on the resource page. --- ide/templates/ide/project/resource.html | 122 ++++++++++++------------ 1 file changed, 61 insertions(+), 61 deletions(-) diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index fdf8ac26..b08a9534 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -30,6 +30,67 @@
+ +
+
+
+ +
+ {# TODO: placeholder/pattern thingybobs #} + + + {% trans 'This is used in your code and must be a valid C identifier.' %}
+ {% trans 'It must end with the desired font size.' %} +
+
+
+
+ +
+ + {% trans 'A PCRE regular expression that restricts characters.' %} +
+
+
+ {# Translators: This refers to the number of additional pixels by which to shift each character #} + +
+ + {% trans 'Number of additional pixels by which to shift each character.' %} +
+
+
+ +
+ + {% trans 'Determines the font rendering algorithm to use. The latest looks better, but can produce slightly larger characters.' %} +
+
+
+
+ +

+ +
+
+ {% for platform in supported_platforms %} +
+ + +
+ {% endfor %} +
+

{% trans "The 'targetPlatforms' option restricts all variants of a resource to a specific set of platforms. It is an advanced option, which you should only use if you require multiple Resource IDs for different platforms." %}

+
+
+
+ +
+
+

{% trans 'Preview per platform' %}

@@ -87,7 +148,6 @@

{{ platform | capfirst }}

- {# TODO: support for/id #}
@@ -102,66 +162,6 @@

{{ platform | capfirst }}

-
-
-
- -
- {# TODO: placeholder/pattern thingybobs #} - - - {% trans 'This is used in your code and must be a valid C identifier.' %}
- {% trans 'It must end with the desired font size.' %} -
-
-
-
- -
- - {% trans 'A PCRE regular expression that restricts characters.' %} -
-
-
- {# Translators: This refers to the number of additional pixels by which to shift each character #} - -
- - {% trans 'Number of additional pixels by which to shift each character.' %} -
-
-
- -
- - {% trans 'Determines the font rendering algorithm to use. The latest looks better, but can produce slightly larger characters.' %} -
-
-
-
- -

- -
-
- {% for platform in supported_platforms %} -
- - -
- {% endfor %} -
-

{% trans "The 'targetPlatforms' option restricts all variants of a resource to a specific set of platforms. It is an advanced option, which you should only use if you require multiple Resource IDs for different platforms." %}

-
-
-
- -
-
-
From e593d019546db3edbdbd619276c79c46b854c108 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 21 Oct 2015 14:00:24 -0700 Subject: [PATCH 05/60] Replace targetPlatforms help box with tooltip --- ide/static/ide/css/ide.css | 14 ++++++++++++++ ide/static/ide/js/resources.js | 9 +++++++++ ide/templates/ide/project/resource.html | 5 +++-- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/ide/static/ide/css/ide.css b/ide/static/ide/css/ide.css index d50b5fd7..08562c78 100644 --- a/ide/static/ide/css/ide.css +++ b/ide/static/ide/css/ide.css @@ -1217,3 +1217,17 @@ button#add-filter { overflow: auto; margin: 0; } + +.text-icon { + border: 2px white solid; + display: inline-block; + text-align: center; + border-radius: 100px; + height: 1em; + width: 1em; + line-height: initial; + content: "?"; +} +label .text-icon { + margin-left: 5px; +} diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 19799228..d497e305 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -639,6 +639,15 @@ CloudPebble.Resources = (function() { }); }); + group.find('.resource-targets-section .text-icon').popover({ + container: 'body', + title: null, + trigger: 'hover', + html: true, + animation: false, + delay: {show: 250}, + }); + parent.append(group); }); pane.find('.btn-delidentifier').toggle(resource.resource_ids.length > 1); diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index 4a2ad7f6..59ab46b8 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -72,7 +72,9 @@
- +

@@ -84,7 +86,6 @@
{% endfor %}
-

{% trans "The 'targetPlatforms' option restricts all variants of a resource to a specific set of platforms. It is an advanced option, which you should only use if you require multiple Resource IDs for different platforms." %}

From a7cd84c919a8afe9b2efd0a2ae5872cabc9936fc Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 21 Oct 2015 14:12:24 -0700 Subject: [PATCH 06/60] Tooltip rewording. --- ide/templates/ide/project/resource.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index 59ab46b8..a6c03751 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -73,7 +73,7 @@

From 3d87aa858e74ed67deb1e7b0114b27650a2c1883 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 21 Oct 2015 16:04:41 -0700 Subject: [PATCH 07/60] Fixed font preview wrapping and bottom margin --- ide/static/ide/js/resources.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index a5d83860..44bbe202 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -596,7 +596,13 @@ CloudPebble.Resources = (function() { _.each(resource.variants, function (tags) { var row = $('
'); var preview_holder = $('
'); - $('
').appendTo(preview_holder).text(_.chain(tags).map(get_tag_data_for_id).pluck('name').value().join(', ') || gettext("No tags")); + var font_tag_preview = $('
').appendTo(preview_holder).text(gettext("For ") + ( + _.chain(tags) + .map(get_tag_data_for_id) + .pluck('name').value() + .join(', ') + || gettext("untagged")) + gettext(" file") + ); var preview = $('
').appendTo(preview_holder); var line = ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^& *()_+[]{}\\|;:\'"<>?`'.match(preview_regex) || []).join(''); var font_size = id_str.match(/[0-9]+$/)[0]; @@ -617,8 +623,20 @@ CloudPebble.Resources = (function() { 'padding': '5px', 'border-radius': '5px', 'background-color': 'white', - 'color': 'black' + 'color': 'black', + 'word-wrap': 'break-word', + 'width': 'calc(100% * 1/0.547945)' }); + // The parent element doesn't take the CSS transform in to account when calculating its own height + // based on it children, so there is a large gap left underneath. + // We fix this calculating the height of the preview's empty space and subtracting it from the + // parent element's margin. + _.defer(function() { + preview_holder.css({ + 'margin-bottom': (60 - (preview.height() * (1 - 96/PEBBLE_PPI))) +'px' + }) + }); + row.append(preview_holder); group.append(row); }); From 061eacce1ed4b119603c4b5614b7c57f672643cf Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 21 Oct 2015 16:21:13 -0700 Subject: [PATCH 08/60] Allow replacement of raw files, and correctly deal with tagged variants in SDK2 projects. --- ide/static/ide/js/resources.js | 12 ++++++++++-- ide/templates/ide/project/resource.html | 9 +++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 44bbe202..06d5c79d 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -89,7 +89,10 @@ CloudPebble.Resources = (function() { return (!is_new_file_well || should_include_new_file_tags); }).map(function(i, input) { // Just extract the tag numbers. - var tags = JSON.parse(input.value); + var tags = []; + if (CloudPebble.ProjectInfo.sdk_version != "2") { + tags = JSON.parse(input.value); + } if (do_sort) tags.sort(); return [tags]; })); @@ -304,6 +307,11 @@ CloudPebble.Resources = (function() { // Validate the tags! var new_tag_values = get_new_tag_values(form, !!file, true); + if (CloudPebble.ProjectInfo.sdk_version == "2" && new_tag_values.length > 1) { + report_error(gettext("SDK 2 projects do not support multiple files per resource. Please delete extra files.")) + returnl + } + // Ensure that all variant's tags are unique if (_.uniq(_.map(new_tag_values, JSON.stringify)).length != new_tag_values.length) { report_error(gettext("Each variant must have a different set of tags")); @@ -396,7 +404,7 @@ CloudPebble.Resources = (function() { return; } if (file !== null) { - var tags = $(this).parents('.image-resource-preview-pane').find('.text-wrap input').val().slice(1, -1); + var tags = $(this).parents('.image-resource-preview-pane, .raw-resource-preview-pane').find('.text-wrap input').val().slice(1, -1); replacement_map.push([tags, replacements_files.length]); replacements_files.push(file); } diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index b4ba4f1b..ae9d1aa0 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -69,7 +69,7 @@ {% trans 'Determines the font rendering algorithm to use. The latest looks better, but can produce slightly larger characters.' %}
-
+
- +
+ +
+ +
+
From 8267f77c5dcbd8a014ab005c88b9a8c6ee1f059b Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 21 Oct 2015 17:34:38 -0700 Subject: [PATCH 09/60] Fix typos, unnecessary/incorrect logic & font preview spacinfg --- ide/static/ide/js/resources.js | 129 ++++++++++++++++----------------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 06d5c79d..79738ed1 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -324,12 +324,11 @@ CloudPebble.Resources = (function() { var okay = true; $.each(form.find('.resource-id-group-single'), function(index, value) { value = $(value); - var resource_id = value.find('.edit-resource-id').val(); - if(resource_id === '') return true; // continue + var resource_id = value.find('.edit-resource-id:visible').val(); var resource = {'id': resource_id}; // Check the resource ID - if(!validate_resource_id(resource_id)) { + if(resource_id === '' || !validate_resource_id(resource_id)) { report_error(gettext("Invalid resource identifier. Use only letters, numbers and underscores.")); okay = false; return false; @@ -588,67 +587,65 @@ CloudPebble.Resources = (function() { } var update_font_preview = function(group) { - if (resource.kind == 'font') { - group.find('.font-preview').remove(); - var regex_str = group.find('.edit-resource-regex').val(); - var id_str = group.find('.edit-resource-id').val(); - var preview_regex = new RegExp(''); - try { - preview_regex = new RegExp(regex_str ? regex_str : '.', 'g'); - group.find('.font-resource-regex-group').removeClass('error').find('.help-block').text(gettext("A PCRE regular expression that restricts characters.")); - } catch (e) { - group.find('.font-resource-regex-group').addClass('error').find('.help-block').text(e); - } - var tracking = parseInt(group.find('.edit-resource-tracking').val(), 10) || 0; - - _.each(resource.variants, function (tags) { - var row = $('
'); - var preview_holder = $('
'); - var font_tag_preview = $('
').appendTo(preview_holder).text(gettext("For ") + ( - _.chain(tags) - .map(get_tag_data_for_id) - .pluck('name').value() - .join(', ') - || gettext("untagged")) + gettext(" file") - ); - var preview = $('
').appendTo(preview_holder); - var line = ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^& *()_+[]{}\\|;:\'"<>?`'.match(preview_regex) || []).join(''); - var font_size = id_str.match(/[0-9]+$/)[0]; - - preview.text(line); - // Officially, a CSS pixel is defined as one pixel at 96 dpi. - // 96 / PEBBLE_PPI should thus be correct. - // We use 'transform' to work around https://bugs.webkit.org/show_bug.cgi?id=20606 - preview.css({ - 'font-family': CloudPebble.Resources.GetFontFamily(resource, tags), - 'font-size': font_size + 'px', - 'line-height': font_size + 'px', - 'letter-spacing': tracking + 'px', - 'transform': 'scale(' + (96 / PEBBLE_PPI) + ')', - 'transform-origin': '0 0', - 'display': 'inline-block', - 'border': (2 * (PEBBLE_PPI / 96)) + 'px solid #767676', - 'padding': '5px', - 'border-radius': '5px', - 'background-color': 'white', - 'color': 'black', - 'word-wrap': 'break-word', - 'width': 'calc(100% * 1/0.547945)' - }); - // The parent element doesn't take the CSS transform in to account when calculating its own height - // based on it children, so there is a large gap left underneath. - // We fix this calculating the height of the preview's empty space and subtracting it from the - // parent element's margin. - _.defer(function() { - preview_holder.css({ - 'margin-bottom': (60 - (preview.height() * (1 - 96/PEBBLE_PPI))) +'px' - }) - }); + group.find('.font-preview').remove(); + var regex_str = group.find('.edit-resource-regex').val(); + var id_str = group.find('.edit-resource-id').val(); + var preview_regex = new RegExp(''); + try { + preview_regex = new RegExp(regex_str ? regex_str : '.', 'g'); + group.find('.font-resource-regex-group').removeClass('error').find('.help-block').text(gettext("A PCRE regular expression that restricts characters.")); + } catch (e) { + group.find('.font-resource-regex-group').addClass('error').find('.help-block').text(e); + } + var tracking = parseInt(group.find('.edit-resource-tracking').val(), 10) || 0; - row.append(preview_holder); - group.append(row); + _.each(resource.variants, function (tags) { + var row = $('
'); + var preview_holder = $('
'); + var font_tag_preview = $('
').appendTo(preview_holder).text(gettext("For ") + ( + _.chain(tags) + .map(get_tag_data_for_id) + .pluck('name').value() + .join(', ') + || gettext("untagged")) + gettext(" file") + ); + var preview = $('
').appendTo(preview_holder); + var line = ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789~!@#$%^& *()_+[]{}\\|;:\'"<>?`'.match(preview_regex) || []).join(''); + var font_size = id_str.match(/[0-9]+$/)[0]; + + preview.text(line); + // Officially, a CSS pixel is defined as one pixel at 96 dpi. + // 96 / PEBBLE_PPI should thus be correct. + // We use 'transform' to work around https://bugs.webkit.org/show_bug.cgi?id=20606 + preview.css({ + 'font-family': CloudPebble.Resources.GetFontFamily(resource, tags), + 'font-size': font_size + 'px', + 'line-height': font_size + 'px', + 'letter-spacing': tracking + 'px', + 'transform': 'scale(' + (96 / PEBBLE_PPI) + ')', + 'transform-origin': '0 0', + 'display': 'inline-block', + 'border': (2 * (PEBBLE_PPI / 96)) + 'px solid #767676', + 'padding': '5px', + 'border-radius': '5px', + 'background-color': 'white', + 'color': 'black', + 'word-wrap': 'break-word', + 'width': 'calc(100% * 1/0.547945)' }); - } + // The parent element doesn't take the CSS transform in to account when calculating its own height + // based on it children, so there is a large gap left underneath. + // We fix this calculating the height of the preview's empty space and subtracting it from the + // parent element's margin. + _.defer(function() { + preview_holder.css({ + 'margin-bottom': (60 - (preview.height() * (1 - 96/PEBBLE_PPI))) +'px' + }) + }); + + row.append(preview_holder); + group.append(row); + }); }; var template = pane.find('.resource-id-group-single').detach(); @@ -709,9 +706,11 @@ CloudPebble.Resources = (function() { clone = template.clone().removeClass('hide').attr('id',''); } parent.append(clone); - clone.find('input[type=text], input[type=number]').on('input', function() { - update_font_preview(clone); - }); + if (resource.kind == 'font') { + clone.find('input[type=text], input[type=number]').on('input', function () { + update_font_preview(clone); + }); + } CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); }); From 57b05b6cc102ab084bef0391c89df0c9bffe8dcd Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 21 Oct 2015 17:44:41 -0700 Subject: [PATCH 10/60] Typo fixes --- ide/static/ide/js/resources.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 79738ed1..981274d1 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -308,11 +308,11 @@ CloudPebble.Resources = (function() { var new_tag_values = get_new_tag_values(form, !!file, true); if (CloudPebble.ProjectInfo.sdk_version == "2" && new_tag_values.length > 1) { - report_error(gettext("SDK 2 projects do not support multiple files per resource. Please delete extra files.")) - returnl + report_error(gettext("SDK 2 projects do not support multiple files per resource. Please delete extra files.")); + return; } - // Ensure that all variant's tags are unique + // Ensure that all variants' tags are unique if (_.uniq(_.map(new_tag_values, JSON.stringify)).length != new_tag_values.length) { report_error(gettext("Each variant must have a different set of tags")); return; From 92b2fb0a7839142416fd042134c3ee70149e4bf1 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Thu, 22 Oct 2015 12:02:28 -0700 Subject: [PATCH 11/60] Update resource.html CSS fix --- ide/templates/ide/project/resource.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index ae9d1aa0..7abe2d8c 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -70,7 +70,7 @@
-
+
From d3a65be487e282fdd0e82365046ee8b6d48aa144 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Fri, 20 Nov 2015 16:14:22 -0800 Subject: [PATCH 12/60] Make both screenshot buttons use the current connection. --- ide/static/ide/js/compile.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/ide/static/ide/js/compile.js b/ide/static/ide/js/compile.js index 827c9c96..2ef5a852 100644 --- a/ide/static/ide/js/compile.js +++ b/ide/static/ide/js/compile.js @@ -119,9 +119,9 @@ CloudPebble.Compile = (function() { e.preventDefault(); show_app_logs(ConnectionType.Phone); }); - pane.find('#screenshot-btn').click(function(e) { + pane.find('#screenshot-btn, #screenshot-qemu-btn').click(function(e) { e.preventDefault(); - take_screenshot(ConnectionType.Phone); + take_screenshot(); }); pane.find('#android-beta-link').click(function(e) { e.preventDefault(); @@ -147,10 +147,7 @@ CloudPebble.Compile = (function() { e.preventDefault(); show_app_logs(ConnectionType.Qemu); }); - pane.find('#screenshot-qemu-btn').click(function(e) { - e.preventDefault(); - take_screenshot(ConnectionType.Qemu); - }); + var targetTabs = pane.find('#run-target-tabs'); targetTabs.on('shown', function(e) { localStorage['activeTarget'] = $(e.target).data('run-target'); From b3cbc6b3fe11fdaa40504a9ed93c50a384d9ed99 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 26 Nov 2015 23:55:41 -0800 Subject: [PATCH 13/60] Try updating requirements.txt? --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index fce9f2e9..8d582c9c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ Django==1.6.2 --e hg+https://bitbucket.org/ubernostrum/django-registration#egg=django-registration +django-registration==1.0 amqp==1.4.4 celery==3.1.10 django_celery==3.1.10 From 90f9cba98d7e7d4ee129edcce4aff3d4185ca3a3 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Sat, 28 Nov 2015 08:06:35 -0800 Subject: [PATCH 14/60] Corrected New file->target help box z-index A z-index of 10002 will ensure that it's drawn above the modal in all circumstances. --- ide/static/ide/css/ide.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/static/ide/css/ide.css b/ide/static/ide/css/ide.css index 56f89a00..72177a55 100644 --- a/ide/static/ide/css/ide.css +++ b/ide/static/ide/css/ide.css @@ -1049,7 +1049,7 @@ span.cm-autofilled-end { } #help-prompt-holder { - z-index: 10001; + z-index: 10002; position: relative; } From 0d294f9ea5d0dbf4facf40dda14fa9331e44e5b7 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 30 Nov 2015 15:40:31 -0800 Subject: [PATCH 15/60] Support for new 'bitmap' resource type --- ide/api/project.py | 2 +- ide/api/resource.py | 65 +++--- ...ier_memory_format__add_field_resourceid.py | 185 +++++++++++++++++ .../0043_convert_resource_kinds_to_bitmap.py | 187 ++++++++++++++++++ ide/models/files.py | 47 ++++- ide/models/project.py | 12 +- ide/static/ide/js/resources.js | 51 +++-- ide/tasks/archive.py | 23 ++- ide/tasks/gist.py | 8 +- ide/templates/ide/project/resource.html | 57 +++++- ide/utils/sdk.py | 6 + 11 files changed, 567 insertions(+), 76 deletions(-) create mode 100644 ide/migrations/0042_auto__add_field_resourceidentifier_memory_format__add_field_resourceid.py create mode 100644 ide/migrations/0043_convert_resource_kinds_to_bitmap.py diff --git a/ide/api/project.py b/ide/api/project.py index dc7c95e5..96408eb2 100644 --- a/ide/api/project.py +++ b/ide/api/project.py @@ -51,7 +51,7 @@ def project_info(request, project_id): 'file_name': x.file_name, 'kind': x.kind, 'identifiers': [y.resource_id for y in x.identifiers.all()], - 'extra': {y.resource_id: {'regex': y.character_regex, 'tracking': y.tracking, 'compatibility': y.compatibility} for y in x.identifiers.all()}, + 'extra': {y.resource_id: y.get_options_dict(with_id=False) for y in x.identifiers.all()}, 'variants': [y.get_tags() for y in x.variants.all()], } for x in resources], 'github': { diff --git a/ide/api/resource.py b/ide/api/resource.py index 680d545f..356bb0b9 100644 --- a/ide/api/resource.py +++ b/ide/api/resource.py @@ -14,6 +14,26 @@ __author__ = 'katharine' +def decode_resource_id_options(request): + """ Extract resource ID options from a HTTP request, making sure the keys have the same names as the + ResourceIdentifier object's fields. """ + return { + # Resource ID + 'resource_id': request['id'], + 'target_platforms': json.dumps(request['target_platforms']) if 'target_platforms' in request else None, + + # Font options + 'character_regex': request.get('regex', None), + 'tracking': int(request['tracking']) if 'tracking' in request else None, + 'compatibility': request.get('compatibility', None), + + # Bitmap options + 'memory_format': request.get('memory_format', None), + 'storage_format': request.get('storage_format', None), + 'space_optimisation': request.get('space_optimisation', None), + } + + @require_POST @login_required def create_resource(request, project_id): @@ -28,12 +48,8 @@ def create_resource(request, project_id): with transaction.atomic(): rf = ResourceFile.objects.create(project=project, file_name=file_name, kind=kind) for r in resource_ids: - regex = r['regex'] if 'regex' in r else None - tracking = int(r['tracking']) if 'tracking' in r else None - target_platforms = json.dumps(r['target_platforms']) if 'target_platforms' in r else None - resources.append(ResourceIdentifier.objects.create(resource_file=rf, resource_id=r['id'], - character_regex=regex, tracking=tracking, - target_platforms=target_platforms)) + resource_options = decode_resource_id_options(r) + resources.append(ResourceIdentifier.objects.create(resource_file=rf, **resource_options)) if posted_file is not None: variant = ResourceVariant.objects.create(resource_file=rf, tags=",".join(str(int(t)) for t in new_tags)) variant.save_file(posted_file, posted_file.size) @@ -55,14 +71,10 @@ def create_resource(request, project_id): "id": rf.id, "kind": rf.kind, "file_name": rf.file_name, - "resource_ids": [{ - 'id': x.resource_id, - 'regex': x.character_regex, - 'target_platforms': json.loads(x.target_platforms) if x.target_platforms else None - } for x in resources], + "resource_ids": [x.get_options_dict(with_id=True) for x in resources], "identifiers": [x.resource_id for x in resources], "variants": [x.get_tags() for x in rf.variants.all()], - "extra": {y.resource_id: {'regex': y.character_regex, 'tracking': y.tracking} for y in rf.identifiers.all()} + "extra": {y.resource_id: y.get_options_dict(with_id=False) for y in rf.identifiers.all()} }}) @@ -83,18 +95,12 @@ def resource_info(request, project_id, resource_id): return json_response({ 'resource': { - 'resource_ids': [{ - 'id': x.resource_id, - 'regex': x.character_regex, - 'tracking': x.tracking, - 'compatibility': x.compatibility, - 'target_platforms': json.loads(x.target_platforms) if x.target_platforms else None - } for x in resources], + 'resource_ids': [x.get_options_dict(with_id=True) for x in resources], 'id': resource.id, 'file_name': resource.file_name, 'kind': resource.kind, "variants": [x.get_tags() for x in resource.variants.all()], - "extra": {y.resource_id: {'regex': y.character_regex, 'tracking': y.tracking, 'compatibility': y.compatibility} for y in resource.identifiers.all()} + "extra": {y.resource_id: y.get_options_dict(with_id=False) for y in resource.identifiers.all()} } }) @@ -168,11 +174,8 @@ def update_resource(request, project_id, resource_id): resources = [] ResourceIdentifier.objects.filter(resource_file=resource).delete() for r in resource_ids: - regex = r['regex'] if 'regex' in r else None - tracking = int(r['tracking']) if 'tracking' in r else None - compat = r['compatibility'] if 'compatibility' in r else None - target_platforms = json.dumps(r['target_platforms']) if 'target_platforms' in r else None - resources.append(ResourceIdentifier.objects.create(resource_file=resource, resource_id=r['id'], character_regex=regex, tracking=tracking, compatibility=compat, target_platforms=target_platforms)) + resource_options = decode_resource_id_options(r) + resources.append(ResourceIdentifier.objects.create(resource_file=resource, **resource_options)) # We get sent a list of (tags_before, tags_after) pairs. updated_variants = [] @@ -194,8 +197,6 @@ def update_resource(request, project_id, resource_id): replacement = replacement_files[int(file_index)] variant.save_file(replacement, replacement.size) - resource.target_platforms = None if target_platforms is None else json.dumps(target_platforms) - if file_name and resource.file_name != file_name: resource.file_name = file_name @@ -215,15 +216,10 @@ def update_resource(request, project_id, resource_id): "id": resource.id, "kind": resource.kind, "file_name": resource.file_name, - "resource_ids": [{ - 'id': x.resource_id, - 'regex': x.character_regex, - 'compatibility': x.compatibility, - 'target_platforms': json.loads(x.target_platforms) if x.target_platforms else None - } for x in resources], + "resource_ids": [x.get_options_dict(with_id=True) for x in resources], "identifiers": [x.resource_id for x in resources], "variants": [x.get_tags() for x in resource.variants.all()], - "extra": {y.resource_id: {'regex': y.character_regex, 'tracking': y.tracking, 'compatibility': y.compatibility} for y in resource.identifiers.all()} + "extra": {y.resource_id: y.get_options_dict(with_id=False) for y in resource.identifiers.all()} }}) @@ -238,6 +234,7 @@ def show_resource(request, project_id, resource_id, variant): content_types = { u'png': 'image/png', u'png-trans': 'image/png', + u'bitmap': 'image/png', u'font': 'application/octet-stream', u'raw': 'application/octet-stream' } diff --git a/ide/migrations/0042_auto__add_field_resourceidentifier_memory_format__add_field_resourceid.py b/ide/migrations/0042_auto__add_field_resourceidentifier_memory_format__add_field_resourceid.py new file mode 100644 index 00000000..d1011248 --- /dev/null +++ b/ide/migrations/0042_auto__add_field_resourceidentifier_memory_format__add_field_resourceid.py @@ -0,0 +1,185 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import SchemaMigration +from django.db import models + + +class Migration(SchemaMigration): + + def forwards(self, orm): + # Adding field 'ResourceIdentifier.memory_format' + db.add_column(u'ide_resourceidentifier', 'memory_format', + self.gf('django.db.models.fields.CharField')(max_length=15, null=True, blank=True), + keep_default=False) + + # Adding field 'ResourceIdentifier.storage_format' + db.add_column(u'ide_resourceidentifier', 'storage_format', + self.gf('django.db.models.fields.CharField')(max_length=3, null=True, blank=True), + keep_default=False) + + # Adding field 'ResourceIdentifier.space_optimisation' + db.add_column(u'ide_resourceidentifier', 'space_optimisation', + self.gf('django.db.models.fields.CharField')(max_length=7, null=True, blank=True), + keep_default=False) + + + def backwards(self, orm): + # Deleting field 'ResourceIdentifier.memory_format' + db.delete_column(u'ide_resourceidentifier', 'memory_format') + + # Deleting field 'ResourceIdentifier.storage_format' + db.delete_column(u'ide_resourceidentifier', 'storage_format') + + # Deleting field 'ResourceIdentifier.space_optimisation' + db.delete_column(u'ide_resourceidentifier', 'space_optimisation') + + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ide.buildresult': { + 'Meta': {'object_name': 'BuildResult'}, + 'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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': "'d9f95e19-62d6-4bdc-aa0f-22f41ae2cc88'", 'max_length': '36'}) + }, + 'ide.buildsize': { + 'Meta': {'object_name': 'BuildSize'}, + 'binary_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sizes'", 'to': "orm['ide.BuildResult']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'resource_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'worker_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.project': { + 'Meta': {'object_name': 'Project'}, + 'app_capabilities': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'app_company_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'app_is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_shown_on_communication': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_watchface': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_jshint': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'app_keys': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'app_long_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': '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': "'479b2c75-8eed-4463-963e-a5b0d01749ee'", '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'}), + 'github_hook_uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'github_last_commit': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'github_last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'github_repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'optimisation': ('django.db.models.fields.CharField', [], {'default': "'s'", 'max_length': '1'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'project_type': ('django.db.models.fields.CharField', [], {'default': "'native'", 'max_length': '10'}), + 'sdk_version': ('django.db.models.fields.CharField', [], {'default': "'2'", 'max_length': '6'}) + }, + 'ide.resourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'ResourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_menu_icon': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'kind': ('django.db.models.fields.CharField', [], {'max_length': '9'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'resources'", 'to': "orm['ide.Project']"}) + }, + 'ide.resourceidentifier': { + 'Meta': {'object_name': 'ResourceIdentifier'}, + 'character_regex': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'compatibility': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'memory_format': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['ide.ResourceFile']"}), + 'resource_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'space_optimisation': ('django.db.models.fields.CharField', [], {'max_length': '7', 'null': 'True', 'blank': 'True'}), + 'storage_format': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'tracking': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.resourcevariant': { + 'Meta': {'unique_together': "(('resource_file', 'tags'),)", 'object_name': 'ResourceVariant'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_legacy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variants'", 'to': "orm['ide.ResourceFile']"}), + 'tags': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'max_length': '50', 'blank': 'True'}) + }, + 'ide.sourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'SourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'folded_lines': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_files'", 'to': "orm['ide.Project']"}), + 'target': ('django.db.models.fields.CharField', [], {'default': "'app'", 'max_length': '10'}) + }, + 'ide.templateproject': { + 'Meta': {'object_name': 'TemplateProject', '_ormbases': ['ide.Project']}, + u'project_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ide.Project']", 'unique': 'True', 'primary_key': 'True'}), + 'template_kind': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) + }, + 'ide.usergithub': { + 'Meta': {'object_name': 'UserGithub'}, + 'avatar': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'github'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['auth.User']"}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}) + }, + 'ide.usersettings': { + 'Meta': {'object_name': 'UserSettings'}, + 'accepted_terms': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'autocomplete': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'keybinds': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}), + 'tab_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '2'}), + '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': '19'}) + } + } + + complete_apps = ['ide'] \ No newline at end of file diff --git a/ide/migrations/0043_convert_resource_kinds_to_bitmap.py b/ide/migrations/0043_convert_resource_kinds_to_bitmap.py new file mode 100644 index 00000000..c9bd2bb5 --- /dev/null +++ b/ide/migrations/0043_convert_resource_kinds_to_bitmap.py @@ -0,0 +1,187 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +class Migration(DataMigration): + + def forwards(self, orm): + """ Convert PNGs and PBIs to bitmaps """ + for resource_file in orm.ResourceFile.objects.all(): + if resource_file.kind in ('png', 'pbi'): + if resource_file.kind == 'pbi': + # Set 1-bit memory format for all resource-IDs for PBIs. + identifiers = orm.ResourceIdentifier.objects.filter(resource_file=resource_file) + for resource_identifier in identifiers: + resource_identifier.memory_format = '1Bit' + resource_identifier.save() + resource_file.kind = 'bitmap' + resource_file.save() + + + def backwards(self, orm): + """ This migration will loose information if CloudPebble has been used since the forward migration, + since it is not possible to determine if a thing was a png or a pbi if its resource IDs are ambiguous.""" + for resource_file in orm.ResourceFile.objects.all(): + if resource_file.kind == 'bitmap': + identifiers = orm.ResourceIdentifier.objects.filter(resource_file=resource_file) + for resource_identifier in identifiers: + # If any resource ID has a memory format that isn't 1 bit, it was a PNG + if resource_identifier.memory_format != '1Bit': + resource_file.kind = 'png' + break + else: + # Otherwise, all formats are 1-bit, so it was a PBI + resource_file.kind = 'pbi' + resource_file.save() + + models = { + u'auth.group': { + 'Meta': {'object_name': 'Group'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), + 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': u"orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) + }, + u'auth.permission': { + 'Meta': {'ordering': "(u'content_type__app_label', u'content_type__model', u'codename')", 'unique_together': "((u'content_type', u'codename'),)", 'object_name': 'Permission'}, + 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['contenttypes.ContentType']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) + }, + u'auth.user': { + 'Meta': {'object_name': 'User'}, + 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), + 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Group']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), + 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), + 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), + 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "u'user_set'", 'blank': 'True', 'to': u"orm['auth.Permission']"}), + 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) + }, + u'contenttypes.contenttype': { + 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, + 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) + }, + 'ide.buildresult': { + 'Meta': {'object_name': 'BuildResult'}, + 'finished': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + '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': "'6fc5613b-a8bf-4ef2-bb75-8937a5db362d'", 'max_length': '36'}) + }, + 'ide.buildsize': { + 'Meta': {'object_name': 'BuildSize'}, + 'binary_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'build': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'sizes'", 'to': "orm['ide.BuildResult']"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'platform': ('django.db.models.fields.CharField', [], {'max_length': '20'}), + 'resource_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'total_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}), + 'worker_size': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.project': { + 'Meta': {'object_name': 'Project'}, + 'app_capabilities': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'app_company_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'app_is_hidden': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_shown_on_communication': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_is_watchface': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'app_jshint': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'app_keys': ('django.db.models.fields.TextField', [], {'default': "'{}'"}), + 'app_long_name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': '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': "'3c67d0b9-5d89-4238-9fb4-2333a95df88f'", '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'}), + 'github_hook_uuid': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'github_last_commit': ('django.db.models.fields.CharField', [], {'max_length': '40', 'null': 'True', 'blank': 'True'}), + 'github_last_sync': ('django.db.models.fields.DateTimeField', [], {'null': 'True', 'blank': 'True'}), + 'github_repo': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}), + 'optimisation': ('django.db.models.fields.CharField', [], {'default': "'s'", 'max_length': '1'}), + 'owner': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['auth.User']"}), + 'project_type': ('django.db.models.fields.CharField', [], {'default': "'native'", 'max_length': '10'}), + 'sdk_version': ('django.db.models.fields.CharField', [], {'default': "'2'", 'max_length': '6'}) + }, + 'ide.resourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'ResourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_menu_icon': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'kind': ('django.db.models.fields.CharField', [], {'max_length': '9'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'resources'", 'to': "orm['ide.Project']"}) + }, + 'ide.resourceidentifier': { + 'Meta': {'object_name': 'ResourceIdentifier'}, + 'character_regex': ('django.db.models.fields.CharField', [], {'max_length': '100', 'null': 'True', 'blank': 'True'}), + 'compatibility': ('django.db.models.fields.CharField', [], {'max_length': '10', 'null': 'True', 'blank': 'True'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'memory_format': ('django.db.models.fields.CharField', [], {'max_length': '15', 'null': 'True', 'blank': 'True'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'identifiers'", 'to': "orm['ide.ResourceFile']"}), + 'resource_id': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'space_optimisation': ('django.db.models.fields.CharField', [], {'max_length': '7', 'null': 'True', 'blank': 'True'}), + 'storage_format': ('django.db.models.fields.CharField', [], {'max_length': '3', 'null': 'True', 'blank': 'True'}), + 'target_platforms': ('django.db.models.fields.CharField', [], {'default': 'None', 'max_length': '30', 'null': 'True', 'blank': 'True'}), + 'tracking': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}) + }, + 'ide.resourcevariant': { + 'Meta': {'unique_together': "(('resource_file', 'tags'),)", 'object_name': 'ResourceVariant'}, + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_legacy': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'resource_file': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'variants'", 'to': "orm['ide.ResourceFile']"}), + 'tags': ('django.db.models.fields.CommaSeparatedIntegerField', [], {'max_length': '50', 'blank': 'True'}) + }, + 'ide.sourcefile': { + 'Meta': {'unique_together': "(('project', 'file_name'),)", 'object_name': 'SourceFile'}, + 'file_name': ('django.db.models.fields.CharField', [], {'max_length': '100'}), + 'folded_lines': ('django.db.models.fields.TextField', [], {'default': "'[]'"}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), + 'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'source_files'", 'to': "orm['ide.Project']"}), + 'target': ('django.db.models.fields.CharField', [], {'default': "'app'", 'max_length': '10'}) + }, + 'ide.templateproject': { + 'Meta': {'object_name': 'TemplateProject', '_ormbases': ['ide.Project']}, + u'project_ptr': ('django.db.models.fields.related.OneToOneField', [], {'to': "orm['ide.Project']", 'unique': 'True', 'primary_key': 'True'}), + 'template_kind': ('django.db.models.fields.IntegerField', [], {'db_index': 'True'}) + }, + 'ide.usergithub': { + 'Meta': {'object_name': 'UserGithub'}, + 'avatar': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}), + 'nonce': ('django.db.models.fields.CharField', [], {'max_length': '36', 'null': 'True', 'blank': 'True'}), + 'token': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}), + 'user': ('django.db.models.fields.related.OneToOneField', [], {'related_name': "'github'", 'unique': 'True', 'primary_key': 'True', 'to': u"orm['auth.User']"}), + 'username': ('django.db.models.fields.CharField', [], {'max_length': '50', 'null': 'True', 'blank': 'True'}) + }, + 'ide.usersettings': { + 'Meta': {'object_name': 'UserSettings'}, + 'accepted_terms': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), + 'autocomplete': ('django.db.models.fields.IntegerField', [], {'default': '1'}), + 'keybinds': ('django.db.models.fields.CharField', [], {'default': "'default'", 'max_length': '20'}), + 'tab_width': ('django.db.models.fields.PositiveSmallIntegerField', [], {'default': '2'}), + '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': '19'}) + } + } + + complete_apps = ['ide'] + symmetrical = True diff --git a/ide/models/files.py b/ide/models/files.py index e7b1f44b..fd39003e 100644 --- a/ide/models/files.py +++ b/ide/models/files.py @@ -2,7 +2,7 @@ import shutil import traceback import datetime -import re +import json from django.conf import settings from django.core.validators import RegexValidator from django.db import models @@ -22,6 +22,7 @@ class ResourceFile(IdeModel): project = models.ForeignKey('Project', related_name='resources') RESOURCE_KINDS = ( ('raw', _('Binary blob')), + ('bitmap', _('Bitmap Image')), ('png', _('1-bit PNG')), ('png-trans', _('1-bit PNG with transparency')), ('font', _('True-Type Font')), @@ -67,6 +68,7 @@ def save(self, *args, **kwargs): 'pbi': 'images', 'png': 'images', 'png-trans': 'images', + 'bitmap': 'images', 'font': 'fonts', 'raw': 'data' } @@ -220,6 +222,49 @@ class ResourceIdentifier(IdeModel): compatibility = models.CharField(max_length=10, blank=True, null=True) target_platforms = models.CharField(max_length=30, null=True, blank=True, default=None) + MEMORY_FORMATS = ( + ('Smallest', _('Smallest')), + ('SmallestPalette', _('Smallest Palette')), + ('1Bit', _('1-bit')), + ('8Bit', _('8-bit')), + ('1BitPalette', _('1-bit Palette')), + ('2BitPalette', _('2-bit Palette')), + ('4BitPalette', _('4-bit Palette')), + ) + memory_format = models.CharField(max_length=15, choices=MEMORY_FORMATS, null=True, blank=True) + + STORAGE_FORMATS = ( + ('pbi', _('1 bit Pebble Image')), + ('png', _('PNG')) + ) + storage_format = models.CharField(max_length=3, choices=STORAGE_FORMATS, null=True, blank=True) + + SPACE_OPTIMISATIONS = ( + ('storage', _('Storage')), + ('memory', _('Memory')) + ) + space_optimisation = models.CharField(max_length=7, choices=SPACE_OPTIMISATIONS, null=True, blank=True) + + def get_options_dict(self, with_id=False): + """ Return the ResourceIdentifier's options as a dictionary. Optionally include its ID in the key 'id' """ + d = { + # Resource ID + 'target_platforms': json.loads(self.target_platforms) if self.target_platforms else None, + + # Font options + 'regex': self.character_regex, + 'tracking': self.tracking, + 'compatibility': self.compatibility, + + # Bitmap options + 'memory_format': self.memory_format, + 'storage_format': self.storage_format, + 'space_optimisation': self.space_optimisation + } + if with_id: + d['id'] = self.resource_id + return d + def save(self, *args, **kwargs): self.resource_file.project.last_modified = now() self.resource_file.project.save() diff --git a/ide/models/project.py b/ide/models/project.py index 1e239e92..b72f03ea 100644 --- a/ide/models/project.py +++ b/ide/models/project.py @@ -105,7 +105,17 @@ def copy_into_project(self, project): new_variant = ResourceVariant.objects.create(resource_file=new_resource, tags=variant.tags) new_variant.save_string(variant.get_contents()) for i in resource.identifiers.all(): - ResourceIdentifier.objects.create(resource_file=new_resource, resource_id=i.resource_id, character_regex=i.character_regex) + ResourceIdentifier.objects.create( + resource_file=new_resource, + resource_id=i.resource_id, + character_regex=i.character_regex, + tracking=i.tracking, + compatability=i.compatibility, + target_platforms=i.target_platforms, + memory_format=i.memory_format, + storage_format=i.storage_format, + space_optmization=i.space_optimisation + ) for source_file in self.source_files.all(): new_file = SourceFile.objects.create(project=project, file_name=source_file.file_name) diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 981274d1..2011131b 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -203,13 +203,12 @@ CloudPebble.Resources = (function() { /** * Given the tags for each variant and a desired platform, figure out which variant will run. - * Returns null if there is no match. Throws an exception if there is a specicifity conflict for the platform. + * Returns null if there is no match. Throws an exception if there is a specificity conflict for the platform. * @param tag_values Array of arrays of tag IDs. * @param platform_name Name of platform to check * @returns {Array|null} */ function get_resource_for_platform(tag_values, platform_name) { - // TODO: This will eventually need to be updated to support setting targetPlatforms for each resource ID. // Find all variants with tags which fully apply to this platform. var platform_tags = PLATFORMS[platform_name]; var filtered_tags = _.filter(tag_values, function(var_tags) { @@ -228,7 +227,7 @@ CloudPebble.Resources = (function() { return "("+_.chain(conflict_tags).map(get_tag_data_for_id).pluck('name').join(', ')+")"; }).join(gettext(' and ')); throw { - description: interpolate(gettext("Conflict for platform '%s'. The variants with tags %s have the same specicifity."), [platform_name, conflict_string]), + description: interpolate(gettext("Conflict for platform '%s'. The variants with tags %s have the same specificity."), [platform_name, conflict_string]), conflicts: conflicts }; } @@ -252,10 +251,9 @@ CloudPebble.Resources = (function() { form.find('input, button, select').attr('disabled', 'disabled'); }; var enable_controls = function() { + form.find('input, button, .resource-id-group-single select').removeAttr('disabled'); if(is_new) { - form.find('input, button, select').removeAttr('disabled'); - } else { - form.find('input, button, .font-compat-option').removeAttr('disabled'); + form.find('select').removeAttr('disabled'); } }; @@ -377,6 +375,16 @@ CloudPebble.Resources = (function() { } _(resource).extend({'regex': regex, 'tracking': tracking, 'compatibility': compat}) } + + // Add in bitmap-specific per-ID settings, if applicable + if (kind == 'bitmap') { + _(resource).extend({ + memory_format: value.find('.bitmap-memory-format-option').val() || null, + storage_format: value.find('.bitmap-storage-format-option').val() || null, + space_optimisation: value.find('.bitmap-space-optimisation-option').val() || null + }); + } + resource_ids[resource_id] = true; resources.push(resource); }); @@ -517,6 +525,7 @@ CloudPebble.Resources = (function() { variant_string = tags.join(','); } switch (kind) { + case 'bitmap': case 'png': case 'png-trans': template_name = 'image'; @@ -579,12 +588,7 @@ CloudPebble.Resources = (function() { pane.find('.btn-delvariant').toggle(resource.variants.length > 1); }; - if (true || resource.kind == 'png' || resource.kind == 'png-trans') { - generate_resource_previews(resource.kind); - } else { - var preview_url = '/ide/project/' + PROJECT_ID + '/resource/' + resource.id + '/0/get'; - pane.find('.resource-download-link').removeClass('hide').find('a').attr('href', preview_url); - } + generate_resource_previews(resource.kind); var update_font_preview = function(group) { group.find('.font-preview').remove(); @@ -667,6 +671,16 @@ CloudPebble.Resources = (function() { group.find('.font-only').remove(); } + if (resource.kind == 'bitmap') { + console.log(value); + group.find('.bitmap-memory-format-option').val(value.memory_format|| ""); + group.find('.bitmap-storage-format-option').val(value.storage_format || ""); + group.find('.bitmap-space-optimisation-option').val(value.space_optimisation || ""); + } + else { + group.find('.bitmap-only').remove() + } + var has_target_platforms = _.isArray(value["target_platforms"]); if (has_target_platforms) { var target_platforms_checkbox = group.find(".edit-resource-target-platforms-enabled"); @@ -714,7 +728,7 @@ CloudPebble.Resources = (function() { CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); }); - pane.find('.image-platform-preview').toggle((resource.kind == 'png' || resource.kind == 'png-trans')); + pane.find('.image-platform-preview').toggle((_.contains(['png', 'png-trans', 'bitmap'], resource.kind))); pane.find("#edit-resource-file-name").val(resource.file_name); @@ -848,13 +862,10 @@ CloudPebble.Resources = (function() { template.find('.nont-font-only').removeClass('hide'); template.find('.image-platform-preview').hide(); template.find('#edit-resource-type').change(function() { - if($(this).val() == 'font') { - template.find('.font-only').removeClass('hide'); - template.find('.non-font-only').addClass('hide'); - } else { - template.find('.font-only').addClass('hide'); - template.find('.non-font-only').removeClass('hide'); - } + var kind = $(this).val(); + template.find('.non-font-only').toggleClass('hide', (kind === 'font')); + template.find('.font-only').toggleClass('hide', (kind !== 'font')); + template.find('.bitmap-only').toggleClass('hide', (kind !== 'bitmap')); }); template.find("#edit-resource-file").change(function() { diff --git a/ide/tasks/archive.py b/ide/tasks/archive.py index a2d61c47..46aba81e 100644 --- a/ide/tasks/archive.py +++ b/ide/tasks/archive.py @@ -255,20 +255,19 @@ def make_valid_filename(zip_entry): # Now add all the resource identifiers for root_file_name in desired_resources: for resource in desired_resources[root_file_name]: - print resource - identifier = resource['name'] - tracking = resource.get('trackingAdjust', None) - regex = resource.get('characterRegex', None) - compatibility = resource.get('compatibility', None) - target_platforms = resource.get('targetPlatforms', None) - target_platforms = json.dumps(target_platforms) if target_platforms is not None else None + target_platforms = json.dumps(resource['targetPlatforms']) if 'targetPlatforms' in resource else None ResourceIdentifier.objects.create( resource_file=resources_files[root_file_name], - resource_id=identifier, - character_regex=regex, - tracking=tracking, - compatibility=compatibility, - target_platforms=target_platforms + resource_id=resource['name'], + target_platforms=target_platforms, + # Font options + character_regex=resource.get('characterRegex', None), + tracking=resource.get('trackingAdjust', None), + compatibility=resource.get('compatibility', None), + # Bitmap options + memory_format=resource.get('memoryFormat', None), + storage_format=resource.get('storageFormat', None), + space_optimisation=resource.get('spaceOptimization', None) ) # Check that at least one variant of each specified resource exists. diff --git a/ide/tasks/gist.py b/ide/tasks/gist.py index efd9a562..a891cc7c 100644 --- a/ide/tasks/gist.py +++ b/ide/tasks/gist.py @@ -85,6 +85,9 @@ def import_gist(user_id, gist_id): filename = resource['file'] regex = resource.get('characterRegex', None) tracking = resource.get('trackingAdjust', None) + memory_format = resource.get('memoryFormat', None) + storage_format = resource.get('storageFormat', None) + space_optimisation = resource.get('spaceOptimization', None) is_menu_icon = resource.get('menuIcon', False) compatibility = resource.get('compatibility', None) if filename not in gist.files: @@ -102,7 +105,10 @@ def import_gist(user_id, gist_id): resource_id=def_name, character_regex=regex, tracking=tracking, - compatibility=compatibility + compatibility=compatibility, + memory_format=memory_format, + storage_format=storage_format, + space_optimisation=space_optimisation ) else: source_file = SourceFile.objects.create(project=project, file_name='app.js') diff --git a/ide/templates/ide/project/resource.html b/ide/templates/ide/project/resource.html index 7abe2d8c..1f9c87c1 100644 --- a/ide/templates/ide/project/resource.html +++ b/ide/templates/ide/project/resource.html @@ -8,14 +8,18 @@
@@ -69,6 +73,47 @@ {% trans 'Determines the font rendering algorithm to use. The latest looks better, but can produce slightly larger characters.' %}
+
+ +
+ + {% trans 'Determines the bitmap type' %} + {# TODO: consider explaining 'best' #} + {# TODO: consider linking to developer documentation #} +
+
+
+ +
+ + {% trans 'Determines the file format used for storage.' %} +
+
+
+ +
+ + {% trans 'Determines whether the output resource is optimised for low runtime memory or low resource space usage.' %} +
+
+
diff --git a/root/static/common/css/common.css b/root/static/common/css/common.css index df92ef2c..8eed2b58 100644 --- a/root/static/common/css/common.css +++ b/root/static/common/css/common.css @@ -406,7 +406,6 @@ input[type=checkbox]:disabled { .help-block { display: block; - clear: both; margin-left: 150px; padding-top: 20px; font-family: PFD-Light, 'Helvetica Neue', Helvetica, Arial, sans-serif; From 52b7fcb6c975e9172dec1b3438d29cc71ca87dac Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Wed, 2 Dec 2015 17:17:58 -0800 Subject: [PATCH 17/60] Build better. --- bin/post_compile | 6 +++--- cloudpebble/settings.py | 2 +- ide/tasks/build.py | 16 ++++++++-------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index e99f0a27..d0abfa0d 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -9,7 +9,7 @@ echo "Downloading SDK 2" curl -o /tmp/pebblesdk2.tar.gz -L https://sdk.getpebble.com/download/2.8.1?source=cloudpebble echo "Downloading SDK 3" -curl -o /tmp/pebblesdk3.tar.gz -L https://sdk.getpebble.com/download/3.7?source=cloudpebble +curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8-beta8.tar.bz2 echo "Downloading the toolchain" curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar @@ -35,8 +35,8 @@ pushd sdk3 tar --strip 1 -xf /tmp/pebblesdk3.tar.gz echo "Extracting the toolchain" tar -xf /tmp/arm-cs-tools.tar - echo "Preparing virtualenv" - virtualenv .env +# echo "Preparing virtualenv" +# virtualenv .env popd touch NO_TRACKING diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 272f680f..31ec7242 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -279,7 +279,7 @@ GITHUB_HOOK_TEMPLATE = _environ.get('GITHUB_HOOK', 'http://example.com/ide/project/%(project)d/github/push_hook?key=%(key)s') SDK2_PEBBLE_TOOL = _environ.get('SDK2_PEBBLE_TOOL', '/home/vagrant/sdk2/bin/pebble') -SDK3_PEBBLE_TOOL = _environ.get('SDK3_PEBBLE_TOOL', '/home/vagrant/sdk3/bin/pebble') +SDK3_PEBBLE_WAF = _environ.get('SDK3_PEBBLE_WAF', '/home/vagrant/sdk3/pebble/waf') ARM_CS_TOOLS = _environ.get('ARM_CS_TOOLS', '/home/vagrant/arm-cs-tools/bin/') diff --git a/ide/tasks/build.py b/ide/tasks/build.py index ad250962..440d4303 100644 --- a/ide/tasks/build.py +++ b/ide/tasks/build.py @@ -78,9 +78,9 @@ def save_debug_info(base_dir, build_result, kind, platform, elf_file): build_result.save_debug_info(debug_info, platform, kind) -def store_size_info(build_result, platform, zip_file): +def store_size_info(project, build_result, platform, zip_file): platform_dir = platform + '/' - if platform == 'aplite': + if platform == 'aplite' and project.sdk_version == '2': platform_dir = '' try: build_size = BuildSize.objects.create( @@ -170,12 +170,12 @@ def run_compile(build_result): try: os.chdir(base_dir) if project.sdk_version == '2': - tool = settings.SDK2_PEBBLE_TOOL + command = [settings.SDK2_PEBBLE_TOOL, "build"] elif project.sdk_version == '3': - tool = settings.SDK3_PEBBLE_TOOL + command = [settings.SDK3_PEBBLE_WAF, "configure", "build"] else: raise Exception("invalid sdk version.") - output = subprocess.check_output([tool, "build"], stderr=subprocess.STDOUT, preexec_fn=_set_resource_limits) + output = subprocess.check_output(command, stderr=subprocess.STDOUT, preexec_fn=_set_resource_limits) except subprocess.CalledProcessError as e: output = e.output print output @@ -200,9 +200,9 @@ def run_compile(build_result): build_result.total_size = s.st_size # Now peek into the zip to see the component parts with zipfile.ZipFile(temp_file, 'r') as z: - store_size_info(build_result, 'aplite', z) - store_size_info(build_result, 'basalt', z) - store_size_info(build_result, 'chalk', z) + store_size_info(project, build_result, 'aplite', z) + store_size_info(project, build_result, 'basalt', z) + store_size_info(project, build_result, 'chalk', z) except Exception as e: print "Couldn't extract filesizes: %s" % e From 86414f440c4aa9d8f62e61ead07e106b86a200b9 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Wed, 2 Dec 2015 14:50:49 -0800 Subject: [PATCH 18/60] Update pebble.js --- ext/pebblejs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/pebblejs b/ext/pebblejs index 97d06e60..d7c7acba 160000 --- a/ext/pebblejs +++ b/ext/pebblejs @@ -1 +1 @@ -Subproject commit 97d06e60db5db84c05fe78159cc2d57ace5a9a1a +Subproject commit d7c7acba1cff9b2e55d6620daa8e3c89758dafc5 From 728d321ec520b0d3a58cf7e5eb234fe15474c706 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Wed, 2 Dec 2015 17:30:54 -0800 Subject: [PATCH 19/60] Put the tools in the PATH. --- ide/tasks/build.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ide/tasks/build.py b/ide/tasks/build.py index 440d4303..959d89ba 100644 --- a/ide/tasks/build.py +++ b/ide/tasks/build.py @@ -170,12 +170,16 @@ def run_compile(build_result): try: os.chdir(base_dir) if project.sdk_version == '2': + environ = os.environ command = [settings.SDK2_PEBBLE_TOOL, "build"] elif project.sdk_version == '3': + environ = os.environ.copy() + environ['PATH'] = '{}:{}'.format(settings.ARM_CS_TOOLS, environ['PATH']) command = [settings.SDK3_PEBBLE_WAF, "configure", "build"] else: raise Exception("invalid sdk version.") - output = subprocess.check_output(command, stderr=subprocess.STDOUT, preexec_fn=_set_resource_limits) + output = subprocess.check_output(command, stderr=subprocess.STDOUT, preexec_fn=_set_resource_limits, + env=environ) except subprocess.CalledProcessError as e: output = e.output print output From 35a8a3591bfcf2d183a5d2c70a7f5dde3401a361 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 2 Dec 2015 17:56:30 -0800 Subject: [PATCH 20/60] Put help-block clear: both back for resource-pane --- ide/static/ide/css/ide.css | 1 + 1 file changed, 1 insertion(+) diff --git a/ide/static/ide/css/ide.css b/ide/static/ide/css/ide.css index 5bd96bf8..ed14ab59 100644 --- a/ide/static/ide/css/ide.css +++ b/ide/static/ide/css/ide.css @@ -637,6 +637,7 @@ table.build-results tr.pending .build-state { .resource-pane .help-block { margin-left: 170px; + clear: both; } .resource-pane input:not([type=checkbox]):not([type=submit]) { From 90238622b8b4f5c2297f5fd359724776457877c3 Mon Sep 17 00:00:00 2001 From: Jonathan Panttaja Date: Thu, 3 Dec 2015 13:05:05 -0800 Subject: [PATCH 21/60] Read and write worker_src files from Github commiting to github is untested --- ide/tasks/archive.py | 7 +++++++ ide/tasks/git.py | 7 ++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/ide/tasks/archive.py b/ide/tasks/archive.py index 91a9c244..e07364e0 100644 --- a/ide/tasks/archive.py +++ b/ide/tasks/archive.py @@ -133,6 +133,7 @@ def do_import_archive(project_id, archive, delete_project=False): # - Parse resource_map.json and import files it references MANIFEST = 'appinfo.json' SRC_DIR = 'src/' + WORKER_SRC_DIR = 'worker_src/' RES_PATH = 'resources' if len(contents) > 400: @@ -298,6 +299,12 @@ def make_valid_filename(zip_entry): source = SourceFile.objects.create(project=project, file_name=base_filename) with z.open(entry.filename) as f: source.save_file(f.read().decode('utf-8')) + elif filename.startswith(WORKER_SRC_DIR): + if (not filename.startswith('.')) and (filename.endswith('.c') or filename.endswith('.h') or filename.endswith('.js')): + base_filename = filename[len(WORKER_SRC_DIR):] + source = SourceFile.objects.create(project=project, file_name=base_filename, target='worker') + with z.open(entry.filename) as f: + source.save_file(f.read().decode('utf-8')) project.save() send_keen_event('cloudpebble', 'cloudpebble_zip_import_succeeded', project=project) diff --git a/ide/tasks/git.py b/ide/tasks/git.py index 6c1b8d58..530becd9 100644 --- a/ide/tasks/git.py +++ b/ide/tasks/git.py @@ -85,10 +85,15 @@ def github_push(user, commit_message, repo_name, project): root = '' src_root = root + 'src/' + worker_src_root = root + 'worker_src/' project_sources = project.source_files.all() has_changed = False for source in project_sources: - repo_path = src_root + source.file_name + repo_path = '' + if source.target == 'worker': + repo_path = worker_src_root + source.file_name + else: + repo_path = src_root + source.file_name if repo_path not in next_tree: has_changed = True next_tree[repo_path] = InputGitTreeElement(path=repo_path, mode='100644', type='blob', From 28d2564d1b03770bc9fe2961ae8f50fdd74d80ca Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Mon, 7 Dec 2015 16:13:10 -0800 Subject: [PATCH 22/60] aplite is 3.0 now. --- ide/api/qemu.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/api/qemu.py b/ide/api/qemu.py index ef0aed2f..1eea97af 100644 --- a/ide/api/qemu.py +++ b/ide/api/qemu.py @@ -24,7 +24,7 @@ def launch_emulator(request): oauth = request.POST['token'] tz_offset = request.POST['tz_offset'] versions = { - 'aplite': '2.9', + 'aplite': '3.0', 'basalt': '3.0', 'chalk': '3.0', } From b7c06efd4222690525a849436ca7ef3bc4ad4e2c Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 7 Dec 2015 16:29:21 -0800 Subject: [PATCH 23/60] Fix for certain log entries not appearing --- ide/static/ide/js/compile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/static/ide/js/compile.js b/ide/static/ide/js/compile.js index 827c9c96..879c0c47 100644 --- a/ide/static/ide/js/compile.js +++ b/ide/static/ide/js/compile.js @@ -377,7 +377,7 @@ CloudPebble.Compile = (function() { var append_log_html = function(html) { // Append the HTML line to the log var at_bottom = logs_scrolled_to_bottom(); - mLogHolder.append(html.append("\n")); + mLogHolder.append($(html).append("\n")); // Then, scroll to the bottom if we were already scrolled to the bottom before. if (at_bottom) { mLogHolder[0].scrollTop = mLogHolder[0].scrollHeight - mLogHolder.outerHeight(); From 181ce816fbcffa4e3d60c5c562499e9872f3ddc8 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Mon, 7 Dec 2015 18:26:24 -0800 Subject: [PATCH 24/60] Update boot-aplite. --- ide/static/ide/img/boot-aplite.png | Bin 4475 -> 4649 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/ide/static/ide/img/boot-aplite.png b/ide/static/ide/img/boot-aplite.png index 371a4810a3979d0e671efd4509a0b7631461e78b..7f7c98cb4a2292ea6f3f404e222ae0271ab7e592 100644 GIT binary patch literal 4649 zcmc&$XIN8Nv<*#)G{;5}h@mJD2)%?5N&rP9R1rid?>0!r#k zSmN&Tn5yb$V;BFGk5T7cSko}ugR@c~N2YKPMnI4#Os6Nb3`w#48Ve&=dqlM9ul1qxWPoclQxwBxnb>A(K64Qn}w)my8_1wuXZ~o z)`l(^9dFY@2ytC_+^NAV#6K0^lbN@0Tv1CFNJ#em(!c&HQEY)b#SIx|+X^G~&r~mh zFR**Zxs?(|?Cm9_J(ELobwBKEbpy1D@4$>&=lO-7FDBM5y0npy=GNmdw|Nlbq`Ihh zw>K&>K@D;&^sWbz_vXDJc^T*DD%`h5Y~l=2gNK#%BB&z`mACFLZV~2h>%7dRSVRew znBK{WRZEFJE>*31^sD1yV(SX~l{xlb3V8^u!nHix@;YjAOeboORc$au@z=PznlP1J z3i3P+k(Z2_4oZ&qk+|O>f2CDPQT{cv<0NOh-{>ljTgQnHK=3$oR*g&rJ~}@1Y9b>b zf2um_jO@4)6%h4+F;zTP^cUB*2|2~sxELAsDQ)2_09Wc^&flB!0yGH*(kl#sKu*2j z;{i;MwFP*CdK`r)%pBTBDBL%KIS7F6fSUx)L_q#9_zj>SKyX;)4Nr1_zZ|oDu$LVB zOCZ~Ogyr2s9}ziGCtLt=Ix113;|%LMr%jcP9TwNUeV#c#SOuYPDrw0e6eN)9kgC|p zG9}P`STpEQs@as#5{rQ&$Hnj`C@h7qG|JAmo7@ukr`pVx2Eh+G2+cwL_W?rSnQ6a-t>Dg{BQ=K-*1x3;e-#opKi+ zoNf>vd&u5MkehzS^~~ua%S+);LFFOk_xWodI9EGoTZ`v0^m4bnH$WXN&Mju4D0s@O zD6J^0z;Cc=MAVJ&sR<=2w+o(pXw;Bje|&6ZB;G6Ojr|uOj`2}r%NW-j_nJ0LFeL&~ zJ6kVOpVBz=j%P&cNE7p=ApUnTyolkW3vB2R(Gc$ULht2J_s;5fgFYW&in!{eQXJy* zjDmcte+q<*PB(;#v6}Nq#+5`^BCmn$Bs9gV&cuBD!^M^;7k92*JT~T0OyOhJrW8qq zV}ADp(fs;){RY`-WOHG&L5pllY>uLGSiIp^18k~&Iy}?=ajGGoVUgIIUj)Ua#IK0W zoPQfFcHx>~#rcA$)@X1HOw!kgc=6uZ44Dau`lp|Pmn5$n8nw;1cxmurq7>c`?`}eM?F1|j?o~6F4nf+Po*7vi}X;0E$ zpUbtSrj-y4Ki4Qv!uXZSz% z&)AV+qSQ7AxD|DzdduG^raA45{!#B2b_{W{4qRtY#~>OOonn@3mixP)v+)?;m{7yl zh9P6HvAHpK0!;pi{IYzTB39MU?IpfJK3A#I4XM1UG~_<$y3_l<7BuKoQ;#r;�L= z(IVS|x9YXh&6UVC^Dw4bQ>CbI!3yIj|WgCNcPjR~Q z!QUmK{qdT3cq}J0OQT4LTYX7GLC9H%2fCt>tzMjzlVq>vp+-vbND8-Gy875Ivf{0m zve)s7w$-dTpPB5LxT*dr^%?Z^>)zZ?f>au{s;=@; zM(b#ifb|%>7f$t)T{pjt@RPpnur|HPzWsR{bw^}3X~%@AgmH}_KM)vo{EgzAO%&4f zW@q8MH^ZsJ&BL*$U!JDwRv<9Xr4f%pkYVLvPeL@=`?yYqdh4Gz0N;1NyT&&yY$MPw zf+#of-+GZ}~$aO`pLt)i=L>+utPR%i3h+j-$K#xJZi z#}rd2O%%~$hyK9t*H-jSNf$`r62}_)>wMd;PtJ@+HfVf^ZQwbhCibhu12L%B%*9j2 z#0yeJ0%PU#Qa<>Ysu&)zI~Un6W*fyJA6~>FMbqnFCXKYrS**ocW#?Uam2=q%KCsGH%#D=1YteFhI7t_y|}tB6$9Xq61l4VezHLZ#Wd$)+~i zdh>euda#zycBeJa3<#bYaH^T8ee_JkS8K&nd8ophx~hvCSq86k30*PRZVtX*9WGBbCaLi_LhCd4GiLD z?YaiM8mHSsD`&h5zc_!b^wJvkpADX4<=K3?ysR}2Kjr)D#uRNVuhGJUnBtUj0v>bI zYUS#*@AB-;8r4+a)?-#o3azd?QQ9RHs6ZHji; z$bqZs#M@*6>9vHVp`5{~YW3yJ#T3=Xw1)6Ojv-`?v_E9GdM3C&ydWYq;#&+R#zN$< z*2g1YJhJY$rTWfD1rV z0fDxxVZ4Y&qnU~6HixE;X!{KzDyBZ=g-h2UqO zbS2r3Ta_qFCM5emS_$*f1XM2@o0z;tn%v3NzwgJX{cZdz9xf_bva9vXSpX~W*+Q&| zW%or56H1Du_atp1kN*Faop9cU1OOa9x_2=E;uHA*0A?|ar8U{w_#)I9kCjEa;GNL2 zK3D>s4FJG=p!6XYO-2EIu%0*))JGGv&w$d$doTn9+^3K|G(pzJra%Oqhz2UlD#^-$ z;2c085Jq%yg_`Rb{3)kDX@cCzWC9cd@%HwX^;VR{6Wt*4>gwtcIR%J!n;2xJT%2i%(%<%GXZ)&zm}jQ;-o$din5{l^JM`m-#0fsnlj zL|#@7@^3P<59U8)dy)T?xj6q_72!J3bH64RX9(I8jYZ?gB)U%iU+w6<{ag6IlJr3l zewghy@ked2z09HJXcFG@`d$kz;V@*l0&KsapW%Nx@DJ{mbJZ__M%-h@n@C+RF}S5szZo0^4+!>J4>iU3pgpbiF!YA*S0S&Y z1cUr*h4BDGaiAjt-owABT1yrJq_j2VMEs8vxEhB*NqX z0LBGFJsnFQ23oHz?u#5JpthuGWTi>d{vn$+TOO!MX^a)RiT()E=c1{Ji1;(J@dC92 z01S-($Af2wx7Ih>`Z#BkMbyBen(G=ul5@W>a8-tA)?|lG&}1XJi;O6X3I3Ca%;6 zyspy0OO=B&Z;xM(no#vL->Brr8lRof3B2?q97e7CEv)T#TOAq;ho%whih7O49HB8d zaJGuuYN!1b^~Fn+bOujcS74oIT@24|EB6`1+U>S?&2{$Fy7;jC**>e%Pcsic_iCM$ zOO&PCTWpz}YpPbugSL*|2yJOE+=^EP8>{RpK0)5nb-XlD=2Mr$O$xe28Qp3YSqWn) z+$H!J*AaOpqj?N)jkLh?tSda9qMJ8i1J}`YR?Qq90_UD_ikG6SN&b#mQg+%+ue`1M zi?Fk`0y!A(I>k!jLIQKxSh_Ti?+q?#K^3i9-m0FB)Y>_{m(oK$>HhPD{!!I42}dHk zzj-!N-8QJ3tku$-tiBzqjhH^vM9txq_HNa@OT6tx_N#47w!U@rsfGq#1ufg=KmQVx zZvKE-ss@#|HF=LkDUWX{^Am<*a`@!8z9J{n>Hnt<^PjsE2cA3WDU8r%VDMPo`%N^| LN9q;mItKj@SHRNH literal 4475 zcmds3S5#Bmx?YAJKsrd1;+86ej<6vjAiWAg5Gery1W7{g9h4?W4ON=-4hlh#BE2X@ zK&411qI9HJ;o^3ma~$K``*O!PFK3Op)|!9$zP~-p8L6kEN(p8G0{}p&uBLqV;vaYM z7L$T5zCo1VApig-Mk*=ksVgaQ>bYQSk&XxeP>XzoB{4Mo$oP1ud6X6Pc*b`V1uQqK zSLKOOh>X!Z7O+wZ0V~tf(C85+ms*O@lL>G~JmSm=BBE5IrnagMB66Q0+O{$`s-0b}x+@L~#4C_@ z2Ghq~wow4h2B9KoWB6!#C(d`E$CFS~8wBW6>{?d6v9IvxIPN|j(?fB1Kc#f-HC5O5`P1V$BBKK(DM!Fl}oy9gsH?kmNYj)8AwB%sEP6v^Wr4(NlTP`sM%|S z*0`j5S-~NST#TKM@8)RPb0m>4C>H2lHzfH1m_4pk!sT);(MXWJyN$`w6#?^fNWf@~ zLh9hMj+9O9E#GZZiFn(YouvFog@emxcbo+M*jvb-w$Xi;dmRSiqtzh29{pZ{T;3quklSEEUy+VE3F4hA*z)lr z@e|348ER)*Slu$->NsRR1qVOPQ4_iDMcqy|MMf0%BJ6Uze7gkvQLnBcPdc4{q-bYz zC#}`Bnv|P5ewy6zU(|U1fZUZpQk8_c#k)mIs2{5_a+bxT`nbN(Z*#d&zKTALIK?XF z^68iJy-Bj^=h9$bBwl1*B%n%GjqXX~&9sx&W!4oO5*ZRS2UT%bNp3REP|R4(Xudx1 zVmc)C)9|bJqi8YnL-?f}bsu66OHLb4ubyt59-Z!;;*RWyoIo0&eb5|e0%S*sCe_l{ zH}ozQN>=}@DzCX-pfg%6fk+n@dW=|(@V1HUHeLZ6=HI#3fPwvL4x$=WfjXxCsFwR%EQT~!x{G_IMZu`OgcY^Q+WblW3A^u#t)U@yqchNy%~U3EJ@$N$hcE@dntP#DRo| z3BCflNks#y{Y-tH0}ufiu2NCI#!%g`9u~(K zff!Nk+QK^9Qrpn4`PevYh7r~%%;!f ziwb+j1V?>ybVodg@fG;W^GSuzob9toZum2MTb=PJ)-tce!%7F-rY zWn(tfhl=oGZ&E*{u1PIR9N9bAq&rSJ9yg^Gnv|O~PplCyQqr&vqEp3E#O+dz({*9J zu(11?^{LfKbv7^~6DTeL2ga4wr`H(O#nwPEKl)XA9$SxE?aX`_^C&{4u=fv+2!BMk znYYahwTzvmg=P{A#|e zqimmSgas=egy#(q2;dL+eD?Z`>&*RZlVA-5Cg1_p5qJ{MP|}h|U3Q@ACORQ8CV`-8 z@otWzZV_-bo!%we#dpgY7!1u6EpD;jXr|&xMT(+m?xozl+-&BnPXxN}rncz zkVia>>gfsZwy-6yww{om%C9fLuxjY(_Nu3?XCzK0B73v#0;4qg zJ^2$$7)uZ(9Pdv@g$s6yUW}gL%22l&{CPUK7N#%nUj7=pp*8WgIW6(e3Qwbr5RXUe zD>Fi^fBGKmUhR>u^NsYiJ~+!PNW7Q2hqh>IiFNE-lbqzYAW1hm)x6T8{ z3cJC@kSdO=?JfnXPxz`LOfb`{MWUVi~oCE|J@z zi<`QSv)P~s_tF~0TJEa((dDI4DnWKZ)3T(}s3FUt{48(h59f|}nd4WD4mB&US0IvW z>5=z0C$nT18XJ8gk3)zXxC-DKe!WL;$UT@QStVHoGs4ohg+rqGnJmtzk2~yNJaj#{ zeq7Ma!4P-@lUx6#Ua?}zlxZq&l4mk>GF-Un!N!Kjs{N7rYEin*#9*yph%va5w#wFG z(R`qxek^&o&gq%Ef2r@ogGAO~>8rb5dlb8k`!kKpPCF{u?)W;>uQ*vu#!QP}ctFv5 z<;dp`0Uc-9Y|211vj~fQvz=d+|FBK9wSC9%IDBAk^BooyV2VnpR30>TH0vdgzyvE8%%^Bdb&Vhgm7jC{68Yi-;P>p5nz%Fz7BVbl2LkLcj&(JGm$ zSD0;lR7-`o-S@y#{`Vzg85?fVi(erBS z0U8wA0gDFl<+ z88xuverfp0OYZHok6=JI-@gJVuR6Co*H@1}_F2W>c2LSw7fm^l0)R{bz@ochI$28i zKvPm|%^GOLo^Lt^F6_xQ+;>qsGn9QT{^W&QVcgy1xw(G^`sed2 zPlOlpUy;zRf0uPpAmpb7A}TBb`QKn}NZbDd_S5nU_E%lM!pZ$4lhs3dAskJVkthV( z^&&N>n6!l4Ut#{u@~=dHL&APTirxGj`J3f8?Zs4}U^&P?CkzGK(|XPU01>CU@@+$}OY0f>=qV9u<-|tZfVXRFEwu>D zo@L>Q_QYyjkbKipXQbK3=jziTBP(M9=ROT$4A@_@vP)|L-y@==A5^pdcFaz3M3vOCnc=fW5x zJHGdAb*~g{7%l5Kh00|5(>23m2RnFAx1_Tk#(|?@=r?e^lh=@Bt)DWp3oQ@Lr)>OO|{+`Cgru$tvFma`ViQmRtcRjG)} z?e*|&q}-O;a(F*2Bz$(6wY6j8b$m*rN0axk67nIb4h{0;NfRY+O+`%$X?gAy9KDg~ zWK5w?+J?ba!@f_al44r~M_V>)Zsv&FdXN3u_q~nl)_q$B$MZ^syOMYN}`!EDJ@g z1mfqptF4NC!=Dp3pTx3sV|*>(U$_?nc+A(AhF9BS>0Qk}_L{DD4s7fvau^m!*gs>M zM0xS)mIw)Uey;D`n5eWx+8?YD;!~?chx2>Yol9Da? zjbbEM^uO8rZ*c@lo2ou`gSl1ZF@EmB4L*y=YFwCO=WaAMQM<*sA3(fgH`G?%4rl-L>eg>6BRa%`N;*>-M*L1QeFGWT_K~ zcEA=JlxD{BpA{`r2-SlAu{e3#0yT!xZkWw&!ZjEAz5-ZrlSWW=!Iq6HFmR#1^y$4< ztxV9O7Pb;_M9NLDwYl(U8UytbS&o-2WfBVn0aF%TDU#~UzV{44LNUA<1h7GyWpEp| uJMrD68+NsBM3( Date: Mon, 14 Dec 2015 23:48:35 -0800 Subject: [PATCH 25/60] Update fabfile. --- bootstrap.sh | 2 +- fabfile.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/bootstrap.sh b/bootstrap.sh index 54284d0c..01f874c8 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -63,7 +63,7 @@ popd # Obtain SDK3. sudo -u vagrant mkdir sdk3 pushd sdk3 - wget --progress=bar:force -O sdk.tar.gz https://sdk.getpebble.com/download/3.7?source=cloudpebble + wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8-beta8.tar.bz2 sudo -u vagrant tar --strip 1 -xzf sdk.tar.gz rm sdk.tar.gz sudo -u vagrant ln -s ~/arm-cs-tools arm-cs-tools diff --git a/fabfile.py b/fabfile.py index 786672ee..3c5962e5 100644 --- a/fabfile.py +++ b/fabfile.py @@ -60,10 +60,11 @@ def restart_qemu_service(): @parallel def update_ycmd_sdk(sdk_version): with cd("/home/ycm"), settings(sudo_user="ycm", shell="/bin/bash -c"): - sudo("wget -nv -O sdk.tar.gz https://sdk.getpebble.com/download/%s?source=cloudpebble" % sdk_version) + sudo("wget -nv -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-%s.tar.bz2" % sdk_version) sudo("tar -xf sdk.tar.gz") sudo("rm -rf sdk3") sudo("mv PebbleSDK-%s sdk3" % sdk_version) + sudo("mv sdk3/Pebble sdk3/pebble") @task @@ -123,6 +124,7 @@ def update_qemu_images(sdk_version): with lcd("~/projects/tintin"): local("git checkout v%s" % sdk_version) + build_qemu_image("bb2", "aplite") build_qemu_image("snowy_bb", "basalt") build_qemu_image("spalding_bb2", "chalk") @@ -134,7 +136,7 @@ def update_qemu_images(sdk_version): @task @runs_once def update_cloudpebble_sdk(sdk_version): - local("sed -i.bak 's/download\/3.[a-z0-9-]*/download\/%s/' bin/post_compile bootstrap.sh" % sdk_version) + local("sed -i.bak 's/sdk-core-3.[a-z0-9-]*\.tar\.bz2/sdk-core-%s.tar.bz2/' bin/post_compile bootstrap.sh" % sdk_version) local("git add bin/post_compile bootstrap.sh") local("git commit -m 'Update to v%s'" % sdk_version) local("git push") From 0cdf4f239957298135801e7b16aec77205c5cc29 Mon Sep 17 00:00:00 2001 From: Meiguro Date: Mon, 14 Dec 2015 14:58:22 -0800 Subject: [PATCH 26/60] Update pebble.js to support 3.x on aplite --- ext/pebblejs | 2 +- ide/utils/sdk.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/pebblejs b/ext/pebblejs index d7c7acba..59fa17f2 160000 --- a/ext/pebblejs +++ b/ext/pebblejs @@ -1 +1 @@ -Subproject commit d7c7acba1cff9b2e55d6620daa8e3c89758dafc5 +Subproject commit 59fa17f214723f3e6f07ea7eb97d5e3ced8b0d2e diff --git a/ide/utils/sdk.py b/ide/utils/sdk.py index 7e4f4a21..fa605b07 100644 --- a/ide/utils/sdk.py +++ b/ide/utils/sdk.py @@ -346,15 +346,15 @@ def generate_pebblejs_resource_dict(resources): media = [ { "menuIcon": True, # This must be the first entry; we adjust it later. - "type": "png", + "type": "bitmap", "name": "IMAGE_MENU_ICON", "file": "images/menu_icon.png" }, { - "type": "png", + "type": "bitmap", "name": "IMAGE_LOGO_SPLASH", "file": "images/logo_splash.png" }, { - "type": "png", + "type": "bitmap", "name": "IMAGE_TILE_SPLASH", "file": "images/tile_splash.png" }, { @@ -365,7 +365,7 @@ def generate_pebblejs_resource_dict(resources): ] for resource in resources: - if resource.kind != 'png': + if resource.kind != 'bitmap': continue d = { From 53c30c0e9b0c6c82123ea65e7d8e76d2fc84aeef Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Tue, 15 Dec 2015 09:06:01 -0800 Subject: [PATCH 27/60] Temporarily disable migration. --- bin/post_compile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index d0abfa0d..edd1971d 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -45,9 +45,9 @@ echo "Cleaning up." rm /tmp/*.tar.* # Make sure the database is up to date. -echo "Performing database migration." -python manage.py syncdb --noinput -python manage.py migrate +# echo "Performing database migration." +# python manage.py syncdb --noinput +# python manage.py migrate echo "Compiling gettext files" python manage.py compilemessages From 2459edc364293be640012726597a30bd36efd444 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Tue, 15 Dec 2015 09:45:15 -0800 Subject: [PATCH 28/60] Added some whatsnew about 3.8. --- ide/utils/whatsnew.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ide/utils/whatsnew.py b/ide/utils/whatsnew.py index cfd2bb2f..faa87c66 100644 --- a/ide/utils/whatsnew.py +++ b/ide/utils/whatsnew.py @@ -38,7 +38,9 @@ "Accordingly, resource management has changed significantly. Now you can tag each variant of a resource with tags such as \"Round\" or \"Monochrome\" to specify which platforms it should be used for.", "See this guide for more information"], ["It's once again possible to take screenshots of your apps directly from the emulator!", - "You can find the screenshot button in the Compilation page, or you can run the \"Take Screenshots\" command from the fuzzy prompt (cmd-shift-P or ctrl-shift-P)"] + "You can find the screenshot button in the Compilation page, or you can run the \"Take Screenshots\" command from the fuzzy prompt (cmd-shift-P or ctrl-shift-P)"], + ["CloudPebble has been updated to SDK 3.8! Aplite now uses SDK 3. Read more at ", + "We have converted all your png and pbi resources to the new 'bitmap' format. Read more at ."], ] From 030ddf2e13cfa7fd391b21f8da58cded74c2df1e Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Tue, 15 Dec 2015 09:46:00 -0800 Subject: [PATCH 29/60] Perform migrations as appropriate again. --- bin/post_compile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index edd1971d..d0abfa0d 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -45,9 +45,9 @@ echo "Cleaning up." rm /tmp/*.tar.* # Make sure the database is up to date. -# echo "Performing database migration." -# python manage.py syncdb --noinput -# python manage.py migrate +echo "Performing database migration." +python manage.py syncdb --noinput +python manage.py migrate echo "Compiling gettext files" python manage.py compilemessages From cc94c1e1b4294e795f21789d5278d7e67112d51a Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Tue, 15 Dec 2015 09:51:23 -0800 Subject: [PATCH 30/60] Update to v3.8 --- bin/post_compile | 2 +- bootstrap.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index d0abfa0d..699e96e6 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -9,7 +9,7 @@ echo "Downloading SDK 2" curl -o /tmp/pebblesdk2.tar.gz -L https://sdk.getpebble.com/download/2.8.1?source=cloudpebble echo "Downloading SDK 3" -curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8-beta8.tar.bz2 +curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8.tar.bz2 echo "Downloading the toolchain" curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar diff --git a/bootstrap.sh b/bootstrap.sh index 01f874c8..79051cf0 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -63,7 +63,7 @@ popd # Obtain SDK3. sudo -u vagrant mkdir sdk3 pushd sdk3 - wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8-beta8.tar.bz2 + wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8.tar.bz2 sudo -u vagrant tar --strip 1 -xzf sdk.tar.gz rm sdk.tar.gz sudo -u vagrant ln -s ~/arm-cs-tools arm-cs-tools From 2760e5bd0cfe71ef2d92500f9d51d7e83a31823e Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Tue, 15 Dec 2015 09:52:40 -0800 Subject: [PATCH 31/60] Various deploy issues --- bin/post_compile | 2 +- bootstrap.sh | 2 +- fabfile.py | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index 699e96e6..278f1260 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -9,7 +9,7 @@ echo "Downloading SDK 2" curl -o /tmp/pebblesdk2.tar.gz -L https://sdk.getpebble.com/download/2.8.1?source=cloudpebble echo "Downloading SDK 3" -curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8.tar.bz2 +curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.tar.bz2 echo "Downloading the toolchain" curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar diff --git a/bootstrap.sh b/bootstrap.sh index 79051cf0..d2a79359 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -63,7 +63,7 @@ popd # Obtain SDK3. sudo -u vagrant mkdir sdk3 pushd sdk3 - wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-3.8.tar.bz2 + wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.tar.bz2 sudo -u vagrant tar --strip 1 -xzf sdk.tar.gz rm sdk.tar.gz sudo -u vagrant ln -s ~/arm-cs-tools arm-cs-tools diff --git a/fabfile.py b/fabfile.py index 3c5962e5..45ec8da5 100644 --- a/fabfile.py +++ b/fabfile.py @@ -60,11 +60,11 @@ def restart_qemu_service(): @parallel def update_ycmd_sdk(sdk_version): with cd("/home/ycm"), settings(sudo_user="ycm", shell="/bin/bash -c"): - sudo("wget -nv -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/beta/sdk-core-%s.tar.bz2" % sdk_version) + sudo("wget -nv -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-%s.tar.bz2" % sdk_version) sudo("tar -xf sdk.tar.gz") sudo("rm -rf sdk3") - sudo("mv PebbleSDK-%s sdk3" % sdk_version) - sudo("mv sdk3/Pebble sdk3/pebble") + sudo("mv sdk-core sdk3") + sudo("mv sdk3/pebble sdk3/Pebble") @task From 60fca6c26c83b52f34398f87f3bc91051868601e Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Tue, 15 Dec 2015 09:57:48 -0800 Subject: [PATCH 32/60] Fix bad html. --- ide/utils/whatsnew.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/utils/whatsnew.py b/ide/utils/whatsnew.py index faa87c66..2840907b 100644 --- a/ide/utils/whatsnew.py +++ b/ide/utils/whatsnew.py @@ -39,8 +39,8 @@ "See this guide for more information"], ["It's once again possible to take screenshots of your apps directly from the emulator!", "You can find the screenshot button in the Compilation page, or you can run the \"Take Screenshots\" command from the fuzzy prompt (cmd-shift-P or ctrl-shift-P)"], - ["CloudPebble has been updated to SDK 3.8! Aplite now uses SDK 3. Read more at ", - "We have converted all your png and pbi resources to the new 'bitmap' format. Read more at ."], + ["CloudPebble has been updated to SDK 3.8! Aplite now uses SDK 3. Read more on our blog.", + "We have converted all your png and pbi resources to the new 'bitmap' format. Read more on our blog."], ] From 1e18f74f72c8739c13435e09f4b0a70154931124 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Wed, 16 Dec 2015 12:22:03 -0800 Subject: [PATCH 33/60] Update to v3.8.1 --- bin/post_compile | 2 +- bootstrap.sh | 2 +- docker_start.sh | 0 3 files changed, 2 insertions(+), 2 deletions(-) create mode 100644 docker_start.sh diff --git a/bin/post_compile b/bin/post_compile index 278f1260..620ec001 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -9,7 +9,7 @@ echo "Downloading SDK 2" curl -o /tmp/pebblesdk2.tar.gz -L https://sdk.getpebble.com/download/2.8.1?source=cloudpebble echo "Downloading SDK 3" -curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.tar.bz2 +curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.1.tar.bz2 echo "Downloading the toolchain" curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar diff --git a/bootstrap.sh b/bootstrap.sh index d2a79359..fdb4b37c 100644 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -63,7 +63,7 @@ popd # Obtain SDK3. sudo -u vagrant mkdir sdk3 pushd sdk3 - wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.tar.bz2 + wget --progress=bar:force -O sdk.tar.gz https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.1.tar.bz2 sudo -u vagrant tar --strip 1 -xzf sdk.tar.gz rm sdk.tar.gz sudo -u vagrant ln -s ~/arm-cs-tools arm-cs-tools diff --git a/docker_start.sh b/docker_start.sh new file mode 100644 index 00000000..e69de29b From 23a82f81c93f9173ce5347b2de3d2f02b3e04eda Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Wed, 16 Dec 2015 14:06:41 -0800 Subject: [PATCH 34/60] Fix pebble.js. --- ide/tasks/build.py | 2 +- ide/utils/sdk.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ide/tasks/build.py b/ide/tasks/build.py index 959d89ba..5a8fc635 100644 --- a/ide/tasks/build.py +++ b/ide/tasks/build.py @@ -152,7 +152,7 @@ def run_compile(build_result): create_source_files(project, base_dir) for f in resources: - if f.kind != 'png': + if f.kind not in ('png', 'bitmap'): continue target_dir = os.path.abspath(os.path.join(base_dir, resource_root, ResourceFile.DIR_MAP[f.kind])) abs_target = os.path.abspath(os.path.join(target_dir, f.file_name)) diff --git a/ide/utils/sdk.py b/ide/utils/sdk.py index af73d407..c528c7cb 100644 --- a/ide/utils/sdk.py +++ b/ide/utils/sdk.py @@ -371,7 +371,7 @@ def generate_pebblejs_resource_dict(resources): ] for resource in resources: - if resource.kind != 'bitmap': + if resource.kind not in ('bitmap', 'png'): continue d = { From 1c7cc496b039cd4c9cd1aab8281ac70faa148acf Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Wed, 16 Dec 2015 19:20:18 -0800 Subject: [PATCH 35/60] Support running with Docker for development. --- .gitignore | 1 + Dockerfile | 70 +++++++++++++++++++++++++++++++++++++++++ cloudpebble/settings.py | 33 ++++++++++--------- docker_start.sh | 16 ++++++++++ utils/s3.py | 20 +++++++++++- 5 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 Dockerfile diff --git a/.gitignore b/.gitignore index 522c3481..9c17deae 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ src/ .cache/ .env/ .idea/ +bower_components diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..1c589d1b --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +FROM python:2.7 + +ENV NPM_CONFIG_LOGLEVEL=info NODE_VERSION=4.2.3 DJANGO_VERSION=1.6 + +# Node stuff. + +# gpg keys listed at https://github.com/nodejs/node +RUN set -ex \ + && for key in \ + 9554F04D7259F04124DE6B476D5A82AC7E37093B \ + 94AE36675C464D64BAFA68DD7434390BDBE9B9C5 \ + 0034A06D9D9B0064CE8ADF6BF1747F4AD2306D93 \ + FD3A5288F042B6850C66B31F09FE44734EB7990E \ + 71DCFD284A79C3B38668286BC97EC7A07EDE3FC1 \ + DD8F2338BAE7501E3DD5AC78C273792F7D83545D \ + ; do \ + gpg --keyserver ha.pool.sks-keyservers.net --recv-keys "$key"; \ + done + +RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" \ + && curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc" \ + && gpg --verify SHASUMS256.txt.asc \ + && grep " node-v$NODE_VERSION-linux-x64.tar.gz\$" SHASUMS256.txt.asc | sha256sum -c - \ + && tar -xzf "node-v$NODE_VERSION-linux-x64.tar.gz" -C /usr/local --strip-components=1 \ + && rm "node-v$NODE_VERSION-linux-x64.tar.gz" SHASUMS256.txt.asc + +# Django stuff + +RUN apt-get update && apt-get install -y \ + gettext \ + postgresql-client libpq-dev \ + --no-install-recommends && rm -rf /var/lib/apt/lists/* + +RUN pip install psycopg2 django=="$DJANGO_VERSION" + +EXPOSE 8000 + +# CloudPebble stuff +RUN npm install -g bower && echo '{"allow_root": true}' > ~/.bowerrc + +# Grab the toolchain +RUN curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar && \ + tar -xf /tmp/arm-cs-tools.tar -C / && rm /tmp/arm-cs-tools.tar + +ADD requirements.txt /tmp/requirements.txt +RUN pip install --no-cache-dir -r /tmp/requirements.txt + +ENV SDK_TWO_VERSION=2.9 + +# Install SDK 2 +RUN mkdir /sdk2 && \ + curl -L "https://s3.amazonaws.com/assets.getpebble.com/sdk3/sdk-core/sdk-core-${SDK_TWO_VERSION}.tar.bz2" | \ + tar --strip-components=1 -xj -C /sdk2 + +ENV SDK_THREE_VERSION=3.8.1 + +# Install SDK 3 +RUN mkdir /sdk3 && \ + curl -L "https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-${SDK_THREE_VERSION}.tar.bz2" | \ + tar --strip-components=1 -xj -C /sdk3 + +COPY . /code +WORKDIR /code + +# Bower is awful. +RUN rm -rf bower_components && cd /tmp && python /code/manage.py bower install && mv bower_components /code/ + +RUN python manage.py compilemessages + +CMD ["sh", "docker_start.sh"] diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 31ec7242..c02c107e 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -21,13 +21,11 @@ if 'DATABASE_URL' not in _environ: DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': os.getcwd() + '/dev.db', # Or path to database file if using sqlite3. - # The following settings are not used with sqlite3: - 'USER': '', - 'PASSWORD': '', - 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP. - 'PORT': '', # Set to empty string for default. + 'ENGINE': 'django.db.backends.postgresql_psycopg2', + 'NAME': 'postgres', + 'USER': 'postgres', + 'HOST': 'postgres', + 'PORT': 5432, } } else: @@ -255,7 +253,7 @@ } } -BROKER_URL = _environ.get('CLOUDAMQP_URL', 'amqp://guest:guest@localhost:5672/') +BROKER_URL = _environ.get('CLOUDAMQP_URL', 'redis://:@redis/1') BROKER_POOL_LIMIT = int(_environ.get('BROKER_POOL_LIMIT', 10)) @@ -278,10 +276,10 @@ GITHUB_HOOK_TEMPLATE = _environ.get('GITHUB_HOOK', 'http://example.com/ide/project/%(project)d/github/push_hook?key=%(key)s') -SDK2_PEBBLE_TOOL = _environ.get('SDK2_PEBBLE_TOOL', '/home/vagrant/sdk2/bin/pebble') -SDK3_PEBBLE_WAF = _environ.get('SDK3_PEBBLE_WAF', '/home/vagrant/sdk3/pebble/waf') +SDK2_PEBBLE_WAF = _environ.get('SDK2_PEBBLE_TOOL', '/sdk2/pebble/waf') +SDK3_PEBBLE_WAF = _environ.get('SDK3_PEBBLE_WAF', '/sdk3/pebble/waf') -ARM_CS_TOOLS = _environ.get('ARM_CS_TOOLS', '/home/vagrant/arm-cs-tools/bin/') +ARM_CS_TOOLS = _environ.get('ARM_CS_TOOLS', '/arm-cs-tools/bin/') KEEN_PROJECT_ID = _environ.get('KEEN_PROJECT_ID', None) KEEN_WRITE_KEY = _environ.get('KEEN_WRITE_KEY', None) @@ -294,20 +292,21 @@ AWS_ACCESS_KEY_ID = _environ.get('AWS_ACCESS_KEY_ID', None) AWS_SECRET_ACCESS_KEY = _environ.get('AWS_SECRET_ACCESS_KEY', None) -AWS_S3_SOURCE_BUCKET = _environ.get('AWS_S3_SOURCE_BUCKET', None) -AWS_S3_BUILDS_BUCKET = _environ.get('AWS_S3_BUILDS_BUCKET', None) -AWS_S3_EXPORT_BUCKET = _environ.get('AWS_S3_EXPORT_BUCKET', None) +AWS_S3_SOURCE_BUCKET = _environ.get('AWS_S3_SOURCE_BUCKET', 'source.cloudpebble.net') +AWS_S3_BUILDS_BUCKET = _environ.get('AWS_S3_BUILDS_BUCKET', 'builds.cloudpebble.net') +AWS_S3_EXPORT_BUCKET = _environ.get('AWS_S3_EXPORT_BUCKET', 'exports.cloudpebble.net') +AWS_S3_FAKE_S3 = _environ.get('AWS_S3_FAKE_S3', None) TYPOGRAPHY_CSS = _environ.get('TYPOGRAPHY_CSS', None) -REDIS_URL = _environ.get('REDIS_URL', None) or _environ.get('REDISCLOUD_URL', 'redis://localhost:6379/') +REDIS_URL = _environ.get('REDIS_URL', None) or _environ.get('REDISCLOUD_URL', 'redis://redis:6379/') LIBPEBBLE_PROXY = _environ.get('LIBPEBBLE_PROXY', None) YCM_URLS = _environ.get('YCM_URLS', 'http://localhost:8002/').split(',') COMPLETION_CERTS = _environ.get('COMPLETION_CERTS', os.getcwd() + '/completion-certs.crt') -QEMU_URLS = _environ.get('QEMU_URLS', 'http://192.168.42.42:8003/').split(',') +QEMU_URLS = _environ.get('QEMU_URLS', 'http://qemu/').split(',') QEMU_LAUNCH_AUTH_HEADER = _environ.get('QEMU_LAUNCH_AUTH_HEADER', 'secret') QEMU_LAUNCH_TIMEOUT = int(_environ.get('QEMU_LAUNCH_TIMEOUT', 15)) @@ -327,6 +326,6 @@ if not DEBUG: for key in _environ.keys(): # We need these ones to run. - if key in {'PATH', 'TZ', 'RUN_MAIN', 'CELERY_LOADER', 'DJANGO_SETTINGS_MODULE', 'DEBUG'}: + if key in {'PATH', 'TZ', 'RUN_MAIN', 'CELERY_LOADER', 'DJANGO_SETTINGS_MODULE', 'DEBUG', 'C_FORCE_ROOT'}: continue del _environ[key] diff --git a/docker_start.sh b/docker_start.sh index e69de29b..4a293d16 100644 --- a/docker_start.sh +++ b/docker_start.sh @@ -0,0 +1,16 @@ +#!/bin/sh +sleep 1 +if [ ! -z "$RUN_WEB" ]; then + # Make sure the database is up to date. + echo "Performing database migration." + python manage.py syncdb --noinput + python manage.py migrate + + python manage.py runserver 0.0.0.0:$PORT +elif [ ! -z "$RUN_CELERY" ]; then + sleep 2 + C_FORCE_ROOT=true python manage.py celery worker --autoreload --loglevel=info +else + echo "Doing nothing!" + exit 1 +fi diff --git a/utils/s3.py b/utils/s3.py index 694f618b..4294326d 100644 --- a/utils/s3.py +++ b/utils/s3.py @@ -1,10 +1,28 @@ import boto from boto.s3.key import Key +from boto.s3.connection import OrdinaryCallingFormat from django.conf import settings import urllib +def _ensure_bucket_exists(s3, bucket): + try: + s3.create_bucket(bucket) + except boto.exception.S3ResponseError: + pass + else: + print "Created bucket %s" % bucket + if settings.AWS_ENABLED: - _s3 = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY) + if settings.AWS_S3_FAKE_S3 is None: + _s3 = boto.connect_s3(settings.AWS_ACCESS_KEY_ID, settings.AWS_SECRET_ACCESS_KEY) + else: + host, port = (settings.AWS_S3_FAKE_S3.split(':', 2) + [80])[:2] + port = int(port) + _s3 = boto.connect_s3("key_id", "secret_key", is_secure=False, port=port, + host=host, calling_format=OrdinaryCallingFormat()) + _ensure_bucket_exists(_s3, settings.AWS_S3_SOURCE_BUCKET) + _ensure_bucket_exists(_s3, settings.AWS_S3_EXPORT_BUCKET) + _ensure_bucket_exists(_s3, settings.AWS_S3_BUILDS_BUCKET) _buckets = { 'source': _s3.get_bucket(settings.AWS_S3_SOURCE_BUCKET), From 44ce211e79cb04069f1ae9b50564aa47426caa7d Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Wed, 16 Dec 2015 23:38:16 -0800 Subject: [PATCH 36/60] Add 12/24h setting. --- ide/static/ide/js/emulator.js | 11 +++++++++++ ide/static/ide/js/libpebble/libpebble.js | 7 ++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/ide/static/ide/js/emulator.js b/ide/static/ide/js/emulator.js index a8e67bf9..3a286a68 100644 --- a/ide/static/ide/js/emulator.js +++ b/ide/static/ide/js/emulator.js @@ -15,12 +15,14 @@ CloudPebble.Emulator = new (function() { popup.find('.battery-level').on('input', setBatteryState).val(self._batteryLevel); popup.find('#is-charging').change(setBatteryState).prop('checked', self._charging); popup.find('#bluetooth-enabled').change(setBluetoothState).prop('checked', self._bluetooth); + popup.find('#24h-enabled').change(set24HState).prop('checked', self._24h) } function setDefaults() { self._batteryLevel = 80; self._charging = false; self._bluetooth = true; + self._24h = true; } function handleClosed() { @@ -42,6 +44,10 @@ CloudPebble.Emulator = new (function() { '' + '
' + '
' + + '
' + + '' + + '
' + + '
' + ' ' + ' ' + '' + @@ -76,6 +82,11 @@ CloudPebble.Emulator = new (function() { SharedPebble.getPebbleNow().emu_bluetooth(self._bluetooth); } + function set24HState(e) { + self._24h = $('.emulator-config #24h-enabled').prop('checked'); + SharedPebble.getPebbleNow().emu_set_24h(self._24h); + } + function doSensors(e) { e.preventDefault(); var prompt = $('#qemu-sensor-prompt').modal('show'); diff --git a/ide/static/ide/js/libpebble/libpebble.js b/ide/static/ide/js/libpebble/libpebble.js index e9eaf432..d62ed101 100644 --- a/ide/static/ide/js/libpebble/libpebble.js +++ b/ide/static/ide/js/libpebble/libpebble.js @@ -490,6 +490,10 @@ Pebble = function(proxy, token) { send_qemu_command(QEmu.Battery, pack("bb", [percent, charging|0])); }; + this.emu_set_24h = function(enabled) { + send_qemu_command(QEmu.TimeFormat, pack("b", [enabled])); + }; + var mButtonState = 0; var mButtonStateQueue = []; var mButtonStateTimer = null; @@ -792,7 +796,8 @@ Pebble = function(proxy, token) { Battery: 5, Accel: 6, VibrationNotification: 7, - Button: 8 + Button: 8, + TimeFormat: 9 }; var send_message = function(endpoint, message) { From 3deb10e64aa37cdbd4e149f3d18632e93e9deae7 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Thu, 17 Dec 2015 10:43:01 -0800 Subject: [PATCH 37/60] Fix IB layer delete button bounds bug --- ide/static/ide/js/ib/property_view.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ide/static/ide/js/ib/property_view.js b/ide/static/ide/js/ib/property_view.js index a2fa240d..992526e4 100644 --- a/ide/static/ide/js/ib/property_view.js +++ b/ide/static/ide/js/ib/property_view.js @@ -9,9 +9,10 @@ this._parent = parent; this._layer = layer; this._root = $('
'); - this._delete = - $('
') - .on('click', _.bind(this._deleteLayer, this)); + this._delete = $('
'); + $('') + .on('click', _.bind(this._deleteLayer, this)) + .appendTo(this._delete); }; IB.PropertyView.prototype = { /** From 722a32f7a54a6bd943fdd9f6f523acf73bde5add Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 11:58:19 -0800 Subject: [PATCH 38/60] WH-53: Make session/csrf Secure and session HttpOnly. --- cloudpebble/settings.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 31ec7242..718822ef 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -191,6 +191,12 @@ SOCIAL_AUTH_PEBBLE_ROOT_URL = _environ.get('PEBBLE_AUTH_URL', None) PEBBLE_AUTH_ADMIN_TOKEN = _environ.get('PEBBLE_AUTH_ADMIN_TOKEN', None) +SHOULD_BE_SECURE = _environ.get('EXPECT_SSL', '') != '' + +SESSION_COOKIE_HTTPONLY = True +SESSION_COOKIE_SECURE = SHOULD_BE_SECURE +CSRF_COOKIE_SECURE = SHOULD_BE_SECURE + SOCIAL_AUTH_PEBBLE_REQUIRED = 'PEBBLE_AUTH_REQUIRED' in _environ ROOT_URLCONF = 'cloudpebble.urls' From 01089566553a7381c54df42629e0eb0a24ea7a5c Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 12:46:34 -0800 Subject: [PATCH 39/60] Added MAINTAINER, updated README. --- Dockerfile | 1 + README.md | 28 ++++------------------------ 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1c589d1b..ec41dc4f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ FROM python:2.7 +MAINTAINER Katharine Berry ENV NPM_CONFIG_LOGLEVEL=info NODE_VERSION=4.2.3 DJANGO_VERSION=1.6 diff --git a/README.md b/README.md index 2ebe0719..961b45e0 100644 --- a/README.md +++ b/README.md @@ -7,30 +7,12 @@ support. Getting Started --------------- -The easiest way to get an instance of CloudPebble running is to use [Vagrant][]. -The supplied virtual machine assumes [VirtualBox][], so you'll want to have that installed too. -You will also need ports 8000 and 8001 to be available on your machine. +The easiest way to get a fully-functional copy of CloudPebble is using Docker and Docker Compose. Instructions can +be found in the [https://github.com/pebble/cloudpebble-composed](cloudpebble-composed) repo. -With those set up, just clone the repo and run `vagrant up`: - host:~/cloudpebble$ vagrant up - -After some waiting, you should have a functional instance running at [http://localhost:8000](http://localhost:8000). - -In general, you can then stop it with `vagrant halt`, start it with `vagrant up`, and restart it with `vagrant reload`. -If you need to recreate the thing, you can use `vagrant destroy` and `vagrant up` again. No persistent data is stored -in the VM, so doing this is safe. - -The server will be running in debug mode with autoreload enabled. However, the worker will not autoreload, so if you -make changes that the worker should pick up on, you must restart it manually: - - - host:~/cloudpebble$ vagrant ssh - vagrant@precise32:~$ sudo restart cloudpebble-celery - - -To locally override the configuration, create a file at `cloudpebble/settings_local.py` and set the appropriate values -there. +To locally override the configuration, you can create a file at `cloudpebble/settings_local.py` and set the +appropriate values there. Setting environment variables also works. Note that you won't be able to set up integration with certain Pebble systems (e.g. Pebble SSO). This shouldn't usually matter; whenever these are used, an alternative route is provided and should be invoked in its absence. @@ -44,6 +26,4 @@ logical sense. Please avoid commits that fix typos in prior commits. If a change is a significant amount of work, it would probably be worth creating an issue to discuss it first. Pull requests are not automatically accepted (though they usually are). -[Vagrant]: http://www.vagrantup.com -[VirtualBox]: http://virtualbox.org [support]: mailto:cloudpebble@getpebble.com From ccac2c663f2b4860ead43309794f424ae6aec19c Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 13:28:14 -0800 Subject: [PATCH 40/60] Assume redis celery backend. --- cloudpebble/settings.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 9304114d..878e4889 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -259,7 +259,10 @@ } } -BROKER_URL = _environ.get('CLOUDAMQP_URL', 'redis://:@redis/1') +REDIS_URL = _environ.get('REDIS_URL', None) or _environ.get('REDISCLOUD_URL', 'redis://redis:6379/') + +BROKER_URL = REDIS_URL + '1' +CELERY_RESULT_BACKEND = BROKER_URL BROKER_POOL_LIMIT = int(_environ.get('BROKER_POOL_LIMIT', 10)) @@ -305,8 +308,6 @@ TYPOGRAPHY_CSS = _environ.get('TYPOGRAPHY_CSS', None) -REDIS_URL = _environ.get('REDIS_URL', None) or _environ.get('REDISCLOUD_URL', 'redis://redis:6379/') - LIBPEBBLE_PROXY = _environ.get('LIBPEBBLE_PROXY', None) YCM_URLS = _environ.get('YCM_URLS', 'http://localhost:8002/').split(',') From 905913cc12c6b074fb47a4a5f0d8436532a8b88b Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 14:01:47 -0800 Subject: [PATCH 41/60] Use waf directly for SDK 2. --- ide/tasks/build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ide/tasks/build.py b/ide/tasks/build.py index 5a8fc635..403b50c8 100644 --- a/ide/tasks/build.py +++ b/ide/tasks/build.py @@ -171,7 +171,8 @@ def run_compile(build_result): os.chdir(base_dir) if project.sdk_version == '2': environ = os.environ - command = [settings.SDK2_PEBBLE_TOOL, "build"] + environ['PATH'] = '{}:{}'.format(settings.ARM_CS_TOOLS, environ['PATH']) + command = [settings.SDK2_PEBBLE_WAF, "configure", "build"] elif project.sdk_version == '3': environ = os.environ.copy() environ['PATH'] = '{}:{}'.format(settings.ARM_CS_TOOLS, environ['PATH']) From e05493278960a83ea16e90745f725c8dff3b01a2 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 14:03:41 -0800 Subject: [PATCH 42/60] Pebble -> pebble for ycm. --- fabfile.py | 1 - 1 file changed, 1 deletion(-) diff --git a/fabfile.py b/fabfile.py index 45ec8da5..ea526cc8 100644 --- a/fabfile.py +++ b/fabfile.py @@ -64,7 +64,6 @@ def update_ycmd_sdk(sdk_version): sudo("tar -xf sdk.tar.gz") sudo("rm -rf sdk3") sudo("mv sdk-core sdk3") - sudo("mv sdk3/pebble sdk3/Pebble") @task From 9d3ffa5a7ed7fe7e8b4b438901ca6427c69c2351 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 14:14:56 -0800 Subject: [PATCH 43/60] Be consistent about slashes. --- cloudpebble/settings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 878e4889..0bef9ce4 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -259,9 +259,9 @@ } } -REDIS_URL = _environ.get('REDIS_URL', None) or _environ.get('REDISCLOUD_URL', 'redis://redis:6379/') +REDIS_URL = _environ.get('REDIS_URL', None) or _environ.get('REDISCLOUD_URL', 'redis://redis:6379') -BROKER_URL = REDIS_URL + '1' +BROKER_URL = REDIS_URL + '/1' CELERY_RESULT_BACKEND = BROKER_URL BROKER_POOL_LIMIT = int(_environ.get('BROKER_POOL_LIMIT', 10)) From 28ecf80d340347076cbbe9507fa158d7a5056b6e Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Thu, 17 Dec 2015 14:29:59 -0800 Subject: [PATCH 44/60] Update app.json --- app.json | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app.json b/app.json index 5c93d5c7..00aed981 100644 --- a/app.json +++ b/app.json @@ -32,14 +32,13 @@ "required": true }, "QEMU_URLS": "https://qemu-us1.cloudpebble.net/", - "SDK2_PEBBLE_TOOL": "/app/sdk2/bin/pebble", - "SDK3_PEBBLE_TOOL": "/app/sdk3/bin/pebble", + "SDK2_PEBBLE_WAF": "/app/sdk2/pebble/waf", + "SDK3_PEBBLE_WAF": "/app/sdk3/pebble/waf", "TYPOGRAPHY_CSS": "//cloud.typography.com/7515512/745724/css/fonts.css", "WEB_CONCURRENCY": "2", "YCM_URLS": "https://ycm1.cloudpebble.net/" }, "addons": [ - "cloudamqp:lemur", "papertrail:choklad", "heroku-postgresql:hobby-dev", "heroku-redis:hobby-dev" From 9e3d66e235344bdda7dd4064094656f8e1aae3a7 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Fri, 18 Dec 2015 17:12:55 -0800 Subject: [PATCH 45/60] Add a global socket timeout. --- cloudpebble/settings.py | 3 +++ requirements.txt | 18 +----------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 0bef9ce4..1bbb1f85 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -2,6 +2,7 @@ # Django settings for cloudpebble project. import os +import socket import dj_database_url _environ = os.environ @@ -329,6 +330,8 @@ print "No local settings overrides." pass +socket.setdefaulttimeout(int(_environ.get("DEFAULT_SOCKET_TIMEOUT", 10))) + # Don't keep these hanging around in the environment. if not DEBUG: for key in _environ.keys(): diff --git a/requirements.txt b/requirements.txt index 8d582c9c..5a294a4a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ python-social-auth==0.1.23 keen==0.2.3 redis==2.9.1 boto==2.27.0 -gevent==1.0.1 +gevent>=1.1rc2 psycogreen==1.0 requests==2.7.0 oauth2client==1.3 @@ -28,23 +28,7 @@ gunicorn dj-database-url newrelic -# This is required to set up the SDK. -virtualenv - # These are in the SDK requirements.txt and included here to save on Heroku deploy time. freetype-py==1.0 sh==1.08 -websocket-client==0.32.0 -pyserial>=2.7 pypng==0.0.17 -libpebble2>=0.0.10 -enum34==1.0.4 -httplib2==0.9.1 -progressbar2==2.7.3 -pyasn1==0.1.8 -pyasn1-modules==0.0.6 -rsa==3.1.4 -six==1.9.0 -wheel==0.24.0 -colorama==0.3.3 -pyqrcode==1.1 From 387c8ddd18babf7c8c03881280bc3295b4a045b7 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Fri, 18 Dec 2015 17:19:40 -0800 Subject: [PATCH 46/60] Use modern legacy SDK build. --- bin/post_compile | 27 +++++++++------------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index 620ec001..e729691f 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -6,37 +6,28 @@ set -e # Make sure we have the SDK in place. echo "Downloading SDK 2" -curl -o /tmp/pebblesdk2.tar.gz -L https://sdk.getpebble.com/download/2.8.1?source=cloudpebble +curl -o /tmp/pebblesdk2.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/sdk-core/sdk-core-2.9.tar.bz2 echo "Downloading SDK 3" curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.1.tar.bz2 echo "Downloading the toolchain" -curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar +pushd / + curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar + tar -xf /tmp/arm-cs-tools.tar +popd + -echo "Extracting SDK 2" -tar -xf /tmp/pebblesdk2.tar.gz -mv PebbleSDK-2.8.1 sdk2 +mkdir sdk2 pushd sdk2 - echo "Extracting the toolchain" - tar -xf /tmp/arm-cs-tools.tar - echo "Preparing virtualenv" - virtualenv .env - # Instead of actually installing the requirements, we instead just use the global ones, which are a superset - # of the ones here. -# source .env/bin/activate -# pip install -r requirements.txt -# deactivate + echo "Extracting SDK 2" + tar --strip 1 -xf /tmp/pebblesdk2.tar.gz popd mkdir sdk3 pushd sdk3 echo "Extracting SDK 3" tar --strip 1 -xf /tmp/pebblesdk3.tar.gz - echo "Extracting the toolchain" - tar -xf /tmp/arm-cs-tools.tar -# echo "Preparing virtualenv" -# virtualenv .env popd touch NO_TRACKING From b0d569a7262ca00ab4e03c1db112b097e6129be6 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Fri, 18 Dec 2015 17:21:53 -0800 Subject: [PATCH 47/60] And use a directory Heroku actually likes. --- bin/post_compile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bin/post_compile b/bin/post_compile index e729691f..7a7f50cf 100644 --- a/bin/post_compile +++ b/bin/post_compile @@ -12,10 +12,8 @@ echo "Downloading SDK 3" curl -o /tmp/pebblesdk3.tar.gz -L https://s3.amazonaws.com/assets.getpebble.com/sdk3/release/sdk-core-3.8.1.tar.bz2 echo "Downloading the toolchain" -pushd / - curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar - tar -xf /tmp/arm-cs-tools.tar -popd +curl -o /tmp/arm-cs-tools.tar https://cloudpebble-vagrant.s3.amazonaws.com/arm-cs-tools-stripped.tar +tar -xf /tmp/arm-cs-tools.tar mkdir sdk2 From 5a51e94fef5bfbd24d251243b7dd01e0ebe08056 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Fri, 18 Dec 2015 18:37:19 -0800 Subject: [PATCH 48/60] TOOL -> WAF --- cloudpebble/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 1bbb1f85..0172f6ce 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -286,7 +286,7 @@ GITHUB_HOOK_TEMPLATE = _environ.get('GITHUB_HOOK', 'http://example.com/ide/project/%(project)d/github/push_hook?key=%(key)s') -SDK2_PEBBLE_WAF = _environ.get('SDK2_PEBBLE_TOOL', '/sdk2/pebble/waf') +SDK2_PEBBLE_WAF = _environ.get('SDK2_PEBBLE_WAF', '/sdk2/pebble/waf') SDK3_PEBBLE_WAF = _environ.get('SDK3_PEBBLE_WAF', '/sdk3/pebble/waf') ARM_CS_TOOLS = _environ.get('ARM_CS_TOOLS', '/arm-cs-tools/bin/') From 8bb5ac57a494392a7e09193250e307e45fe44f10 Mon Sep 17 00:00:00 2001 From: Katharine Berry Date: Sat, 19 Dec 2015 02:54:19 -0800 Subject: [PATCH 49/60] Add task time limits. --- cloudpebble/settings.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index 0172f6ce..d9860e1a 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -265,6 +265,9 @@ BROKER_URL = REDIS_URL + '/1' CELERY_RESULT_BACKEND = BROKER_URL +CELERYD_TASK_TIME_LIMIT = int(_environ.get('CELERYD_TASK_TIME_LIMIT', 620)) +CELERYD_TASK_SOFT_TIME_LIMIT = int(_environ.get('CELERYD_TASK_SOFT_TIME_LIMIT', 600)) + BROKER_POOL_LIMIT = int(_environ.get('BROKER_POOL_LIMIT', 10)) LOGIN_REDIRECT_URL = '/ide/' From 48f3c2359121d71f269bf94fabf7359ae589119d Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 21 Dec 2015 16:10:45 -0800 Subject: [PATCH 50/60] Correctly delete files from S3 --- ide/models/files.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/models/files.py b/ide/models/files.py index fd39003e..813e50fe 100644 --- a/ide/models/files.py +++ b/ide/models/files.py @@ -353,7 +353,7 @@ def delete_file(sender, instance, **kwargs): if sender == SourceFile or sender == ResourceVariant: if settings.AWS_ENABLED: try: - s3.delete_file(instance.s3_path) + s3.delete_file('source', instance.s3_path) except: traceback.print_exc() else: From 7388a097ca917b7ea7d727358c6a5e19325e55f7 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Tue, 5 Jan 2016 15:55:38 -0800 Subject: [PATCH 51/60] Ensure that the delete identifier button is initialized on creation. --- ide/static/ide/js/resources.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 2011131b..4f29c2f0 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -652,6 +652,16 @@ CloudPebble.Resources = (function() { }); }; + var initialise_resource_id_group = function(group, resource) { + group.find('.btn-delidentifier').click(function() { + CloudPebble.Prompts.Confirm(gettext("Do you want to this resource identifier?"), gettext("This cannot be undone."), function () { + group.remove(); + CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); + save(); + }); + }); + }; + var template = pane.find('.resource-id-group-single').detach(); var parent = $('#resource-id-group').removeClass('hide'); $.each(resource.resource_ids, function(index, value) { @@ -693,13 +703,7 @@ CloudPebble.Resources = (function() { update_platform_labels(pane); }); - group.find('.btn-delidentifier').click(function() { - CloudPebble.Prompts.Confirm(gettext("Do you want to this resource identifier?"), gettext("This cannot be undone."), function () { - group.remove(); - CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); - save(); - }); - }); + initialise_resource_id_group(group, resource); group.find('.resource-targets-section .text-icon').popover({ container: 'body', @@ -725,6 +729,7 @@ CloudPebble.Resources = (function() { update_font_preview(clone); }); } + initialise_resource_id_group(clone, resource); CloudPebble.Sidebar.SetIcon('resource-'+resource.id, 'edit'); }); From e7e7b73620e06efc46a8cd55b44a272123068f18 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Tue, 5 Jan 2016 17:12:17 -0800 Subject: [PATCH 52/60] Initialise jquery_csrf_setup() in settings.html This ensures that the AJAX request sends the CSRF token correctly. --- ide/templates/ide/settings.html | 81 +++++++++++++++++---------------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/ide/templates/ide/settings.html b/ide/templates/ide/settings.html index b69897f8..6544c99e 100644 --- a/ide/templates/ide/settings.html +++ b/ide/templates/ide/settings.html @@ -23,7 +23,6 @@

{% trans 'Editor settings' %}

{% if saved %}
{% trans 'Settings saved!' %}
{% endif %} - {%csrf_token%} {% for hidden in form.hidden_fields %} {{ hidden }} {% endfor %} @@ -64,49 +63,53 @@

{{ github.username }}

{% endblock %} {% block scripts %} + {% endblock %} From ea1f340a7ae4d247987863fd239d356489b81f43 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 6 Jan 2016 10:17:03 -0800 Subject: [PATCH 53/60] Made cancel button type="button" --- ide/templates/ide/project.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ide/templates/ide/project.html b/ide/templates/ide/project.html index 595c08e7..d0504653 100644 --- a/ide/templates/ide/project.html +++ b/ide/templates/ide/project.html @@ -251,7 +251,7 @@

{% trans 'Push New Commit' %}

From 0a3293ee15728b46ba83c3fb6be206101b53034a Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Wed, 6 Jan 2016 16:03:32 -0800 Subject: [PATCH 54/60] Fix errors in git push change detection Cloudpebble's git push command would return "nothing to commit" if the user only added or deleted a resource variant or changed a project setting. This commit fixes those cases. --- ide/tasks/git.py | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/ide/tasks/git.py b/ide/tasks/git.py index 6c1b8d58..1b90b164 100644 --- a/ide/tasks/git.py +++ b/ide/tasks/git.py @@ -1,6 +1,4 @@ import base64 -import shutil -import tempfile import urllib2 import json import os @@ -104,24 +102,16 @@ def github_push(user, commit_message, repo_name, project): next_tree[repo_path]._InputGitTreeElement__content = our_content has_changed = True - expected_source_files = [src_root + x.file_name for x in project_sources] - for path in next_tree.keys(): - if not path.startswith(src_root): - continue - if path not in expected_source_files: - del next_tree[path] - print "Deleted file: %s" % path - has_changed = True - # Now try handling resource files. - resources = project.resources.all() - resource_root = root + 'resources/' - + expected_resource_variant_paths = set() for res in resources: for variant in res.variants.all(): repo_path = resource_root + variant.path + split_repo_path = repo_path.split('/') + # This adds the resource *and* all parent directories to the list of expected paths. + expected_resource_variant_paths.update('/'.join(split_repo_path[:x]) for x in range(2, len(split_repo_path)+1)) if repo_path in next_tree: content = variant.get_contents() if git_sha(content) != next_tree[repo_path]._InputGitTreeElement__sha: @@ -132,10 +122,23 @@ def github_push(user, commit_message, repo_name, project): next_tree[repo_path]._InputGitTreeElement__sha = blob.sha else: print "New resource: %s" % repo_path + has_changed = True blob = repo.create_git_blob(base64.b64encode(variant.get_contents()), 'base64') print "Created blob %s" % blob.sha next_tree[repo_path] = InputGitTreeElement(path=repo_path, mode='100644', type='blob', sha=blob.sha) + # Manage deleted files + expected_source_file_paths = {src_root + x.file_name for x in project_sources} + expected_paths = expected_resource_variant_paths | expected_source_file_paths + for path in next_tree.keys(): + if not (path.startswith(src_root) or path.startswith(resource_root)): + continue + if path not in expected_paths: + del next_tree[path] + print "Deleted file: %s" % path + has_changed = True + + # Compare the resource dicts remote_manifest_path = root + 'appinfo.json' remote_wscript_path = root + 'wscript' @@ -163,6 +166,7 @@ def github_push(user, commit_message, repo_name, project): # This one is separate because there's more than just the resource map changing. if their_manifest_dict != our_manifest_dict: + has_changed = True if remote_manifest_path in next_tree: next_tree[remote_manifest_path]._InputGitTreeElement__sha = NotSet next_tree[remote_manifest_path]._InputGitTreeElement__content = generate_manifest(project, resources) From aca7939c9676fec96060332f84feb2e7aa30feb8 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Fri, 8 Jan 2016 12:07:44 -0800 Subject: [PATCH 55/60] Explicitly enable celery's pickle serializer. --- cloudpebble/settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloudpebble/settings.py b/cloudpebble/settings.py index d9860e1a..b025f1fa 100644 --- a/cloudpebble/settings.py +++ b/cloudpebble/settings.py @@ -264,6 +264,8 @@ BROKER_URL = REDIS_URL + '/1' CELERY_RESULT_BACKEND = BROKER_URL +CELERY_ACCEPT_CONTENT = ['pickle'] +CELERY_TASK_SERIALIZER = 'pickle' CELERYD_TASK_TIME_LIMIT = int(_environ.get('CELERYD_TASK_TIME_LIMIT', 620)) CELERYD_TASK_SOFT_TIME_LIMIT = int(_environ.get('CELERYD_TASK_SOFT_TIME_LIMIT', 600)) From 50a4f7ae8141198ef6c5b8fb7921c9f7f3c03bc9 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 11 Jan 2016 12:06:18 -0800 Subject: [PATCH 56/60] Removed nested $(function() {...}) --- ide/templates/ide/settings.html | 48 ++++++++++++++++----------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/ide/templates/ide/settings.html b/ide/templates/ide/settings.html index 6544c99e..05fe70b7 100644 --- a/ide/templates/ide/settings.html +++ b/ide/templates/ide/settings.html @@ -77,36 +77,36 @@

{{ github.username }}

$(this).siblings('.help-block').addClass('hide'); } }); + $('#unlink-btn').click(function(e) { e.preventDefault(); $('#unlink-form').submit(); }); - $(function() { - var save = function() { - var defer = $.Deferred(); - $.ajax({ - type: "POST", - url: "settings", - data: $("#user-settings-form").serialize(), // serializes the form's elements. - success: function(data) { - defer.resolve(); - }, - error: function(e) { - defer.reject(interpolate(gettext("Error: %s"), [error])); - } - }); - return defer.promise(); - }; - make_live_settings_form({ - save_function: save, - form: $("#user-settings-form") - }).init(); - - $("#user-settings-form").submit(function(e) { - e.preventDefault(); - return false; + var save = function() { + var defer = $.Deferred(); + $.ajax({ + type: "POST", + url: "settings", + data: $("#user-settings-form").serialize(), // serializes the form's elements. + success: function(data) { + defer.resolve(); + }, + error: function(e) { + defer.reject(interpolate(gettext("Error: %s"), [error])); + } }); + return defer.promise(); + }; + + make_live_settings_form({ + save_function: save, + form: $("#user-settings-form") + }).init(); + + $("#user-settings-form").submit(function(e) { + e.preventDefault(); + return false; }); }); From a101588a94f88d53a215bf93323fe5ac56e032ab Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 11 Jan 2016 13:00:11 -0800 Subject: [PATCH 57/60] Include worker_src in expected paths --- ide/tasks/git.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/ide/tasks/git.py b/ide/tasks/git.py index 8e3baeaf..1a58f93d 100644 --- a/ide/tasks/git.py +++ b/ide/tasks/git.py @@ -81,17 +81,23 @@ def github_push(user, commit_message, repo_name, project): root = find_project_root(paths) except: root = '' + expected_paths = set() + + def update_expected_paths(new_path): + # This adds the path *and* all parent directories to the list of expected paths. + split_path = new_path.split('/') + expected_paths.update('/'.join(split_path[:p]) for p in range(2, len(split_path) + 1)) src_root = root + 'src/' worker_src_root = root + 'worker_src/' project_sources = project.source_files.all() has_changed = False for source in project_sources: - repo_path = '' if source.target == 'worker': repo_path = worker_src_root + source.file_name else: repo_path = src_root + source.file_name + update_expected_paths(repo_path) if repo_path not in next_tree: has_changed = True next_tree[repo_path] = InputGitTreeElement(path=repo_path, mode='100644', type='blob', @@ -110,13 +116,10 @@ def github_push(user, commit_message, repo_name, project): # Now try handling resource files. resources = project.resources.all() resource_root = root + 'resources/' - expected_resource_variant_paths = set() for res in resources: for variant in res.variants.all(): repo_path = resource_root + variant.path - split_repo_path = repo_path.split('/') - # This adds the resource *and* all parent directories to the list of expected paths. - expected_resource_variant_paths.update('/'.join(split_repo_path[:x]) for x in range(2, len(split_repo_path)+1)) + update_expected_paths(repo_path) if repo_path in next_tree: content = variant.get_contents() if git_sha(content) != next_tree[repo_path]._InputGitTreeElement__sha: @@ -133,10 +136,8 @@ def github_push(user, commit_message, repo_name, project): next_tree[repo_path] = InputGitTreeElement(path=repo_path, mode='100644', type='blob', sha=blob.sha) # Manage deleted files - expected_source_file_paths = {src_root + x.file_name for x in project_sources} - expected_paths = expected_resource_variant_paths | expected_source_file_paths for path in next_tree.keys(): - if not (path.startswith(src_root) or path.startswith(resource_root)): + if not (any(path.startswith(root) for root in (src_root, resource_root, worker_src_root))): continue if path not in expected_paths: del next_tree[path] From 487c3510706f23a6b9d9a1adf2452104a4ca7103 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 11 Jan 2016 13:00:42 -0800 Subject: [PATCH 58/60] PEP-8 fixes --- ide/tasks/git.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ide/tasks/git.py b/ide/tasks/git.py index 1a58f93d..c15e7525 100644 --- a/ide/tasks/git.py +++ b/ide/tasks/git.py @@ -216,10 +216,12 @@ def update_expected_paths(new_path): return False + def get_root_path(path): path, extension = os.path.splitext(path) return path.split('~', 1)[0] + extension + @git_auth_check def github_pull(user, project): g = get_github(user) @@ -262,7 +264,7 @@ def github_pull(user, project): for resource in media: path = resource_root + resource['file'] if project_type == 'pebblejs' and resource['name'] in { - 'MONO_FONT_14', 'IMAGE_MENU_ICON', 'IMAGE_LOGO_SPLASH', 'IMAGE_TILE_SPLASH'}: + 'MONO_FONT_14', 'IMAGE_MENU_ICON', 'IMAGE_LOGO_SPLASH', 'IMAGE_TILE_SPLASH'}: continue if path not in paths_notags: raise Exception("Resource %s not found in repo." % path) From 651d8050cd2ede1ae116d616209a686fb7bf4190 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 11 Jan 2016 13:39:27 -0800 Subject: [PATCH 59/60] Elaborated update_expected_paths() explanation --- ide/tasks/git.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ide/tasks/git.py b/ide/tasks/git.py index c15e7525..cd2f4ea6 100644 --- a/ide/tasks/git.py +++ b/ide/tasks/git.py @@ -84,7 +84,10 @@ def github_push(user, commit_message, repo_name, project): expected_paths = set() def update_expected_paths(new_path): - # This adds the path *and* all parent directories to the list of expected paths. + # This adds the path *and* its parent directories to the list of expected paths. + # The parent directories are already keys in next_tree, so if they aren't present in expected_paths + # then, when iterating over next_tree to see which files have been deleted, we would have to treat + # directories as special cases. split_path = new_path.split('/') expected_paths.update('/'.join(split_path[:p]) for p in range(2, len(split_path) + 1)) From 3bdbb3dd0fc8d66fd4409f3fd0e6d08b9cd06386 Mon Sep 17 00:00:00 2001 From: Joseph Atkins-Turkish Date: Mon, 11 Jan 2016 17:12:28 -0800 Subject: [PATCH 60/60] Enforce PNG uploads for bitmap and pbi. Also set image/png as the content type of PBI downloads. --- ide/api/resource.py | 1 + ide/static/ide/js/resources.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/ide/api/resource.py b/ide/api/resource.py index 356bb0b9..4895cc10 100644 --- a/ide/api/resource.py +++ b/ide/api/resource.py @@ -235,6 +235,7 @@ def show_resource(request, project_id, resource_id, variant): u'png': 'image/png', u'png-trans': 'image/png', u'bitmap': 'image/png', + u'pbi': 'image/png', u'font': 'application/octet-stream', u'raw': 'application/octet-stream' } diff --git a/ide/static/ide/js/resources.js b/ide/static/ide/js/resources.js index 4f29c2f0..0099da90 100644 --- a/ide/static/ide/js/resources.js +++ b/ide/static/ide/js/resources.js @@ -195,7 +195,7 @@ CloudPebble.Resources = (function() { if(files.length != 1) { return null; } - if((kind == 'png' || kind == 'png-trans') && file.type != "image/png") { + if(_.contains(['bitmap', 'png', 'png-trans', 'pbi'], kind) && file.type != "image/png") { throw (gettext("You must upload a PNG image.")); } return file; @@ -528,10 +528,10 @@ CloudPebble.Resources = (function() { case 'bitmap': case 'png': case 'png-trans': + case 'pbi': template_name = 'image'; break; case 'raw': - case 'pbi': case 'font': template_name = 'raw'; break; @@ -964,7 +964,7 @@ CloudPebble.Resources = (function() { return names; }, GetBitmaps: function() { - return _.filter(project_resources, function(item) { return /^png/.test(item.kind); }); + return _.filter(project_resources, function(item) { return /^(png|pbi|bitmap)/.test(item.kind); }); }, GetFonts: function() { return _.where(project_resources, {kind: 'font'});