New feature: Export Project Summary

* opens a new browser tab on an editable HTML document containing an
overview of the current project which can be further processed (saved,
edited, printed, turned into a PDF) to create a project report.

* now, when you export script pics attached comments get included.

* also new: Support to detect and react to “any” keystroke (hat and
sensing blocks)
dev
Jens Mönig 2015-10-02 12:39:41 +02:00
rodzic cd7f99534b
commit 2172084dae
8 zmienionych plików z 279 dodań i 15 usunięć

Wyświetl plik

@ -156,7 +156,7 @@ DialogBoxMorph, BlockInputFragmentMorph, PrototypeHatBlockMorph, Costume*/
// Global stuff ////////////////////////////////////////////////////////
modules.blocks = '2015-September-23';
modules.blocks = '2015-October-02';
var SyntaxElementMorph;
var BlockMorph;
@ -1073,6 +1073,7 @@ SyntaxElementMorph.prototype.labelPart = function (spec) {
'right arrow': ['right arrow'],
'left arrow': ['left arrow'],
space : ['space'],
any : ['any'],
a : ['a'],
b : ['b'],
c : ['c'],
@ -2312,7 +2313,7 @@ BlockMorph.prototype.userMenu = function () {
menu.addItem(
"script pic...",
function () {
window.open(myself.topBlock().fullImage().toDataURL());
window.open(myself.topBlock().scriptPic().toDataURL());
},
'open a new window\nwith a picture of this script'
);
@ -3234,7 +3235,7 @@ BlockMorph.prototype.activeProcess = function () {
return null;
};
// BlockMorph thumbnail
// BlockMorph thumbnail and script pic
BlockMorph.prototype.thumbnail = function (scale, clipWidth) {
var nb = this.nextBlock(),
@ -3276,6 +3277,31 @@ BlockMorph.prototype.thumbnail = function (scale, clipWidth) {
return trgt;
};
BlockMorph.prototype.scriptPic = function () {
// answer a canvas image that also includes comments
var scr = this.fullImage(),
fb = this.stackFullBounds(),
pic = newCanvas(fb.extent()),
ctx = pic.getContext('2d');
this.allComments().forEach(function (comment) {
var anchor = comment.anchor;
if (anchor) {
ctx.drawImage(
anchor.image,
anchor.left() - fb.left(),
anchor.top() - fb.top()
);
}
ctx.drawImage(
comment.fullImageClassic(),
comment.left() - fb.left(),
comment.top() - fb.top()
);
});
ctx.drawImage(scr, 0, 0);
return pic;
};
// BlockMorph dragging and dropping
BlockMorph.prototype.rootForGrab = function () {
@ -3358,6 +3384,22 @@ BlockMorph.prototype.stackHeight = function () {
return Math.max(fb.bottom(), commentsBottom) - fb.top();
};
BlockMorph.prototype.stackFullBounds = function () {
var fb = this.fullBounds();
this.allComments().forEach(function (comment) {
fb.mergeWith(comment.bounds);
});
return fb;
};
BlockMorph.prototype.stackWidth = function () {
var fb = this.fullBounds(),
commentsRight = Math.max(this.allComments().map(
function (comment) {return comment.right(); }
)) || this.right();
return Math.max(fb.right(), commentsRight) - fb.left();
};
BlockMorph.prototype.snap = function () {
var top = this.topBlock();
top.allComments().forEach(function (comment) {
@ -5419,6 +5461,20 @@ ScriptsMorph.prototype.clearDropHistory = function () {
this.lastNextBlock = null;
};
// ScriptsMorph sorting blocks and comments
ScriptsMorph.prototype.sortedElements = function () {
// return all scripts and unattached comments
var scripts = this.children.filter(function (each) {
return each instanceof CommentMorph ? !each.block : true;
});
scripts.sort(function (a, b) {
// make sure the prototype hat block always stays on top
return a instanceof PrototypeHatBlockMorph ? 0 : a.top() - b.top();
});
return scripts;
};
// ScriptsMorph blocks layout fix
ScriptsMorph.prototype.fixMultiArgs = function () {
@ -10984,7 +11040,7 @@ CommentMorph.prototype.userMenu = function () {
menu.addItem(
"comment pic...",
function () {
window.open(myself.fullImage().toDataURL());
window.open(myself.fullImageClassic().toDataURL());
},
'open a new window\nwith a picture of this comment'
);

14
byob.js
Wyświetl plik

@ -106,7 +106,7 @@ SymbolMorph, isNil, CursorMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.byob = '2015-July-28';
modules.byob = '2015-October-02';
// Declarations
@ -341,6 +341,16 @@ CustomBlockDefinition.prototype.parseSpec = function (spec) {
// CustomBlockDefinition picturing
CustomBlockDefinition.prototype.scriptsPicture = function () {
return this.scriptsModel().scriptsPicture();
};
CustomBlockDefinition.prototype.sortedElements = function () {
return this.scriptsModel().sortedElements();
};
CustomBlockDefinition.prototype.scriptsModel = function () {
// answer a restored scripting area for the sake
// of creating script pictures
var scripts, proto, block, comment;
scripts = new ScriptsMorph();
@ -372,7 +382,7 @@ CustomBlockDefinition.prototype.scriptsPicture = function () {
});
proto.children[0].fixLayout();
scripts.fixMultiArgs();
return scripts.scriptsPicture();
return scripts;
};
// CustomCommandBlockMorph /////////////////////////////////////////////

183
gui.js
Wyświetl plik

@ -66,11 +66,11 @@ ScriptsMorph, isNil, SymbolMorph, BlockExportDialogMorph,
BlockImportDialogMorph, SnapTranslator, localize, List, InputSlotMorph,
SnapCloud, Uint8Array, HandleMorph, SVG_Costume, fontHeight, hex_sha512,
sb, CommentMorph, CommandBlockMorph, BlockLabelPlaceHolderMorph, Audio,
SpeechBubbleMorph, ScriptFocusMorph*/
SpeechBubbleMorph, ScriptFocusMorph, XML_Element*/
// Global stuff ////////////////////////////////////////////////////////
modules.gui = '2015-September-07';
modules.gui = '2015-October-02';
// Declarations
@ -2539,6 +2539,12 @@ IDE_Morph.prototype.projectMenu = function () {
'show global custom block definitions as XML\nin a new browser window'
);
menu.addItem(
'Export summary...',
function () {myself.exportProjectSummary(); },
'open a new browser browser window\n with a summary of this project'
);
if (shiftClicked) {
menu.addItem(
'Export all scripts as pic...',
@ -3099,6 +3105,179 @@ IDE_Morph.prototype.exportScriptsPicture = function () {
window.open(pic.toDataURL());
};
IDE_Morph.prototype.exportProjectSummary = function () {
var html, head, meta, body, pname, notes, globalVars, globalBlocks;
function addNode(tag, node, contents) {
if (!node) {node = body; }
return new XML_Element(tag, contents, node);
}
function add(contents, tag, node) {
if (!tag) {tag = 'p'; }
if (!node) {node = body; }
return new XML_Element(tag, contents, node);
}
function addImage(canvas, node, inline) {
if (!node) {node = body; }
var para = !inline ? addNode('p', node) : null,
pic = addNode('img', para || node);
pic.attributes.src = canvas.toDataURL();
return pic;
}
function addBlocks(definitions) {
if (definitions.length) {
add(localize('Blocks'), 'h3');
SpriteMorph.prototype.categories.forEach(function (category) {
var isFirst = true,
ul;
definitions.forEach(function (def) {
var li;
if (def.category === category) {
if (isFirst) {
add(
localize(
category[0].toUpperCase().concat(
category.slice(1)
)
),
'h4'
);
ul = addNode('ul');
isFirst = false;
}
li = addNode('li', ul);
addImage(def.templateInstance().scriptPic(), li);
def.sortedElements().forEach(function (script) {
addImage(
script instanceof BlockMorph ?
script.scriptPic()
: script.fullImageClassic(),
li
);
});
}
});
});
}
}
pname = this.projectName || localize('untitled');
html = new XML_Element('html');
html.attributes.contenteditable = 'true';
head = addNode('head', html);
meta = addNode('meta', head);
meta.attributes['http-equiv'] = 'Content-Type';
meta.attributes.content = 'text/html; charset=UTF-8';
add(pname, 'title', head);
body = addNode('body', html);
add(pname, 'h1');
/*
if (SnapCloud.username) {
add(localize('by ' + SnapCloud.username));
}
*/
add(this.serializer.app);
addImage(this.stage.thumbnail(
this.serializer.thumbnailSize.multiplyBy(2)
));
// project notes
notes = Process.prototype.reportTextSplit(this.projectNotes, 'line');
notes.asArray().forEach(
function (paragraph) {add(paragraph); }
);
// sprites & stage
this.sprites.asArray().concat([this.stage]).forEach(function (sprite) {
var scripts = sprite.scripts.sortedElements(),
varNames = sprite.variables.names(),
cl = sprite.costumes.length(),
ol;
add(sprite.name, 'h2');
if (sprite instanceof SpriteMorph || sprite.costume) {
addImage(sprite.image);
}
// costumes
if (cl > 1 || (sprite.getCostumeIdx() !== cl)) {
add(localize('Costumes'), 'h3');
ol = addNode('ol');
sprite.costumes.asArray().forEach(function (costume) {
var li = addNode('li', ol, costume.name);
addNode('br', li);
addImage(costume.thumbnail(new Point(40, 40)), li, true);
});
}
// sounds
if (sprite.sounds.length()) {
add(localize('Sounds'), 'h3');
ol = addNode('ol');
sprite.sounds.asArray().forEach(function (sound) {
add(sound.name, 'li', ol);
});
}
// variables
if (varNames.length) {
add(localize('Variables'), 'h3');
varNames.forEach(function (name) {
addImage(
SpriteMorph.prototype.variableBlock(name).scriptPic()
);
});
}
// scripts
if (scripts.length) {
add(localize('Scripts'), 'h3');
scripts.forEach(function (script) {
addImage(script instanceof BlockMorph ? script.scriptPic()
: script.fullImageClassic());
});
}
// custom blocks
addBlocks(sprite.customBlocks);
});
// globals
globalVars = this.stage.globalVariables().names();
globalBlocks = this.stage.globalBlocks;
if (globalVars.length || globalBlocks.length) {
addNode('hr');
add(localize('For all Sprites'), 'h2');
// variables
if (globalVars.length) {
add(localize('Variables'), 'h3');
globalVars.forEach(function (name) {
addImage(
SpriteMorph.prototype.variableBlock(name).scriptPic()
);
});
}
// custom blocks
addBlocks(globalBlocks);
}
window.open('data:text/html;charset=utf-8,' + encodeURIComponent(
'<!DOCTYPE html>' + html.toString()
));
};
IDE_Morph.prototype.openProjectString = function (str) {
var msg,
myself = this;

Wyświetl plik

@ -2579,3 +2579,9 @@ ______
* Morphic, Objects: Improve display precision (stop rounding display coordinates)
* Added “ceiling” function, thanks, Michael
* Updated various translations
151002
------
* GUI, Blocks, BYOB: New “Export Project Summary” Feature, also: exporting script pics now includes attached comments
* Blocks, Objects, Threads: Key hat block and key sensor support for “any” key
* German translation update

Wyświetl plik

@ -185,7 +185,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org', // optional
'last_changed':
'2015-09-23', // this, too, will appear in the Translators tab
'2015-10-02', // this, too, will appear in the Translators tab
// GUI
// control bar:
@ -680,6 +680,15 @@ SnapTranslator.dict.de = {
'Bl\u00f6cke exportieren...',
'show global custom block definitions as XML\nin a new browser window':
'zeigt globale Benutzerblockdefinitionen\nals XML im Browser an',
'Export summary...':
'Zusammenfassung exportieren...',
'open a new browser browser window\n with a summary of this project':
'eine Zusammenfassung diese Projects\nin einem neuen Browserfenster'
+ 'anzeigen',
'Blocks':
'Bausteine',
'For all Sprites':
'F\u00fcr alle',
'Import tools':
'Tools laden',
'load the official library of\npowerful blocks':
@ -1302,5 +1311,5 @@ SnapTranslator.dict.de = {
'last':
'letztes',
'any':
'beliebiges'
'beliebig'
};

Wyświetl plik

@ -42,7 +42,7 @@
/*global modules, contains*/
modules.locale = '2015-September-23';
modules.locale = '2015-October-02';
// Global stuff
@ -149,7 +149,7 @@ SnapTranslator.dict.de = {
'translator_e-mail':
'jens@moenig.org',
'last_changed':
'2015-09-23'
'2015-10-02'
};
SnapTranslator.dict.it = {

Wyświetl plik

@ -125,7 +125,7 @@ PrototypeHatBlockMorph*/
// Global stuff ////////////////////////////////////////////////////////
modules.objects = '2015-September-23';
modules.objects = '2015-October-02';
var SpriteMorph;
var StageMorph;
@ -3673,7 +3673,8 @@ SpriteMorph.prototype.allHatBlocksForKey = function (key) {
return this.scripts.children.filter(function (morph) {
if (morph.selector) {
if (morph.selector === 'receiveKey') {
return morph.inputs()[0].evaluate()[0] === key;
var evt = morph.inputs()[0].evaluate()[0];
return evt === key || evt === 'any';
}
}
return false;

Wyświetl plik

@ -83,7 +83,7 @@ ArgLabelMorph, localize, XML_Element, hex_sha512*/
// Global stuff ////////////////////////////////////////////////////////
modules.threads = '2015-September-23';
modules.threads = '2015-October-02';
var ThreadManager;
var Process;
@ -2636,6 +2636,9 @@ Process.prototype.reportKeyPressed = function (keyString) {
if (this.homeContext.receiver) {
stage = this.homeContext.receiver.parentThatIsA(StageMorph);
if (stage) {
if (this.inputOption(keyString) === 'any') {
return Object.keys(stage.keysPressed).length > 0;
}
return stage.keysPressed[keyString] !== undefined;
}
}