2015-02-05 00:41:44 +03:00
|
|
|
///<reference path='refs.ts'/>
|
|
|
|
|
|
|
|
module TDev
|
|
|
|
{
|
|
|
|
export interface KindBoxModel
|
|
|
|
{
|
|
|
|
getKind() : Kind;
|
|
|
|
setKind(k:Kind) : void;
|
|
|
|
getContexts() : KindContext;
|
|
|
|
immutableReason():string;
|
|
|
|
kindBoxHeader():string;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class VariableProperties
|
|
|
|
extends CodeView
|
|
|
|
implements KindBoxModel
|
|
|
|
{
|
|
|
|
private theVariable:AST.GlobalDef;
|
|
|
|
constructor() {
|
|
|
|
super()
|
|
|
|
this.kindContainer = VariableProperties.mkKindContainer(this);
|
|
|
|
}
|
|
|
|
private variableName = HTML.mkTextInputWithOk("text", lf("variable name"));
|
|
|
|
private kindContainer:HTMLElement;
|
|
|
|
private formRoot = div("varProps");
|
|
|
|
private varRender = div("");
|
|
|
|
private description = HTML.mkTextArea();
|
|
|
|
private artEditor:ArtEditor = null;
|
|
|
|
private persistentCheckbox:HTMLElement;
|
|
|
|
private renderer = new TDev.EditorRenderer();
|
|
|
|
private persistanceRadio:HTML.RadioGroup;
|
|
|
|
|
|
|
|
public getTick() { return Ticks.viewVariableInit; }
|
|
|
|
|
|
|
|
public nodeType() { return "globalDef"; }
|
|
|
|
public editedStmt():AST.Stmt { return this.theVariable ? this.theVariable : null; }
|
|
|
|
|
|
|
|
public kindBoxHeader()
|
|
|
|
{
|
|
|
|
return this.theVariable.isResource ? lf("art resource") : lf("global variable")
|
|
|
|
}
|
|
|
|
|
|
|
|
public init(e:Editor)
|
|
|
|
{
|
|
|
|
super.init(e);
|
|
|
|
this.variableName.id = "renameBox2";
|
|
|
|
this.variableName.addEventListener("change", () => this.nameUpdated())
|
|
|
|
this.description.className = "variableDesc";
|
|
|
|
}
|
|
|
|
|
|
|
|
static kindSelectorVisible = false;
|
|
|
|
static mkKindContainer(model:KindBoxModel) : HTMLElement
|
|
|
|
{
|
|
|
|
function selectKind() {
|
|
|
|
var reason = model.immutableReason();
|
|
|
|
if (!!reason) {
|
|
|
|
HTML.showErrorNotification(reason);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
function doKind(hd:string, ctx, f:(k:Kind)=>void)
|
|
|
|
{
|
|
|
|
var m = new ModalDialog();
|
|
|
|
var kindList = DeclRender.mkKindList(ctx, model.getKind(),
|
|
|
|
(k:Kind) => {
|
|
|
|
m.dismiss();
|
|
|
|
if (k.getParameterCount() == 0 || k.getRoot() != k) f(k);
|
|
|
|
else {
|
|
|
|
Util.assert(k.getParameterCount() == 1)
|
|
|
|
var hd0 = k.getName() + " of ..."
|
|
|
|
if (/ of \.\.\.$/.test(hd))
|
|
|
|
hd0 = hd.replace(/\.\.\.$/, hd0)
|
|
|
|
doKind(hd0, KindContext.Parameter, (kk) => {
|
|
|
|
f((<ParametricKind>k).createInstance([kk]))
|
|
|
|
})
|
|
|
|
}
|
|
|
|
});
|
|
|
|
m.onDismiss = () => { VariableProperties.kindSelectorVisible = false; TheEditor.updateTutorial() }
|
|
|
|
m.choose(kindList, { header: hd });
|
|
|
|
VariableProperties.kindSelectorVisible = true
|
|
|
|
if (TheEditor.stepTutorial)
|
|
|
|
TheEditor.stepTutorial.notifyKindList(kindList)
|
|
|
|
}
|
|
|
|
|
|
|
|
doKind("select type of this " + model.kindBoxHeader(), model.getContexts(), k => model.setKind(k))
|
|
|
|
}
|
|
|
|
|
|
|
|
var d = div("kindContainer");
|
|
|
|
HTML.setTickCallback(d, Ticks.btnChangeKind, selectKind);
|
|
|
|
(<any>d).refresh = () => {
|
|
|
|
d.setChildren([DeclRender.mkKindBox(model.getKind())]);
|
|
|
|
};
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
private isActive() { return !!this.theVariable; }
|
|
|
|
|
|
|
|
public getContexts() {
|
|
|
|
return this.theVariable.isResource ? KindContext.ArtResource
|
|
|
|
: this.theVariable.getRecordPersistence() == AST.RecordPersistence.Temporary ? KindContext.GlobalVar
|
|
|
|
: KindContext.CloudField;
|
|
|
|
}
|
|
|
|
public getKind() { return this.theVariable.getKind(); }
|
|
|
|
public immutableReason():string { return null; }
|
|
|
|
|
|
|
|
public newNameHint(newName:string, defl:string = null)
|
|
|
|
{
|
|
|
|
if (!newName) return;
|
|
|
|
newName = newName.slice(0, 30);
|
|
|
|
|
|
|
|
var n = this.theVariable.getName()
|
|
|
|
var k = this.theVariable.getKind()
|
|
|
|
var defls = ["v", k.getStemName(), newName]
|
|
|
|
if (defl) defls.push(defl);
|
|
|
|
|
|
|
|
if (defls.some(s => Script.namesMatch(n, s))) {
|
|
|
|
this.commit();
|
|
|
|
this.theVariable.setName(Script.freshName(newName))
|
|
|
|
this.syncAll();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public setKind(k:Kind)
|
|
|
|
{
|
|
|
|
var k0 = this.theVariable.getKind() || api.core.Unknown;
|
|
|
|
|
|
|
|
this.theVariable.setKind(k);
|
|
|
|
Script.resetStableName(this.theVariable);
|
|
|
|
this.commit();
|
|
|
|
|
|
|
|
var n = this.theVariable.getName()
|
|
|
|
if (k != k0 && (Script.namesMatch(n, "v") || Script.namesMatch(n, k0.getStemName())))
|
|
|
|
this.theVariable.setName(Script.freshName(k.getStemName()))
|
|
|
|
|
|
|
|
if (this.theVariable.isResource)
|
|
|
|
this.load(this.theVariable); // need to get new art editor
|
|
|
|
else
|
|
|
|
this.syncAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
private syncAll(tc = true)
|
|
|
|
{
|
|
|
|
AST.TypeChecker.tcApp(Script);
|
|
|
|
this.variableName.value = this.theVariable.getName();
|
|
|
|
if (!!this.artEditor) this.artEditor.set(this.theVariable.url);
|
|
|
|
this.description.value = this.theVariable.comment;
|
|
|
|
(<any>this.kindContainer).refresh();
|
|
|
|
|
|
|
|
this.varRender.setChildren([this.renderer.declDiv(this.theVariable)]);
|
|
|
|
this.renderer.attachHandlers();
|
|
|
|
this.persistanceRadio.change(this.theVariable.getRecordPersistence());
|
|
|
|
|
|
|
|
TheEditor.updateTutorial()
|
|
|
|
}
|
|
|
|
|
|
|
|
private persistanceChanged()
|
|
|
|
{
|
|
|
|
if (this.theVariable.getRecordPersistence() == this.persistanceRadio.current) return;
|
|
|
|
var cloud = this.persistanceRadio.current
|
|
|
|
this.theVariable.cloudEnabled = cloud == AST.RecordPersistence.Cloud;
|
|
|
|
this.theVariable.isTransient = !(cloud == AST.RecordPersistence.Local || cloud == AST.RecordPersistence.Cloud);
|
|
|
|
this.theVariable.notifyChange();
|
|
|
|
this.syncAll();
|
|
|
|
}
|
|
|
|
|
|
|
|
public renderCore(a:AST.Decl) { return this.load(<AST.GlobalDef>a); }
|
|
|
|
|
|
|
|
private load(a:AST.GlobalDef) :void
|
|
|
|
{
|
|
|
|
this.theVariable = null;
|
|
|
|
TheEditor.dismissSidePane();
|
|
|
|
this.theVariable = a;
|
|
|
|
if (a.isResource) {
|
|
|
|
this.artEditor = ArtEditor.lookup(a.getKind());
|
|
|
|
if (this.artEditor)
|
|
|
|
this.artEditor.newNameHint = (s) => this.newNameHint(s);
|
|
|
|
} else
|
|
|
|
this.artEditor = null;
|
|
|
|
|
|
|
|
this.persistanceRadio = HTML.mkRadioButtons(
|
|
|
|
Script.isCloud ? (Script.isLibrary ? RecordDefProperties.cloudlibraryVarPersistenceLabels : RecordDefProperties.servicePersistenceLabels)
|
|
|
|
: RecordDefProperties.cloudstatePersistenceLabels);
|
|
|
|
|
|
|
|
this.persistanceRadio.onchange = () => this.persistanceChanged();
|
|
|
|
|
|
|
|
var saveBox = div(null,
|
|
|
|
Editor.mkHelpLink("persistent data"),
|
|
|
|
this.persistanceRadio.elt)
|
|
|
|
|
|
|
|
var renderedEditor = null;
|
|
|
|
|
|
|
|
if (this.theVariable.isResource)
|
|
|
|
saveBox.style.display = "none";
|
|
|
|
|
|
|
|
this.formRoot.setChildren([
|
|
|
|
Editor.mkHelpButton(a.isResource ? "art" : "data"),
|
|
|
|
div("varLabel", a.isResource ? lf("art resource") : lf("global variable")),
|
|
|
|
this.variableName,
|
|
|
|
// div("varLabel", lf("of type")),
|
|
|
|
this.kindContainer,
|
|
|
|
this.varRender,
|
|
|
|
saveBox,
|
|
|
|
//div("formHint", lf("You can read the colon symbol (':') as 'of type' everywhere in TouchDevelop.")),
|
|
|
|
ActionProperties.copyCutRefs("the current variable", this.theVariable),
|
|
|
|
!this.artEditor ? null : (renderedEditor = this.artEditor.render()),
|
|
|
|
div("varLabel", lf("comments")),
|
|
|
|
this.description,
|
|
|
|
]);
|
|
|
|
this.editor.displayLeft([this.formRoot]);
|
|
|
|
this.syncAll();
|
|
|
|
|
|
|
|
if (this.theVariable.getKind() == api.core.Unknown)
|
|
|
|
KeyboardMgr.triggerClick(this.kindContainer);
|
|
|
|
else if (this.theVariable.isResource && !this.theVariable.url && renderedEditor && renderedEditor.blinkSection) {
|
|
|
|
Util.ensureVisible(renderedEditor.blinkSection);
|
|
|
|
Util.coreAnim("blinkLocation", 4000, renderedEditor.blinkSection);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private nameUpdated()
|
|
|
|
{
|
|
|
|
if (this.theVariable.getName() != this.variableName.value) {
|
|
|
|
this.theVariable.setName(Script.freshName(this.variableName.value));
|
|
|
|
this.syncAll()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public commit()
|
|
|
|
{
|
|
|
|
if(!this.theVariable) return;
|
|
|
|
if (this.theVariable.getName() != this.variableName.value)
|
|
|
|
this.theVariable.setName(Script.freshName(this.variableName.value));
|
|
|
|
if (!!this.artEditor) {
|
|
|
|
var newUrl = this.artEditor.get();
|
|
|
|
if (newUrl != this.theVariable.url) {
|
|
|
|
// force reload
|
|
|
|
Script.resetStableName(this.theVariable);
|
|
|
|
this.theVariable.url = newUrl;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
this.theVariable.comment = this.description.value;
|
|
|
|
this.theVariable.notifyChange();
|
|
|
|
TheEditor.queueNavRefresh();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ArtEditor
|
|
|
|
{
|
|
|
|
public set(url:string) { return Util.abstract() }
|
|
|
|
public get() : string { return Util.abstract() }
|
|
|
|
public render() : HTMLElement { return Util.abstract() }
|
|
|
|
public init(k:Kind) {}
|
|
|
|
public newNameHint:(s:string)=>void;
|
|
|
|
|
|
|
|
static editors:any;
|
|
|
|
|
|
|
|
static lookup(k:Kind)
|
|
|
|
{
|
|
|
|
var fn = ArtEditor.editors[k.getName()];
|
|
|
|
if (!fn) fn = UrlEditor;
|
|
|
|
var obj = <ArtEditor> new fn();
|
|
|
|
obj.init(k);
|
|
|
|
return obj;
|
|
|
|
}
|
|
|
|
|
|
|
|
static initEditors()
|
|
|
|
{
|
|
|
|
ArtEditor.editors =
|
|
|
|
{
|
|
|
|
Picture: ImgEditor,
|
|
|
|
Sound: SoundEditor,
|
|
|
|
Color: ColorEditor,
|
|
|
|
Number : NumberEditor,
|
|
|
|
String : StringEditor
|
|
|
|
};
|
|
|
|
Object.keys(ArtEditor.editors).forEach((kn:string) => {
|
|
|
|
var k = api.getKind(kn);
|
|
|
|
k._contexts |= KindContext.ArtResource;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ImgEditor
|
|
|
|
extends ArtEditor
|
|
|
|
{
|
|
|
|
private url = HTML.mkTextInput("text", lf("picture url"));
|
|
|
|
private img = <HTMLImageElement>createElement("img", "varImg checker");
|
|
|
|
private imgInfo = div("varImgInfo", lf("no picture loaded yet"));
|
|
|
|
private progressBar = HTML.mkProgressBar();
|
|
|
|
private uploadButton : HTMLElement;
|
|
|
|
private searchOnlineButton : HTMLElement;
|
|
|
|
|
|
|
|
private uploadHandler() {
|
|
|
|
ArtUtil.uploadPictureDialogAsync().done((a: TDev.JsonArt) => {
|
|
|
|
if(!!a) {
|
|
|
|
this.set(a.pictureurl);
|
|
|
|
this.newNameHint(a.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
private searchOnlineHandler() {
|
|
|
|
var m = new ModalDialog();
|
|
|
|
|
|
|
|
var converter = (s: Browser.ArtInfo) => {
|
|
|
|
return s.mkSmallBoxNoClick().withClick(() => {
|
|
|
|
m.dismiss();
|
|
|
|
s.getJsonAsync().done(() => {
|
|
|
|
if (s.art.pictureurl) {
|
|
|
|
this.set(s.art.pictureurl);
|
|
|
|
this.newNameHint(s.name)
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var queryAsync = (terms: string) => Meta.searchArtAsync(terms, "picture")
|
|
|
|
.then((itms: Browser.ArtInfo[]) => itms.map(itm => converter(itm)).filter(itm => itm != null));
|
|
|
|
m.choose([], { queryAsync: queryAsync,
|
|
|
|
searchHint: lf("Type to search..."),
|
|
|
|
initialEmptyQuery: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
super()
|
|
|
|
this.url.style.width = '60%';
|
|
|
|
this.url.onchange = (ev: Event) => {
|
|
|
|
this.img.src = this.url.value;
|
|
|
|
};
|
|
|
|
this.img.onloadstart = () => {
|
|
|
|
this.progressBar.start();
|
|
|
|
this.imgInfo.innerHTML = "loading...";
|
|
|
|
};
|
|
|
|
this.img.onerror = () => {
|
|
|
|
this.progressBar.stop();
|
|
|
|
if (this.url.value)
|
|
|
|
this.imgInfo.setChildren([lf("Ooops, there was an error loading the picture.")]);
|
|
|
|
else
|
|
|
|
this.imgInfo.setChildren([lf("no picture loaded yet")]);
|
|
|
|
};
|
|
|
|
this.img.onload = () => {
|
|
|
|
this.progressBar.stop();
|
|
|
|
this.imgInfo.innerHTML = '';
|
|
|
|
};
|
|
|
|
this.uploadButton = HTML.mkButton(lf("upload"), () => {
|
|
|
|
this.uploadHandler();
|
|
|
|
});
|
|
|
|
this.searchOnlineButton = HTML.mkButton(lf("search art pictures"), () => {
|
|
|
|
this.searchOnlineHandler();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(v: string) {
|
|
|
|
this.url.value = v;
|
|
|
|
this.img.src = v;
|
|
|
|
}
|
|
|
|
public get() { return this.url.value; }
|
|
|
|
|
|
|
|
public render()
|
|
|
|
{
|
|
|
|
var durl = div('', [this.searchOnlineButton,this.uploadButton]);
|
|
|
|
var r = div("artEditor",
|
|
|
|
div("varLabel", lf("i want to find pictures")),
|
|
|
|
durl,
|
|
|
|
div("varLabel", lf("url")),
|
|
|
|
div('', this.url),
|
|
|
|
div("varLabel", lf("preview")),
|
|
|
|
div('', [<HTMLElement>this.progressBar, this.img, this.imgInfo])
|
|
|
|
);
|
|
|
|
(<any>r).blinkSection = durl
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class UrlEditor
|
|
|
|
extends ArtEditor
|
|
|
|
{
|
|
|
|
private url: HTMLInputElement;
|
|
|
|
constructor(inputType : string = "text") {
|
|
|
|
super()
|
|
|
|
this.url = HTML.mkTextInput("text", lf("url"));
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(v:string) { this.url.value = v; }
|
|
|
|
public get() { return this.url.value; }
|
|
|
|
|
|
|
|
public render()
|
|
|
|
{
|
|
|
|
return div("artEditor",
|
|
|
|
div("varLabel", lf("value")),
|
|
|
|
this.url
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class NumberEditor
|
|
|
|
extends UrlEditor
|
|
|
|
{
|
|
|
|
constructor () {
|
|
|
|
super("number")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class StringEditor
|
|
|
|
extends ArtEditor
|
|
|
|
{
|
|
|
|
private url: HTMLInputElement;
|
|
|
|
private value: HTMLTextAreaElement;
|
|
|
|
private keyUrl: HTMLInputElement;
|
|
|
|
constructor() {
|
|
|
|
super()
|
|
|
|
this.value = HTML.mkTextArea("variableDesc");
|
|
|
|
this.url = HTML.mkTextInput("text", lf("url"));
|
|
|
|
this.keyUrl = HTML.mkTextInput("text", lf("key uri"));
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(v: string) {
|
|
|
|
var value = TDev.RT.String_.valueFromArtUrl(v);
|
|
|
|
if (value) {
|
|
|
|
// TODO: limit size of value
|
|
|
|
this.value.value = value;
|
|
|
|
this.url.value = "";
|
|
|
|
this.keyUrl.value = "";
|
|
|
|
} else {
|
|
|
|
var key = TDev.RT.String_.valueFromKeyUrl(v);
|
|
|
|
if (key) {
|
|
|
|
this.value.value = "";
|
|
|
|
this.url.value = "";
|
|
|
|
this.keyUrl.value = key;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.value.value = "";
|
|
|
|
this.url.value = v;
|
|
|
|
this.keyUrl.value = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
public get() {
|
|
|
|
var v = this.value.value;
|
|
|
|
if (v) return TDev.RT.String_.valueToArtUrl(v);
|
|
|
|
|
|
|
|
var k = this.keyUrl.value;
|
|
|
|
if (k) return TDev.RT.String_.valueToKeyUrl(k);
|
|
|
|
|
|
|
|
return this.url.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
public render()
|
|
|
|
{
|
|
|
|
var d = div("artEditor",
|
|
|
|
div("varLabel", lf("url")),
|
|
|
|
this.url,
|
|
|
|
div("varLabel", lf("value")),
|
|
|
|
this.value,
|
|
|
|
div("varLabel", lf("key url")),
|
|
|
|
this.keyUrl);
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class SoundEditor
|
|
|
|
extends ArtEditor
|
|
|
|
{
|
|
|
|
private url = HTML.mkTextInput("text", lf("sound url"));
|
|
|
|
private audioDiv = div("");
|
|
|
|
private progressBar = HTML.mkProgressBar();
|
|
|
|
private uploadButton: HTMLElement;
|
|
|
|
private searchOnlineButton: HTMLElement;
|
|
|
|
|
|
|
|
private searchOnlineHandler() {
|
|
|
|
var m = new ModalDialog();
|
|
|
|
|
|
|
|
var converter = (s: Browser.ArtInfo) => {
|
|
|
|
return s.mkSmallBoxNoClick().withClick(() => {
|
|
|
|
m.dismiss();
|
|
|
|
s.getJsonAsync().done(() => {
|
|
|
|
if (s.art.wavurl) {
|
|
|
|
this.set(s.art.wavurl);
|
|
|
|
this.newNameHint(s.art.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
var queryAsync = (terms: string) => Meta.searchArtAsync(terms, "sound")
|
|
|
|
.then((itms: Browser.ArtInfo[]) => itms.map(itm => converter(itm)).filter(itm => itm != null));
|
|
|
|
m.choose([], { queryAsync: queryAsync, searchHint: lf("Type to search..."), initialEmptyQuery: true });
|
|
|
|
}
|
|
|
|
|
|
|
|
private uploadHandler() {
|
|
|
|
ArtUtil.uploadSoundDialogAsync().done((a: TDev.JsonArt) => {
|
|
|
|
if (!!a) {
|
|
|
|
this.set(a.wavurl);
|
|
|
|
this.newNameHint(a.name);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
constructor () {
|
|
|
|
super()
|
|
|
|
|
|
|
|
this.uploadButton = HTML.mkButton(lf("upload"), () => {
|
|
|
|
this.uploadHandler();
|
|
|
|
});
|
|
|
|
this.searchOnlineButton = HTML.mkButton(lf("search online art sounds"), () => {
|
|
|
|
this.searchOnlineHandler();
|
|
|
|
});
|
|
|
|
this.url.onchange = (ev: Event) => {
|
|
|
|
this.set(this.url.value);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(v: string) {
|
|
|
|
this.url.value = v;
|
|
|
|
if (v) {
|
|
|
|
var audio = HTML.mkAudio(this.url.value, HTML.patchWavToMp4Url(this.url.value), null, true);
|
|
|
|
HTML.audioLoadAsync(audio).done();
|
|
|
|
this.audioDiv.setChildren([audio]);
|
|
|
|
} else {
|
|
|
|
this.audioDiv.setChildren([]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
public get() { return this.url.value; }
|
|
|
|
|
|
|
|
public render() {
|
|
|
|
var durl = div('', [this.searchOnlineButton, this.uploadButton])
|
|
|
|
var r = div("artEditor",
|
|
|
|
div("varLabel", lf("i want to find sounds")),
|
|
|
|
durl,
|
|
|
|
div("varLabel", lf("url")),
|
|
|
|
this.url,
|
|
|
|
this.audioDiv
|
|
|
|
);
|
|
|
|
(<any>r).blinkSection = durl;
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export class ColorEditor
|
|
|
|
extends UrlEditor
|
|
|
|
{
|
|
|
|
private labels = ["alpha", "red", "green", "blue"];
|
|
|
|
private inputs:HTMLElement[] = [];
|
|
|
|
private sliders:HTMLInputElement[];
|
|
|
|
constructor() {
|
|
|
|
super()
|
|
|
|
}
|
|
|
|
private currentHex = div("wallText", "");
|
|
|
|
private backgrounds = [div("colorSample whiteText", lf("white")), div("colorSample blackText", lf("black"))];
|
|
|
|
private foregrounds = [div("colorSample whiteBackground", lf("on white")), div("colorSample blackBackground", lf("on black"))];
|
|
|
|
|
|
|
|
public init(k:Kind)
|
|
|
|
{
|
|
|
|
var mkSlider = (l:string):HTMLInputElement => {
|
|
|
|
var r = HTML.mkTextInput("range", lf("color range"));
|
|
|
|
r.className = "colorSlider";
|
|
|
|
r.min = "0";
|
|
|
|
r.max = "255";
|
|
|
|
r.step = "1";
|
|
|
|
r.onchange = Util.catchErrors("colorEditorSlider", () => { this.sliderUpdate() });
|
|
|
|
this.inputs.push(div("sliderWithLabel", r, l));
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
this.sliders = this.labels.map(mkSlider);
|
|
|
|
}
|
|
|
|
|
|
|
|
private sliderUpdate()
|
|
|
|
{
|
|
|
|
this.currentHex.innerHTML = this.get();
|
|
|
|
var htmlColor = "rgba(" + [1, 2, 3, 0].map((i) => i == 0 ? parseInt(this.sliders[i].value)/255 + "" : this.sliders[i].value).join(", ") + ")";
|
|
|
|
this.backgrounds.forEach((e:HTMLElement) => { e.style.backgroundColor = htmlColor });
|
|
|
|
this.foregrounds.forEach((e:HTMLElement) => { e.style.color = htmlColor });
|
|
|
|
}
|
|
|
|
|
|
|
|
public set(v:string)
|
|
|
|
{
|
|
|
|
v = v.slice(1); // strip #
|
|
|
|
for (var i = 0; i < 4; ++i)
|
|
|
|
this.sliders[i].value = parseInt(v.slice(i*2, i*2+2), 16) + "";
|
|
|
|
this.sliderUpdate();
|
|
|
|
}
|
|
|
|
|
|
|
|
public get()
|
|
|
|
{
|
|
|
|
var r = "#";
|
|
|
|
for (var i = 0; i < 4; ++i)
|
|
|
|
r += (parseInt(this.sliders[i].value) | 0x100).toString(16).slice(1, 3);
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
public render()
|
|
|
|
{
|
|
|
|
return div("artEditor",
|
|
|
|
div("varLabel", lf("color")),
|
|
|
|
this.inputs,
|
|
|
|
this.currentHex,
|
|
|
|
this.backgrounds,
|
|
|
|
this.foregrounds);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export module ArtUtil {
|
|
|
|
export function artImg(id: string, thumb = false): HTMLElement {
|
|
|
|
var d = div('iconThumb');
|
|
|
|
d.style.backgroundImage = HTML.cssImage(ArtUtil.artUrl(id, true));
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function artUrl(id: string, thumb = false): string {
|
|
|
|
return id ?
|
|
|
|
Util.fmt("https://az31353.vo.msecnd.net/{0}/{1:q}", thumb ? "thumb" : "pub", id)
|
|
|
|
: undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function setupDragAndDrop(r: HTMLElement) {
|
|
|
|
if (!Browser.dragAndDrop) return;
|
|
|
|
|
|
|
|
r.addEventListener('dragover', function(e) {
|
|
|
|
if (e.dataTransfer.types[0] == 'Files') {
|
|
|
|
if (e.preventDefault) e.preventDefault(); // Necessary. Allows us to drop.
|
|
|
|
e.dataTransfer.dropEffect = 'copy'; // See the section on the DataTransfer object.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}, false);
|
|
|
|
r.addEventListener('drop', (e) => {
|
|
|
|
var file = e.dataTransfer.files[0];
|
|
|
|
if (file) {
|
|
|
|
e.stopPropagation(); // Stops some browsers from redirecting.
|
|
|
|
e.preventDefault();
|
|
|
|
if (Cloud.anonMode(lf("upload pictures and sounds"))) return;
|
|
|
|
if (file.size > 1000000) {
|
|
|
|
ModalDialog.info('file too big', 'sorry, the picture is too big (max 1Mb)');
|
|
|
|
} else {
|
|
|
|
var name = file.name;
|
|
|
|
var m = /^([\w ]+)(\.[a-z0-9]+)$/i.exec(file.name);
|
|
|
|
if (m) name = m[1];
|
|
|
|
if (/^image\/(png|jpeg)$/i.test(file.type)) {
|
|
|
|
ArtUtil.uploadPictureDialogAsync(/^image\/png$/i.test(file.type), HTML.mkFileInput(file, 1), name)
|
|
|
|
.done((art: JsonArt) => {
|
|
|
|
if (art && Script) {
|
|
|
|
var n = TheEditor.freshPictureResource(art.name, art.pictureurl);
|
|
|
|
TheEditor.addNode(n);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else if (/^audio\/(wav|x-wav)$/i.test(file.type)) {
|
|
|
|
ArtUtil.uploadSoundDialogAsync(HTML.mkFileInput(file, 1), name).done((art: JsonArt) => {
|
|
|
|
if (art && Script) {
|
|
|
|
var n = TheEditor.freshSoundResource(art.name, art.wavurl);
|
|
|
|
TheEditor.addNode(n);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
ModalDialog.info('unsupported file type', 'sorry, you can only upload pictures (PNG and JPEG) or sounds (WAV)');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}, false);
|
|
|
|
r.addEventListener('dragend', (e) => {
|
|
|
|
return false;
|
|
|
|
}, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function uploadSoundDialogAsync(input? : TDev.HTML.IMediaInputElement, initialName? : string): Promise {
|
|
|
|
if (Cloud.anonMode(lf("uploading sounds"))) {
|
|
|
|
return Promise.as();
|
|
|
|
}
|
|
|
|
return new Promise((onSuccess, onError, onProgress) => {
|
|
|
|
var m = new ModalDialog();
|
|
|
|
var art: JsonArt = null;
|
|
|
|
m.onDismiss = () => onSuccess(art);
|
|
|
|
var name = HTML.mkTextInput("text", lf("sound name"));
|
|
|
|
name.value = initialName || "";
|
|
|
|
var description = HTML.mkTextInput("text", lf("description"));
|
|
|
|
var file = input || HTML.mkAudioInput(false, 1);
|
|
|
|
var errorDiv = div('validation-error');
|
|
|
|
var progressDiv = div('');
|
|
|
|
var progressBar = HTML.mkProgressBar();
|
|
|
|
m.add(div("wall-dialog-header", lf("upload sound")));
|
|
|
|
m.add(div("wall-dialog-body",
|
|
|
|
[
|
|
|
|
div('', div('', lf("1. choose a WAV sound (less than 1MB, PCM, mono or stereo, 8 or 16 bit per channel)")), file.element),
|
|
|
|
div('', div('', lf("2. give it a name (minimum 4 characters)")), name),
|
|
|
|
div('', div('', lf("3. describe it")), description),
|
|
|
|
div('', progressBar),
|
|
|
|
errorDiv,
|
|
|
|
progressDiv
|
|
|
|
]));
|
|
|
|
var publishBtn = null;
|
|
|
|
m.add(div("wall-dialog-body", lf("Everyone will be able to listen to your sound on the Internet forever. ")));
|
|
|
|
m.add(Cloud.mkLegalDiv());
|
|
|
|
m.add(div("wall-dialog-buttons", publishBtn = HTML.mkButton(lf("4. publish"), () => {
|
|
|
|
errorDiv.setChildren([]);
|
|
|
|
if (name.value.length < 4) {
|
|
|
|
errorDiv.setChildren([lf("Oops, the sound name is too short...")]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var ef = file.validate();
|
|
|
|
if (ef) {
|
|
|
|
errorDiv.setChildren([ef]);
|
|
|
|
return; // no file selected
|
|
|
|
}
|
|
|
|
progressBar.start();
|
|
|
|
progressDiv.setChildren([lf("publishing... PLEASE WAIT, publishing a sound can take a few minutes")]);
|
|
|
|
file.readAsync()
|
|
|
|
.then(data => {
|
|
|
|
if (!data) return Promise.as(undefined);
|
|
|
|
else {
|
|
|
|
Util.log('upload sound: uploading');
|
|
|
|
return uploadArtAsync(name.value, description.value, data);
|
|
|
|
}
|
|
|
|
}).done((resp) => {
|
|
|
|
progressBar.stop();
|
|
|
|
progressDiv.setChildren([]);
|
|
|
|
art = resp;
|
|
|
|
if (!art) {
|
|
|
|
Util.log('upload sound: could not read sound');
|
|
|
|
errorDiv.setChildren([lf("Could not read sound.")]);
|
|
|
|
} else {
|
|
|
|
Util.log('upload sound: success');
|
|
|
|
m.dismiss();
|
|
|
|
ModalDialog.info(lf("sound published!"), lf("You can find your sound under 'my art' in the hub."));
|
|
|
|
}
|
|
|
|
}, e => {
|
|
|
|
Util.log('upload sound: error ' + e.status);
|
|
|
|
progressBar.stop();
|
|
|
|
progressDiv.setChildren([]);
|
|
|
|
if (e.status == 502)
|
|
|
|
errorDiv.setChildren([lf("Could not publish sound. ") + Cloud.onlineInfo()]);
|
|
|
|
else if (e.status == 503)
|
|
|
|
errorDiv.setChildren([lf("Could not publish sound. Did you publish a lot recently? Please try again later.")]);
|
|
|
|
else if (e.status == 403)
|
|
|
|
errorDiv.setChildren([lf("Access denied; Please return to the main hub and then try again.")]);
|
|
|
|
else if (e.status == 400)
|
|
|
|
errorDiv.setChildren([lf("Could not publish sound: ") + e.errorMessage]);
|
|
|
|
else
|
|
|
|
throw e;
|
|
|
|
});
|
|
|
|
})));
|
|
|
|
m.show();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function uploadArtAsync(name: string, description: string, dataUri: string): Promise { // JsonArt
|
|
|
|
var dataUrl = Util.splitDataUrl(dataUri);
|
|
|
|
if (!dataUrl)
|
|
|
|
return Promise.as(null);
|
|
|
|
else {
|
|
|
|
var request = {
|
|
|
|
kind: 'art',
|
|
|
|
name: name || "",
|
|
|
|
description: description || "",
|
|
|
|
content: dataUrl.content,
|
|
|
|
contentType: dataUrl.contentType,
|
|
|
|
userplatform: Browser.platformCaps
|
|
|
|
};
|
|
|
|
return Cloud.postPrivateApiAsync("art", request);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function uploadPictureDialogAsync(removeWhite = false, input? : TDev.HTML.IMediaInputElement, initialName? : string): Promise {
|
|
|
|
if (Cloud.anonMode(lf("uploading pictures")))
|
|
|
|
return Promise.as();
|
|
|
|
return new Promise((onSuccess, onError, onProgress) => {
|
|
|
|
var m = new ModalDialog();
|
|
|
|
var art: JsonArt = null;
|
|
|
|
m.onDismiss = () => onSuccess(art);
|
|
|
|
var name = HTML.mkTextInput("text", lf("picture name"));
|
|
|
|
name.required = true;
|
|
|
|
name.value = initialName || "";
|
|
|
|
var description = HTML.mkTextInput("text", lf("description"));
|
|
|
|
var file = input || HTML.mkImageInput(false, 1);
|
|
|
|
var removeWhiteCheck = HTML.mkCheckBox(lf("remove white background (best for game sprites)"), (b) => removeWhite = b, removeWhite);
|
|
|
|
var errorDiv = div('validation-error');
|
|
|
|
var progressDiv = div('');
|
|
|
|
var progressBar = HTML.mkProgressBar();
|
|
|
|
m.add(div("wall-dialog-header", lf("upload picture")));
|
|
|
|
m.add(div("wall-dialog-body",
|
|
|
|
[
|
|
|
|
div("", div("", lf("choose a picture (less than 1MB, smaller than 2048x2048)")), file.element),
|
|
|
|
div("", div("", lf("picture name (minimum 4 characters)")), name),
|
|
|
|
div("", div("", lf("description (helps search)")), description),
|
|
|
|
div('', div('', removeWhiteCheck)),
|
|
|
|
div('', progressBar),
|
|
|
|
errorDiv,
|
|
|
|
progressDiv
|
|
|
|
]));
|
|
|
|
var publishBtn, cancelBtn;
|
|
|
|
m.add(div("wall-dialog-body", lf("Everyone will be able to see your picture on the Internet forever. ")));
|
|
|
|
m.add(TDev.Cloud.mkLegalDiv());
|
|
|
|
m.add(div("wall-dialog-buttons",
|
|
|
|
cancelBtn = HTML.mkButton(lf("cancel"), () => m.dismiss()),
|
|
|
|
publishBtn = HTML.mkButton(lf("publish"), () => {
|
|
|
|
errorDiv.setChildren([]);
|
|
|
|
if (name.value.length < 4) {
|
|
|
|
errorDiv.setChildren([lf("Oops, the picture name is too short...")]);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
var ef = file.validate();
|
|
|
|
if (ef) {
|
|
|
|
errorDiv.setChildren([ef]);
|
|
|
|
return; // no file selected
|
|
|
|
}
|
|
|
|
progressBar.start();
|
|
|
|
progressDiv.setChildren([lf("publishing...")]);
|
|
|
|
file.readAsync()
|
|
|
|
.then(data => {
|
|
|
|
if (!data) return Promise.as(undefined);
|
|
|
|
else if (removeWhite) {
|
|
|
|
var localPic = null;
|
|
|
|
return TDev.RT.Picture.fromUrl(data, false, false)
|
|
|
|
.then(p => {
|
|
|
|
localPic = p;
|
|
|
|
return p.eraseWhiteBackgroundAsync();
|
|
|
|
})
|
|
|
|
.then(() => localPic.getDataUriAsync(1.0));
|
|
|
|
}
|
|
|
|
else return Promise.as(data);
|
|
|
|
})
|
|
|
|
.then(data => {
|
|
|
|
if (!data) return Promise.as(undefined);
|
|
|
|
else return uploadArtAsync(name.value, description.value, data);
|
|
|
|
}).done(resp => {
|
|
|
|
progressBar.stop();
|
|
|
|
progressDiv.setChildren([]);
|
|
|
|
art = resp;
|
|
|
|
if (!art) {
|
|
|
|
errorDiv.setChildren(lf("We failed to read the file. Please try again with another picture."));
|
|
|
|
} else {
|
|
|
|
m.dismiss();
|
|
|
|
HTML.showProgressNotification(lf("picture published!"));
|
|
|
|
}
|
|
|
|
}, e => {
|
|
|
|
if (e.status == 502)
|
|
|
|
errorDiv.setChildren([lf("Could not publish picture. ") + Cloud.onlineInfo()]);
|
|
|
|
else if (e.status == 503)
|
|
|
|
errorDiv.setChildren([lf("Could not publish picture. Did you publish a lot recently? Please try again later.")]);
|
|
|
|
else if (e.status == 403)
|
|
|
|
errorDiv.setChildren([lf("Access denied; Please return to the main hub and then try again.")]);
|
|
|
|
else if (e.status == 400)
|
|
|
|
errorDiv.setChildren([lf("Could not publish picture: ") + e.errorMessage]);
|
|
|
|
else
|
|
|
|
throw e;
|
|
|
|
});
|
|
|
|
})));
|
|
|
|
m.setScroll();
|
|
|
|
m.show();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|