From 4fdfc480fe8128a637f52b1acef792a96aacee0c Mon Sep 17 00:00:00 2001 From: Yueyu Date: Sat, 9 Oct 2021 06:55:21 +0800 Subject: [PATCH 1/7] export: add library assets to zip --- .../android/JavaScriptDirectInterface.java | 32 +++++++++++-------- .../org/scratchjr/android/ScratchJrUtil.java | 4 +++ ios/ScratchJr/src/IO.m | 22 +++++++++---- src/tablet/IO.js | 2 +- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java index 188dccb..cf275b5 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java @@ -628,20 +628,14 @@ public class JavaScriptDirectInterface { } for (int i = 0; i < files.length(); i++) { String file = files.optString(i); - if (file == null) { - continue; - } - File srcFile = new File(_activity.getFilesDir() + File.separator + file); - if (!srcFile.exists()) { - Log.e(LOG_TAG, "src file not exists" + file); - continue; - } - File targetFile = new File(folder.getAbsolutePath() + File.separator + file); - // Log.d(LOG_TAG, "copying assets" + file); - try { - ScratchJrUtil.copyFile(srcFile, targetFile); - } catch (IOException e) { - e.printStackTrace(); + if (file != null) { + File targetFile = new File(folder.getAbsolutePath() + File.separator + file); + try { + this.copyAssetTo(file, targetFile); + } catch (IOException e) { + Log.e(LOG_TAG, "Asset for " + file + " copy failed."); + e.printStackTrace(); + } } } } @@ -657,6 +651,16 @@ public class JavaScriptDirectInterface { return fullName; } + private void copyAssetTo(String file, File targetFile) throws IOException { + File srcFile = new File(_activity.getFilesDir() + File.separator + file); + if (srcFile.exists()) { + ScratchJrUtil.copyFile(srcFile, targetFile); + } else { + InputStream inputStream = _activity.getAssets().open("HTML5/svglibrary/" + file); + ScratchJrUtil.copyFile(inputStream, targetFile); + } + } + @JavascriptInterface public void sendSjrUsingShareDialog(String fileName, String emailSubject, String emailBody, int shareType) { diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrUtil.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrUtil.java index d7879e5..ad51c00 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrUtil.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrUtil.java @@ -98,6 +98,10 @@ public class ScratchJrUtil { throws IOException { InputStream in = new FileInputStream(sourceLocation); + copyFile(in, targetLocation); + } + + public static void copyFile(InputStream in, File targetLocation) throws IOException { OutputStream out = new FileOutputStream(targetLocation); // Copy the bits from instream to outstream diff --git a/ios/ScratchJr/src/IO.m b/ios/ScratchJr/src/IO.m index eb8c189..8536a5f 100644 --- a/ios/ScratchJr/src/IO.m +++ b/ios/ScratchJr/src/IO.m @@ -242,13 +242,7 @@ NSMutableDictionary *soundtimers; NSString *subDir = [projectDir stringByAppendingPathComponent:key]; [fileManager createDirectoryAtPath:subDir withIntermediateDirectories:true attributes:nil error:nil]; for (NSString *file in [metadata valueForKey:key]) { - // copy file to target folder - // NSLog(@"%@ %@", key, file); - NSString *srcPath = [[IO getpath] stringByAppendingPathComponent:file]; - NSString *toPath = [subDir stringByAppendingPathComponent:file]; - if ([fileManager fileExistsAtPath:srcPath]) { - [fileManager copyItemAtPath:srcPath toPath:toPath error:nil]; - } + [IO copyAssetTo:file :subDir]; } } @@ -270,6 +264,20 @@ NSMutableDictionary *soundtimers; return fullName; } ++ (void) copyAssetTo: (NSString *) file :(NSString *) toFolder { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString *srcPath = [[IO getpath] stringByAppendingPathComponent:file]; + NSString *toPath = [toFolder stringByAppendingPathComponent:file]; + if (![fileManager fileExistsAtPath:srcPath]) { + // It's not a user created asset, goto svglibrary to find it. + NSString* libraryPath = [@"/HTML5/svglibrary/" stringByAppendingString:file]; + srcPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingString:libraryPath]; + } + if ([fileManager fileExistsAtPath:srcPath]) { + [fileManager copyItemAtPath:srcPath toPath:toPath error:nil]; + } +} + // Receive a .sjr file from inside the app. Send using native UI - Airdrop or Email + (NSString*) sendSjrUsingShareDialog:(NSString *)fileName :(NSString*)emailSubject :(NSString*)emailBody :(int)shareType { diff --git a/src/tablet/IO.js b/src/tablet/IO.js index 35b156e..f579475 100644 --- a/src/tablet/IO.js +++ b/src/tablet/IO.js @@ -4,7 +4,7 @@ import {setCanvasSize, drawThumbnail} from '../utils/lib'; import SVG2Canvas from '../utils/SVG2Canvas'; const database = 'projects'; -const collectLibraryAssets = false; +const collectLibraryAssets = true; // Sharing state let zipFileName = ''; From 032e7b1d911d3ef8e8b0119b7f29b86044c301a2 Mon Sep 17 00:00:00 2001 From: Yueyu Date: Sat, 9 Oct 2021 07:22:10 +0800 Subject: [PATCH 2/7] Import: don't add library assets to database --- .../java/org/scratchjr/android/IOManager.java | 5 ++++ .../android/JavaScriptDirectInterface.java | 5 ++++ .../scratchjr/android/ScratchJrActivity.java | 25 +++++++++++++++++++ ios/ScratchJr/src/IO.m | 4 +++ ios/ScratchJr/src/JsBridge.m | 6 +++++ ios/ScratchJr/src/ScratchJr.h | 3 +++ ios/ScratchJr/src/ScratchJr.m | 15 +++++++++++ src/entry/app.js | 5 +++- src/tablet/Android.js | 5 ++++ src/tablet/OS.js | 4 +++ src/tablet/iOS.js | 7 ++++++ 11 files changed, 83 insertions(+), 1 deletion(-) diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java index 8f769cd..635d0ca 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java @@ -339,6 +339,11 @@ public class IOManager { if ("thumbnails".equals(folderName) || "sounds".equals(folderName)) { continue; } + if (activity.libraryHasAsset(fileName)) { + Log.e(LOG_TAG, "asset for " + fileName + " exists in library"); + // this is in library assets. + continue; + } String table = "characters".equals(folderName) ? "usershapes" : "userbkgs"; String statement = String.format("SELECT id FROM %s WHERE md5 = ?", table); JSONArray rows = _databaseManager.query(statement, new String[]{fileName}); diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java index cf275b5..56cc90c 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java @@ -661,6 +661,11 @@ public class JavaScriptDirectInterface { } } + @JavascriptInterface + public void registerLibraryAssets(String assets) { + _activity.registerLibraryAssets(assets.split(",")); + } + @JavascriptInterface public void sendSjrUsingShareDialog(String fileName, String emailSubject, String emailBody, int shareType) { diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java index 72a1285..59ce749 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java @@ -38,6 +38,7 @@ import com.google.firebase.analytics.FirebaseAnalytics; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.Vector; /** @@ -112,6 +113,13 @@ public class ScratchJrActivity */ private ArrayList projectUris = new ArrayList<>(); + /** + * This set will contain all the library assets. + * We are using set here so that we can find the asset + * whether in library in O(1) time. + */ + private final HashSet assetList = new HashSet<>(200); + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -657,4 +665,21 @@ public class ScratchJrActivity } }); } + + /** + * We record all library assets names when app starts, + * so that we can know whether an asset should be marked + * as a user created one when importing. + * @param assets library asset md5 + */ + public void registerLibraryAssets(String[] assets) { + int length = assets.length; + for (int i = 0; i < length; i++) { + assetList.add(assets[i]); + } + } + + public boolean libraryHasAsset(String md5) { + return assetList.contains(md5); + } } diff --git a/ios/ScratchJr/src/IO.m b/ios/ScratchJr/src/IO.m index 8536a5f..71cae34 100644 --- a/ios/ScratchJr/src/IO.m +++ b/ios/ScratchJr/src/IO.m @@ -342,6 +342,10 @@ NSMutableDictionary *soundtimers; [fileManager copyItemAtPath:fromPath toPath:toPath error:nil]; } + if ([ScratchJr libraryHasAsset:fileName]) { + continue; + } + NSArray *parts = [path componentsSeparatedByString:@"/"]; if (parts.count > 1) { NSString *folder = parts[1]; diff --git a/ios/ScratchJr/src/JsBridge.m b/ios/ScratchJr/src/JsBridge.m index c6e66a9..f156ad2 100644 --- a/ios/ScratchJr/src/JsBridge.m +++ b/ios/ScratchJr/src/JsBridge.m @@ -207,4 +207,10 @@ [request callback:[[UIDevice currentDevice] name]]; } +- (void) registerLibraryAssets: (JsRequest *) request { + NSString *assets = request.params[0]; + [ScratchJr registerLibraryAssets: [assets componentsSeparatedByString:@","]]; + [request callback:@"1"]; +} + @end diff --git a/ios/ScratchJr/src/ScratchJr.h b/ios/ScratchJr/src/ScratchJr.h index 5a414af..fea87f5 100644 --- a/ios/ScratchJr/src/ScratchJr.h +++ b/ios/ScratchJr/src/ScratchJr.h @@ -170,4 +170,7 @@ // Imports + (void) receiveProject:(NSURL *) url; + ++ (void) registerLibraryAssets: (NSArray *)assets; ++ (BOOL) libraryHasAsset: (NSString *)md5; @end diff --git a/ios/ScratchJr/src/ScratchJr.m b/ios/ScratchJr/src/ScratchJr.m index 247f75e..ad79933 100644 --- a/ios/ScratchJr/src/ScratchJr.m +++ b/ios/ScratchJr/src/ScratchJr.m @@ -16,6 +16,8 @@ AVCaptureVideoPreviewLayer* captureVideoPreviewLayer; NSString *oncomplete; +NSMutableSet *assets; + ////////////////////////// // Init functions ///////////////////////// @@ -169,4 +171,17 @@ NSString *oncomplete; cameraMask = nil; } ++ (void) registerLibraryAssets: (NSArray *)assetArr { + if (assets == nil) { + assets = [[NSMutableSet alloc] init]; + } + for (NSString* md5 in assetArr) { + [assets addObject:md5]; + } +} + ++ (BOOL) libraryHasAsset:(NSString *)md5 { + return [assets containsObject:md5]; +} + @end diff --git a/src/entry/app.js b/src/entry/app.js index 2b4dabd..b3cfa69 100644 --- a/src/entry/app.js +++ b/src/entry/app.js @@ -41,7 +41,10 @@ window.onload = () => { preprocessAndLoadCss('css', 'css/thumbs.css'); /* For parental gate. These CSS properties should be refactored */ preprocessAndLoadCss('css', 'css/editor.css'); - entryFunction = () => OS.waitForInterface(indexMain); + entryFunction = () => OS.waitForInterface(function () { + var assets = Object.keys(MediaLib.keys).join(','); + OS.registerLibraryAssets(assets, indexMain); + }); break; case 'home': // Lobby pages diff --git a/src/tablet/Android.js b/src/tablet/Android.js index fd0eb5f..3d8e15d 100644 --- a/src/tablet/Android.js +++ b/src/tablet/Android.js @@ -254,6 +254,11 @@ export default class Android { // return 1; // } + static registerLibraryAssets (assets, fcn) { + AndroidInterface.registerLibraryAssets(assets); + fcn && fcn(); + } + // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { diff --git a/src/tablet/OS.js b/src/tablet/OS.js index 8cde8e0..d8242d4 100644 --- a/src/tablet/OS.js +++ b/src/tablet/OS.js @@ -247,6 +247,10 @@ export default class OS { return 1; } + static registerLibraryAssets (assets, fcn) { + tabletInterface.registerLibraryAssets(assets, fcn); + } + // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { diff --git a/src/tablet/iOS.js b/src/tablet/iOS.js index 8093987..0064be3 100644 --- a/src/tablet/iOS.js +++ b/src/tablet/iOS.js @@ -367,6 +367,13 @@ export default class iOS { iOS.call('sendSjrUsingShareDialog', fileName, emailSubject, emailBody, shareType); } + static registerLibraryAssets (assets, fcn) { + (async () => { + await iOS.call('registerLibraryAssets', assets); + fcn && fcn(); + })(); + } + // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { From e9120b0854f9e0a87afc37ac6fa3c7b97edfd6c2 Mon Sep 17 00:00:00 2001 From: Yueyu Date: Sat, 9 Oct 2021 19:24:30 +0800 Subject: [PATCH 3/7] export `assetLibraryVersion` field in `media.json` file --- editions/free/src/media.json | 1 + src/tablet/IO.js | 1 + src/tablet/MediaLib.js | 9 +++++++++ 3 files changed, 11 insertions(+) diff --git a/editions/free/src/media.json b/editions/free/src/media.json index becae6f..d471d53 100644 --- a/editions/free/src/media.json +++ b/editions/free/src/media.json @@ -1,4 +1,5 @@ { + "assetLibraryVersion": 1, "path": "./svglibrary/", "samples": [ "samples/Star.txt" diff --git a/src/tablet/IO.js b/src/tablet/IO.js index f579475..7a38a7b 100644 --- a/src/tablet/IO.js +++ b/src/tablet/IO.js @@ -302,6 +302,7 @@ export default class IO { 'sounds': [] }; var jsonData = IO.parseProjectData(JSON.parse(projectFromDB)[0]); + jsonData.assetLibraryVersion = MediaLib.version; // Collect project assets for inclusion in zip file // Parse JSON representations of project data / thumbnail into usable types diff --git a/src/tablet/MediaLib.js b/src/tablet/MediaLib.js index f5d233d..9106e1e 100644 --- a/src/tablet/MediaLib.js +++ b/src/tablet/MediaLib.js @@ -7,6 +7,7 @@ let backgrounds; let sprites; let sounds; let keys = {}; +let version = 0; export default class MediaLib { static get path () { @@ -33,6 +34,10 @@ export default class MediaLib { return keys; } + static get version () { + return version; + } + static loadMediaLib (root, whenDone) { IO.requestFromServer(root + 'media.json', (result) => { let parsedResult = JSON.parse(result); @@ -42,6 +47,10 @@ export default class MediaLib { backgrounds = parsedResult.backgrounds; sounds = parsedResult.sounds; + if (parsedResult.assetLibraryVersion) { + version = parsedResult.assetLibraryVersion; + } + MediaLib.localizeMediaNames(); MediaLib.generateKeys(); From c74319cfe008caa8be0b7abb44c12956f726afd0 Mon Sep 17 00:00:00 2001 From: Yueyu Date: Sat, 9 Oct 2021 19:25:14 +0800 Subject: [PATCH 4/7] register assetLibraryVersion to native --- .../scratchjr/android/JavaScriptDirectInterface.java | 3 ++- .../java/org/scratchjr/android/ScratchJrActivity.java | 2 ++ ios/ScratchJr/src/JsBridge.m | 3 ++- ios/ScratchJr/src/ScratchJr.h | 2 ++ ios/ScratchJr/src/ScratchJr.m | 10 ++++++++++ src/entry/app.js | 2 +- src/tablet/Android.js | 4 ++-- src/tablet/OS.js | 4 ++-- src/tablet/iOS.js | 4 ++-- 9 files changed, 25 insertions(+), 9 deletions(-) diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java index 56cc90c..d8d3959 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java @@ -662,7 +662,8 @@ public class JavaScriptDirectInterface { } @JavascriptInterface - public void registerLibraryAssets(String assets) { + public void registerLibraryAssets(int version, String assets) { + _activity.assetLibraryVersion = version; _activity.registerLibraryAssets(assets.split(",")); } diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java index 59ce749..a639fa9 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/ScratchJrActivity.java @@ -120,6 +120,8 @@ public class ScratchJrActivity */ private final HashSet assetList = new HashSet<>(200); + public int assetLibraryVersion = 0; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/ios/ScratchJr/src/JsBridge.m b/ios/ScratchJr/src/JsBridge.m index f156ad2..4aaf06e 100644 --- a/ios/ScratchJr/src/JsBridge.m +++ b/ios/ScratchJr/src/JsBridge.m @@ -208,7 +208,8 @@ } - (void) registerLibraryAssets: (JsRequest *) request { - NSString *assets = request.params[0]; + ScratchJr.assetLibraryVersion = (NSInteger) request.params[0]; + NSString *assets = request.params[1]; [ScratchJr registerLibraryAssets: [assets componentsSeparatedByString:@","]]; [request callback:@"1"]; } diff --git a/ios/ScratchJr/src/ScratchJr.h b/ios/ScratchJr/src/ScratchJr.h index fea87f5..fb2c21a 100644 --- a/ios/ScratchJr/src/ScratchJr.h +++ b/ios/ScratchJr/src/ScratchJr.h @@ -155,6 +155,8 @@ @interface ScratchJr : NSObject +@property (class, nonatomic, assign) NSInteger assetLibraryVersion; + + (void)sendBase64Image:(NSData *)imagedata; + (void)reportImageError; + (void)cameraInit; diff --git a/ios/ScratchJr/src/ScratchJr.m b/ios/ScratchJr/src/ScratchJr.m index ad79933..4c27b74 100644 --- a/ios/ScratchJr/src/ScratchJr.m +++ b/ios/ScratchJr/src/ScratchJr.m @@ -14,6 +14,16 @@ AVCaptureVideoPreviewLayer* captureVideoPreviewLayer; @implementation ScratchJr : NSObject +static NSInteger _assetLibraryVersion = 0; + ++ (NSInteger) assetLibraryVersion { + return _assetLibraryVersion; +} + ++ (void) setAssetLibraryVersion:(NSInteger)newValue { + _assetLibraryVersion = newValue; +} + NSString *oncomplete; NSMutableSet *assets; diff --git a/src/entry/app.js b/src/entry/app.js index b3cfa69..5985e73 100644 --- a/src/entry/app.js +++ b/src/entry/app.js @@ -43,7 +43,7 @@ window.onload = () => { preprocessAndLoadCss('css', 'css/editor.css'); entryFunction = () => OS.waitForInterface(function () { var assets = Object.keys(MediaLib.keys).join(','); - OS.registerLibraryAssets(assets, indexMain); + OS.registerLibraryAssets(MediaLib.version, assets, indexMain); }); break; case 'home': diff --git a/src/tablet/Android.js b/src/tablet/Android.js index 3d8e15d..248a8fe 100644 --- a/src/tablet/Android.js +++ b/src/tablet/Android.js @@ -254,8 +254,8 @@ export default class Android { // return 1; // } - static registerLibraryAssets (assets, fcn) { - AndroidInterface.registerLibraryAssets(assets); + static registerLibraryAssets (version, assets, fcn) { + AndroidInterface.registerLibraryAssets(version, assets); fcn && fcn(); } diff --git a/src/tablet/OS.js b/src/tablet/OS.js index d8242d4..186ad26 100644 --- a/src/tablet/OS.js +++ b/src/tablet/OS.js @@ -247,8 +247,8 @@ export default class OS { return 1; } - static registerLibraryAssets (assets, fcn) { - tabletInterface.registerLibraryAssets(assets, fcn); + static registerLibraryAssets (version, assets, fcn) { + tabletInterface.registerLibraryAssets(version, assets, fcn); } // Name of the device/iPad to display on the sharing dialog page diff --git a/src/tablet/iOS.js b/src/tablet/iOS.js index 0064be3..6649bb5 100644 --- a/src/tablet/iOS.js +++ b/src/tablet/iOS.js @@ -367,9 +367,9 @@ export default class iOS { iOS.call('sendSjrUsingShareDialog', fileName, emailSubject, emailBody, shareType); } - static registerLibraryAssets (assets, fcn) { + static registerLibraryAssets (version, assets, fcn) { (async () => { - await iOS.call('registerLibraryAssets', assets); + await iOS.call('registerLibraryAssets', version, assets); fcn && fcn(); })(); } From d32d2214e7bbd2fc5e3b7701e1e9c305cadd0883 Mon Sep 17 00:00:00 2001 From: Yueyu Date: Mon, 11 Oct 2021 08:14:34 +0800 Subject: [PATCH 5/7] duplicate library and sample assets --- .../android/JavaScriptDirectInterface.java | 17 ++++++++++++++ ios/ScratchJr/src/IO.m | 10 ++++++++ ios/ScratchJr/src/JsBridge.m | 9 ++++++++ ios/ScratchJr/src/ScratchJr.h | 2 ++ src/editor/engine/Page.js | 23 +++++++++++++++++-- src/editor/engine/Sprite.js | 21 ++++++++++++++++- src/tablet/Android.js | 5 ++++ src/tablet/IO.js | 4 ++++ src/tablet/OS.js | 4 ++++ src/tablet/iOS.js | 7 ++++++ 10 files changed, 99 insertions(+), 3 deletions(-) diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java index d8d3959..2a2b172 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/JavaScriptDirectInterface.java @@ -667,6 +667,23 @@ public class JavaScriptDirectInterface { _activity.registerLibraryAssets(assets.split(",")); } + @JavascriptInterface + public void duplicateAsset(String path, String fileName) { + Log.d(LOG_TAG, "duplicate asset " + path); + File toFile = new File(_activity.getFilesDir() + File.separator + fileName); + if (!toFile.exists()) { + try { + if (path.startsWith("./")) { + path = path.substring(2); + } + InputStream inputStream = _activity.getAssets().open("HTML5/" + path); + ScratchJrUtil.copyFile(inputStream, toFile); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + @JavascriptInterface public void sendSjrUsingShareDialog(String fileName, String emailSubject, String emailBody, int shareType) { diff --git a/ios/ScratchJr/src/IO.m b/ios/ScratchJr/src/IO.m index 71cae34..6fdacb7 100644 --- a/ios/ScratchJr/src/IO.m +++ b/ios/ScratchJr/src/IO.m @@ -652,4 +652,14 @@ NSMutableDictionary *soundtimers; return [mutableData copy]; } ++ (void) duplicateAsset:(NSString *)path :(NSString *)fileName { + NSFileManager *fileManager = [NSFileManager defaultManager]; + NSString* libraryPath = [@"/HTML5/" stringByAppendingString:path]; + NSString* fullPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:libraryPath]; + NSString* toPath = [[NSHomeDirectory() stringByAppendingPathComponent:@"Documents"] stringByAppendingPathComponent:fileName]; + if (![fileManager fileExistsAtPath:toPath] && [fileManager fileExistsAtPath:fullPath]) { + [fileManager copyItemAtPath:fullPath toPath:toPath error:nil]; + } +} + @end diff --git a/ios/ScratchJr/src/JsBridge.m b/ios/ScratchJr/src/JsBridge.m index 4aaf06e..52d0e84 100644 --- a/ios/ScratchJr/src/JsBridge.m +++ b/ios/ScratchJr/src/JsBridge.m @@ -214,4 +214,13 @@ [request callback:@"1"]; } +// duplicate library/sample assets for further usage +- (void) duplicateAsset: (JsRequest *) request { + NSString *path = request.params[0]; + NSString *name = request.params[1]; + NSLog(@"duplicate asset %@", path); + [IO duplicateAsset:path :name]; + [request callback:@"1"]; +} + @end diff --git a/ios/ScratchJr/src/ScratchJr.h b/ios/ScratchJr/src/ScratchJr.h index fb2c21a..0ab8a76 100644 --- a/ios/ScratchJr/src/ScratchJr.h +++ b/ios/ScratchJr/src/ScratchJr.h @@ -151,6 +151,8 @@ + (NSString *)registerSound:(NSString *)dir :(NSString *)name; + (NSString *)playSound:(NSString *)name; + (NSString *)stopSound:(NSString *)name; + ++ (void) duplicateAsset: (NSString *)folder :(NSString *)fileName; @end @interface ScratchJr : NSObject diff --git a/src/editor/engine/Page.js b/src/editor/engine/Page.js index c6ab847..369ce2b 100644 --- a/src/editor/engine/Page.js +++ b/src/editor/engine/Page.js @@ -131,7 +131,19 @@ export default class Page { (name.indexOf('/') < 0) ? OS.path + name : name; var md5 = (MediaLib.keys[name]) ? MediaLib.path + name : name; - if (md5.substr(md5.length - 3) == 'png') { + var duplicateBkg = function () { + var fileName = IO.getFilenameWithExt(md5); + if (MediaLib.keys[name]) { + OS.duplicateAsset(md5, fileName); + } else if (name.indexOf('/') > -1) { + OS.duplicateAsset(md5, fileName); + me.md5 = name; + } + } + + var isPng = md5.substr(md5.length - 3) == 'png'; + if (isPng && (MediaLib.keys[name] || name.indexOf('/') > -1)) { + duplicateBkg(); this.setBackgroundImage(url, fcn); this.svg = null; return; @@ -143,9 +155,16 @@ export default class Page { OS.getmedia(md5, nextStep); } function nextStep (base64) { - doNext(atob(base64)); + if (isPng) { + var data = IO.getImageDataURL(name, base64); + me.setBackgroundImage(data, fcn); + me.svg = null; + } else { + doNext(atob(base64)); + } } function doNext (str) { + duplicateBkg(); str = str.replace(/>\s*<'); me.setSVG(str); IO.getImagesInSVG(str, function () { diff --git a/src/editor/engine/Sprite.js b/src/editor/engine/Sprite.js index 91dac56..f084995 100755 --- a/src/editor/engine/Sprite.js +++ b/src/editor/engine/Sprite.js @@ -61,7 +61,17 @@ export default class Sprite { this.name = Localization.localize('SAMPLE_TEXT_' + this.name); } for (var i = 0; i < this.sounds.length; i++) { - ScratchAudio.loadProjectSound(this.sounds[i]); + var sound = this.sounds[i]; + if (sound.indexOf('/') > -1) { + // duplicate sample sounds + var name = IO.getFilenameWithExt(sound); + OS.duplicateAsset(sound, name, function () { + ScratchAudio.loadProjectSound(name); + }); + this.sounds[i] = name; + } else { + ScratchAudio.loadProjectSound(sound); + } } var sprites = JSON.parse(page.sprites); sprites.push(this.id); @@ -88,6 +98,15 @@ export default class Sprite { doNext(atob(base64)); } function doNext (str) { + if (MediaLib.keys[spr.md5] || spr.md5.indexOf('/') > -1) { + // duplicate asset in library or sample + // in case this asset is removed from library or sample + // we can still use this asset and open the project in the future + var name = IO.getFilenameWithExt(spr.md5); + OS.duplicateAsset(md5, name); + // use the duplicated one next time. + spr.md5 = name; + } str = str.replace(/>\s*<'); spr.setSVG(str); IO.getImagesInSVG(str, function () { diff --git a/src/tablet/Android.js b/src/tablet/Android.js index 248a8fe..c03fffd 100644 --- a/src/tablet/Android.js +++ b/src/tablet/Android.js @@ -259,6 +259,11 @@ export default class Android { fcn && fcn(); } + static duplicateAsset (folder, name, fcn) { + AndroidInterface.duplicateAsset(folder, name); + fcn && fcn(); + } + // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { diff --git a/src/tablet/IO.js b/src/tablet/IO.js index 7a38a7b..eba3eee 100644 --- a/src/tablet/IO.js +++ b/src/tablet/IO.js @@ -281,6 +281,10 @@ export default class IO { return str.substring(0, str.indexOf('.')); } + static getFilenameWithExt (str) { + return str.substring(str.lastIndexOf('/') + 1, str.length); + } + static parseProjectData (data) { var res = new Object(); for (var key in data) { diff --git a/src/tablet/OS.js b/src/tablet/OS.js index 186ad26..d4023c6 100644 --- a/src/tablet/OS.js +++ b/src/tablet/OS.js @@ -251,6 +251,10 @@ export default class OS { tabletInterface.registerLibraryAssets(version, assets, fcn); } + static duplicateAsset (path, name, fcn) { + tabletInterface.duplicateAsset(path, name, fcn); + } + // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { diff --git a/src/tablet/iOS.js b/src/tablet/iOS.js index 6649bb5..41e1dc6 100644 --- a/src/tablet/iOS.js +++ b/src/tablet/iOS.js @@ -374,6 +374,13 @@ export default class iOS { })(); } + static duplicateAsset (path, name, fcn) { + (async () => { + await iOS.call('duplicateAsset', path, name); + fcn && fcn(); + })(); + } + // Name of the device/iPad to display on the sharing dialog page // fcn is called with the device name as an arg static deviceName (fcn) { From d656f28ca8c991dd44c2568a18f09f2a7e5c2a41 Mon Sep 17 00:00:00 2001 From: Yueyu Date: Mon, 11 Oct 2021 08:15:50 +0800 Subject: [PATCH 6/7] add support to handle mp3 sound --- .../app/src/main/java/org/scratchjr/android/IOManager.java | 2 +- ios/ScratchJr/src/IO.m | 2 +- src/utils/ScratchAudio.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java b/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java index 635d0ca..e5a5396 100644 --- a/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java +++ b/android/ScratchJr/app/src/main/java/org/scratchjr/android/IOManager.java @@ -324,7 +324,7 @@ public class IOManager { if (entry == null) { continue; } - if (!(entry.endsWith(".png") || entry.endsWith(".wav") || entry.endsWith(".svg"))) { + if (!(entry.endsWith(".png") || entry.endsWith(".wav") || entry.endsWith(".mp3") || entry.endsWith(".svg"))) { continue; } // copy file to target file diff --git a/ios/ScratchJr/src/IO.m b/ios/ScratchJr/src/IO.m index 6fdacb7..e7bedfe 100644 --- a/ios/ScratchJr/src/IO.m +++ b/ios/ScratchJr/src/IO.m @@ -332,7 +332,7 @@ NSMutableDictionary *soundtimers; NSString *path; while ((path = [enumerator nextObject]) != nil) { // we are only interested in images and sounds - if ([path hasSuffix:@".png"] || [path hasSuffix:@".wav"] || [path hasSuffix:@".svg"]) { + if ([path hasSuffix:@".png"] || [path hasSuffix:@".wav"] || [path hasSuffix:@".mp3"] || [path hasSuffix:@".svg"]) { NSString *fileName = [path lastPathComponent]; // extract file NSString *toPath = [[IO getpath] stringByAppendingPathComponent:fileName]; diff --git a/src/utils/ScratchAudio.js b/src/utils/ScratchAudio.js index 1330654..d8398be 100755 --- a/src/utils/ScratchAudio.js +++ b/src/utils/ScratchAudio.js @@ -86,7 +86,7 @@ export default class ScratchAudio { var dir = ''; if (!isAndroid) { if (md5.indexOf('/') > -1) dir = 'HTML5/'; - else if (md5.indexOf('wav') > -1) dir = 'Documents'; + else if (md5.indexOf('wav') > -1 || md5.indexOf('mp3') > -1) dir = 'Documents'; } ScratchAudio.loadFromLocal(dir, md5, fcn); } From 06626cca07de2181fa642a2eacec78ada5e01402 Mon Sep 17 00:00:00 2001 From: Yueyu Date: Wed, 13 Oct 2021 06:28:38 +0800 Subject: [PATCH 7/7] code lint --- src/editor/engine/Page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/engine/Page.js b/src/editor/engine/Page.js index 369ce2b..a0c759c 100644 --- a/src/editor/engine/Page.js +++ b/src/editor/engine/Page.js @@ -139,7 +139,7 @@ export default class Page { OS.duplicateAsset(md5, fileName); me.md5 = name; } - } + }; var isPng = md5.substr(md5.length - 3) == 'png'; if (isPng && (MediaLib.keys[name] || name.indexOf('/') > -1)) {