Skip to content

Commit b47a7db

Browse files
committed
introduces plugin selector
SQUASHED: AUTO-COMMIT-src-client-reactive-reactive-jsx-babel-plugin-jsx-lively.js,AUTO-COMMIT-src-components-tools-babel-plugin-explorer-playground.js,AUTO-COMMIT-src-components-tools-lively-plugin-explorer.html,AUTO-COMMIT-src-components-tools-lively-plugin-explorer.js,AUTO-COMMIT-src-components-tools-lively-plugin-explorer-playground.js,AUTO-COMMIT-src-components-tools-lively-plugin-explorer-playground.workspace,AUTO-COMMIT-src-components-tools-plugin-selector.html,AUTO-COMMIT-src-components-tools-plugin-selector.js,
1 parent 2d0d549 commit b47a7db

File tree

6 files changed

+284
-25
lines changed

6 files changed

+284
-25
lines changed

src/components/tools/lively-plugin-explorer-playground.js renamed to src/components/tools/babel-plugin-explorer-playground.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ export default function (babel) {
44
name: "underscore-decorator",
55
visitor: {
66
FunctionDeclaration(path) {
7+
if(path.node.isDecorated) {
8+
return;
9+
}
710
const nameParts = path.node.id.name.split('_');
811
if (nameParts.length < 2) {
912
return;
@@ -12,7 +15,7 @@ export default function (babel) {
1215

1316
for (const id of nameParts.slice(0, -1).reverse()) {
1417
body = t.callExpression(
15-
t.identifiers(id),
18+
t.identifier(id),
1619
[t.arrowFunctionExpression([], body)])
1720
}
1821

@@ -21,10 +24,9 @@ export default function (babel) {
2124
path.node.params,
2225
t.blockStatement([t.returnStatement(body)]
2326
));
24-
path.replaceWith(fun);
25-
// not exactly what I want as this does not decorate function declared in the current function
26-
path.stop();
27-
},
27+
fun.isDecorated = true;
28+
path.replaceWith(fun);
29+
},//*/
2830

2931
ReturnStatement(path) {
3032
// debugger
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"source":"/src/components/tools/lively-ast-explorer-example-source.js","plugin":"/src/components/tools/lively-plugin-explorer-playground.js","options":{"autoUpdateAST":true,"autoUpdateTransformation":true,"autoExecute":true,"systemJS":false,"autoRunTests":false,"autoSaveWorkspace":false}}
1+
{"source":"/src/components/tools/lively-ast-explorer-example-source.js","plugin":"https://lively-kernel.org/lively4/lively4-tom/src/components/tools/babel-plugin-explorer-playground.js","options":{"autoUpdateAST":true,"autoUpdateTransformation":true,"autoExecute":true,"systemJS":false,"autoRunTests":false,"autoSaveWorkspace":false},"pluginSelection":[{"url":"https://lively-kernel.org/lively4/lively4-tom/src/external/babel-plugin-syntax-function-bind.js","name":"babel-plugin-syntax-function-bind.js","size":"149","modified":"2020-11-08 20:33:44","version":"cf788e085403d48c22bb7a2e2bdb67be0968fcb1","content":"export default function () {\r\n return {\r\n manipulateOptions(opts, parserOpts) {\r\n parserOpts.plugins.push(\"functionBind\");\r\n }\r\n };\r\n}\r\n","type":"file","title":"export default function () {\r","tags":[],"unboundIdentifiers":["manipulateOptions","plugins","push"]},{"url":"https://lively-kernel.org/lively4/lively4-tom/src/client/reactive/reactive-jsx/babel-plugin-jsx-lively.js","name":"babel-plugin-jsx-lively.js","size":"7640","modified":"2020-11-08 20:33:44","version":"ab4c0473f913da10ed34169b50b78eb1c2190928","content":"import jsx from \"babel-plugin-syntax-jsx\";\nimport Preferences from 'src/client/preferences.js';\n\nfunction detectUnsupportedNodes(path, filename) {\n function gainPrintableFullPath(path) {\n let fullPath = [];\n\n while (path) {\n fullPath.unshift(path.node.type);\n path = path.parentPath;\n }\n\n return fullPath.map((nodeType, index) => ' '.repeat(index) + nodeType).join('\\n');\n }\n\n path.traverse({\n /**\n * No support for JSXMemberExpression yet. #TODO: what are the semantics outside of react for this?\n * \n * <foo.bar></foo.bar>\n */\n JSXMemberExpression(path, state) {\n throw new SyntaxError(`JSXMemberExpression not yet supported.\n${gainPrintableFullPath(path)}`, filename, path.node.loc.start.line);\n },\n /**\n * No support for JSXEmptyExpression yet. #TODO: where is this feature even useful?\n * \n * <div id={}></div>\n * or\n * <div>{}</div>\n */\n JSXEmptyExpression(path, state) {\n throw new SyntaxError(`JSXEmptyExpression not yet supported.\n${gainPrintableFullPath(path)}`, filename);\n },\n /**\n * No support for jSXNamespacedName yet.\n * \n * <div ns:attr=\"val\" />;\n */\n JSXNamespacedName(path, state) {\n throw new SyntaxError(`jSXNamespacedName not yet supported.\n${gainPrintableFullPath(path)}`, filename, path.node.loc.start.line);\n }\n });\n}\n\n/**\n * Resources for JSX Syntax\n * JSX babel Preset: https://github.com/babel/babel/blob/master/packages/babel-preset-react/src/index.js\n * JSX spec draft: https://github.com/facebook/jsx\n * JSX Syntax definition in babel: https://github.com/babel/babel/blob/master/packages/babel-types/src/definitions/jsx.js#L10\n * Babel nodes list: https://babeljs.io/docs/core-packages/babel-types/#apij-sxidentifier\n */\nexport default function ({ types: t, template, traverse }) {\n const GENERATED_IMPORT_IDENTIFIER = Symbol(\"generated import identifier\");\n\n // #TODO: duplicate with aexpr transform -> extract it\n function addCustomTemplate(file, name) {\n let declar = file.declarations[name];\n if (declar) return declar;\n\n let identifier = file.declarations[name] = file.addImport(\"reactive-jsx\", name, name);\n identifier[GENERATED_IMPORT_IDENTIFIER] = true;\n return identifier;\n }\n\n return {\n inherits: jsx,\n visitor: {\n Program(path, state) {\n detectUnsupportedNodes(path, state && state.opts && state.opts.filename);\n\n const fileName = state && state.file && state.file.log && state.file.log.filename || 'no_file_given';\n const sourceLocation = template(`({\n file: '${fileName}',\n end: {\n column: END_COLUMN,\n line: END_LINE\n },\n start: {\n column: START_COLUMN,\n line: START_LINE\n }\n })`);\n function buildSourceLocation(node) {\n return sourceLocation({\n END_COLUMN: t.numericLiteral(node.loc.end.column),\n END_LINE: t.numericLiteral(node.loc.end.line),\n START_COLUMN: t.numericLiteral(node.loc.start.column),\n START_LINE: t.numericLiteral(node.loc.start.line)\n }).expression;\n }\n\n function transformPath(path, programState) {\n function jSXAttributeToBuilder(path) {\n\n function getCallExpressionFor(functionName, ...additionalParameters) {\n return t.callExpression(addCustomTemplate(programState.file, functionName), // builder function\n [t.stringLiteral(path.get(\"name\").node.name), // key\n ...additionalParameters]);\n }\n\n let attributeValue = path.get(\"value\");\n if (path.isJSXSpreadAttribute()) {\n return t.callExpression(addCustomTemplate(programState.file, \"attributeSpread\"), [path.get(\"argument\").node]);\n } else if (!path.node.value) {\n return getCallExpressionFor(\"attributeEmpty\");\n } else if (attributeValue.isStringLiteral()) {\n return getCallExpressionFor(\"attributeStringLiteral\", attributeValue.node);\n } else if (attributeValue.isJSXExpressionContainer()) {\n return getCallExpressionFor(\"attributeExpression\", attributeValue.node.expression);\n } else if (attributeValue.isJSXElement()) {\n // #TODO: what would that even mean?\n throw new SyntaxError(`JSXElement as property value of JSXAttribute not yet supported.`);\n }\n\n throw new Error('unknown node type in JSXAttribute value ' + attributeValue.node.type);\n }\n\n function jSXChildrenToBuilder(child) {\n function getCallExpressionFor(functionName, childSpec) {\n return t.callExpression(addCustomTemplate(programState.file, functionName), // builder function\n [childSpec]);\n }\n\n if (child.isJSXText()) {\n return getCallExpressionFor(\"childText\", t.stringLiteral(child.node.value));\n }\n if (child.isJSXElement()) {\n return getCallExpressionFor(\"childElement\", child.node);\n }\n if (child.isJSXExpressionContainer()) {\n return getCallExpressionFor(\"childExpression\", child.get(\"expression\").node);\n }\n if (child.isJSXSpreadChild()) {\n return getCallExpressionFor(\"childSpread\", child.get(\"expression\").node);\n //throw new SyntaxError(`JSXSpreadChild as child of JSXElement not yet supported.`);\n }\n throw new Error('unknown node type in children of JSXElement ' + child.node.type);\n }\n\n path.traverse({\n JSXElement(path, state) {\n const jSXAttributes = path.get(\"openingElement\").get(\"attributes\");\n const jSXChildren = path.get(\"children\");\n\n let newNode = t.callExpression(addCustomTemplate(programState.file, \"element\"), [t.stringLiteral(path.get(\"openingElement\").get(\"name\").node.name), t.callExpression(addCustomTemplate(programState.file, \"attributes\"), jSXAttributes.map(jSXAttributeToBuilder)), t.callExpression(addCustomTemplate(programState.file, \"children\"), jSXChildren.map(jSXChildrenToBuilder)), buildSourceLocation(path.node)]);\n\n path.replaceWith(newNode);\n },\n CallExpression(path) {\n if (path.node.__wrappedInMetaInformation__) {\n return;\n }\n path.node.__wrappedInMetaInformation__ = true;\n\n function isDomCreatingCall(path) {\n const callee = path.get('callee');\n if (!callee.isMemberExpression() || callee.node.computed) {\n return false;\n }\n\n const object = callee.get('object');\n const property = callee.get('property');\n\n if (!object.isIdentifier()) {\n return false;\n }\n if (!property.isIdentifier()) {\n return false;\n }\n\n const livelyCreate = object.node.name === 'lively' && property.node.name === 'create';\n const documentCreateElement = object.node.name === 'document' && property.node.name === 'createElement';\n return livelyCreate || documentCreateElement;\n }\n\n if (isDomCreatingCall(path)) {\n path.replaceWith(t.callExpression(addCustomTemplate(programState.file, \"addSourceLocation\"), [path.node, buildSourceLocation(path.node)]));\n }\n }\n });\n\n return path;\n }\n\n transformPath(path, state);\n }\n }\n };\n}","type":"file","title":"import jsx from \"babel-plugin-syntax-jsx\";","tags":[],"unboundIdentifiers":["unshift","node","type","parentPath","map","repeat","join","traverse","JSXMemberExpression","SyntaxError","loc","start","line","JSXEmptyExpression","JSXNamespacedName","types","Symbol","declarations","addImport","inherits","visitor","Program","opts","filename","file","log","END_COLUMN","numericLiteral","end","column","END_LINE","START_COLUMN","START_LINE","expression","callExpression","stringLiteral","get","name","isJSXSpreadAttribute","value","isStringLiteral","isJSXExpressionContainer","isJSXElement","Error","isJSXText","isJSXSpreadChild","JSXElement","replaceWith","CallExpression","__wrappedInMetaInformation__","isMemberExpression","computed","isIdentifier"]}]}

src/components/tools/lively-plugin-explorer.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,9 @@
126126
<button class="toggle" id="toggleSystemJS" title="Toggle: SystemJS.">
127127
<i class="fa fa-cloud"></i>
128128
</button>
129+
<button id="selectPlugins" title="Opens settings for plugins that should get applied while transformation">
130+
<i class="fa fa-cog"></i>
131+
</button>
129132
</div>
130133

131134
<div id="main" class="pane layout-row">

src/components/tools/lively-plugin-explorer.js

Lines changed: 74 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class Transitions {
4747
code,
4848
alias: "output",
4949
type: "exit",
50-
size: new Blob([code], { type: "text/plain" }).size,
50+
size: new Blob([code], { type: "text/plain" }).size
5151
});
5252
};
5353

@@ -57,10 +57,10 @@ class Transitions {
5757
callback.call(this, ...args);
5858

5959
this.savedTransitions.push({
60-
node: _.cloneDeep(args[0].node),
60+
node: JSON.parse(JSON.stringify((args[0].node))),
6161
parent: _.cloneDeep(this._getProgramParent(args[0]).node),
6262
alias: pluginAlias,
63-
type: visitorType,
63+
type: visitorType
6464
})
6565
} catch(e) {
6666
this.errorState = {
@@ -84,7 +84,7 @@ class Transitions {
8484
alias: transition.alias,
8585
type: transition.type,
8686
currentNode: transition.node.type,
87-
size: new Blob([code], { type: "text/plain" }).size,
87+
size: new Blob([code], { type: "text/plain" }).size
8888
});
8989
}
9090
}
@@ -126,14 +126,21 @@ export default class PluginExplorer extends Morph {
126126
get pluginCM() { return this.pluginEditor.currentEditor(); }
127127
get pluginSource() { return this.pluginCM.getValue(); }
128128

129-
async getPlugin() {
130-
const url = this.fullUrl(this.pluginURL) || "";
129+
async getPlugin(url = this.pluginURL) {
130+
url = this.fullUrl(url) || "";
131131
const module = await System.import(url);
132-
// url += "?" + Date.now(); // #HACK, we thought we don't have this to do any more, but ran into a problem when dealing with syntax errors...
133-
// assumend problem: there is a bad version of the code in either the browser or system.js cache
134-
// idea: we have to find and flush it...
135-
// wip: the browser does not cache it, but system.js does...
136-
return module.default;
132+
133+
// we know no better way to get the plugin-file now, so we append the path to it to the name
134+
// and can read it from there later on
135+
const plugin = module.default;
136+
const modifiedPlugin = function(...args) {
137+
const result = plugin(...args)
138+
result.name = result.name || 'Please name your plugin!';
139+
result.name += ' ' + url
140+
return result;
141+
}
142+
143+
return modifiedPlugin;
137144
}
138145

139146
get transformedSourceLCM() { return this.get("#transformedSource"); }
@@ -379,12 +386,22 @@ export default class PluginExplorer extends Morph {
379386
lively.error(`Failed to load workspace '${urlString}'`);
380387
}
381388
}
389+
390+
loadPluginFile(url) {
391+
this.pluginEditor.setURL(url);
392+
this.pluginEditor.loadFile();
393+
}
394+
395+
changeSelectedPlugin(url) {
396+
this.workspace.plugin = url;
397+
this.saveWorkspace();
398+
this.loadPluginFile(this.workspace.plugin);
399+
}
382400

383401
async loadWorkspace(ws) {
384402
this.workspace = ws;
385403
this.loadOptions(ws.options);
386-
this.pluginEditor.setURL(new URL(this.fullUrl(ws.plugin)));
387-
this.pluginEditor.loadFile();
404+
this.loadPluginFile(new URL(this.fullUrl(ws.plugin)));
388405
//TODO
389406
this.sourceLCM.value = ""; //new URL(this.fullUrl(ws.source))
390407
}
@@ -402,7 +419,19 @@ export default class PluginExplorer extends Morph {
402419
this.pluginEditor.saveFile();
403420
this.saveWorkspaceFile(this.workspaceURL);
404421
}
405-
422+
423+
/*MD # Plugin selection MD*/
424+
425+
426+
onSelectPlugins() {
427+
lively.openComponentInWindow('plugin-selector')
428+
.then(elm => elm.pluginExplorer = this);
429+
}
430+
431+
savePluginSelection(selection) {
432+
this.workspace.pluginSelection = selection;
433+
this.saveWorkspace();
434+
}
406435
/*MD ## Execution MD*/
407436

408437
async updateAST() {
@@ -442,11 +471,21 @@ export default class PluginExplorer extends Morph {
442471

443472
setTransitionButtons(transitions) {
444473
this.replaceButtonsWith(transitions.getValue().map((transition, index) => {
474+
const nameComponents = transition.alias.split(' ');
475+
let buttonContent = transition.alias;
476+
let path = null;
477+
if (nameComponents.length > 1) {
478+
buttonContent = nameComponents.slice(0, -1).join(' ');
479+
path = nameComponents.last;
480+
}
481+
445482
const button = document.createElement('button');
446-
button.innerText = `${index}: ${transition.alias}`;
483+
button.innerText = `${index}: ${buttonContent}`;
447484
button.addEventListener('click', e => {
448485
// transition.inspect()
449-
this.updateAndExecute(transition.code);
486+
if(path) {
487+
lively.openBrowser(path);
488+
}
450489
});
451490
button.addEventListener('mouseover', e => {
452491
this.updateAndExecute(transition.code);
@@ -463,7 +502,7 @@ export default class PluginExplorer extends Morph {
463502
try {
464503
console.group("PLUGIN TRANSFORMATION");
465504
if (!ast) return;
466-
if (this.systemJS) {
505+
/*if (this.systemJS) {
467506
// use SystemJS config do do a full transform
468507
if (!self.lively4lastSystemJSBabelConfig) {
469508
lively.error("lively4lastSystemJSBabelConfig missing");
@@ -487,7 +526,23 @@ export default class PluginExplorer extends Morph {
487526
wrapPluginVisitorMethod: transitions.wrapPluginVisitorMethod
488527
.bind(transitions)
489528
});
490-
}
529+
}*/
530+
531+
532+
533+
const config = {};
534+
const selection = this.workspace.pluginSelection;
535+
536+
config.plugins = await Promise.all(selection.map(obj => this.getPlugin(obj.url)));
537+
config.plugins.push(plugin);
538+
539+
const filename = 'tempfile.js';
540+
config.sourceFileName = filename
541+
config.moduleIds = false;
542+
543+
config.wrapPluginVisitorMethod = transitions.wrapPluginVisitorMethod.bind(transitions);
544+
this.transformationResult = babel.transform(this.sourceText, config);
545+
491546

492547
// Todo: find in original code how to use
493548
// transitions.addExitTransition(this.transformationResult.code);
@@ -539,7 +594,7 @@ export default class PluginExplorer extends Morph {
539594
this.executionConsole.textContent += "-> " + result;
540595
} catch (e) {
541596
console.error(e);
542-
this.executionConsole.textContent += "Error: " + e;
597+
this.executionConsole.textContent += e.stack;
543598
} finally {
544599
console.log = oldLog
545600
console.groupEnd();
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<template id="plugin-selector">
2+
<style data-src="/src/external/font-awesome/css/font-awesome.css"></style>
3+
<style data-src="/templates/livelystyle.css"></style>
4+
<style>
5+
6+
:host {
7+
overflow: scroll;
8+
width: 400px;
9+
}
10+
11+
button.toggle {
12+
color: black;
13+
}
14+
15+
button.toggle.on {
16+
color: white;
17+
background: steelblue;
18+
}
19+
20+
.entry {
21+
position: relative;
22+
width: 100%;
23+
background: #ddd;
24+
}
25+
26+
.entry.contrast {
27+
background: #ccc;
28+
}
29+
30+
.options {
31+
position: absolute;
32+
left: 300px;
33+
}
34+
35+
</style>
36+
37+
38+
<div id="pluginList"></div>
39+
40+
</template>
41+

0 commit comments

Comments
 (0)