diff --git a/HISTORY.md b/HISTORY.md
index 7287cf2d..f091c02e 100755
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -8,6 +8,33 @@
* **Notable Fixes:**
* made scrollbars in the wardrobe and jukebox more responsive
+### 2021-05-21
+* gui, scenes, store: proxied thumbnail, name and notes in project, restored in XML
+* gui: distinguished project name from scene names, removed hidden "export as plain text" option
+* gui: sceneified project notes
+* gui: adjusted project thumbnail in "save" dialog
+* gui: some cleanups
+* gui, scenes: sceneified unsaved changes management
+* blocks: fixed search-blocks for scenesMenu
+
+### 2021-05-20
+* gui: marked projectName to be refactored and sceneified
+
+### 2021-05-19
+* gui: disabled scene icon context menu for project scene
+* gui: disabled dragging the project scene icon
+* gui: made sure the project scene stays in place
+* gui: added exporting single scenes
+* scenes, store: removed redundant properties "notes" and "thumbnail" from project
+* store: removed "thumbnail" property from scene xml
+
+### 2021-05-18
+* gui: fixed exporting media only for a single scene
+* gui: fixed cloud file format components
+* gui: "projectized" cloud file format for a single scene
+* gui: fixed cloud file format for multi-scene projects
+* gui: ensured unique scene names
+
### 2021-05-11
* gui: add multi-scene projects
* gui: adjusted scene album rendering
diff --git a/snap.html b/snap.html
index 6e55365a..bfa09d78 100755
--- a/snap.html
+++ b/snap.html
@@ -8,11 +8,11 @@
-
+
-
-
+
+
@@ -21,7 +21,7 @@
-
+
diff --git a/src/blocks.js b/src/blocks.js
index 2e035ecc..1bb60c60 100644
--- a/src/blocks.js
+++ b/src/blocks.js
@@ -158,7 +158,7 @@ CustomCommandBlockMorph, SymbolMorph, ToggleButtonMorph, DialMorph*/
// Global stuff ////////////////////////////////////////////////////////
-modules.blocks = '2021-April-12';
+modules.blocks = '2021-May-21';
var SyntaxElementMorph;
var BlockMorph;
@@ -9745,15 +9745,17 @@ InputSlotMorph.prototype.audioMenu = function (searching) {
};
InputSlotMorph.prototype.scenesMenu = function (searching) {
- var scenes = this.parentThatIsA(IDE_Morph).scenes,
- dict = {};
-
- if (!searching && scenes.length() > 1) {
- scenes.itemsArray().forEach(scn => {
- if (scn.name) {
- dict[scn.name] = scn.name;
- }
- });
+ var dict = {},
+ scenes;
+ if (!searching) {
+ scenes = this.parentThatIsA(IDE_Morph).scenes;
+ if (scenes.length() > 1) {
+ scenes.itemsArray().forEach(scn => {
+ if (scn.name) {
+ dict[scn.name] = scn.name;
+ }
+ });
+ }
}
dict['~'] = null;
dict.next = ['next'];
diff --git a/src/gui.js b/src/gui.js
index e66d12da..f0d69a08 100644
--- a/src/gui.js
+++ b/src/gui.js
@@ -68,7 +68,7 @@
/*global modules, Morph, SpriteMorph, SyntaxElementMorph, Color, Cloud, Audio,
ListWatcherMorph, TextMorph, newCanvas, useBlurredShadows, Sound, Scene, Note,
-StringMorph, Point, MenuMorph, morphicVersion, DialogBoxMorph, normalizeCanvas,
+StringMorph, Point, MenuMorph, morphicVersion, DialogBoxMorph, BlockEditorMorph,
ToggleButtonMorph, contains, ScrollFrameMorph, StageMorph, PushButtonMorph, sb,
InputFieldMorph, FrameMorph, Process, nop, SnapSerializer, ListMorph, detect,
AlignmentMorph, TabMorph, Costume, MorphicPreferences,BlockMorph, ToggleMorph,
@@ -79,11 +79,11 @@ CommandBlockMorph, BooleanSlotMorph, RingReporterSlotMorph, ScriptFocusMorph,
BlockLabelPlaceHolderMorph, SpeechBubbleMorph, XML_Element, WatcherMorph, WHITE,
BlockRemovalDialogMorph,TableMorph, isSnapObject, isRetinaEnabled, SliderMorph,
disableRetinaSupport, enableRetinaSupport, isRetinaSupported, MediaRecorder,
-Animation, BoxMorph, BlockEditorMorph, BlockDialogMorph, Project, ZERO, BLACK*/
+Animation, BoxMorph, BlockDialogMorph, Project, ZERO, BLACK*/
// Global stuff ////////////////////////////////////////////////////////
-modules.gui = '2021-May-11';
+modules.gui = '2021-May-21';
// Declarations
@@ -248,8 +248,6 @@ IDE_Morph.prototype.init = function (isAutoFill) {
this.globalVariables = this.scene.globalVariables;
this.currentSprite = this.scene.addDefaultSprite();
this.sprites = this.scene.sprites;
- this.projectName = this.scene.name;
- this.projectNotes = this.scene.notes;
if (this.scene.unifiedPalette) {
this.currentCategory = 'unified';
} else {
@@ -283,9 +281,7 @@ IDE_Morph.prototype.init = function (isAutoFill) {
this.filePicker = null;
// incrementally saving projects to the cloud is currently unused
- this.hasChangedMedia = false;
-
- this.hasUnsavedEdits = false; // keeping track of when to internally backup
+ this.hasChangedMedia = false; // +++ sceneify, or get of it
this.isAnimating = true;
this.paletteWidth = 200; // initially same as logo width
@@ -1181,7 +1177,7 @@ IDE_Morph.prototype.createControlBar = function () {
};
this.controlBar.updateLabel = function () {
- var prefix = myself.hasUnsavedEdits ? '\u270E ' : '',
+ var prefix = myself.hasUnsavedEdits() ? '\u270E ' : '',
suffix = myself.world().isDevMode ?
' - ' + localize('development mode') : '',
txt;
@@ -1193,7 +1189,7 @@ IDE_Morph.prototype.createControlBar = function () {
return;
}
txt = new StringMorph(
- prefix + (myself.projectName || localize('untitled')) + suffix,
+ prefix + (myself.scene.name || localize('untitled')) + suffix,
14,
'sans-serif',
true,
@@ -1941,17 +1937,17 @@ IDE_Morph.prototype.createCorral = function (keepSceneAlbum) {
this.corral.frame = frame;
this.corral.add(frame);
- // scenes ++++
+ // scenes corral
this.corral.album = keepSceneAlbum ? album
: new SceneAlbumMorph(this, this.sliderColor);
- this.corral.album.color = this.frameColor; // this.groupColor;
+ this.corral.album.color = this.frameColor;
this.corral.add(this.corral.album);
this.corral.fixLayout = function () {
this.stageIcon.setCenter(this.center());
this.stageIcon.setLeft(this.left() + padding);
- // scenes +++
+ // scenes
if (myself.scenes.length() < 2) {
this.album.hide();
} else {
@@ -1960,7 +1956,9 @@ IDE_Morph.prototype.createCorral = function (keepSceneAlbum) {
this.album.setLeft(this.left());
this.album.setTop(this.stageIcon.bottom() + padding);
this.album.setWidth(this.stageIcon.width() + padding * 2);
- this.album.setHeight(this.height() - this.stageIcon.height() - padding); // +++
+ this.album.setHeight(
+ this.height() - this.stageIcon.height() - padding
+ );
}
this.frame.setLeft(this.stageIcon.right() + padding);
@@ -2130,10 +2128,40 @@ IDE_Morph.prototype.fixLayout = function (situation) {
}
};
+// IDE_Morph project properties
+
+IDE_Morph.prototype.getProjectName = function () {
+ return this.scenes.at(1).name;
+};
+
IDE_Morph.prototype.setProjectName = function (string) {
- this.projectName = string.replace(/['"]/g, ''); // filter quotation marks
- this.hasChangedMedia = true;
- this.controlBar.updateLabel();
+ var projectScene = this.scenes.at(1),
+ name = this.newSceneName(string, projectScene);
+ if (name !== projectScene.name) {
+ projectScene.name = name;
+ projectScene.stage.version = Date.now();
+ this.recordUnsavedChanges(); // +++ sceneify this
+ if (projectScene === this.scene) {
+ this.controlBar.updateLabel();
+ }
+ }
+ return name;
+};
+
+IDE_Morph.prototype.getProjectNotes = function () {
+ return this.scenes.at(1).notes;
+};
+
+IDE_Morph.prototype.setProjectNotes = function (string) {
+ var projectScene = this.scenes.at(1);
+ if (string !== projectScene.notes) {
+ projectScene.notes = string;
+ projectScene.stage.version = Date.now();
+ this.recordUnsavedChanges(); // +++ sceneify this
+ if (projectScene === this.scene) {
+ this.controlBar.updateLabel();
+ }
+ }
};
// IDE_Morph resizing
@@ -2783,13 +2811,17 @@ IDE_Morph.prototype.hasLocalStorage = function () {
// IDE_Morph recording unsaved changes
+IDE_Morph.prototype.hasUnsavedEdits = function () {
+ return this.scenes.itemsArray().some(any => any.hasUnsavedEdits);
+};
+
IDE_Morph.prototype.recordUnsavedChanges = function () {
- this.hasUnsavedEdits = true;
+ this.scene.hasUnsavedEdits = true;
this.updateChanges();
};
IDE_Morph.prototype.recordSavedChanges = function () {
- this.hasUnsavedEdits = false;
+ this.scenes.itemsArray().forEach(scene => scene.hasUnsavedEdits = false);
this.updateChanges();
};
@@ -2813,7 +2845,7 @@ IDE_Morph.prototype.backup = function (callback) {
// Save the current project for the currently logged in user
// to localstorage, then perform the given callback, e.g.
// load a new project.
- if (this.hasUnsavedEdits) {
+ if (this.hasUnsavedEdits()) {
this.confirm(
'Replace the current project with a new one?',
'Unsaved Changes!',
@@ -3229,6 +3261,14 @@ IDE_Morph.prototype.newSpriteName = function (name, ignoredSprite) {
return this.newName(name, all);
};
+IDE_Morph.prototype.newSceneName = function (name, ignoredScene) {
+ var sName = name.replace(/['"]/g, ''), // filter out quotation marks
+ all = this.scenes.asArray().filter(each =>
+ each !== ignoredScene
+ ).map(each => each.name);
+ return this.newName(sName, all);
+};
+
IDE_Morph.prototype.newName = function (name, elements) {
var ix = name.indexOf('('),
stem = (ix < 0) ? name : name.substring(0, ix),
@@ -3372,8 +3412,9 @@ IDE_Morph.prototype.cloudMenu = function () {
menu.addItem(
'export project media only...',
() => {
- if (this.projectName) {
- this.exportProjectMedia(this.projectName);
+ var pn = this.getProjectName();
+ if (pn) {
+ this.exportProjectMedia(pn);
} else {
this.prompt(
'Export Project As...',
@@ -3389,8 +3430,9 @@ IDE_Morph.prototype.cloudMenu = function () {
menu.addItem(
'export project without media...',
() => {
- if (this.projectName) {
- this.exportProjectNoMedia(this.projectName);
+ var pn = this.getProjectName();
+ if (pn) {
+ this.exportProjectNoMedia(pn);
} else {
this.prompt(
'Export Project As...',
@@ -3406,8 +3448,9 @@ IDE_Morph.prototype.cloudMenu = function () {
menu.addItem(
'export project as cloud data...',
() => {
- if (this.projectName) {
- this.exportProjectAsCloudData(this.projectName);
+ var pn = this.getProjectName();
+ if (pn) {
+ this.exportProjectAsCloudData(pn);
} else {
this.prompt(
'Export Project As...',
@@ -3984,10 +4027,7 @@ IDE_Morph.prototype.projectMenu = function () {
backup = this.availableBackup(shiftClicked);
menu = new MenuMorph(this);
- if (this.scenes.length() > 1) {
- menu.addItem('Scenes...', 'scenesMenu');
- }
- menu.addItem('Project notes...', 'editProjectNotes');
+ menu.addItem('Notes...', 'editNotes');
menu.addLine();
menu.addPair('New', 'createNewProject', '^N');
menu.addPair('Open...', 'openProjectsBrowser', '^O');
@@ -4015,46 +4055,22 @@ IDE_Morph.prototype.projectMenu = function () {
'importLocalFile',
'file menu import hint' // looks up the actual text in the translator
);
-
- if (shiftClicked) {
- menu.addItem(
- localize(
- 'Export project...') + ' ' + localize('(in a new window)'
- ),
- () => {
- if (this.projectName) {
- this.exportProject(this.projectName, shiftClicked);
- } else {
- this.prompt(
- 'Export Project As...',
- // false - override the shiftClick setting to use XML:
- name => this.exportProject(name, false),
- null,
- 'exportProject'
- );
- }
- },
- 'show project data as XML\nin a new browser window',
- new Color(100, 0, 0)
- );
- }
menu.addItem(
- shiftClicked ?
- 'Export project as plain text...' : 'Export project...',
+ 'Export project...',
() => {
- if (this.projectName) {
- this.exportProject(this.projectName, shiftClicked);
+ var pn = this.getProjectName();
+ if (pn) {
+ this.exportProject(pn);
} else {
this.prompt(
'Export Project As...',
- name => this.exportProject(name, shiftClicked),
+ name => this.exportProject(name),
null,
'exportProject'
);
}
},
- 'save project data as XML\nto your downloads folder',
- shiftClicked ? new Color(100, 0, 0) : null
+ 'save project data as XML\nto your downloads folder'
);
if (this.stage.globalBlocks.length) {
@@ -4095,12 +4111,12 @@ IDE_Morph.prototype.projectMenu = function () {
);
}
- // +++
menu.addLine();
+ if (this.scenes.length() > 1) {
+ menu.addItem('Scenes...', 'scenesMenu');
+ }
menu.addPair('New scene', 'createNewScene');
menu.addPair('Add scene...', 'addScene');
- // +++
-
menu.addLine();
menu.addItem(
'Libraries...',
@@ -4682,10 +4698,10 @@ IDE_Morph.prototype.scenesMenu = function () {
menu.popup(world, pos);
};
-IDE_Morph.prototype.editProjectNotes = function () {
- var dialog = new DialogBoxMorph().withKey('projectNotes'),
+IDE_Morph.prototype.editNotes = function () {
+ var dialog = new DialogBoxMorph().withKey('notes'),
frame = new ScrollFrameMorph(),
- text = new TextMorph(this.projectNotes || ''),
+ text = new TextMorph(this.scene.notes || ''),
size = 250,
world = this.world();
@@ -4715,13 +4731,13 @@ IDE_Morph.prototype.editProjectNotes = function () {
dialog.target = this;
dialog.action = (note) => {
- this.projectNotes = note;
- this.recordUnsavedChanges();
+ this.scene.notes = note;
+ this.recordUnsavedChanges(); // +++ sceneify this
};
dialog.justDropped = () => text.edit();
- dialog.labelString = 'Project Notes';
+ dialog.labelString = 'Notes';
dialog.createLabel();
dialog.addBody(frame);
dialog.addButton('ok', 'OK');
@@ -4743,7 +4759,7 @@ IDE_Morph.prototype.newProject = function () {
this.openProject(project);
};
-IDE_Morph.prototype.createNewScene = function () { // +++
+IDE_Morph.prototype.createNewScene = function () {
var setting = this.isAddingScenes;
this.isAddingScenes = true;
this.newProject();
@@ -4753,13 +4769,14 @@ IDE_Morph.prototype.createNewScene = function () { // +++
IDE_Morph.prototype.save = function () {
// temporary hack - only allow exporting projects to disk
// when running Snap! locally without a web server
+ var pn = this.getProjectName();
if (location.protocol === 'file:') {
- if (this.projectName) {
- this.exportProject(this.projectName, false);
+ if (pn) {
+ this.exportProject(pn);
} else {
this.prompt(
'Export Project As...',
- name => this.exportProject(name, false),
+ name => this.exportProject(name),
null,
'exportProject'
);
@@ -4774,11 +4791,11 @@ IDE_Morph.prototype.save = function () {
if (this.cloud.disabled) {this.source = 'disk'; }
- if (this.projectName) {
+ if (pn) {
if (this.source === 'disk') {
- this.exportProject(this.projectName);
+ this.exportProject(pn);
} else if (this.source === 'cloud') {
- this.saveProjectToCloud(this.projectName);
+ this.saveProjectToCloud(pn);
} else {
this.saveProjectsBrowser();
}
@@ -4787,22 +4804,17 @@ IDE_Morph.prototype.save = function () {
}
};
-IDE_Morph.prototype.exportProject = function (name, plain) {
+IDE_Morph.prototype.exportProject = function (name) {
// Export project XML, saving a file to disk
- // newWindow requests displaying the project in a new tab.
- var menu, str, dataPrefix;
-
- name = this.scenes.at(1).name; // +++
-
+ var menu, str;
if (name) {
- this.setProjectName(name);
- dataPrefix = 'data:text/' + plain ? 'plain,' : 'xml,';
+ name = this.setProjectName(name);
try {
menu = this.showMessage('Exporting');
str = this.serializer.serialize(
new Project(this.scenes, this.scene)
);
- this.setURL('#open:' + dataPrefix + encodeURIComponent(str));
+ this.setURL('#open:data:text/xml,' + encodeURIComponent(str));
this.saveXMLAs(str, name);
menu.destroy();
this.recordSavedChanges();
@@ -4928,7 +4940,7 @@ IDE_Morph.prototype.exportScriptsPicture = function () {
y += padding;
y += each.height;
});
- this.saveCanvasAs(pic, this.projectName || localize('Untitled'));
+ this.saveCanvasAs(pic, this.scene.name || localize('Untitled'));
};
IDE_Morph.prototype.exportProjectSummary = function (useDropShadows) {
@@ -5031,7 +5043,7 @@ IDE_Morph.prototype.exportProjectSummary = function (useDropShadows) {
}
}
- pname = this.projectName || localize('untitled');
+ pname = this.scene.name || localize('untitled');
html = new XML_Element('html');
html.attributes.lang = SnapTranslator.language;
@@ -5092,7 +5104,7 @@ IDE_Morph.prototype.exportProjectSummary = function (useDropShadows) {
}
// project notes
- notes = Process.prototype.reportTextSplit(this.projectNotes, 'line');
+ notes = Process.prototype.reportTextSplit(this.scene.notes, 'line');
notes.asArray().forEach(paragraph => add(paragraph));
// table of contents
@@ -5212,7 +5224,7 @@ IDE_Morph.prototype.exportProjectSummary = function (useDropShadows) {
IDE_Morph.prototype.openProjectString = function (str, callback) {
var msg;
- if (this.bulkDropInProgress || this.isAddingScenes) { // +++
+ if (this.bulkDropInProgress || this.isAddingScenes) {
this.rawOpenProjectString(str);
if (callback) {callback(); }
return;
@@ -5260,7 +5272,7 @@ IDE_Morph.prototype.openCloudDataString = function (str) {
IDE_Morph.prototype.rawOpenCloudDataString = function (str) {
var model,
- setting = this.isAddingScenes; // +++
+ setting = this.isAddingScenes;
if (this.isAddingNextScene) {
this.isAddingScenes = true;
@@ -5484,7 +5496,10 @@ IDE_Morph.prototype.openProjectName = function (name) {
IDE_Morph.prototype.openProject = function (project) {
if (this.isAddingScenes) {
- project.scenes.itemsArray().forEach(scene => this.scenes.add(scene));
+ project.scenes.itemsArray().forEach(scene => {
+ scene.name = this.newSceneName(scene.name, scene);
+ this.scenes.add(scene);
+ });
} else {
this.scenes = project.scenes;
}
@@ -5503,8 +5518,6 @@ IDE_Morph.prototype.switchToScene = function (scene, refreshAlbum) {
);
this.scene.captureGlobalSettings();
this.scene = scene;
- this.projectName = scene.name;
- this.projectNotes = scene.notes || '';
this.globalVariables = scene.globalVariables;
this.stage.destroy();
this.add(scene.stage);
@@ -5521,7 +5534,6 @@ IDE_Morph.prototype.switchToScene = function (scene, refreshAlbum) {
}
});
scene.applyGlobalSettings();
- this.hasUnsavedEdits = false;
this.world().keyboardFocus = this.stage;
};
@@ -5978,7 +5990,7 @@ IDE_Morph.prototype.createNewProject = function () {
this.backup(() => this.newProject());
};
-IDE_Morph.prototype.addScene = function () { // +++
+IDE_Morph.prototype.addScene = function () {
var setting = this.isAddingScenes;
if (location.protocol === 'file:') {
// bypass the project import dialog and directly pop up
@@ -6013,7 +6025,7 @@ IDE_Morph.prototype.saveProjectsBrowser = function () {
if (location.protocol === 'file:') {
this.prompt(
'Export Project As...',
- name => this.exportProject(name, false),
+ name => this.exportProject(name),
null,
'exportProject'
);
@@ -6574,22 +6586,23 @@ IDE_Morph.prototype.logout = function () {
);
};
-IDE_Morph.prototype.buildProjectRequest = function () { // +++ Oh, sweet Jesus!
- var xml = this.serializer.serialize(this.scene),
- thumbnail = normalizeCanvas(
- this.stage.thumbnail(
- SnapSerializer.prototype.thumbnailSize
- )).toDataURL(),
- body;
+IDE_Morph.prototype.buildProjectRequest = function () {
+ var proj = new Project(this.scenes, this.scene),
+ body,
+ xml;
this.serializer.isCollectingMedia = true;
+ xml = this.serializer.serialize(proj);
body = {
- notes: this.projectNotes,
+ notes: proj.notes,
xml: xml,
- media: this.hasChangedMedia ?
- this.serializer.mediaXML(this.projectName) : null,
- thumbnail: thumbnail,
- remixID: this.stage.remixID
+ /*
+ media: this.hasChangedMedia ? // incremental media upload, disabled
+ this.serializer.mediaXML(proj.name) : null,
+ */
+ media: this.serializer.mediaXML(proj.name),
+ thumbnail: proj.thumbnail.toDataURL(),
+ remixID: this.stage.remixID // +++ sceneify remixID
};
this.serializer.isCollectingMedia = false;
this.serializer.flushMedia();
@@ -6637,7 +6650,7 @@ IDE_Morph.prototype.saveProjectToCloud = function (name) {
var projectBody, projectSize;
if (name) {
- this.setProjectName(name);
+ name = this.setProjectName(name);
}
this.showMessage('Saving project\nto the cloud...');
@@ -6648,7 +6661,7 @@ IDE_Morph.prototype.saveProjectToCloud = function (name) {
'Uploading ' + Math.round(projectSize / 1024) + ' KB...'
);
this.cloud.saveProject(
- this.projectName,
+ name,
projectBody,
() => {
this.recordSavedChanges();
@@ -6665,8 +6678,9 @@ IDE_Morph.prototype.exportProjectMedia = function (name) {
this.setProjectName(name);
try {
menu = this.showMessage('Exporting');
+ this.serializer.serialize(new Project(this.scenes, this.scene));
media = this.serializer.mediaXML(name);
- this.saveXMLAs(media, this.projectName + ' media');
+ this.saveXMLAs(media, this.getProjectName() + ' media');
menu.destroy();
this.showMessage('Exported!', 1);
} catch (err) {
@@ -6683,16 +6697,18 @@ IDE_Morph.prototype.exportProjectMedia = function (name) {
// this.hasChangedMedia = false;
};
-IDE_Morph.prototype.exportProjectNoMedia = function (name) { // +++ Sigh...
+IDE_Morph.prototype.exportProjectNoMedia = function (name) {
var menu, str;
this.serializer.isCollectingMedia = true;
if (name) {
- this.setProjectName(name);
+ name = this.setProjectName(name);
if (Process.prototype.isCatchingErrors) {
try {
menu = this.showMessage('Exporting');
- str = this.serializer.serialize(this.scene);
- this.saveXMLAs(str, this.projectName);
+ str = this.serializer.serialize(
+ new Project(this.scenes, this.scene)
+ );
+ this.saveXMLAs(str, name);
menu.destroy();
this.showMessage('Exported!', 1);
} catch (err) {
@@ -6701,8 +6717,10 @@ IDE_Morph.prototype.exportProjectNoMedia = function (name) { // +++ Sigh...
}
} else {
menu = this.showMessage('Exporting');
- str = this.serializer.serialize(this.scene);
- this.saveXMLAs(str, this.projectName);
+ str = this.serializer.serialize(
+ new Project(this.scenes, this.scene)
+ );
+ this.saveXMLAs(str, name);
menu.destroy();
this.showMessage('Exported!', 1);
}
@@ -6711,18 +6729,20 @@ IDE_Morph.prototype.exportProjectNoMedia = function (name) { // +++ Sigh...
this.serializer.flushMedia();
};
-IDE_Morph.prototype.exportProjectAsCloudData = function (name) { // +++ revisit
+IDE_Morph.prototype.exportProjectAsCloudData = function (name) {
var menu, str, media, dta;
this.serializer.isCollectingMedia = true;
if (name) {
- this.setProjectName(name);
+ name = this.setProjectName(name);
if (Process.prototype.isCatchingErrors) {
try {
menu = this.showMessage('Exporting');
- str = this.serializer.serialize(this.scene);
+ str = this.serializer.serialize(
+ new Project(this.scenes, this.scene)
+ );
media = this.serializer.mediaXML(name);
dta = '' + str + media + '';
- this.saveXMLAs(str, this.projectName);
+ this.saveXMLAs(dta, name);
menu.destroy();
this.showMessage('Exported!', 1);
} catch (err) {
@@ -6731,10 +6751,12 @@ IDE_Morph.prototype.exportProjectAsCloudData = function (name) { // +++ revisit
}
} else {
menu = this.showMessage('Exporting');
- str = this.serializer.serialize(this.scene);
+ str = this.serializer.serialize(
+ new Project(this.scenes, this.scene)
+ );
media = this.serializer.mediaXML(name);
dta = '' + str + media + '';
- this.saveXMLAs(str, this.projectName);
+ this.saveXMLAs(str, name);
menu.destroy();
this.showMessage('Exported!', 1);
}
@@ -7097,7 +7119,7 @@ ProjectDialogMorph.prototype.buildContents = function () {
this.body.add(this.srcBar);
if (this.task === 'save') {
- this.nameField = new InputFieldMorph(this.ide.projectName);
+ this.nameField = new InputFieldMorph(this.ide.getProjectName());
this.body.add(this.nameField);
}
@@ -7137,7 +7159,7 @@ ProjectDialogMorph.prototype.buildContents = function () {
this.body.add(this.preview);
if (this.task === 'save') {
- thumbnail = this.ide.stage.thumbnail(
+ thumbnail = this.ide.scenes.at(1).stage.thumbnail(
SnapSerializer.prototype.thumbnailSize
);
this.preview.texture = null;
@@ -7161,7 +7183,7 @@ ProjectDialogMorph.prototype.buildContents = function () {
if (this.task === 'open' || this.task === 'add') {
this.notesText = new TextMorph('');
} else { // 'save'
- this.notesText = new TextMorph(this.ide.projectNotes);
+ this.notesText = new TextMorph(this.ide.getProjectNotes());
this.notesText.isEditable = true;
this.notesText.enableSelecting();
}
@@ -7698,7 +7720,7 @@ ProjectDialogMorph.prototype.recoveryDialog = function () {
new ProjectRecoveryDialogMorph(this.ide, proj.projectname, this).popUp();
};
-ProjectDialogMorph.prototype.addScene = function () { // +++
+ProjectDialogMorph.prototype.addScene = function () {
var proj = this.listField.selected,
src;
if (!proj) {return; }
@@ -7784,8 +7806,8 @@ ProjectDialogMorph.prototype.saveProject = function () {
var name = this.nameField.contents().text.text,
notes = this.notesText.text;
- if (this.ide.projectNotes !== notes) {
- this.ide.projectNotes = notes;
+ if (this.ide.getProjectNotes() !== notes) {
+ this.ide.setProjectNotes(notes);
}
if (name) {
if (this.source === 'cloud') {
@@ -7808,7 +7830,7 @@ ProjectDialogMorph.prototype.saveProject = function () {
this.saveCloudProject();
}
} else if (this.source === 'disk') {
- this.ide.exportProject(name, false);
+ this.ide.exportProject(name);
this.ide.source = 'disk';
this.destroy();
}
@@ -7895,7 +7917,7 @@ ProjectDialogMorph.prototype.shareProject = function () {
this.ide.showMessage('shared.', 2);
// Set the Shared URL if the project is currently open
- if (proj.projectname === ide.projectName) {
+ if (proj.projectname === ide.getProjectName()) {
var usr = ide.cloud.username,
projectId = 'Username=' +
encodeURIComponent(usr.toLowerCase()) +
@@ -7939,7 +7961,7 @@ ProjectDialogMorph.prototype.unshareProject = function () {
this.buttons.fixLayout();
this.rerender();
this.ide.showMessage('unshared.', 2);
- if (proj.projectname === ide.projectName) {
+ if (proj.projectname === ide.getProjectName()) {
location.hash = '';
}
},
@@ -7979,7 +8001,7 @@ ProjectDialogMorph.prototype.publishProject = function () {
this.ide.showMessage('published.', 2);
// Set the Shared URL if the project is currently open
- if (proj.projectname === ide.projectName) {
+ if (proj.projectname === ide.getProjectName()) {
var usr = ide.cloud.username,
projectId = 'Username=' +
encodeURIComponent(usr.toLowerCase()) +
@@ -10253,7 +10275,7 @@ SceneIconMorph.uber = ToggleButtonMorph.prototype;
// SceneIconMorph settings
-SceneIconMorph.prototype.thumbSize = new Point(40, 30); // +++ (40, 40), (80, 60);
+SceneIconMorph.prototype.thumbSize = new Point(40, 30);
SceneIconMorph.prototype.labelShadowOffset = null;
SceneIconMorph.prototype.labelShadowColor = null;
SceneIconMorph.prototype.labelColor = WHITE;
@@ -10302,7 +10324,7 @@ SceneIconMorph.prototype.init = function (aScene) {
colors, // color overrides, : [normal, highlight, pressed]
null, // target - not needed here
action, // a toggle function
- this.object.name || localize('untitled'), // label string // +++
+ this.object.name || localize('untitled'), // label string
query, // predicate/selector
null, // environment
null // hint
@@ -10332,12 +10354,7 @@ SceneIconMorph.prototype.createThumbnail = function () {
this.add(this.thumbnail);
};
-/* +++
-SceneIconMorph.prototype.createLabel
- = SpriteIconMorph.prototype.createLabel;
-*/
-
-SceneIconMorph.prototype.createLabel = function () { // +++
+SceneIconMorph.prototype.createLabel = function () {
var txt;
if (this.label) {
@@ -10384,31 +10401,34 @@ SceneIconMorph.prototype.fixLayout
// SceneIconMorph menu
SceneIconMorph.prototype.userMenu = function () {
- var menu = new MenuMorph(this),
- ide = this.parentThatIsA(IDE_Morph);
- if (!(this.object instanceof Scene)) {return null; }
- menu.addItem("rename", "renameScene");
- if (ide.scenes.length() > 1) {
+ var menu = new MenuMorph(this);
+ if (!(this.object instanceof Scene)) {
+ return null;
+ }
+ if (!this.isProjectScene()) {
+ menu.addItem("rename", "renameScene");
menu.addItem("delete", "removeScene");
}
- // menu.addItem("export", "exportScene");
+ menu.addItem("export", "exportScene");
return menu;
};
SceneIconMorph.prototype.renameScene = function () {
var scene = this.object,
- album = this.parentThatIsA(SceneAlbumMorph),
ide = this.parentThatIsA(IDE_Morph);
new DialogBoxMorph(
null,
answer => {
if (answer && (answer !== scene.name)) {
- scene.name = album.scene.newSceneName(
+ scene.name = ide.newSceneName(
answer,
scene
);
scene.stage.version = Date.now(); // +++ also do this in other places
- ide.recordUnsavedChanges();
+ if (scene === ide.scene) {
+ ide.controlBar.updateLabel();
+ }
+ ide.recordUnsavedChanges(); // ++++ sceneify unsaved changes
}
}
).prompt(
@@ -10430,9 +10450,35 @@ SceneIconMorph.prototype.removeScene = function () {
};
SceneIconMorph.prototype.exportScene = function () {
- // under construction
- var ide = this.parentThatIsA(IDE_Morph);
- ide.saveFileAs(this.object.contents.src, 'text/svg', this.object.name);
+ // Export scene as project XML, saving a file to disk
+ var menu, str,
+ ide = this.parentThatIsA(IDE_Morph),
+ name = this.object.name || localize('untitled');
+
+ try {
+ menu = ide.showMessage('Exporting');
+ str = ide.serializer.serialize(
+ new Project(new List([this.object]), this.object)
+ );
+ ide.saveXMLAs(str, name);
+ menu.destroy();
+ ide.showMessage('Exported!', 1);
+ } catch (err) {
+ if (Process.prototype.isCatchingErrors) {
+ ide.showMessage('Export failed: ' + err);
+ } else {
+ throw err;
+ }
+ }
+};
+
+// SceneIconMorph ops
+
+SceneIconMorph.prototype.isProjectScene = function (anIDE) {
+ // the first scene of a project cannot be renamed, deleted or rearranged,
+ // because its name and project notes are those of the project
+ var ide = anIDE || this.parentThatIsA(IDE_Morph);
+ return ide.scenes.indexOf(this.object) === 1;
};
// SceneIconMorph drawing
@@ -10443,8 +10489,6 @@ SceneIconMorph.prototype.render
// SceneIconMorph drag & drop
SceneIconMorph.prototype.rootForGrab = function () {
- // +++ to do: make sure scene icons can only be grabbed if there are
- // more than 1
return this;
};
@@ -10503,8 +10547,11 @@ SceneAlbumMorph.prototype.updateList = function () {
};
this.addBack(this.contents);
- this.ide.scenes.asArray().forEach(scene => {
+ this.ide.scenes.asArray().forEach((scene, i) => {
icon = new SceneIconMorph(scene);
+ if (i < 1) {
+ icon.isDraggable = false; // project scene cannot be rearranged
+ }
icon.setPosition(new Point(x, y));
this.addContents(icon);
y = icon.bottom() + padding;
@@ -10561,6 +10608,7 @@ SceneAlbumMorph.prototype.reactToDropOf = function (icon) {
idx += 1;
}
});
+ idx = Math.max(idx, 1); // the project scene cannot the rearranged
this.ide.scenes.add(scene, idx + 1);
this.updateList();
icon.mouseClickLeft(); // select
diff --git a/src/scenes.js b/src/scenes.js
index fb08842e..e797141b 100644
--- a/src/scenes.js
+++ b/src/scenes.js
@@ -48,9 +48,10 @@
// Global stuff ////////////////////////////////////////////////////////
-/*global modules, VariableFrame, StageMorph, SpriteMorph, Process, List*/
+/*global modules, VariableFrame, StageMorph, SpriteMorph, Process, List,
+normalizeCanvas, SnapSerializer*/
-modules.scenes = '2021-April-23';
+modules.scenes = '2021-May-21';
// Projecct /////////////////////////////////////////////////////////
@@ -61,12 +62,25 @@ modules.scenes = '2021-April-23';
// Project instance creation:
function Project(scenes, current) {
- this.name = 'Test';
- this.notes = 'some notes';
- this.thumbnail = null;
+ var projectScene;
+
this.scenes = scenes || new List();
this.currentScene = current;
+ // proxied for display
+ this.name = null;
+ this.notes = null;
+ this.thumbnail = 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;
@@ -102,6 +116,7 @@ function Scene(aStageMorph) {
this.globalVariables = aStageMorph ?
aStageMorph.globalVariables() : new VariableFrame();
this.stage = aStageMorph || new StageMorph(this.globalVariables);
+ this.hasUnsavedEdits = false;
// cached IDE state
this.sprites = new List();
diff --git a/src/store.js b/src/store.js
index 32d8b084..80438983 100644
--- a/src/store.js
+++ b/src/store.js
@@ -61,7 +61,7 @@ Project*/
// Global stuff ////////////////////////////////////////////////////////
-modules.store = '2021-April-23';
+modules.store = '2021-May-21';
// XML_Serializer ///////////////////////////////////////////////////////
@@ -1672,7 +1672,6 @@ Project.prototype.toXML = function (serializer) {
Scene.prototype.toXML = function (serializer) {
var tmp = new Scene(),
- thumbdata,
xml;
function code(key) {
@@ -1691,19 +1690,11 @@ Scene.prototype.toXML = function (serializer) {
return str;
}
- // catch cross-origin tainting exception when using SVG costumes
- try {
- thumbdata = this.thumbnail.toDataURL('image/png');
- } catch (error) {
- thumbdata = null;
- }
-
tmp.captureGlobalSettings();
this.applyGlobalSettings();
xml = serializer.format(
'' +
'$' +
- '$' +
'$' +
'%' +
'%
' +
@@ -1713,7 +1704,6 @@ Scene.prototype.toXML = function (serializer) {
'',
this.name || localize('Untitled'),
this.notes || '',
- thumbdata,
Object.keys(StageMorph.prototype.hiddenPrimitives).reduce(
(a, b) => a + ' ' + b,
''