kopia lustrzana https://github.com/backface/turtlestitch
444 wiersze
15 KiB
JavaScript
444 wiersze
15 KiB
JavaScript
SnapSerializer.prototype.app = 'TurtleStitch 2.7, http://www.turtlestitch.org';
|
|
SnapSerializer.prototype.thumbnailSize = new Point(480, 360);
|
|
|
|
// Project instance creation:
|
|
|
|
function Project(scenes, current) {
|
|
var projectScene;
|
|
|
|
this.scenes = scenes || new List();
|
|
this.currentScene = current;
|
|
|
|
// proxied for display
|
|
this.name = null;
|
|
this.notes = null;
|
|
this.thumbnail = null;
|
|
this.origName = null;
|
|
this.creator = null;
|
|
this.origCreator = null;
|
|
this.remixHistory = null;
|
|
|
|
projectScene = this.scenes.at(1);
|
|
if (projectScene) {
|
|
this.name = projectScene.name;
|
|
this.notes = projectScene.notes;
|
|
this.thumbnail = normalizeCanvas(
|
|
projectScene.stage.thumbnail(SnapSerializer.prototype.thumbnailSize)
|
|
);
|
|
}
|
|
|
|
// for deserializing - do not persist
|
|
this.sceneIdx = null;
|
|
|
|
// for undeleting scenes - do not persist
|
|
this.trash = [];
|
|
}
|
|
|
|
Project.prototype.initialize = function () {
|
|
// initialize after deserializing
|
|
// only to be called by store
|
|
this.currentScene = this.scenes.at(+this.sceneIdx || 1);
|
|
return this;
|
|
};
|
|
|
|
Project.prototype.addDefaultScene = function () {
|
|
var scene = new Scene();
|
|
scene.addDefaultSprite();
|
|
this.scenes.add(scene);
|
|
};
|
|
|
|
Project.prototype.toXML = function (serializer) {
|
|
var thumbdata;
|
|
|
|
// thumb data catch cross-origin tainting exception when using SVG costumes
|
|
try {
|
|
thumbdata = this.thumbnail.toDataURL('image/png');
|
|
} catch (error) {
|
|
thumbdata = null;
|
|
}
|
|
|
|
return serializer.format(
|
|
'<project name="@" app="@" version="@">' +
|
|
'<notes>$</notes>' +
|
|
'<thumbnail>$</thumbnail>' +
|
|
'<scenes select="@">%</scenes>\n' +
|
|
'<creator>$</creator>\n' +
|
|
'<origCreator>$</origCreator>\n' +
|
|
'<origName>$</origName>\n' +
|
|
'<remixHistory>$</remixHistory>\n' +
|
|
'</project>',
|
|
this.name || localize('Untitled'),
|
|
serializer.app,
|
|
serializer.version,
|
|
this.scenes.at(1).notes || '',
|
|
thumbdata,
|
|
this.scenes.asArray().indexOf(this.currentScene) + 1,
|
|
serializer.store(this.scenes.itemsArray()),
|
|
this.creator || '',
|
|
this.origCreator || '',
|
|
this.origName || '',
|
|
this.remixHistory || ''
|
|
);
|
|
};
|
|
|
|
SnapSerializer.prototype.loadProjectModel = function (xmlNode, ide, remixID) {
|
|
// public - answer a new Project represented by the given XML top node
|
|
// show a warning if the origin apps differ
|
|
|
|
var appInfo = xmlNode.attributes.app,
|
|
app = appInfo ? appInfo.split(' ')[0] : null,
|
|
scenesModel = xmlNode.childNamed('scenes'),
|
|
project = new Project();
|
|
|
|
if (ide && app && app !== this.app.split(' ')[0]) {
|
|
ide.inform(
|
|
app + ' Project',
|
|
'This project has been created by a different app:\n\n' +
|
|
app +
|
|
'\n\nand may be incompatible or fail to load here.'
|
|
);
|
|
}
|
|
|
|
project_model = {project: xmlNode };
|
|
//project.notes = project_model.project.childNamed('notes') ? project_model.project.childNamed('notes').contents : ""
|
|
project.name = project_model.project.attributes.name;
|
|
project.origName = project_model.project.childNamed('origName') ? project_model.project.childNamed('origName').contents : ""
|
|
project.creator = project_model.project.childNamed('creator') ? project_model.project.childNamed('creator').contents : ""
|
|
project.origCreator = project_model.project.childNamed('origCreator') ? project_model.project.childNamed('origCreator').contents : ""
|
|
project.remixHistory = project_model.project.childNamed('remixHistory') ? project_model.project.childNamed('remixHistory').contents : ""
|
|
|
|
ide.projectName = project.name;
|
|
//ide.projectNotes = project.notes || '';
|
|
ide.origName = project.origName || '';
|
|
ide.origCreator = project.origCreator || '';
|
|
ide.creator = project.creator || '';
|
|
ide.remixHistory = project.remixHistory || '';
|
|
|
|
if (scenesModel) {
|
|
if (scenesModel.attributes.select) {
|
|
project.sceneIdx = +scenesModel.attributes.select;
|
|
}
|
|
scenesModel.childrenNamed('scene').forEach(model => {
|
|
ide.scene.captureGlobalSettings();
|
|
project.scenes.add(this.loadScene(model));
|
|
ide.scene.applyGlobalSettings();
|
|
});
|
|
} else {
|
|
project.scenes.add(this.loadScene(xmlNode, remixID));
|
|
}
|
|
return project.initialize();
|
|
};
|
|
|
|
|
|
// =============================================
|
|
|
|
SnapSerializer.prototype.loadScene = function (xmlNode, remixID) {
|
|
// private
|
|
var scene = new Scene(),
|
|
model,
|
|
nameID;
|
|
|
|
this.scene = scene;
|
|
|
|
model = {scene: xmlNode };
|
|
if (+xmlNode.attributes.version > this.version) {
|
|
throw 'Project uses newer version of Serializer';
|
|
}
|
|
|
|
/* Project Info */
|
|
|
|
this.objects = {};
|
|
scene.name = model.scene.attributes.name;
|
|
if (!scene.name) {
|
|
nameID = 1;
|
|
while (
|
|
Object.prototype.hasOwnProperty.call(
|
|
localStorage,
|
|
'-snap-project-Untitled ' + nameID
|
|
)
|
|
) {
|
|
nameID += 1;
|
|
}
|
|
scene.name = 'Untitled ' + nameID;
|
|
}
|
|
scene.unifiedPalette = model.scene.attributes.palette === 'single';
|
|
scene.showCategories = model.scene.attributes.categories !== 'false';
|
|
scene.showPaletteButtons = model.scene.attributes.buttons !== 'false';
|
|
scene.disableClickToRun = model.scene.attributes.clickrun === 'false';
|
|
scene.penColorModel = model.scene.attributes.colormodel === 'hsl' ?
|
|
'hsl' : 'hsv';
|
|
model.notes = model.scene.childNamed('notes');
|
|
if (model.notes) {
|
|
scene.notes = model.notes.contents;
|
|
}
|
|
model.palette = model.scene.childNamed('palette');
|
|
if (model.palette) {
|
|
scene.customCategories = this.loadPalette(model.palette);
|
|
SpriteMorph.prototype.customCategories = scene.customCategories;
|
|
}
|
|
model.globalVariables = model.scene.childNamed('variables');
|
|
|
|
/* Stage */
|
|
|
|
model.stage = model.scene.require('stage');
|
|
scene.stage.remixID = remixID;
|
|
|
|
if (Object.prototype.hasOwnProperty.call(
|
|
model.stage.attributes,
|
|
'id'
|
|
)) {
|
|
this.objects[model.stage.attributes.id] = scene.stage;
|
|
}
|
|
if (model.stage.attributes.name) {
|
|
scene.stage.name = model.stage.attributes.name;
|
|
}
|
|
if (model.stage.attributes.color) {
|
|
scene.stage.color = this.loadColor(model.stage.attributes.color);
|
|
scene.stage.cachedColorDimensions = scene.stage.color[
|
|
SpriteMorph.prototype.penColorModel
|
|
]();
|
|
}
|
|
if (model.stage.attributes.volume) {
|
|
scene.stage.volume = +model.stage.attributes.volume;
|
|
}
|
|
if (model.stage.attributes.pan) {
|
|
scene.stage.pan = +model.stage.attributes.pan;
|
|
}
|
|
if (model.stage.attributes.penlog) {
|
|
scene.enablePenLogging =
|
|
(model.stage.attributes.penlog === 'true');
|
|
}
|
|
|
|
model.pentrails = model.stage.childNamed('pentrails');
|
|
if (model.pentrails) {
|
|
scene.pentrails = new Image();
|
|
/*
|
|
scene.pentrails.onload = function () {
|
|
if (scene.stage.trailsCanvas) { // work-around a bug in FF
|
|
normalizeCanvas(scene.stage.trailsCanvas);
|
|
var context = scene.stage.trailsCanvas.getContext('2d');
|
|
context.drawImage(scene.pentrails, 0, 0);
|
|
scene.stage.changed();
|
|
}
|
|
};
|
|
*/
|
|
scene.pentrails.src = model.pentrails.contents;
|
|
}
|
|
scene.stage.setTempo(model.stage.attributes.tempo);
|
|
if (model.stage.attributes.width) {
|
|
scene.stage.dimensions.x =
|
|
Math.max(+model.stage.attributes.width, 240);
|
|
}
|
|
if (model.stage.attributes.height) {
|
|
scene.stage.dimensions.y =
|
|
Math.max(+model.stage.attributes.height, 180);
|
|
}
|
|
scene.stage.setExtent(scene.stage.dimensions);
|
|
scene.useFlatLineEnds =
|
|
model.stage.attributes.lines === 'flat';
|
|
BooleanSlotMorph.prototype.isTernary =
|
|
model.stage.attributes.ternary !== 'false';
|
|
scene.enableHyperOps =
|
|
model.stage.attributes.hyperops !== 'false';
|
|
scene.stage.isThreadSafe =
|
|
model.stage.attributes.threadsafe === 'true';
|
|
scene.enableCodeMapping =
|
|
model.stage.attributes.codify === 'true';
|
|
scene.enableInheritance =
|
|
model.stage.attributes.inheritance !== 'false';
|
|
scene.enableSublistIDs =
|
|
model.stage.attributes.sublistIDs === 'true';
|
|
|
|
model.hiddenPrimitives = model.scene.childNamed('hidden');
|
|
if (model.hiddenPrimitives) {
|
|
model.hiddenPrimitives.contents.split(' ').forEach(
|
|
sel => {
|
|
if (sel) {
|
|
scene.hiddenPrimitives[sel] = true;
|
|
}
|
|
}
|
|
);
|
|
}
|
|
|
|
model.codeHeaders = model.scene.childNamed('headers');
|
|
if (model.codeHeaders) {
|
|
model.codeHeaders.children.forEach(
|
|
xml => scene.codeHeaders[xml.tag] = xml.contents
|
|
);
|
|
}
|
|
|
|
model.codeMappings = model.scene.childNamed('code');
|
|
if (model.codeMappings) {
|
|
model.codeMappings.children.forEach(
|
|
xml => scene.codeMappings[xml.tag] = xml.contents
|
|
);
|
|
}
|
|
|
|
model.globalBlocks = model.scene.childNamed('blocks');
|
|
if (model.globalBlocks) {
|
|
this.loadCustomBlocks(scene.stage, model.globalBlocks, true);
|
|
this.populateCustomBlocks(
|
|
scene.stage,
|
|
model.globalBlocks,
|
|
true
|
|
);
|
|
}
|
|
this.loadObject(scene.stage, model.stage);
|
|
|
|
/* Sprites */
|
|
|
|
model.sprites = model.stage.require('sprites');
|
|
if (model.sprites.attributes.select) {
|
|
scene.spriteIdx = +model.sprites.attributes.select;
|
|
}
|
|
scene.spritesDict[scene.stage.name] = scene.stage;
|
|
model.sprites.childrenNamed('sprite').forEach(
|
|
model => this.loadValue(model)
|
|
);
|
|
|
|
// restore inheritance and nesting associations
|
|
this.scene.stage.children.forEach(sprite => {
|
|
var exemplar, anchor;
|
|
if (sprite.inheritanceInfo) { // only sprites can inherit
|
|
exemplar = this.scene.spritesDict[
|
|
sprite.inheritanceInfo.exemplar
|
|
];
|
|
if (exemplar) {
|
|
sprite.setExemplar(exemplar);
|
|
}
|
|
sprite.inheritedAttributes = sprite.inheritanceInfo.delegated || [];
|
|
sprite.updatePropagationCache();
|
|
}
|
|
if (sprite.nestingInfo) { // only sprites may have nesting info
|
|
anchor = this.scene.spritesDict[sprite.nestingInfo.anchor];
|
|
if (anchor) {
|
|
anchor.attachPart(sprite);
|
|
}
|
|
sprite.rotatesWithAnchor = (sprite.nestingInfo.synch === 'true');
|
|
}
|
|
});
|
|
this.scene.stage.children.forEach(sprite => {
|
|
var costume;
|
|
if (sprite.nestingInfo) { // only sprites may have nesting info
|
|
sprite.nestingScale = +(sprite.nestingInfo.scale || sprite.scale);
|
|
delete sprite.nestingInfo;
|
|
}
|
|
['scripts', 'costumes', 'sounds'].forEach(att => {
|
|
if (sprite.inheritsAttribute(att)) {
|
|
sprite.refreshInheritedAttribute(att);
|
|
}
|
|
});
|
|
if (sprite.inheritsAttribute('costumes')) {
|
|
if (sprite.inheritsAttribute('costume #')) {
|
|
costume = sprite.exemplar.costume;
|
|
} else {
|
|
costume = sprite.costumes.asArray()[
|
|
sprite.inheritanceInfo.costumeNumber - 1
|
|
];
|
|
}
|
|
if (costume) {
|
|
if (costume.loaded) {
|
|
sprite.wearCostume(costume, true);
|
|
} else {
|
|
costume.loaded = function () {
|
|
this.loaded = true;
|
|
sprite.wearCostume(costume, true);
|
|
};
|
|
}
|
|
}
|
|
}
|
|
delete sprite.inheritanceInfo;
|
|
});
|
|
|
|
/* Global Variables */
|
|
|
|
if (model.globalVariables) {
|
|
this.loadVariables(
|
|
scene.globalVariables,
|
|
model.globalVariables
|
|
);
|
|
}
|
|
|
|
this.objects = {};
|
|
|
|
/* Watchers */
|
|
|
|
model.sprites.childrenNamed('watcher').forEach(model => {
|
|
var watcher, color, target, hidden, extX, extY;
|
|
|
|
color = this.loadColor(model.attributes.color);
|
|
target = Object.prototype.hasOwnProperty.call(
|
|
model.attributes,
|
|
'scope'
|
|
) ? scene.spritesDict[model.attributes.scope] : null;
|
|
|
|
// determine whether the watcher is hidden, slightly
|
|
// complicated to retain backward compatibility
|
|
// with former tag format: hidden="hidden"
|
|
// now it's: hidden="true"
|
|
hidden = Object.prototype.hasOwnProperty.call(
|
|
model.attributes,
|
|
'hidden'
|
|
) && (model.attributes.hidden !== 'false');
|
|
|
|
if (Object.prototype.hasOwnProperty.call(
|
|
model.attributes,
|
|
'var'
|
|
)) {
|
|
watcher = new WatcherMorph(
|
|
model.attributes['var'],
|
|
color,
|
|
isNil(target) ? scene.globalVariables
|
|
: target.variables,
|
|
model.attributes['var'],
|
|
hidden
|
|
);
|
|
} else {
|
|
watcher = new WatcherMorph(
|
|
localize(this.watcherLabels[model.attributes.s]),
|
|
color,
|
|
target,
|
|
model.attributes.s,
|
|
hidden
|
|
);
|
|
}
|
|
watcher.setStyle(model.attributes.style || 'normal');
|
|
if (watcher.style === 'slider') {
|
|
watcher.setSliderMin(model.attributes.min || '1', true);
|
|
watcher.setSliderMax(model.attributes.max || '100', true);
|
|
}
|
|
watcher.setPosition(
|
|
scene.stage.topLeft().add(new Point(
|
|
+model.attributes.x || 0,
|
|
+model.attributes.y || 0
|
|
))
|
|
);
|
|
scene.stage.add(watcher);
|
|
watcher.onNextStep = function () {this.currentValue = null; };
|
|
|
|
// set watcher's contentsMorph's extent if it is showing a list and
|
|
// its monitor dimensions are given
|
|
if (watcher.currentValue instanceof List &&
|
|
watcher.cellMorph.contentsMorph) {
|
|
extX = model.attributes.extX;
|
|
if (extX) {
|
|
watcher.cellMorph.contentsMorph.setWidth(+extX);
|
|
}
|
|
extY = model.attributes.extY;
|
|
if (extY) {
|
|
watcher.cellMorph.contentsMorph.setHeight(+extY);
|
|
}
|
|
// adjust my contentsMorph's handle position
|
|
watcher.cellMorph.contentsMorph.handle.fixLayout();
|
|
}
|
|
});
|
|
|
|
// clear sprites' inherited methods caches, if any
|
|
this.scene.stage.children.forEach(
|
|
sprite => sprite.inheritedMethodsCache = []
|
|
);
|
|
|
|
this.objects = {};
|
|
return scene.initialize();
|
|
};
|