mirror of
https://github.com/scratchfoundation/scratchjr.git
synced 2024-11-25 00:28:20 -05:00
Merge pull request #527 from yueyuzhao/issue/413-export-all-assets
export all assets when sharing
This commit is contained in:
commit
bfeec221dc
18 changed files with 250 additions and 29 deletions
|
@ -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
|
||||
|
@ -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});
|
||||
|
|
|
@ -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,39 @@ 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 registerLibraryAssets(int version, String assets) {
|
||||
_activity.assetLibraryVersion = version;
|
||||
_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) {
|
||||
|
|
|
@ -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,15 @@ public class ScratchJrActivity
|
|||
*/
|
||||
private ArrayList<Uri> 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<String> assetList = new HashSet<>(200);
|
||||
|
||||
public int assetLibraryVersion = 0;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
@ -665,4 +675,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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{
|
||||
"assetLibraryVersion": 1,
|
||||
"path": "./svglibrary/",
|
||||
"samples": [
|
||||
"samples/Star.txt"
|
||||
|
|
|
@ -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 {
|
||||
|
@ -324,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];
|
||||
|
@ -334,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];
|
||||
|
@ -640,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
|
||||
|
|
|
@ -207,4 +207,20 @@
|
|||
[request callback:[[UIDevice currentDevice] name]];
|
||||
}
|
||||
|
||||
- (void) registerLibraryAssets: (JsRequest *) request {
|
||||
ScratchJr.assetLibraryVersion = (NSInteger) request.params[0];
|
||||
NSString *assets = request.params[1];
|
||||
[ScratchJr registerLibraryAssets: [assets componentsSeparatedByString:@","]];
|
||||
[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
|
||||
|
|
|
@ -151,10 +151,14 @@
|
|||
+ (NSString *)registerSound:(NSString *)dir :(NSString *)name;
|
||||
+ (NSString *)playSound:(NSString *)name;
|
||||
+ (NSString *)stopSound:(NSString *)name;
|
||||
|
||||
+ (void) duplicateAsset: (NSString *)folder :(NSString *)fileName;
|
||||
@end
|
||||
|
||||
@interface ScratchJr : NSObject
|
||||
|
||||
@property (class, nonatomic, assign) NSInteger assetLibraryVersion;
|
||||
|
||||
+ (void)sendBase64Image:(NSData *)imagedata;
|
||||
+ (void)reportImageError;
|
||||
+ (void)cameraInit;
|
||||
|
@ -170,4 +174,7 @@
|
|||
|
||||
// Imports
|
||||
+ (void) receiveProject:(NSURL *) url;
|
||||
|
||||
+ (void) registerLibraryAssets: (NSArray<NSString *> *)assets;
|
||||
+ (BOOL) libraryHasAsset: (NSString *)md5;
|
||||
@end
|
||||
|
|
|
@ -14,8 +14,20 @@ AVCaptureVideoPreviewLayer* captureVideoPreviewLayer;
|
|||
|
||||
@implementation ScratchJr : NSObject
|
||||
|
||||
static NSInteger _assetLibraryVersion = 0;
|
||||
|
||||
+ (NSInteger) assetLibraryVersion {
|
||||
return _assetLibraryVersion;
|
||||
}
|
||||
|
||||
+ (void) setAssetLibraryVersion:(NSInteger)newValue {
|
||||
_assetLibraryVersion = newValue;
|
||||
}
|
||||
|
||||
NSString *oncomplete;
|
||||
|
||||
NSMutableSet *assets;
|
||||
|
||||
//////////////////////////
|
||||
// Init functions
|
||||
/////////////////////////
|
||||
|
@ -169,4 +181,17 @@ NSString *oncomplete;
|
|||
cameraMask = nil;
|
||||
}
|
||||
|
||||
+ (void) registerLibraryAssets: (NSArray<NSString *> *)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
|
||||
|
|
|
@ -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*</g, '><');
|
||||
me.setSVG(str);
|
||||
IO.getImagesInSVG(str, function () {
|
||||
|
|
|
@ -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*</g, '><');
|
||||
spr.setSVG(str);
|
||||
IO.getImagesInSVG(str, function () {
|
||||
|
|
|
@ -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(MediaLib.version, assets, indexMain);
|
||||
});
|
||||
break;
|
||||
case 'home':
|
||||
// Lobby pages
|
||||
|
|
|
@ -254,6 +254,16 @@ export default class Android {
|
|||
// return 1;
|
||||
// }
|
||||
|
||||
static registerLibraryAssets (version, assets, fcn) {
|
||||
AndroidInterface.registerLibraryAssets(version, assets);
|
||||
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) {
|
||||
|
|
|
@ -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 = '';
|
||||
|
@ -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) {
|
||||
|
@ -302,6 +306,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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -247,6 +247,14 @@ export default class OS {
|
|||
return 1;
|
||||
}
|
||||
|
||||
static registerLibraryAssets (version, assets, fcn) {
|
||||
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) {
|
||||
|
|
|
@ -367,6 +367,20 @@ export default class iOS {
|
|||
iOS.call('sendSjrUsingShareDialog', fileName, emailSubject, emailBody, shareType);
|
||||
}
|
||||
|
||||
static registerLibraryAssets (version, assets, fcn) {
|
||||
(async () => {
|
||||
await iOS.call('registerLibraryAssets', version, assets);
|
||||
fcn && fcn();
|
||||
})();
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue