External editors.
- Fix a few dependencies in the Jakefile. - Add metadata support. Currently only offering script name and script description. * Forward the metadata properly to the backend. Reflect it in the local storage. * Update the messaging protocol accordingly. * Add some basic UI for that purpose in the Blockly editor. * Merge that data too.
This commit is contained in:
Родитель
64424a0758
Коммит
924cc93a00
10
Jakefile
10
Jakefile
|
@ -241,8 +241,14 @@ mkSimpleTask('build/runner.d.ts', [
|
|||
'build/libcordova.d.ts',
|
||||
'runner'
|
||||
], "runner/refs.ts");
|
||||
mkSimpleTask('build/ace.js', [ "www/ace/ace-main.ts" ], "www/ace/refs.ts");
|
||||
mkSimpleTask('build/blockly.js', [ "www/blockly/blockly-main.ts" ], "www/blockly/refs.ts");
|
||||
mkSimpleTask('build/ace.js', [
|
||||
"www/ace/ace-main.ts",
|
||||
"editor/messages.ts"
|
||||
], "www/ace/refs.ts");
|
||||
mkSimpleTask('build/blockly.js', [
|
||||
"www/blockly/blockly-main.ts",
|
||||
"editor/messages.ts"
|
||||
], "www/blockly/refs.ts");
|
||||
|
||||
// Now come the rules for files that are obtained by concatenating multiple
|
||||
// _js_ files into another one. The sequence exactly reproduces what happened
|
||||
|
|
|
@ -2984,7 +2984,7 @@ module TDev
|
|||
}
|
||||
else return resp;
|
||||
})
|
||||
.then(resp => {
|
||||
.then(resp => {
|
||||
header = resp;
|
||||
if (!header) Util.oops(lf("script not installed"));
|
||||
return this.saveStateAsync({ forReal: true });
|
||||
|
@ -3058,6 +3058,7 @@ module TDev
|
|||
scriptVersionInCloud: scriptVersionInCloud,
|
||||
editorState: editorState,
|
||||
baseSnapshot: header.scriptVersion.baseSnapshot,
|
||||
metadata: header.meta,
|
||||
});
|
||||
ProgressOverlay.hide();
|
||||
return new PromiseInv();
|
||||
|
|
|
@ -79,6 +79,18 @@ module TDev {
|
|||
var scriptText = message.script.scriptText;
|
||||
var editorState = message.script.editorState;
|
||||
header.scriptVersion.baseSnapshot = message.script.baseSnapshot;
|
||||
|
||||
var metadata = message.script.metadata;
|
||||
Object.keys(metadata).forEach(k => {
|
||||
var v = metadata[k];
|
||||
if (k == "name")
|
||||
v = v || "unnamed";
|
||||
header.meta[k] = v;
|
||||
});
|
||||
// [name] deserves a special treatment because it
|
||||
// appears both on the header and in the metadata.
|
||||
header.name = metadata.name;
|
||||
|
||||
// Writes into local storage.
|
||||
World.updateInstalledScriptAsync(header, scriptText, editorState, false, "").then(() => {
|
||||
console.log("[external] script saved properly");
|
||||
|
@ -88,6 +100,7 @@ module TDev {
|
|||
status: Status.Ok,
|
||||
});
|
||||
});
|
||||
|
||||
// Schedules a cloud sync; set the right state so
|
||||
// that [scheduleSaveToCloudAsync] writes the
|
||||
// baseSnapshot where we can read it back.
|
||||
|
@ -175,6 +188,7 @@ module TDev {
|
|||
editorState: string;
|
||||
scriptVersionInCloud: string;
|
||||
baseSnapshot: string;
|
||||
metadata: Metadata;
|
||||
};
|
||||
|
||||
// The [scriptVersionInCloud] name is the one that's used by [world.ts];
|
||||
|
@ -207,11 +221,7 @@ module TDev {
|
|||
var extra = JSON.parse(data.scriptVersionInCloud || "{}");
|
||||
TheChannel.post(<Message_Init>{
|
||||
type: MessageType.Init,
|
||||
script: {
|
||||
scriptText: data.scriptText,
|
||||
editorState: data.editorState,
|
||||
baseSnapshot: data.baseSnapshot,
|
||||
},
|
||||
script: data,
|
||||
merge: ("theirs" in extra) ? extra : null
|
||||
});
|
||||
});
|
||||
|
|
|
@ -68,6 +68,12 @@ module TDev {
|
|||
scriptText: string;
|
||||
editorState: string;
|
||||
baseSnapshot: string;
|
||||
metadata: Metadata; // Must be set to the correct value every time.
|
||||
}
|
||||
|
||||
export interface Metadata {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
// In case local and remote modifications have been posted on top of the same cloud
|
||||
|
|
|
@ -80,8 +80,9 @@ module TDev {
|
|||
if (script && !header.editor && (!header.meta || header.meta.comment === undefined))
|
||||
header.meta = getScriptMeta(script);
|
||||
if (header.editor && (!header.meta || !header.meta.name)) {
|
||||
Util.log("ERROR pre-condition not met for [setInstalledAsync]");
|
||||
Util.log("ERROR pre-condition not met for [setInstalledAsync]; bailing");
|
||||
debugger;
|
||||
return Promise.as();
|
||||
}
|
||||
headerItem[header.guid] = JSON.stringify(header);
|
||||
var bodyItem = {}
|
||||
|
@ -164,12 +165,14 @@ module TDev {
|
|||
theirs: {
|
||||
scriptText: theirs.script,
|
||||
editorState: theirs.editorState,
|
||||
baseSnapshot: header.scriptVersion.baseSnapshot
|
||||
baseSnapshot: header.scriptVersion.baseSnapshot,
|
||||
metadata: header.meta,
|
||||
},
|
||||
base: {
|
||||
scriptText: baseVer.script,
|
||||
editorState: baseVer.editorState,
|
||||
baseSnapshot: hd.scriptVersion.baseSnapshot
|
||||
baseSnapshot: hd.scriptVersion.baseSnapshot,
|
||||
metadata: hd.meta,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
|
|
@ -22,6 +22,7 @@ module TDev {
|
|||
|
||||
// Also written once at initialization-time.
|
||||
var editor: AceAjax.Editor = null;
|
||||
var scriptName: string = null;
|
||||
|
||||
// A global that remembers the current version we're editing
|
||||
var currentVersion: string;
|
||||
|
@ -91,6 +92,7 @@ module TDev {
|
|||
editor.setValue(message.script.scriptText);
|
||||
editor.clearSelection();
|
||||
|
||||
scriptName = message.script.metadata.name;
|
||||
|
||||
log("[loaded] version from " + state.lastSave);
|
||||
}
|
||||
|
@ -105,6 +107,10 @@ module TDev {
|
|||
lastSave: new Date()
|
||||
}),
|
||||
baseSnapshot: currentVersion,
|
||||
metadata: {
|
||||
name: scriptName,
|
||||
description: ""
|
||||
}
|
||||
},
|
||||
};
|
||||
post(message);
|
||||
|
|
|
@ -12,6 +12,8 @@ module TDev {
|
|||
"https://mbitmain.azurewebsites.net": null
|
||||
};
|
||||
|
||||
var $ = (s: string) => document.querySelector(s);
|
||||
|
||||
// Both of these are written once when we receive the first (trusted)
|
||||
// message.
|
||||
var outer: Window = null;
|
||||
|
@ -124,15 +126,29 @@ module TDev {
|
|||
b.addEventListener("click", f);
|
||||
return b;
|
||||
};
|
||||
var box = document.querySelector("#merge-commands");
|
||||
var box = $("#merge-commands");
|
||||
var clearMerge = () => {
|
||||
while (box.firstChild)
|
||||
box.removeChild(box.firstChild);
|
||||
};
|
||||
var mineText = saveBlockly();
|
||||
var mineButton = mkButton("🔍", "see mine", () => loadBlockly(mineText));
|
||||
var theirsButton = mkButton("🔍", "see theirs", () => loadBlockly(merge.theirs.scriptText));
|
||||
var baseButton = mkButton("🔍", "see base", () => loadBlockly(merge.base.scriptText));
|
||||
var mineName = getName();
|
||||
var mineDescription = getDescription();
|
||||
var mineButton = mkButton("🔍", "see mine", () => {
|
||||
loadBlockly(mineText);
|
||||
setName(mineName);
|
||||
setDescription(mineDescription);
|
||||
});
|
||||
var theirsButton = mkButton("🔍", "see theirs", () => {
|
||||
loadBlockly(merge.theirs.scriptText);
|
||||
setName(merge.theirs.metadata.name);
|
||||
setDescription(merge.theirs.metadata.description);
|
||||
});
|
||||
var baseButton = mkButton("🔍", "see base", () => {
|
||||
loadBlockly(merge.base.scriptText);
|
||||
setName(merge.base.metadata.name);
|
||||
setDescription(merge.base.metadata.description);
|
||||
});
|
||||
var mergeButton = mkButton("👍", "finish merge", () => {
|
||||
inMerge = false;
|
||||
currentVersion = merge.theirs.baseSnapshot;
|
||||
|
@ -162,7 +178,7 @@ module TDev {
|
|||
}
|
||||
|
||||
function statusMsg(s: string, st: External.Status) {
|
||||
var box = <HTMLElement> document.querySelector("#log");
|
||||
var box = <HTMLElement> $("#log");
|
||||
var elt = document.createElement("div");
|
||||
elt.classList.add("status");
|
||||
if (st == External.Status.Error)
|
||||
|
@ -200,14 +216,30 @@ module TDev {
|
|||
return text;
|
||||
}
|
||||
|
||||
function setDescription(x: string) {
|
||||
(<HTMLInputElement> $("#script-description")).value = (x || "");
|
||||
}
|
||||
|
||||
function setName(x: string) {
|
||||
(<HTMLInputElement> $("#script-name")).value = x;
|
||||
}
|
||||
|
||||
function getDescription() {
|
||||
return (<HTMLInputElement> $("#script-description")).value;
|
||||
}
|
||||
|
||||
function getName() {
|
||||
return (<HTMLInputElement> $("#script-name")).value;
|
||||
}
|
||||
|
||||
var dirty = false;
|
||||
|
||||
// Called once at startup
|
||||
function setupEditor(message: External.Message_Init) {
|
||||
var state = loadEditorState(message.script.editorState);
|
||||
|
||||
Blockly.inject(document.querySelector("#editor"), {
|
||||
toolbox: document.querySelector("#blockly-toolbox")
|
||||
Blockly.inject($("#editor"), {
|
||||
toolbox: $("#blockly-toolbox")
|
||||
});
|
||||
loadBlockly(message.script.scriptText);
|
||||
// Hack alert! Blockly's [fireUiEvent] function [setTimeout]'s (with a 0 delay) the actual
|
||||
|
@ -220,6 +252,17 @@ module TDev {
|
|||
dirty = true;
|
||||
});
|
||||
}, 1);
|
||||
$("#script-name").addEventListener("input", () => {
|
||||
statusMsg("✎ local changes", External.Status.Ok);
|
||||
dirty = true;
|
||||
});
|
||||
$("#script-description").addEventListener("input", () => {
|
||||
statusMsg("✎ local changes", External.Status.Ok);
|
||||
dirty = true;
|
||||
});
|
||||
|
||||
setName(message.script.metadata.name);
|
||||
setDescription(message.script.metadata.description);
|
||||
|
||||
// That's triggered when the user closes or reloads the whole page, but
|
||||
// doesn't help if the user hits the "back" button in our UI.
|
||||
|
@ -240,7 +283,7 @@ module TDev {
|
|||
}
|
||||
|
||||
function doSave() {
|
||||
if (!dirty || inMerge)
|
||||
if (!dirty)
|
||||
return;
|
||||
|
||||
var text = saveBlockly();
|
||||
|
@ -253,27 +296,31 @@ module TDev {
|
|||
lastSave: new Date()
|
||||
}),
|
||||
baseSnapshot: currentVersion,
|
||||
metadata: {
|
||||
name: getName(),
|
||||
description: getDescription()
|
||||
}
|
||||
},
|
||||
});
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
function setupButtons() {
|
||||
document.querySelector("#command-quit").addEventListener("click", () => {
|
||||
$("#command-quit").addEventListener("click", () => {
|
||||
doSave();
|
||||
post({ type: External.MessageType.Quit });
|
||||
});
|
||||
document.querySelector("#command-save").addEventListener("click", () => {
|
||||
$("#command-save").addEventListener("click", () => {
|
||||
doSave();
|
||||
});
|
||||
document.querySelector("#command-compile").addEventListener("click", () => {
|
||||
$("#command-compile").addEventListener("click", () => {
|
||||
post(<External.Message_Compile> {
|
||||
type: External.MessageType.Compile,
|
||||
text: "", // TODO
|
||||
language: External.Language.TouchDevelop
|
||||
});
|
||||
});
|
||||
document.querySelector("#command-run").addEventListener("click", () => {
|
||||
$("#command-run").addEventListener("click", () => {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -36,6 +36,10 @@
|
|||
</div>
|
||||
<div id="merge-commands" class="hbox">
|
||||
</div>
|
||||
<div class="vbox" id="metadata">
|
||||
<div><input id="script-name" placeholder="script name" /></div>
|
||||
<div><input id="script-description" placeholder="script description" /></div>
|
||||
</div>
|
||||
<div id="log">
|
||||
<div id="status">✔ loaded</div>
|
||||
</div>
|
||||
|
|
|
@ -40,6 +40,14 @@ body {
|
|||
margin-right: 1em;
|
||||
}
|
||||
|
||||
#metadata {
|
||||
padding-right: 2em;
|
||||
}
|
||||
|
||||
#metadata > div:first-child {
|
||||
padding-bottom: 1ex;
|
||||
}
|
||||
|
||||
#toolbox {
|
||||
padding: .5em;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче