diff --git a/contentcuration/contentcuration/frontend/channelEdit/views/files/__tests__/fileUpload.spec.js b/contentcuration/contentcuration/frontend/channelEdit/views/files/__tests__/fileUpload.spec.js index 9929ff4872..6eb2bd69b7 100644 --- a/contentcuration/contentcuration/frontend/channelEdit/views/files/__tests__/fileUpload.spec.js +++ b/contentcuration/contentcuration/frontend/channelEdit/views/files/__tests__/fileUpload.spec.js @@ -71,9 +71,9 @@ describe('fileUpload', () => { }); describe('computed', () => { it('should map the files to the correct presets', () => { - expect(wrapper.vm.primaryFileMapping[0].file.id).toBe('file-3'); - expect(wrapper.vm.primaryFileMapping[1].file.id).toBe('file-1'); - expect(wrapper.vm.primaryFileMapping).toHaveLength(2); + expect(wrapper.vm.primaryFileMapping[1].file.id).toBe('file-3'); + expect(wrapper.vm.primaryFileMapping[2].file.id).toBe('file-1'); + expect(wrapper.vm.primaryFileMapping).toHaveLength(3); }); it('should disallow file removal if there is only one primary file', () => { const testFiles2 = [ @@ -95,7 +95,7 @@ describe('fileUpload', () => { describe('methods', () => { let uploadItem; beforeEach(() => { - uploadItem = wrapper.findAll(FileUploadItem).at(1); + uploadItem = wrapper.findAll(FileUploadItem).at(2); }); it('should automatically select the first file on load', () => { expect(wrapper.vm.selected).toBe('file-3'); diff --git a/contentcuration/contentcuration/frontend/shared/leUtils/FormatPresets.js b/contentcuration/contentcuration/frontend/shared/leUtils/FormatPresets.js index 4dc73e8fa4..a5897d1902 100644 --- a/contentcuration/contentcuration/frontend/shared/leUtils/FormatPresets.js +++ b/contentcuration/contentcuration/frontend/shared/leUtils/FormatPresets.js @@ -45,7 +45,23 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'audio', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], + }, + ], + [ + 'bloompub', + { + id: 'bloompub', + readable_name: 'Bloom Document', + multi_language: false, + supplementary: false, + thumbnail: false, + subtitle: false, + display: true, + order: 1, + kind_id: 'document', + allowed_formats: ['bloompub', 'bloomd'], + associated_mimetypes: ['application/bloompub+zip'], }, ], [ @@ -61,7 +77,7 @@ const FormatPresetsMap = new Map([ order: 0, kind_id: null, allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -93,7 +109,7 @@ const FormatPresetsMap = new Map([ order: 3, kind_id: 'document', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -141,7 +157,7 @@ const FormatPresetsMap = new Map([ order: 4, kind_id: 'exercise', allowed_formats: ['svg', 'json', 'graphie'], - associated_mimetypes: ['image/svg', '.graphie', 'application/json'], + associated_mimetypes: ['application/json', 'image/svg', '.graphie'], }, ], [ @@ -157,7 +173,7 @@ const FormatPresetsMap = new Map([ order: 3, kind_id: 'exercise', allowed_formats: ['png', 'jpg', 'jpeg', 'gif', 'svg'], - associated_mimetypes: ['image/svg', 'image/gif', 'image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg', 'image/svg', 'image/gif'], }, ], [ @@ -173,7 +189,7 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'exercise', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -205,7 +221,7 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'h5p', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -253,7 +269,7 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'html5', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -272,6 +288,22 @@ const FormatPresetsMap = new Map([ associated_mimetypes: ['.zip'], }, ], + [ + 'imscp_zip', + { + id: 'imscp_zip', + readable_name: 'IMSCP Zip', + multi_language: false, + supplementary: false, + thumbnail: false, + subtitle: false, + display: true, + order: 1, + kind_id: 'html5', + allowed_formats: ['zip'], + associated_mimetypes: ['.zip'], + }, + ], [ 'low_res_video', { @@ -317,7 +349,7 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'exercise', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -333,7 +365,7 @@ const FormatPresetsMap = new Map([ order: 3, kind_id: 'slideshow', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -365,7 +397,7 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'slideshow', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -381,7 +413,7 @@ const FormatPresetsMap = new Map([ order: 1, kind_id: 'topic', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -429,7 +461,7 @@ const FormatPresetsMap = new Map([ order: 3, kind_id: 'video', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], [ @@ -461,7 +493,7 @@ const FormatPresetsMap = new Map([ order: 2, kind_id: 'zim', allowed_formats: ['png', 'jpg', 'jpeg'], - associated_mimetypes: ['image/jpeg', 'image/png'], + associated_mimetypes: ['image/png', 'image/jpeg'], }, ], ]); @@ -474,6 +506,7 @@ export const FormatPresetsNames = { AUDIO: 'audio', AUDIO_DEPENDENCY: 'audio_dependency', AUDIO_THUMBNAIL: 'audio_thumbnail', + BLOOMPUB: 'bloompub', CHANNEL_THUMBNAIL: 'channel_thumbnail', DOCUMENT: 'document', DOCUMENT_THUMBNAIL: 'document_thumbnail', @@ -488,6 +521,7 @@ export const FormatPresetsNames = { HTML5_DEPENDENCY: 'html5_dependency', HTML5_THUMBNAIL: 'html5_thumbnail', HTML5_ZIP: 'html5_zip', + IMSCP_ZIP: 'imscp_zip', LOW_RES_VIDEO: 'low_res_video', QTI: 'qti', QTI_THUMBNAIL: 'qti_thumbnail', diff --git a/contentcuration/contentcuration/frontend/shared/mixins.js b/contentcuration/contentcuration/frontend/shared/mixins.js index 5f200ef18c..01095f6df2 100644 --- a/contentcuration/contentcuration/frontend/shared/mixins.js +++ b/contentcuration/contentcuration/frontend/shared/mixins.js @@ -117,6 +117,7 @@ export const constantStrings = createTranslator('ConstantStrings', { mp3: 'MP3 audio', pdf: 'PDF document', epub: 'EPub document', + bloompub: 'BloomPub document', jpg: 'JPG image', jpeg: 'JPEG image', png: 'PNG image', diff --git a/contentcuration/contentcuration/frontend/shared/views/files/Uploader.vue b/contentcuration/contentcuration/frontend/shared/views/files/Uploader.vue index 3d4997e1c6..7e208cc82f 100644 --- a/contentcuration/contentcuration/frontend/shared/views/files/Uploader.vue +++ b/contentcuration/contentcuration/frontend/shared/views/files/Uploader.vue @@ -14,7 +14,7 @@ ref="fileUpload" style="display: none;" type="file" - :accept="acceptedMimetypes" + :accept="acceptedFileTypes" :multiple="allowMultiple" data-test="upload-dialog" @change="handleFiles($event.target.files)" @@ -129,8 +129,12 @@ : !fp.supplementary && (!this.displayOnly || fp.display) ); }, - acceptedMimetypes() { - return flatMap(this.acceptedFiles, f => f.associated_mimetypes).join(','); + acceptedFileTypes() { + return uniq( + flatMap(this.acceptedFiles, f => f.associated_mimetypes).concat( + this.acceptedExtensions.map(ext => `.${ext}`) + ) + ).join(','); }, acceptedExtensions() { return uniq(flatMap(this.acceptedFiles, f => f.allowed_formats)); diff --git a/contentcuration/contentcuration/migrations/0150_bloompub_format_and_preset.py b/contentcuration/contentcuration/migrations/0150_bloompub_format_and_preset.py new file mode 100644 index 0000000000..e1ffc389ef --- /dev/null +++ b/contentcuration/contentcuration/migrations/0150_bloompub_format_and_preset.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.24 on 2024-09-17 16:37 +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + ('contentcuration', '0149_unpublishable_change_field'), + ] + + operations = [ + migrations.AlterField( + model_name='fileformat', + name='extension', + field=models.CharField(choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'Bloom Document'), ('bloomd', 'Bloom Document')], max_length=40, primary_key=True, serialize=False), + ), + migrations.AlterField( + model_name='formatpreset', + name='id', + field=models.CharField(choices=[('high_res_video', 'High Resolution'), ('low_res_video', 'Low Resolution'), ('video_thumbnail', 'Thumbnail'), ('video_subtitle', 'Subtitle'), ('video_dependency', 'Video (dependency)'), ('audio', 'Audio'), ('audio_thumbnail', 'Thumbnail'), ('audio_dependency', 'audio (dependency)'), ('document', 'Document'), ('epub', 'ePub Document'), ('document_thumbnail', 'Thumbnail'), ('exercise', 'Exercise'), ('exercise_thumbnail', 'Thumbnail'), ('exercise_image', 'Exercise Image'), ('exercise_graphie', 'Exercise Graphie'), ('channel_thumbnail', 'Channel Thumbnail'), ('topic_thumbnail', 'Thumbnail'), ('html5_zip', 'HTML5 Zip'), ('html5_dependency', 'HTML5 Dependency (Zip format)'), ('html5_thumbnail', 'HTML5 Thumbnail'), ('h5p', 'H5P Zip'), ('h5p_thumbnail', 'H5P Thumbnail'), ('zim', 'Zim'), ('zim_thumbnail', 'Zim Thumbnail'), ('qti', 'QTI Zip'), ('qti_thumbnail', 'QTI Thumbnail'), ('slideshow_image', 'Slideshow Image'), ('slideshow_thumbnail', 'Slideshow Thumbnail'), ('slideshow_manifest', 'Slideshow Manifest'), ('imscp_zip', 'IMSCP Zip'), ('bloompub', 'Bloom Document')], max_length=150, primary_key=True, serialize=False), + ), + ] diff --git a/contentcuration/kolibri_content/migrations/0021_auto_20240612_1847.py b/contentcuration/kolibri_content/migrations/0021_auto_20240612_1847.py new file mode 100644 index 0000000000..8795b6bbd2 --- /dev/null +++ b/contentcuration/kolibri_content/migrations/0021_auto_20240612_1847.py @@ -0,0 +1,28 @@ +# Generated by Django 3.2.24 on 2024-06-12 18:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0020_alter_file_preset'), + ] + + operations = [ + migrations.AlterField( + model_name='file', + name='extension', + field=models.CharField(blank=True, choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'bloom Document')], max_length=40), + ), + migrations.AlterField( + model_name='file', + name='preset', + field=models.CharField(blank=True, choices=[('high_res_video', 'High Resolution'), ('low_res_video', 'Low Resolution'), ('video_thumbnail', 'Thumbnail'), ('video_subtitle', 'Subtitle'), ('video_dependency', 'Video (dependency)'), ('audio', 'Audio'), ('audio_thumbnail', 'Thumbnail'), ('audio_dependency', 'audio (dependency)'), ('document', 'Document'), ('epub', 'ePub Document'), ('document_thumbnail', 'Thumbnail'), ('exercise', 'Exercise'), ('exercise_thumbnail', 'Thumbnail'), ('exercise_image', 'Exercise Image'), ('exercise_graphie', 'Exercise Graphie'), ('channel_thumbnail', 'Channel Thumbnail'), ('topic_thumbnail', 'Thumbnail'), ('html5_zip', 'HTML5 Zip'), ('html5_dependency', 'HTML5 Dependency (Zip format)'), ('html5_thumbnail', 'HTML5 Thumbnail'), ('h5p', 'H5P Zip'), ('h5p_thumbnail', 'H5P Thumbnail'), ('zim', 'Zim'), ('zim_thumbnail', 'Zim Thumbnail'), ('qti', 'QTI Zip'), ('qti_thumbnail', 'QTI Thumbnail'), ('slideshow_image', 'Slideshow Image'), ('slideshow_thumbnail', 'Slideshow Thumbnail'), ('slideshow_manifest', 'Slideshow Manifest'), ('imscp_zip', 'IMSCP Zip'), ('bloompub', 'Bloom Document')], max_length=150), + ), + migrations.AlterField( + model_name='localfile', + name='extension', + field=models.CharField(blank=True, choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'bloom Document')], max_length=40), + ), + ] diff --git a/contentcuration/kolibri_content/migrations/0022_auto_20240915_1414.py b/contentcuration/kolibri_content/migrations/0022_auto_20240915_1414.py new file mode 100644 index 0000000000..c090382987 --- /dev/null +++ b/contentcuration/kolibri_content/migrations/0022_auto_20240915_1414.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.24 on 2024-09-15 14:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('content', '0021_auto_20240612_1847'), + ] + + operations = [ + migrations.AlterField( + model_name='file', + name='extension', + field=models.CharField(blank=True, choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'Bloom Document'), ('bloomd', 'Bloom Document')], max_length=40), + ), + migrations.AlterField( + model_name='localfile', + name='extension', + field=models.CharField(blank=True, choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'Bloom Document'), ('bloomd', 'Bloom Document')], max_length=40), + ), + ] diff --git a/contentcuration/kolibri_public/migrations/0004_auto_20240612_1847.py b/contentcuration/kolibri_public/migrations/0004_auto_20240612_1847.py new file mode 100644 index 0000000000..2c3110c2d8 --- /dev/null +++ b/contentcuration/kolibri_public/migrations/0004_auto_20240612_1847.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.24 on 2024-06-12 18:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kolibri_public', '0003_alter_file_preset'), + ] + + operations = [ + migrations.AlterField( + model_name='file', + name='preset', + field=models.CharField(blank=True, choices=[('high_res_video', 'High Resolution'), ('low_res_video', 'Low Resolution'), ('video_thumbnail', 'Thumbnail'), ('video_subtitle', 'Subtitle'), ('video_dependency', 'Video (dependency)'), ('audio', 'Audio'), ('audio_thumbnail', 'Thumbnail'), ('audio_dependency', 'audio (dependency)'), ('document', 'Document'), ('epub', 'ePub Document'), ('document_thumbnail', 'Thumbnail'), ('exercise', 'Exercise'), ('exercise_thumbnail', 'Thumbnail'), ('exercise_image', 'Exercise Image'), ('exercise_graphie', 'Exercise Graphie'), ('channel_thumbnail', 'Channel Thumbnail'), ('topic_thumbnail', 'Thumbnail'), ('html5_zip', 'HTML5 Zip'), ('html5_dependency', 'HTML5 Dependency (Zip format)'), ('html5_thumbnail', 'HTML5 Thumbnail'), ('h5p', 'H5P Zip'), ('h5p_thumbnail', 'H5P Thumbnail'), ('zim', 'Zim'), ('zim_thumbnail', 'Zim Thumbnail'), ('qti', 'QTI Zip'), ('qti_thumbnail', 'QTI Thumbnail'), ('slideshow_image', 'Slideshow Image'), ('slideshow_thumbnail', 'Slideshow Thumbnail'), ('slideshow_manifest', 'Slideshow Manifest'), ('imscp_zip', 'IMSCP Zip'), ('bloompub', 'Bloom Document')], max_length=150), + ), + migrations.AlterField( + model_name='localfile', + name='extension', + field=models.CharField(blank=True, choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'bloom Document')], max_length=40), + ), + ] diff --git a/contentcuration/kolibri_public/migrations/0005_alter_localfile_extension.py b/contentcuration/kolibri_public/migrations/0005_alter_localfile_extension.py new file mode 100644 index 0000000000..cbdc55dce3 --- /dev/null +++ b/contentcuration/kolibri_public/migrations/0005_alter_localfile_extension.py @@ -0,0 +1,18 @@ +# Generated by Django 3.2.24 on 2024-09-15 14:14 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('kolibri_public', '0004_auto_20240612_1847'), + ] + + operations = [ + migrations.AlterField( + model_name='localfile', + name='extension', + field=models.CharField(blank=True, choices=[('mp4', 'MP4 Video'), ('webm', 'WEBM Video'), ('vtt', 'VTT Subtitle'), ('mp3', 'MP3 Audio'), ('pdf', 'PDF Document'), ('jpg', 'JPG Image'), ('jpeg', 'JPEG Image'), ('png', 'PNG Image'), ('gif', 'GIF Image'), ('json', 'JSON'), ('svg', 'SVG Image'), ('perseus', 'Perseus Exercise'), ('graphie', 'Graphie Exercise'), ('zip', 'HTML5 Zip'), ('h5p', 'H5P'), ('zim', 'ZIM'), ('epub', 'ePub Document'), ('bloompub', 'Bloom Document'), ('bloomd', 'Bloom Document')], max_length=40), + ), + ] diff --git a/requirements.txt b/requirements.txt index 09dce49a93..525d9d2e57 100644 --- a/requirements.txt +++ b/requirements.txt @@ -155,7 +155,7 @@ jsonschema==4.17.3 # via -r requirements.in kombu==5.2.4 # via celery -le-utils==0.2.5 +le-utils==0.2.7 # via -r requirements.in packaging==24.0 # via