switch to es6 from coffeescript

This commit is contained in:
Xuan Sang LE 2020-05-24 13:17:59 +02:00
parent f11509120e
commit 759cd1fc6f
122 changed files with 10658 additions and 8702 deletions

View File

@ -11,63 +11,62 @@ ifeq ($(UNAME_S),Darwin)
endif
coffees= src/core/core.coffee \
src/core/settings.coffee \
src/core/handles/RemoteHandle.coffee \
src/core/Announcerment.coffee \
src/core/vfs.coffee \
src/core/vfs/GoogleDriveHandle.coffee \
src/core/db.coffee \
src/core/gui.coffee \
src/core/BaseModel.coffee \
src/core/BaseApplication.coffee \
src/core/BaseService.coffee \
src/core/BaseEvent.coffee \
src/core/BaseDialog.coffee \
src/core/tags/tag.coffee \
src/core/tags/WindowTag.coffee \
src/core/tags/TileLayoutTags.coffee \
src/core/tags/ResizerTag.coffee \
src/core/tags/LabelTag.coffee \
src/core/tags/ButtonTag.coffee \
src/core/tags/ListViewTag.coffee \
src/core/tags/SwitchTag.coffee \
src/core/tags/NSpinnerTag.coffee \
src/core/tags/MenuTag.coffee \
src/core/tags/GridViewTag.coffee \
src/core/tags/TabBarTag.coffee \
src/core/tags/TabContainerTag.coffee \
src/core/tags/TreeViewTag.coffee \
src/core/tags/SliderTag.coffee \
src/core/tags/FloatListTag.coffee \
src/core/tags/CalendarTag.coffee \
src/core/tags/ColorPickerTag.coffee \
src/core/tags/FileViewTag.coffee \
src/core/tags/OverlayTag.coffee \
src/core/tags/AppDockTag.coffee \
src/core/tags/SystemPanelTag.coffee \
src/antos.coffee
javascripts= src/core/core.js \
src/core/settings.js \
src/core/handles/RemoteHandle.js \
src/core/Announcerment.js \
src/core/vfs.js \
src/core/vfs/GoogleDriveHandle.js \
src/core/db.js \
src/core/gui.js \
src/core/BaseModel.js \
src/core/BaseApplication.js \
src/core/BaseService.js \
src/core/BaseEvent.js \
src/core/BaseDialog.js \
src/core/tags/tag.js \
src/core/tags/WindowTag.js \
src/core/tags/TileLayoutTags.js \
src/core/tags/ResizerTag.js \
src/core/tags/LabelTag.js \
src/core/tags/ButtonTag.js \
src/core/tags/ListViewTag.js \
src/core/tags/SwitchTag.js \
src/core/tags/NSpinnerTag.js \
src/core/tags/MenuTag.js \
src/core/tags/GridViewTag.js \
src/core/tags/TabBarTag.js \
src/core/tags/TabContainerTag.js \
src/core/tags/TreeViewTag.js \
src/core/tags/SliderTag.js \
src/core/tags/FloatListTag.js \
src/core/tags/CalendarTag.js \
src/core/tags/ColorPickerTag.js \
src/core/tags/FileViewTag.js \
src/core/tags/OverlayTag.js \
src/core/tags/AppDockTag.js \
src/core/tags/SystemPanelTag.js \
src/antos.js
packages = Syslog Files Setting CodePad MarketPlace
main: initd build_coffees build_themes libs build_packages languages
main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/
initd:
- mkdir -p $(BUILDDIR)
lite: build_coffees build_themes build_packages
lite: build_javascripts build_themes build_packages
#%.js: %.coffee
# coffee --compile $<
build_coffees:
@echo "$(BLUE)Building coffee files$(NC)"
build_javascripts:
@echo "$(BLUE)Bundling javascript files$(NC)"
- mkdir $(BUILDDIR)/scripts
- rm $(BUILDDIR)/scripts/antos.js
- rm $(BUILDDIR)/scripts/antos.coffee
for f in $(coffees); do (cat "$${f}"; echo) >> $(BUILDDIR)/scripts/antos.coffee; done
coffee --compile $(BUILDDIR)/scripts/antos.coffee
- rm $(BUILDDIR)/scripts/antos.coffee
echo "(function() {" > $(BUILDDIR)/scripts/antos.js
for f in $(javascripts); do (cat "$${f}"; echo) >> $(BUILDDIR)/scripts/antos.js; done
echo "}).call(this);" >> $(BUILDDIR)/scripts/antos.js
libs:

View File

@ -1,21 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
this.onload = () ->
($ document).on 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', ()->
Ant.OS.GUI.fullscreen = not Ant.OS.GUI.fullscreen
Ant.OS.boot()

27
src/antos.js Normal file
View File

@ -0,0 +1,27 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
this.onload = function() {
($(document)).on('webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', () => Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen);
return Ant.OS.boot();
};

View File

@ -1,88 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class Announcer
constructor: () ->
@observable = {}
@enable = true
disable: () ->
@off("*")
@enable = false
on: (evtName, callback) ->
return unless @enable
@observable[evtName] = { one: new Set(), many: new Set() } unless @observable[evtName]
@observable[evtName].many.add callback
one: (evtName, callback) ->
return unless @enable
@observable[evtName] = { one: new Set(), many: new Set() } unless @observable[evtName]
@observable[evtName].one.add callback
off: (evtName, callback) ->
fn = (evt, cb) =>
return unless @observable[evt]
if cb
@observable[evt].one.delete(cb)
@observable[evt].many.delete(cb)
else
delete @observable[evt] if @observable[evt]
if evtName is "*" then fn k, callback for k, v of @observable else fn evtName, callback
trigger: (evtName, data) ->
trig = (name, d) =>
names = [name, "*"]
for evt in names
continue unless @observable[evt]
@observable[evt].one.forEach (f) ->
f d
@observable[evt].one = new Set()
@observable[evt].many.forEach (f) ->
f d
if evtName is "*"
trig k, data for k, v of @observable when k isnt "*"
else
trig evtName, data
Ant.OS.API.Announcer = Announcer
Ant.OS.announcer =
observable: new Ant.OS.API.Announcer()
quota: 0
listeners: {}
on: (e, f, a) ->
Ant.OS.announcer.listeners[a.pid] = [] unless Ant.OS.announcer.listeners[a.pid]
Ant.OS.announcer.listeners[a.pid].push { e: e, f: f }
Ant.OS.announcer.observable.on e, f
trigger: (e, d) -> Ant.OS.announcer.observable.trigger e, d
osfail: (m, e) ->
Ant.OS.announcer.ostrigger "fail", { m: m, e: e }
oserror: (m, e) ->
Ant.OS.announcer.ostrigger "error", { m: m, e: e }
osinfo: (m) ->
Ant.OS.announcer.ostrigger "info", { m: m, e: null }
ostrigger: (e, d) ->
Ant.OS.announcer.trigger e, { id: 0, data: d, name: "OS" }
unregister: (app) ->
return unless Ant.OS.announcer.listeners[app.pid] and Ant.OS.announcer.listeners[app.pid].length > 0
Ant.OS.announcer.observable.off i.e, i.f for i in Ant.OS.announcer.listeners[app.pid]
delete Ant.OS.announcer.listeners[app.pid]
# Ant.OS.announcer.listeners[app.pid]
getMID: () ->
Ant.OS.announcer.quota += 1
Ant.OS.announcer.quota

133
src/core/Announcerment.js Normal file
View File

@ -0,0 +1,133 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class Announcer {
constructor() {
this.observable = {};
this.enable = true;
}
disable() {
this.off("*");
return this.enable = false;
}
on(evtName, callback) {
if (!this.enable) { return; }
if (!this.observable[evtName]) { this.observable[evtName] = { one: new Set(), many: new Set() }; }
return this.observable[evtName].many.add(callback);
}
one(evtName, callback) {
if (!this.enable) { return; }
if (!this.observable[evtName]) { this.observable[evtName] = { one: new Set(), many: new Set() }; }
return this.observable[evtName].one.add(callback);
}
off(evtName, callback) {
const fn = (evt, cb) => {
if (!this.observable[evt]) { return; }
if (cb) {
this.observable[evt].one.delete(cb);
return this.observable[evt].many.delete(cb);
} else {
if (this.observable[evt]) { return delete this.observable[evt]; }
}
};
if (evtName === "*") { return (() => {
const result = [];
for (let k in this.observable) {
const v = this.observable[k];
result.push(fn(k, callback));
}
return result;
})(); } else { return fn(evtName, callback); }
}
trigger(evtName, data) {
const trig = (name, d) => {
const names = [name, "*"];
return (() => {
const result = [];
for (let evt of Array.from(names)) {
if (!this.observable[evt]) { continue; }
this.observable[evt].one.forEach(f => f(d));
this.observable[evt].one = new Set();
result.push(this.observable[evt].many.forEach(f => f(d)));
}
return result;
})();
};
if (evtName === "*") {
return (() => {
const result = [];
for (let k in this.observable) {
const v = this.observable[k];
if (k !== "*") {
result.push(trig(k, data));
}
}
return result;
})();
} else {
return trig(evtName, data);
}
}
}
Ant.OS.API.Announcer = Announcer;
Ant.OS.announcer = {
observable: new Ant.OS.API.Announcer(),
quota: 0,
listeners: {},
on(e, f, a) {
if (!Ant.OS.announcer.listeners[a.pid]) { Ant.OS.announcer.listeners[a.pid] = []; }
Ant.OS.announcer.listeners[a.pid].push({ e, f });
return Ant.OS.announcer.observable.on(e, f);
},
trigger(e, d) { return Ant.OS.announcer.observable.trigger(e, d); },
osfail(m, e) {
return Ant.OS.announcer.ostrigger("fail", { m, e });
},
oserror(m, e) {
return Ant.OS.announcer.ostrigger("error", { m, e });
},
osinfo(m) {
return Ant.OS.announcer.ostrigger("info", { m, e: null });
},
ostrigger(e, d) {
return Ant.OS.announcer.trigger(e, { id: 0, data: d, name: "OS" });
},
unregister(app) {
if (!Ant.OS.announcer.listeners[app.pid] || !(Ant.OS.announcer.listeners[app.pid].length > 0)) { return; }
for (let i of Array.from(Ant.OS.announcer.listeners[app.pid])) { Ant.OS.announcer.observable.off(i.e, i.f); }
return delete Ant.OS.announcer.listeners[app.pid];
},
// Ant.OS.announcer.listeners[app.pid]
getMID() {
Ant.OS.announcer.quota += 1;
return Ant.OS.announcer.quota;
}
};

View File

@ -1,149 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class BaseApplication extends this.OS.GUI.BaseModel
constructor: (name, args) ->
super name, args
if (not Ant.OS.setting.applications[@name]) or (Array.isArray OS.setting.applications[@name])
Ant.OS.setting.applications[@name] = {}
@setting = Ant.OS.setting.applications[@name]
@keycomb =
ALT: {}
CTRL: {}
SHIFT: {}
META: {}
init: ->
@off "*"
@on "exit", () => @quit()
# first register some base event to the app
@on "focus", () =>
@sysdock.set "selectedApp", @
@appmenu.pid = @pid
@appmenu.set "items", (@baseMenu() || [])
@appmenu.set "onmenuselect", (d) =>
@trigger("menuselect", d)
@dialog.show() if @dialog
@on "hide", () =>
@sysdock.set "selectedApp", null
@appmenu.set "items", []
@dialog.hide() if @dialog
@on "menuselect", (d) =>
switch d.data.item.get("data").dataid
when "#{@name}-about" then @openDialog "AboutDialog"
when "#{@name}-exit" then @trigger "exit"
@on "apptitlechange", () => @sysdock.update()
@updateLocale @systemsetting.system.locale
@loadScheme()
loadScheme: () ->
#now load the scheme
path = "#{@meta().path}/scheme.html"
@.render path
load: (promise) ->
q = @_api.mid()
new Promise (resolve, reject) =>
@_api.loading q, @name
promise.then () =>
@_api.loaded q, @name, "OK"
resolve()
.catch (e) =>
@_api.loaded q, @name, "FAIL"
reject __e e
bindKey: (k, f) ->
arr = k.split "-"
return unless arr.length is 2
fnk = arr[0].toUpperCase()
c = arr[1].toUpperCase()
return unless @keycomb[fnk]
@keycomb[fnk][c] = f
updateLocale: (name) ->
meta = @meta()
return unless meta and meta.locales
return unless meta.locales[name]
for k, v of meta.locales[name]
@_api.lang[k] = v
shortcut: (fnk, c, e) ->
return true unless @keycomb[fnk]
return true unless @keycomb[fnk][c]
@keycomb[fnk][c](e)
return false
applySetting: (k) ->
applyAllSetting: () ->
@applySetting k for k, v of @setting
registry: (k, v) ->
@setting[k] = v
@publish "appregistry", k
show: () ->
@trigger "focus"
blur: () ->
@.appmenu.set "items", [] if @.appmenu and @.pid == @.appmenu.pid
@trigger "blur"
hide: () ->
@trigger "hide"
toggle: () ->
@trigger "toggle"
title: () ->
@scheme.get "apptitle"
onexit: (evt) ->
@cleanup(evt)
if not evt.prevent
@.appmenu.set "items", [] if @.pid == @.appmenu.pid
($ @scheme).remove()
meta: () -> Ant.OS.APP[@name].meta
baseMenu: ->
mn =
[{
text: Ant.OS.APP[@name].meta.name,
child: [
{ text: "__(About)", dataid: "#{@name}-about" },
{ text: "__(Exit)", dataid: "#{@name}-exit" }
]
}]
mn = mn.concat @menu() || []
mn
main: ->
#main program
# implement by subclasses
menu: ->
# implement by subclasses
# to add menu to application
[]
open: () ->
#implement by subclasses
data: ->
#implement by subclasses
# to return app data
cleanup: (e) ->
#implement by subclasses
# to handle the exit event
# use e.preventDefault() to
# discard the quit command
BaseApplication.type = 1
this.OS.GUI.BaseApplication = BaseApplication

197
src/core/BaseApplication.js Normal file
View File

@ -0,0 +1,197 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class BaseApplication extends this.OS.GUI.BaseModel {
constructor(name, args) {
super(name, args);
if ((!Ant.OS.setting.applications[this.name]) || (Array.isArray(OS.setting.applications[this.name]))) {
Ant.OS.setting.applications[this.name] = {};
}
this.setting = Ant.OS.setting.applications[this.name];
this.keycomb = {
ALT: {},
CTRL: {},
SHIFT: {},
META: {}
};
}
init() {
this.off("*");
this.on("exit", () => this.quit());
// first register some base event to the app
this.on("focus", () => {
this.sysdock.set("selectedApp", this);
this.appmenu.pid = this.pid;
this.appmenu.set("items", (this.baseMenu() || []));
this.appmenu.set("onmenuselect", d => {
return this.trigger("menuselect", d);
});
if (this.dialog) { return this.dialog.show(); }
});
this.on("hide", () => {
this.sysdock.set("selectedApp", null);
this.appmenu.set("items", []);
if (this.dialog) { return this.dialog.hide(); }
});
this.on("menuselect", d => {
switch (d.data.item.get("data").dataid) {
case `${this.name}-about`: return this.openDialog("AboutDialog");
case `${this.name}-exit`: return this.trigger("exit");
}
});
this.on("apptitlechange", () => this.sysdock.update());
this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme();
}
loadScheme() {
//now load the scheme
const path = `${this.meta().path}/scheme.html`;
return this.render(path);
}
load(promise) {
const q = this._api.mid();
return new Promise((resolve, reject) => {
this._api.loading(q, this.name);
return promise.then(() => {
this._api.loaded(q, this.name, "OK");
return resolve();
}).catch(e => {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e));
});
});
}
bindKey(k, f) {
const arr = k.split("-");
if (arr.length !== 2) { return; }
const fnk = arr[0].toUpperCase();
const c = arr[1].toUpperCase();
if (!this.keycomb[fnk]) { return; }
return this.keycomb[fnk][c] = f;
}
updateLocale(name) {
const meta = this.meta();
if (!meta || !meta.locales) { return; }
if (!meta.locales[name]) { return; }
return (() => {
const result = [];
for (let k in meta.locales[name]) {
const v = meta.locales[name][k];
result.push(this._api.lang[k] = v);
}
return result;
})();
}
shortcut(fnk, c, e) {
if (!this.keycomb[fnk]) { return true; }
if (!this.keycomb[fnk][c]) { return true; }
this.keycomb[fnk][c](e);
return false;
}
applySetting(k) {}
applyAllSetting() {
return (() => {
const result = [];
for (let k in this.setting) {
const v = this.setting[k];
result.push(this.applySetting(k));
}
return result;
})();
}
registry(k, v) {
this.setting[k] = v;
return this.publish("appregistry", k);
}
show() {
return this.trigger("focus");
}
blur() {
if (this.appmenu && (this.pid === this.appmenu.pid)) { this.appmenu.set("items", []); }
return this.trigger("blur");
}
hide() {
return this.trigger("hide");
}
toggle() {
return this.trigger("toggle");
}
title() {
return this.scheme.get("apptitle");
}
onexit(evt) {
this.cleanup(evt);
if (!evt.prevent) {
if (this.pid === this.appmenu.pid) { this.appmenu.set("items", []); }
return ($(this.scheme)).remove();
}
}
meta() { return Ant.OS.APP[this.name].meta; }
baseMenu() {
let mn =
[{
text: Ant.OS.APP[this.name].meta.name,
child: [
{ text: "__(About)", dataid: `${this.name}-about` },
{ text: "__(Exit)", dataid: `${this.name}-exit` }
]
}];
mn = mn.concat(this.menu() || []);
return mn;
}
main() {}
//main program
// implement by subclasses
menu() {
// implement by subclasses
// to add menu to application
return [];
}
open() {}
//implement by subclasses
data() {}
//implement by subclasses
// to return app data
cleanup(e) {}
}
//implement by subclasses
// to handle the exit event
// use e.preventDefault() to
// discard the quit command
BaseApplication.type = 1;
this.OS.GUI.BaseApplication = BaseApplication;

View File

@ -1,483 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class SubWindow extends this.OS.GUI.BaseModel
constructor: (name) ->
super name, null
@parent = undefined
@modal = false
quit: () ->
evt = new Ant.OS.GUI.BaseEvent("exit")
@onexit(evt)
if not evt.prevent
delete @.observable
($ @scheme).remove() if @scheme
@dialog.quit() if @dialog
init: () ->
main: () ->
meta: () ->
return @parent.meta() if @parent and @parent.meta
{}
show: () ->
@trigger 'focus'
($ @scheme).css "z-index", Ant.OS.GUI.zindex + 2
hide: () ->
@trigger 'hide'
SubWindow.type = 3
this.OS.GUI.SubWindow = SubWindow
class BaseDialog extends SubWindow
constructor: (name) ->
super name
@handle = undefined
onexit: (e) ->
@parent.dialog = undefined if @parent
this.OS.GUI.BaseDialog = BaseDialog
class BasicDialog extends BaseDialog
constructor: ( name, @markup) ->
super name
init: () ->
if @markup
if typeof @markup is "string"
Ant.OS.GUI.htmlToScheme @markup, @, @host
else # a file handle
@render @markup.path
else if Ant.OS.GUI.subwindows[@name] and Ant.OS.GUI.subwindows[@name].scheme
scheme = Ant.OS.GUI.subwindows[@name].scheme
Ant.OS.GUI.htmlToScheme scheme, @, @host
main: () ->
@scheme.set "apptitle", @data.title if @data and @data.title
@scheme.set "resizable", false
@scheme.set "minimizable", false
this.OS.GUI.BasicDialog = BasicDialog
class PromptDialog extends BasicDialog
constructor: () ->
super "PromptDialog"
main: () ->
super.main()
$input = $(@find "txtInput")
@find("lbl").set "text", @data.label if @data and @data.label
$input.val @data.value if @data and @data.value
(@find "btnOk").set "onbtclick", (e) =>
@handle($input.val()) if @handle
@quit()
(@find "btnCancel").set "onbtclick", (e) =>
@quit()
$input.keyup (e) =>
return unless e.which is 13
@handle($input.val()) if @handle
@quit()
$input.focus()
PromptDialog.scheme = """
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<input type = "text" data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "PromptDialog", PromptDialog
class TextDialog extends this.OS.GUI.BasicDialog
constructor: () ->
super "TextDialog"
main: () ->
super.main()
$input = $(@find "txtInput")
$input.val @data.value if @data and @data.value
@find("btnOk").set "onbtclick", (e) =>
value = $input.val()
return unless value and value isnt ""
@handle value if @handle
@quit()
@find("btnCancel").set "onbtclick", (e) =>
@quit()
$input.focus()
TextDialog.scheme = """
<afx-app-window data-id = "TextDialog" width='400' height='300'>
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<textarea data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "TextDialog", TextDialog
class CalendarDialog extends BasicDialog
constructor: () ->
super "CalendarDialog"
main: () ->
super.main()
(@find "btnOk").set "onbtclick", (e) =>
date = (@find "cal").get "selectedDate"
return @notify __("Please select a day") unless date
@handle(date) if @handle
@quit()
(@find "btnCancel").set "onbtclick", (e) =>
@quit()
CalendarDialog.scheme = """
<afx-app-window width='300' height='230' apptitle = "Calendar" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-calendar-view data-id = "cal" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "CalendarDialog", CalendarDialog
class ColorPickerDialog extends BasicDialog
constructor: () ->
super "ColorPickerDialog"
main: () ->
super.main()
(@find "btnOk").set "onbtclick", (e) =>
color = (@find "cpicker").get "selectedColor"
return @notify __("Please select color") unless color
@handle(color) if @handle
@quit()
(@find "btnCancel").set "onbtclick", (e) =>
@quit()
ColorPickerDialog.scheme = """
<afx-app-window width='320' height='250' apptitle = "Color picker" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-color-picker data-id = "cpicker" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "ColorPickerDialog", ColorPickerDialog
class InfoDialog extends BasicDialog
constructor: () ->
super "InfoDialog"
main: () ->
super.main()
rows = []
delete @data.title if @data and @data.title
rows.push [ { text: k }, { text: v } ] for k, v of @data
(@find "grid").set "header", [ { text: __("Name"), width: 70 }, { text: __("Value") } ]
(@find "grid").set "rows", rows
(@find "btnCancel").set "onbtclick", (e) =>
@quit()
InfoDialog.scheme = """
<afx-app-window width='250' height='300' apptitle = "Info" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-grid-view data-id = "grid" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "InfoDialog", InfoDialog
class YesNoDialog extends BasicDialog
constructor: () ->
super "YesNoDialog"
main: () ->
super.main()
@find("lbl").set "*", @data if @data
(@find "btnYes").set "onbtclick", (e) =>
@handle(true) if @handle
@quit()
(@find "btnNo").set "onbtclick", (e) =>
@handle(false) if @handle
@quit()
YesNoDialog.scheme = """
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnYes" text = "__(Yes)" data-width = "40" />
<afx-button data-id = "btnNo" text = "__(No)" data-width = "40" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "YesNoDialog", YesNoDialog
class SelectionDialog extends BasicDialog
constructor: () ->
super "SelectionDialog"
main: () ->
super.main()
(@find "list").set "data", @data.data if @data and @data.data
fn = (e) =>
data = (@find "list").get "selectedItem"
return @notify __("Please select an item") unless data
@handle(data.get("data")) if @handle
@quit()
(@find "list").set "onlistdbclick", fn
(@find "btnOk").set "onbtclick", fn
(@find "btnCancel").set "onbtclick", (e) =>
@quit()
SelectionDialog.scheme = """
<afx-app-window width='250' height='300' apptitle = "Selection">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-list-view data-id = "list" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "SelectionDialog", SelectionDialog
class AboutDialog extends BasicDialog
constructor: () ->
super "AboutDialog"
main: () ->
super.main()
mt = @meta()
@scheme.set "apptitle", __("About: {0}", mt.name)
(@find "mylabel").set "*", {
icon: mt.icon,
iconclass: mt.iconclass,
text: "#{mt.name}(v#{mt.version})"
}
($ @find "mydesc").html mt.description
# grid data for author info
return unless mt.info
rows = []
rows.push [ { text: k }, { text: v } ] for k, v of mt.info
(@find "mygrid").set "header", [ { text: "", width: 100 }, { text: "" } ]
(@find "mygrid").set "rows", rows
(@find "btnCancel").set "onbtclick", (e) =>
@quit()
AboutDialog.scheme = """
<afx-app-window data-id = 'about-window' width='300' height='200'>
<afx-vbox>
<div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel'></afx-label>
</h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div>
<afx-hbox>
<div data-width="10"></div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-hbox>
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" />
</afx-hbox>
<div data-height = "10"/>
</afx-vbox>
</afx-app-window>
"""
this.OS.register "AboutDialog", AboutDialog
class FileDialog extends BasicDialog
constructor: () ->
super "FileDialog"
main: () ->
super.main()
fileview = @find "fileview"
location = @find "location"
filename = @find "filename"
fileview.set "fetch", (path) ->
new Promise (resolve, reject) ->
return resolve() unless path
path.asFileHandle().read()
.then (d) ->
return reject d if d.error
resolve d.result
.catch (e) -> reject __e e
setroot = (path) =>
path.asFileHandle().read().then (d) =>
if(d.error)
return @error __("Resource not found: {0}", path)
fileview.set "path", path
if not @data or not @data.root
location.set "onlistselect", (e) ->
return unless e and e.data.item
setroot e.data.item.get("data").path
location.set "data", ( i for i in @systemsetting.VFS.mountpoints when i.type isnt "app" )
location.set "selected", 0 if location.get("selectedItem") is undefined
else
$(location).hide()
@trigger "resize"
setroot @data.root
fileview.set "onfileselect", (e) ->
($ filename).val e.data.filename if e.data.type is "file"
(@find "bt-ok").set "onbtclick", (e) =>
f = fileview.get "selectedFile"
return @notify __("Please select a file/fofler") unless f
if @data and @data.type and @data.type isnt f.type
return @notify __("Please select {0} only", @data.type)
if @data and @data.mimes
#verify the mime
m = false
if f.mime
for v in @data.mimes
if f.mime.match (new RegExp v, "g")
m = true
break
return @notify __("Only {0} could be selected", @data.mimes.join(",")) unless m
name = $(filename).val()
@handle { file: f, name: name } if @handle
@quit()
(@find "bt-cancel").set "onbtclick", (e) =>
@quit()
if @data and @data.file
($ filename).css("display", "block").val @data.file.basename or "Untitled"
@trigger "resize"
fileview.set "showhidden", @data.hidden if @data and @data.hidden
FileDialog.scheme = """
<afx-app-window width='400' height='300'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" />
<div data-height = '30' style=' text-align:right;padding:3px;'>
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
</afx-vbox>
</afx-hbox>
</afx-app-window>
"""
this.OS.register "FileDialog", FileDialog

565
src/core/BaseDialog.js Normal file
View File

@ -0,0 +1,565 @@
/*
* decaffeinate suggestions:
* DS001: Remove Babel/TypeScript constructor workaround
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class SubWindow extends this.OS.GUI.BaseModel {
constructor(name) {
super(name, null);
this.parent = undefined;
this.modal = false;
}
quit() {
const evt = new Ant.OS.GUI.BaseEvent("exit");
this.onexit(evt);
if (!evt.prevent) {
delete this.observable;
if (this.scheme) { ($(this.scheme)).remove(); }
if (this.dialog) { return this.dialog.quit(); }
}
}
init() {}
main() {}
meta() {
if (this.parent && this.parent.meta) { return this.parent.meta(); }
return {};
}
show() {
this.trigger('focus');
return ($(this.scheme)).css("z-index", Ant.OS.GUI.zindex + 2);
}
hide() {
return this.trigger('hide');
}
}
SubWindow.type = 3;
this.OS.GUI.SubWindow = SubWindow;
class BaseDialog extends SubWindow {
constructor(name) {
super(name);
this.handle = undefined;
}
onexit(e) {
if (this.parent) { return this.parent.dialog = undefined; }
}
}
this.OS.GUI.BaseDialog = BaseDialog;
class BasicDialog extends BaseDialog {
constructor( name, markup) {
super(name);
this.markup = markup;
}
init() {
if (this.markup) {
if (typeof this.markup === "string") {
return Ant.OS.GUI.htmlToScheme(this.markup, this, this.host);
} else { // a file handle
return this.render(this.markup.path);
}
} else if (Ant.OS.GUI.subwindows[this.name] && Ant.OS.GUI.subwindows[this.name].scheme) {
const {
scheme
} = Ant.OS.GUI.subwindows[this.name];
return Ant.OS.GUI.htmlToScheme(scheme, this, this.host);
}
}
main() {
if (this.data && this.data.title) { this.scheme.set("apptitle", this.data.title); }
this.scheme.set("resizable", false);
return this.scheme.set("minimizable", false);
}
}
this.OS.GUI.BasicDialog = BasicDialog;
class PromptDialog extends BasicDialog {
constructor() {
super("PromptDialog");
}
main() {
super.main();
const $input = $(this.find("txtInput"));
if (this.data && this.data.label) { this.find("lbl").set("text", this.data.label); }
if (this.data && this.data.value) { $input.val(this.data.value); }
(this.find("btnOk")).set("onbtclick", e => {
if (this.handle) { this.handle($input.val()); }
return this.quit();
});
(this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
$input.keyup(e => {
if (e.which !== 13) { return; }
if (this.handle) { this.handle($input.val()); }
return this.quit();
});
return $input.focus();
}
}
PromptDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<input type = "text" data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("PromptDialog", PromptDialog);
class TextDialog extends this.OS.GUI.BasicDialog {
constructor() {
super("TextDialog");
}
main() {
super.main();
const $input = $(this.find("txtInput"));
if (this.data && this.data.value) { $input.val(this.data.value); }
this.find("btnOk").set("onbtclick", e => {
const value = $input.val();
if (!value || (value === "")) { return; }
if (this.handle) { this.handle(value); }
return this.quit();
});
this.find("btnCancel").set("onbtclick", e => {
return this.quit();
});
return $input.focus();
}
}
TextDialog.scheme = `\
<afx-app-window data-id = "TextDialog" width='400' height='300'>
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<textarea data-id= "txtInput" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("TextDialog", TextDialog);
class CalendarDialog extends BasicDialog {
constructor() {
super("CalendarDialog");
}
main() {
super.main();
(this.find("btnOk")).set("onbtclick", e => {
const date = (this.find("cal")).get("selectedDate");
if (!date) { return this.notify(__("Please select a day")); }
if (this.handle) { this.handle(date); }
return this.quit();
});
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
CalendarDialog.scheme = `\
<afx-app-window width='300' height='230' apptitle = "Calendar" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-calendar-view data-id = "cal" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("CalendarDialog", CalendarDialog);
class ColorPickerDialog extends BasicDialog {
constructor() {
super("ColorPickerDialog");
}
main() {
super.main();
(this.find("btnOk")).set("onbtclick", e => {
const color = (this.find("cpicker")).get("selectedColor");
if (!color) { return this.notify(__("Please select color")); }
if (this.handle) { this.handle(color); }
return this.quit();
});
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
ColorPickerDialog.scheme = `\
<afx-app-window width='320' height='250' apptitle = "Color picker" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-color-picker data-id = "cpicker" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("ColorPickerDialog", ColorPickerDialog);
class InfoDialog extends BasicDialog {
constructor() {
super("InfoDialog");
}
main() {
super.main();
const rows = [];
if (this.data && this.data.title) { delete this.data.title; }
for (let k in this.data) { const v = this.data[k]; rows.push([ { text: k }, { text: v } ]); }
(this.find("grid")).set("header", [ { text: __("Name"), width: 70 }, { text: __("Value") } ]);
(this.find("grid")).set("rows", rows);
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
InfoDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Info" >
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-grid-view data-id = "grid" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
<div data-height="10" />
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("InfoDialog", InfoDialog);
class YesNoDialog extends BasicDialog {
constructor() {
super("YesNoDialog");
}
main() {
super.main();
if (this.data) { this.find("lbl").set("*", this.data); }
(this.find("btnYes")).set("onbtclick", e => {
if (this.handle) { this.handle(true); }
return this.quit();
});
return (this.find("btnNo")).set("onbtclick", e => {
if (this.handle) { this.handle(false); }
return this.quit();
});
}
}
YesNoDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-label data-id = "lbl" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnYes" text = "__(Yes)" data-width = "40" />
<afx-button data-id = "btnNo" text = "__(No)" data-width = "40" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("YesNoDialog", YesNoDialog);
class SelectionDialog extends BasicDialog {
constructor() {
super("SelectionDialog");
}
main() {
super.main();
if (this.data && this.data.data) { (this.find("list")).set("data", this.data.data); }
const fn = e => {
const data = (this.find("list")).get("selectedItem");
if (!data) { return this.notify(__("Please select an item")); }
if (this.handle) { this.handle(data.get("data")); }
return this.quit();
};
(this.find("list")).set("onlistdbclick", fn);
(this.find("btnOk")).set("onbtclick", fn);
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
SelectionDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Selection">
<afx-vbox>
<afx-hbox>
<div data-width = "10" />
<afx-vbox>
<div data-height="10" />
<afx-list-view data-id = "list" />
<div data-height="10" />
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" />
</afx-hbox>
</afx-vbox>
<div data-width = "10" />
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("SelectionDialog", SelectionDialog);
class AboutDialog extends BasicDialog {
constructor() {
super("AboutDialog");
}
main() {
super.main();
const mt = this.meta();
this.scheme.set("apptitle", __("About: {0}", mt.name));
(this.find("mylabel")).set("*", {
icon: mt.icon,
iconclass: mt.iconclass,
text: `${mt.name}(v${mt.version})`
});
($(this.find("mydesc"))).html(mt.description);
// grid data for author info
if (!mt.info) { return; }
const rows = [];
for (let k in mt.info) { const v = mt.info[k]; rows.push([ { text: k }, { text: v } ]); }
(this.find("mygrid")).set("header", [ { text: "", width: 100 }, { text: "" } ]);
(this.find("mygrid")).set("rows", rows);
return (this.find("btnCancel")).set("onbtclick", e => {
return this.quit();
});
}
}
AboutDialog.scheme = `\
<afx-app-window data-id = 'about-window' width='300' height='200'>
<afx-vbox>
<div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel'></afx-label>
</h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div>
<afx-hbox>
<div data-width="10"></div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-hbox>
<afx-hbox data-height="30">
<div />
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" />
</afx-hbox>
<div data-height = "10"/>
</afx-vbox>
</afx-app-window>\
`;
this.OS.register("AboutDialog", AboutDialog);
class FileDialog extends BasicDialog {
constructor() {
super("FileDialog");
}
main() {
super.main();
const fileview = this.find("fileview");
const location = this.find("location");
const filename = this.find("filename");
fileview.set("fetch", path => new Promise(function(resolve, reject) {
if (!path) { return resolve(); }
return path.asFileHandle().read()
.then(function(d) {
if (d.error) { return reject(d); }
return resolve(d.result);}).catch(e => reject(__e(e)));
}));
const setroot = path => {
return path.asFileHandle().read().then(d => {
if(d.error) {
return this.error(__("Resource not found: {0}", path));
}
return fileview.set("path", path);
});
};
if (!this.data || !this.data.root) {
location.set("onlistselect", function(e) {
if (!e || !e.data.item) { return; }
return setroot(e.data.item.get("data").path);
});
location.set("data", ( Array.from(this.systemsetting.VFS.mountpoints).filter((i) => i.type !== "app") ));
if (location.get("selectedItem") === undefined) { location.set("selected", 0); }
} else {
$(location).hide();
this.trigger("resize");
setroot(this.data.root);
}
fileview.set("onfileselect", function(e) {
if (e.data.type === "file") { return ($(filename)).val(e.data.filename); }
});
(this.find("bt-ok")).set("onbtclick", e => {
const f = fileview.get("selectedFile");
if (!f) { return this.notify(__("Please select a file/fofler")); }
if (this.data && this.data.type && (this.data.type !== f.type)) {
return this.notify(__("Please select {0} only", this.data.type));
}
if (this.data && this.data.mimes) {
//verify the mime
let m = false;
if (f.mime) {
for (let v of Array.from(this.data.mimes)) {
if (f.mime.match((new RegExp(v, "g")))) {
m = true;
break;
}
}
}
if (!m) { return this.notify(__("Only {0} could be selected", this.data.mimes.join(","))); }
}
const name = $(filename).val();
if (this.handle) { this.handle({ file: f, name }); }
return this.quit();
});
(this.find("bt-cancel")).set("onbtclick", e => {
return this.quit();
});
if (this.data && this.data.file) {
($(filename)).css("display", "block").val(this.data.file.basename || "Untitled");
this.trigger("resize");
}
if (this.data && this.data.hidden) { return fileview.set("showhidden", this.data.hidden); }
}
}
FileDialog.scheme = `\
<afx-app-window width='400' height='300'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" />
<div data-height = '30' style=' text-align:right;padding:3px;'>
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
</afx-vbox>
</afx-hbox>
</afx-app-window>\
`;
this.OS.register("FileDialog", FileDialog);

View File

@ -1,24 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class BaseEvent
constructor: (@name, @force) ->
@prevent = false
preventDefault: () ->
@prevent = true if not @force
this.OS.GUI.BaseEvent = BaseEvent

35
src/core/BaseEvent.js Normal file
View File

@ -0,0 +1,35 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class BaseEvent {
constructor(name, force) {
this.name = name;
this.force = force;
this.prevent = false;
}
preventDefault() {
if (!this.force) { return this.prevent = true; }
}
}
this.OS.GUI.BaseEvent = BaseEvent;

View File

@ -1,129 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class BaseModel
constructor: (@name, @args) ->
@observable = new Announcer()
@_api = Ant.OS.API
@_gui = Ant.OS.GUI
@systemsetting = Ant.OS.setting
@on "exit", () => @quit()
@host = @_gui.workspace
@dialog = undefined
render: (p) ->
Ant.OS.GUI.loadScheme p, @, @host
quit: (force) ->
evt = new Ant.OS.GUI.BaseEvent("exit", force)
@onexit(evt)
if not evt.prevent
@observable.off "*"
delete @.observable
@dialog.quit() if @dialog
Ant.OS.PM.kill @
path: () ->
mt = @meta()
return mt.path if mt and mt.path
return null
# call a server side script
call: (cmd, func) ->
@_api.apigateway cmd, false, func
# get a stream
stream: () ->
return @_api.apigateway null, true, null
init: ->
#implement by sub class
onexit: (e) ->
#implement by subclass
one: (e, f) -> @observable.one e, f
on: (e, f) -> @observable.on e, f
off: (e, f) ->
return @observable.off e unless f
@observable.off e, f
trigger: (e, d) -> @observable.trigger e, d
subscribe: (e, f) ->
Ant.OS.announcer.on e, f, @
openDialog: (d, data) ->
new Promise (resolve, reject) =>
if @dialog
@dialog.show()
return
if typeof d is "string"
if not Ant.OS.GUI.subwindows[d]
@error __("Dialog {0} not found", d)
return
@dialog = new Ant.OS.GUI.subwindows[d]()
else
@dialog = d
#@dialog.observable = riot.observable() unless @dialog
@dialog.parent = @
@dialog.handle = resolve
@dialog.reject = reject
@dialog.pid = @pid
@dialog.data = data
@dialog.title = data.title if data and data.title
@dialog.init()
ask: (data) ->
@._gui.openDialog("YesNoDialog", data)
publish: (t, m, e) ->
mt = @meta()
icon = undefined
icon = "#{mt.path}/#{mt.icon}" if mt.icon
Ant.OS.announcer.trigger t, {
id: @pid,
name: @name,
data: {
m: m,
icon: icon,
iconclass: mt.iconclass,
e: e
}
}
notify: (m) ->
@publish "notification", m
warn: (m) ->
@publish "warning", m
error: (m, e) ->
@publish "error", m, if e then e else (@_api.throwe m)
fail: (m) ->
@publish "fail", m
throwe: () ->
@_api.throwe @name
update: () ->
@scheme.update() if @scheme
find: (id) -> ($ "[data-id='#{id}']", @scheme)[0] if @scheme
select: (sel) -> $ sel, @scheme if @scheme
this.OS.GUI.BaseModel = BaseModel

160
src/core/BaseModel.js Normal file
View File

@ -0,0 +1,160 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class BaseModel {
constructor(name, args) {
this.name = name;
this.args = args;
this.observable = new Announcer();
this._api = Ant.OS.API;
this._gui = Ant.OS.GUI;
this.systemsetting = Ant.OS.setting;
this.on("exit", () => this.quit());
this.host = this._gui.workspace;
this.dialog = undefined;
}
render(p) {
return Ant.OS.GUI.loadScheme(p, this, this.host);
}
quit(force) {
const evt = new Ant.OS.GUI.BaseEvent("exit", force);
this.onexit(evt);
if (!evt.prevent) {
this.observable.off("*");
delete this.observable;
if (this.dialog) { this.dialog.quit(); }
return Ant.OS.PM.kill(this);
}
}
path() {
const mt = this.meta();
if (mt && mt.path) { return mt.path; }
return null;
}
// call a server side script
call(cmd, func) {
return this._api.apigateway(cmd, false, func);
}
// get a stream
stream() {
return this._api.apigateway(null, true, null);
}
init() {}
//implement by sub class
onexit(e) {}
//implement by subclass
one(e, f) { return this.observable.one(e, f); }
on(e, f) { return this.observable.on(e, f); }
off(e, f) {
if (!f) { return this.observable.off(e); }
return this.observable.off(e, f);
}
trigger(e, d) { return this.observable.trigger(e, d); }
subscribe(e, f) {
return Ant.OS.announcer.on(e, f, this);
}
openDialog(d, data) {
return new Promise((resolve, reject) => {
if (this.dialog) {
this.dialog.show();
return;
}
if (typeof d === "string") {
if (!Ant.OS.GUI.subwindows[d]) {
this.error(__("Dialog {0} not found", d));
return;
}
this.dialog = new (Ant.OS.GUI.subwindows[d])();
} else {
this.dialog = d;
}
//@dialog.observable = riot.observable() unless @dialog
this.dialog.parent = this;
this.dialog.handle = resolve;
this.dialog.reject = reject;
this.dialog.pid = this.pid;
this.dialog.data = data;
if (data && data.title) { this.dialog.title = data.title; }
return this.dialog.init();
});
}
ask(data) {
return this._gui.openDialog("YesNoDialog", data);
}
publish(t, m, e) {
const mt = this.meta();
let icon = undefined;
if (mt.icon) { icon = `${mt.path}/${mt.icon}`; }
return Ant.OS.announcer.trigger(t, {
id: this.pid,
name: this.name,
data: {
m,
icon,
iconclass: mt.iconclass,
e
}
});
}
notify(m) {
return this.publish("notification", m);
}
warn(m) {
return this.publish("warning", m);
}
error(m, e) {
return this.publish("error", m, e ? e : (this._api.throwe(m)));
}
fail(m) {
return this.publish("fail", m);
}
throwe() {
return this._api.throwe(this.name);
}
update() {
if (this.scheme) { return this.scheme.update(); }
}
find(id) { if (this.scheme) { return ($(`[data-id='${id}']`, this.scheme))[0]; } }
select(sel) { if (this.scheme) { return $(sel, this.scheme); } }
}
this.OS.GUI.BaseModel = BaseModel;

View File

@ -1,62 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class BaseService extends this.OS.GUI.BaseModel
constructor: (name, args) ->
super name, args
@icon = undefined
@iconclass = "fa-paper-plane-o"
@text = ""
@timer = undefined
@holder = undefined
@onmenuselect = (d) =>
@awake d
init: ()->
#implement by user
# event registe, etc
# scheme loader
update: () ->
@domel.set "data", @
meta: () ->
Ant.OS.APP[@name].meta
attach: (h) ->
@holder = h
watch: ( t, f) ->
func = () =>
f()
@timer = setTimeout (() -> func()), t
func()
onexit: (evt) ->
console.log "clean timer" if @timer
clearTimeout @timer if @timer
@cleanup(evt)
($ @scheme).remove() if @scheme
main: () ->
show: () ->
awake: (e) ->
#implement by user to tart the service
cleanup: (evt) ->
#implemeted by user
BaseService.type = 2
BaseService.singleton = true
this.OS.GUI.BaseService = BaseService

77
src/core/BaseService.js Normal file
View File

@ -0,0 +1,77 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class BaseService extends this.OS.GUI.BaseModel {
constructor(name, args) {
super(name, args);
this.icon = undefined;
this.iconclass = "fa-paper-plane-o";
this.text = "";
this.timer = undefined;
this.holder = undefined;
this.onmenuselect = d => {
return this.awake(d);
};
}
init(){}
//implement by user
// event registe, etc
// scheme loader
update() {
return this.domel.set("data", this);
}
meta() {
return Ant.OS.APP[this.name].meta;
}
attach(h) {
return this.holder = h;
}
watch( t, f) {
var func = () => {
f();
return this.timer = setTimeout((() => func()), t);
};
return func();
}
onexit(evt) {
if (this.timer) { console.log("clean timer"); }
if (this.timer) { clearTimeout(this.timer); }
this.cleanup(evt);
if (this.scheme) { return ($(this.scheme)).remove(); }
}
main() {}
show() {}
awake(e) {}
//implement by user to tart the service
cleanup(evt) {}
}
//implemeted by user
BaseService.type = 2;
BaseService.singleton = true;
this.OS.GUI.BaseService = BaseService;

View File

@ -1,577 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
'use strict'
Ant = this
class FormatedString
constructor: (@fs, args) ->
@values = []
return unless args
@values[i] = args[i] for i in [0..args.length - 1]
toString: () ->
@__()
__: () ->
return @fs.l().replace /{(\d+)}/g, (match, number) =>
return if typeof @values[number] != 'undefined' then @values[number].__() else match
hash: () ->
@__().hash()
match: (t) ->
@__().match t
asBase64: () ->
@__().asBase64()
unescape: () ->
@__().unescape()
asUint8Array: () ->
@__().asUint8Array()
format: () ->
args = arguments
@values[i] = args[i] for i in [0..args.length - 1]
class Version
constructor:(@string) ->
arr = @string.split "-"
br =
"r": 3,
"b": 2,
"a": 1
@branch = 3
@branch = br[arr[1]] if arr.length is 2 and br[arr[1]]
mt = arr[0].match /\d+/g
throw new Error __("Version string is in invalid format: {0}", @string) if not mt
@major = 0
@minor = 0
@patch = 0
@major = Number mt[0] if mt.length >= 1
@minor = Number mt[1] if mt.length >= 2
@patch = Number mt[2] if mt.length >= 3
# this function return
# 0 if the version is unchanged
# 1 if the current version is newer
# -1 if the current version is older
compare: (o) ->
other = o.__v()
return 1 if @branch > other.branch
return -1 if @branch < other.branch
return 0 if @major is other.major and @minor is other.minor and @patch is other.patch
return 1 if @major > other.major
return -1 if @major < other.major
return 1 if @minor > other.minor
return -1 if @minor < other.minor
return 1 if @patch > other.patch
return -1
nt: (o) ->
return (@compare o) is 1
ot: (o) ->
return (@compare o) is -1
__v: () -> @
toString: () -> @string
Object.defineProperty Object.prototype, '__',
value: () ->
return @toString()
enumerable: false
writable: true
String.prototype.hash = () ->
hash = 5381
i = this.length
hash = (hash * 33) ^ this.charCodeAt(--i) while i
hash >>> 0
String.prototype.__v = () ->
return new Version @
String.prototype.asBase64 = () ->
tmp = encodeURIComponent this
return btoa ( tmp.replace /%([0-9A-F]{2})/g, (match, p1) ->
return String.fromCharCode (parseInt p1, 16)
)
String.prototype.unescape = () ->
d = @
d = d.replace /\\\\/g, "\\"
d = d.replace /\\"/g, '"'
d = d.replace /\\n/g, "\n"
d = d.replace /\\t/g, "\t"
d = d.replace /\\b/g, "\b"
d = d.replace /\\f/g, "\f"
d = d.replace /\\r/g, "\r"
d
String.prototype.asUint8Array = () ->
bytes = []
for i in [0..(@length - 1)]
bytes.push @charCodeAt i
bytes = new Uint8Array(bytes)
return bytes
if not String.prototype.format
String.prototype.format = () ->
args = arguments
return @replace /{(\d+)}/g, (match, number) ->
return if typeof args[number] != 'undefined' then args[number].__() else match
String.prototype.f = () ->
args = arguments
return new FormatedString(@, args)
String.prototype.__ = () ->
match = @match(/^__\((.*)\)$/)
return match[1].l() if match
return @
String.prototype.l = () ->
Ant.OS.API.lang[@] = @ unless Ant.OS.API.lang[@]
return Ant.OS.API.lang[@]
# language directive
this.__ = () ->
args = arguments
return "Undefined" unless args.length > 0
d = args[0]
d.l()
return new FormatedString d, (args[i] for i in [1 .. args.length - 1])
Date.prototype.toString = () ->
dd = @getDate()
mm = @getMonth() + 1
yyyy = @getFullYear()
hh = @getHours()
mi = @getMinutes()
se = @getSeconds()
dd = "0#{dd}" if dd < 10
mm = "0#{mm}" if mm < 10
hh = "0#{hh}" if hh < 10
mi = "0#{mi}" if mi < 10
se = "0#{se}" if se < 10
return "#{dd}/#{mm}/#{yyyy} #{hh}:#{mi}:#{se}"
Date.prototype.timestamp = () ->
return @getTime() / 1000 | 0
# chaning error
this.__e = (e) ->
reason = new Error(e.toString())
reason.stack += "\nCaused By:\n" + e.stack
return reason
#define the OS object
Ant.OS or=
API: {}
GUI: {}
APP: {}
setting:
user: {}
applications: {}
desktop: {}
appearance: {}
VFS: {}
system: {}
register: (name, x) ->
if x.type is 3 then Ant.OS.GUI.subwindows[name] = x else Ant.OS.APP[name] = x
# import proprety from an App
PM:
pidalloc: 0
processes: {}
createProcess: (app, cls, args) ->
new Promise (resolve, reject) ->
f = () ->
#if it is single ton
# and a process is existing
# just return it
if cls.singleton and Ant.OS.PM.processes[app] and Ant.OS.PM.processes[app].length == 1
obj = Ant.OS.PM.processes[app][0]
obj.show()
else
Ant.OS.PM.processes[app] = [] if not Ant.OS.PM.processes[app]
obj = new cls(args)
obj.birth = (new Date).getTime()
Ant.OS.PM.pidalloc++
obj.pid = Ant.OS.PM.pidalloc
Ant.OS.PM.processes[app].push obj
if cls.type is 1 then Ant.OS.GUI.dock obj, cls.meta else Ant.OS.GUI.attachservice obj
obj
if cls.dependencies
libs = (v for v in cls.dependencies)
Ant.OS.API.require libs
.then () ->
resolve f()
.catch (e) ->
reject __e e
else
resolve f()
appByPid: (pid) ->
app = undefined
find = (l) ->
return a for a in l when a.pid is pid
for k, v of Ant.OS.PM.processes
app = find v
break if app
app
kill: (app) ->
return if not app.name or not Ant.OS.PM.processes[app.name]
i = Ant.OS.PM.processes[app.name].indexOf app
if i >= 0
if Ant.OS.APP[app.name].type == 1 then Ant.OS.GUI.undock app else Ant.OS.GUI.detachservice app
Ant.OS.announcer.unregister app
delete Ant.OS.PM.processes[app.name][i]
Ant.OS.PM.processes[app.name].splice i, 1
killAll: (app, force) ->
return unless Ant.OS.PM.processes[app]
a.quit(force) for a in Ant.OS.PM.processes[app]
cleanup: ->
console.log "Clean up system"
Ant.OS.PM.killAll a, true for a, v of Ant.OS.PM.processes
Ant.OS.announcer.observable.off("*") if Ant.OS.announcer.observable
$(window).off('keydown')
($ "#workspace").off("mouseover")
delete Ant.OS.announcer.observable
($ "#wrapper").empty()
Ant.OS.GUI.clearTheme()
Ant.OS.announcer.observable = new Ant.OS.API.Announcer()
Ant.OS.announcer.quota = 0
Ant.OS.APP = {}
Ant.OS.setting =
user: {}
applications: {}
desktop: {}
appearance: {}
VFS: {}
system: {}
Ant.OS.PM.processes = {}
Ant.OS.PM.pidalloc = 0
boot: ->
#first login
console.log "Booting sytem"
Ant.OS.API.handle.auth()
.then (d) ->
# in case someone call it more than once :)
if d.error
# show login screen
Ant.OS.GUI.login()
else
# startX :)
Ant.OS.GUI.startAntOS d.result
.catch (e) ->
console.error e
cleanupHandles: {}
exit: ->
#do clean up first
f() for n, f of Ant.OS.cleanupHandles
Ant.OS.API.handle.setting()
.then (r) ->
Ant.OS.cleanup()
Ant.OS.API.handle.logout()
.then (d) -> Ant.OS.boot()
.catch (e) ->
console.error e
onexit: (n, f) ->
Ant.OS.cleanupHandles[n] = f unless Ant.OS.cleanupHandles[n]
Ant.OS.API =
# the handle object could be a any remote or local handle to
# fetch user data, used by the API to make requests
# handles are defined in /src/handles
handle: {}
shared: {} # shared libraries
searchHandle: {}
lang: {}
#request a user data
mid: () ->
return Ant.OS.announcer.getMID()
post: (p, d) ->
new Promise (resolve, reject) ->
q = Ant.OS.announcer.getMID()
Ant.OS.API.loading q, p
$.ajax {
type: 'POST',
url: p,
contentType: 'application/json',
data: JSON.stringify(d,
(k, v) ->
return undefined if k is "domel"
return v
, 4),
dataType: 'json',
success: null
}
.done (data) ->
Ant.OS.API.loaded q, p, "OK"
resolve(data)
.fail (j, s, e) ->
Ant.OS.API.loaded q, p, "FAIL"
if e
reject __e e
else
reject(Ant.OS.API.throwe s)
blob: (p) ->
new Promise (resolve, reject) ->
q = Ant.OS.announcer.getMID()
r = new XMLHttpRequest()
r.open "GET", p, true
r.responseType = "arraybuffer"
r.onload = (e) ->
if @status is 200 and @readyState is 4
Ant.OS.API.loaded q, p, "OK"
resolve @response
else
Ant.OS.API.loaded q, p, "FAIL"
reject Ant.OS.API.throwe __("Unable to get blob: {0}", p)
Ant.OS.API.loading q, p
r.send()
upload: (p, d) ->
new Promise (resolve, reject) ->
q = Ant.OS.announcer.getMID()
#insert a temporal file selector
o = ($ '<input>').attr('type', 'file').css("display", "none")
o.change () ->
Ant.OS.API.loading q, p
formd = new FormData()
formd.append 'path', d
# TODO: only one file is selected at this time
formd.append 'upload', o[0].files[0]
$.ajax {
url: p,
data: formd,
type: 'POST',
contentType: false,
processData: false,
}
.done (data) ->
Ant.OS.API.loaded q, p, "OK"
resolve(data)
o.remove()
.fail (j, s, e) ->
Ant.OS.API.loaded q, p, "FAIL"
if e
reject __e e
else
reject(Ant.OS.API.throwe s)
o.remove()
o.click()
saveblob: (name, b) ->
url = window.URL.createObjectURL b
o = ($ '<a>')
.attr("href", url)
.attr("download", name)
.css("display", "none")
.appendTo("body")
o[0].click()
window.URL.revokeObjectURL(url)
o.remove()
loading: (q, p) ->
Ant.OS.announcer.trigger "loading", { id: q, data: { m: "#{p}", s: true }, name: "OS" }
loaded: (q, p, m ) ->
Ant.OS.announcer.trigger "loaded", {
id: q, data: { m: "#{m}: #{p}", s: false }, name: "OS" }
get: (p, t) ->
new Promise (resolve, reject) ->
conf =
type: 'GET',
url: p
conf.dataType = t if t
q = Ant.OS.announcer.getMID()
Ant.OS.API.loading q, p
$.ajax conf
.done (data) ->
Ant.OS.API.loaded q, p, "OK"
resolve(data)
.fail (j, s, e) ->
Ant.OS.API.loaded q, p, "FAIL"
if e
reject __e e
else
reject(Ant.OS.API.throwe s)
script: (p) ->
Ant.OS.API.get p, "script"
resource: (r) ->
path = "resources/#{r}"
Ant.OS.API.get path
libready: (l) ->
return Ant.OS.API.shared[l] || false
requires: (l) ->
new Promise (resolve, reject) ->
if not Ant.OS.API.shared[l]
libfp = l.asFileHandle()
switch libfp.ext
when "css"
libfp.onready()
.then () ->
$('<link>', {
rel: 'stylesheet',
type: 'text/css',
'href': "#{libfp.getlink()}"
})
.appendTo 'head'
Ant.OS.API.shared[l] = true
console.log "Loaded :", l
Ant.OS.announcer.trigger "sharedlibraryloaded", l
resolve undefined
.catch (e) -> reject __e e
when "js"
Ant.OS.API.script libfp.getlink()
.then (data) ->
Ant.OS.API.shared[l] = true
console.log "Loaded :", l
Ant.OS.announcer.trigger "sharedlibraryloaded", l
resolve(data)
.catch (e) ->
reject __e e
else
reject Ant.OS.API.throwe __("Invalid library: {0}", l)
else
console.log l, "Library exist, no need to load"
Ant.OS.announcer.trigger "sharedlibraryloaded", l
resolve()
require: (libs) ->
new Promise (resolve, reject) ->
return resolve() unless libs.length > 0
Ant.OS.announcer.observable.one "sharedlibraryloaded", (l) ->
libs.splice 0, 1
Ant.OS.API.require libs
.catch (e) -> reject __e e
.then (r) -> resolve(r)
Ant.OS.API.requires libs[0]
.catch (e) -> reject __e e
packages:
fetch: () ->
Ant.OS.API.handle.packages {
command: "list", args: { paths: (v for k, v of Ant.OS.setting.system.pkgpaths) }
}
cache: () ->
Ant.OS.API.handle.packages {
command: "cache", args: { paths: (v for k, v of Ant.OS.setting.system.pkgpaths) }
}
setting: (f) ->
Ant.OS.API.handle.setting f
apigateway: (d, ws) ->
return Ant.OS.API.handle.apigateway d, ws
search: (text) ->
r = []
for k, v of Ant.OS.API.searchHandle
ret = Ant.OS.API.searchHandle[k](text)
if ret.length > 0
ret.unshift { text: k, class: "search-header", dataid: "header" }
r = r.concat ret
return r
onsearch: (name, fn) ->
Ant.OS.API.searchHandle[name] = fn unless Ant.OS.API.searchHandle[name]
setLocale: (name) ->
new Promise (resolve, reject) ->
path = "resources/languages/#{name}.json"
Ant.OS.API.get(path, "json")
.then (d) ->
Ant.OS.setting.system.locale = name
Ant.OS.API.lang = d
Ant.OS.announcer.trigger "systemlocalechange", name
resolve d
.catch (e) ->
reject __e e
throwe: (n) ->
err = undefined
try
throw new Error(n)
catch e
err = e
return "" if not err
return err
setClipboard: (v) ->
$el = $("#clipboard")
$el.val v
$el[0].select()
$el[0].setSelectionRange(0, 99999)
document.execCommand("copy")
getClipboard: () ->
new Promise (resolve, reject) ->
$el = $("#clipboard")
return resolve $el.val() unless navigator.clipboard
navigator.clipboard.readText().then (d) ->
resolve d
.catch (e) -> reject __e e
# utilities functioncs
switcher: () ->
o = {}
p = {}
p[arguments[i]] = false for i in [0..arguments.length - 1 ]
Object.defineProperty o, "__p", {
enumerable: false,
value: p
}
fn = (o, v) ->
Object.defineProperty o, v, {
enumerable: true,
set: (value) ->
for k, l of @__p
@__p[k] = false
o.__p[v] = value
, get: () ->
return o.__p[v]
}
for k, v of o.__p
fn o, k
Object.defineProperty o, "selected", {
configurable: true,
enumerable: false,
get: () ->
for k, v of o.__p
return k if v
}
return o

715
src/core/core.js Normal file
View File

@ -0,0 +1,715 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS202: Simplify dynamic range loops
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
'use strict';
const Ant = this;
class FormatedString {
constructor(fs, args) {
this.fs = fs;
this.values = [];
if (!args) { return; }
for (let i = 0, end = args.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { this.values[i] = args[i]; }
}
toString() {
return this.__();
}
__() {
return this.fs.l().replace(/{(\d+)}/g, (match, number) => {
if (typeof this.values[number] !== 'undefined') { return this.values[number].__(); } else { return match; }
});
}
hash() {
return this.__().hash();
}
match(t) {
return this.__().match(t);
}
asBase64() {
return this.__().asBase64();
}
unescape() {
return this.__().unescape();
}
asUint8Array() {
return this.__().asUint8Array();
}
format() {
const args = arguments;
return __range__(0, args.length - 1, true).map((i) => (this.values[i] = args[i]));
}
}
class Version {
constructor(string) {
this.string = string;
const arr = this.string.split("-");
const br = {
"r": 3,
"b": 2,
"a": 1
};
this.branch = 3;
if ((arr.length === 2) && br[arr[1]]) { this.branch = br[arr[1]]; }
const mt = arr[0].match(/\d+/g);
if (!mt) { throw new Error(__("Version string is in invalid format: {0}", this.string)); }
this.major = 0;
this.minor = 0;
this.patch = 0;
if (mt.length >= 1) { this.major = Number(mt[0]); }
if (mt.length >= 2) { this.minor = Number(mt[1]); }
if (mt.length >= 3) { this.patch = Number(mt[2]); }
}
// this function return
// 0 if the version is unchanged
// 1 if the current version is newer
// -1 if the current version is older
compare(o) {
const other = o.__v();
if (this.branch > other.branch) { return 1; }
if (this.branch < other.branch) { return -1; }
if ((this.major === other.major) && (this.minor === other.minor) && (this.patch === other.patch)) { return 0; }
if (this.major > other.major) { return 1; }
if (this.major < other.major) { return -1; }
if (this.minor > other.minor) { return 1; }
if (this.minor < other.minor) { return -1; }
if (this.patch > other.patch) { return 1; }
return -1;
}
nt(o) {
return (this.compare(o)) === 1;
}
ot(o) {
return (this.compare(o)) === -1;
}
__v() { return this; }
toString() { return this.string; }
}
Object.defineProperty(Object.prototype, '__', {
value() {
return this.toString();
},
enumerable: false,
writable: true
}
);
String.prototype.hash = function() {
let hash = 5381;
let i = this.length;
while (i) { hash = (hash * 33) ^ this.charCodeAt(--i); }
return hash >>> 0;
};
String.prototype.__v = function() {
return new Version(this);
};
String.prototype.asBase64 = function() {
const tmp = encodeURIComponent(this);
return btoa(( tmp.replace(/%([0-9A-F]{2})/g, (match, p1) => String.fromCharCode((parseInt(p1, 16)))))
);
};
String.prototype.unescape = function() {
let d = this;
d = d.replace(/\\\\/g, "\\");
d = d.replace(/\\"/g, '"');
d = d.replace(/\\n/g, "\n");
d = d.replace(/\\t/g, "\t");
d = d.replace(/\\b/g, "\b");
d = d.replace(/\\f/g, "\f");
d = d.replace(/\\r/g, "\r");
return d;
};
String.prototype.asUint8Array = function() {
let bytes = [];
for (let i = 0, end = this.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
bytes.push(this.charCodeAt(i));
}
bytes = new Uint8Array(bytes);
return bytes;
};
if (!String.prototype.format) {
String.prototype.format = function() {
const args = arguments;
return this.replace(/{(\d+)}/g, function(match, number) {
if (typeof args[number] !== 'undefined') { return args[number].__(); } else { return match; }
});
};
}
String.prototype.f = function() {
const args = arguments;
return new FormatedString(this, args);
};
String.prototype.__ = function() {
const match = this.match(/^__\((.*)\)$/);
if (match) { return match[1].l(); }
return this;
};
String.prototype.l = function() {
if (!Ant.OS.API.lang[this]) { Ant.OS.API.lang[this] = this; }
return Ant.OS.API.lang[this];
};
// language directive
this.__ = function() {
const args = arguments;
if (!(args.length > 0)) { return "Undefined"; }
const d = args[0];
d.l();
return new FormatedString(d, (__range__(1, args.length - 1, true).map((i) => args[i])));
};
Date.prototype.toString = function() {
let dd = this.getDate();
let mm = this.getMonth() + 1;
const yyyy = this.getFullYear();
let hh = this.getHours();
let mi = this.getMinutes();
let se = this.getSeconds();
if (dd < 10) { dd = `0${dd}`; }
if (mm < 10) { mm = `0${mm}`; }
if (hh < 10) { hh = `0${hh}`; }
if (mi < 10) { mi = `0${mi}`; }
if (se < 10) { se = `0${se}`; }
return `${dd}/${mm}/${yyyy} ${hh}:${mi}:${se}`;
};
Date.prototype.timestamp = function() {
return (this.getTime() / 1000) | 0;
};
// chaning error
this.__e = function(e) {
const reason = new Error(e.toString());
reason.stack += "\nCaused By:\n" + e.stack;
return reason;
};
//define the OS object
if (!Ant.OS) { Ant.OS = {
API: {},
GUI: {},
APP: {},
setting: {
user: {},
applications: {},
desktop: {},
appearance: {},
VFS: {},
system: {}
},
register(name, x) {
if (x.type === 3) { return Ant.OS.GUI.subwindows[name] = x; } else { return Ant.OS.APP[name] = x; }
},
// import proprety from an App
PM: {
pidalloc: 0,
processes: {},
createProcess(app, cls, args) {
return new Promise(function(resolve, reject) {
const f = function() {
//if it is single ton
// and a process is existing
// just return it
let obj;
if (cls.singleton && Ant.OS.PM.processes[app] && (Ant.OS.PM.processes[app].length === 1)) {
obj = Ant.OS.PM.processes[app][0];
obj.show();
} else {
if (!Ant.OS.PM.processes[app]) { Ant.OS.PM.processes[app] = []; }
obj = new cls(args);
obj.birth = (new Date).getTime();
Ant.OS.PM.pidalloc++;
obj.pid = Ant.OS.PM.pidalloc;
Ant.OS.PM.processes[app].push(obj);
if (cls.type === 1) { Ant.OS.GUI.dock(obj, cls.meta); } else { Ant.OS.GUI.attachservice(obj); }
}
return obj;
};
if (cls.dependencies) {
const libs = (Array.from(cls.dependencies));
return Ant.OS.API.require(libs)
.then(() => resolve(f())).catch(e => reject(__e(e)));
} else {
return resolve(f());
}
});
},
appByPid(pid) {
let app = undefined;
const find = function(l) {
for (let a of Array.from(l)) { if (a.pid === pid) { return a; } }
};
for (let k in Ant.OS.PM.processes) {
const v = Ant.OS.PM.processes[k];
app = find(v);
if (app) { break; }
}
return app;
},
kill(app) {
if (!app.name || !Ant.OS.PM.processes[app.name]) { return; }
const i = Ant.OS.PM.processes[app.name].indexOf(app);
if (i >= 0) {
if (Ant.OS.APP[app.name].type === 1) { Ant.OS.GUI.undock(app); } else { Ant.OS.GUI.detachservice(app); }
Ant.OS.announcer.unregister(app);
delete Ant.OS.PM.processes[app.name][i];
return Ant.OS.PM.processes[app.name].splice(i, 1);
}
},
killAll(app, force) {
if (!Ant.OS.PM.processes[app]) { return; }
return Array.from(Ant.OS.PM.processes[app]).map((a) => a.quit(force));
}
},
cleanup() {
console.log("Clean up system");
for (let a in Ant.OS.PM.processes) { const v = Ant.OS.PM.processes[a]; Ant.OS.PM.killAll(a, true); }
if (Ant.OS.announcer.observable) { Ant.OS.announcer.observable.off("*"); }
$(window).off('keydown');
($("#workspace")).off("mouseover");
delete Ant.OS.announcer.observable;
($("#wrapper")).empty();
Ant.OS.GUI.clearTheme();
Ant.OS.announcer.observable = new Ant.OS.API.Announcer();
Ant.OS.announcer.quota = 0;
Ant.OS.APP = {};
Ant.OS.setting = {
user: {},
applications: {},
desktop: {},
appearance: {},
VFS: {},
system: {}
};
Ant.OS.PM.processes = {};
return Ant.OS.PM.pidalloc = 0;
},
boot() {
//first login
console.log("Booting sytem");
return Ant.OS.API.handle.auth()
.then(function(d) {
// in case someone call it more than once :)
if (d.error) {
// show login screen
return Ant.OS.GUI.login();
} else {
// startX :)
return Ant.OS.GUI.startAntOS(d.result);
}}).catch(e => console.error(e));
},
cleanupHandles: {},
exit() {
//do clean up first
for (let n in Ant.OS.cleanupHandles) { const f = Ant.OS.cleanupHandles[n]; f(); }
return Ant.OS.API.handle.setting()
.then(function(r) {
Ant.OS.cleanup();
return Ant.OS.API.handle.logout()
.then(d => Ant.OS.boot());}).catch(e => console.error(e));
},
onexit(n, f) {
if (!Ant.OS.cleanupHandles[n]) { return Ant.OS.cleanupHandles[n] = f; }
}
}; }
Ant.OS.API = {
// the handle object could be a any remote or local handle to
// fetch user data, used by the API to make requests
// handles are defined in /src/handles
handle: {},
shared: {}, // shared libraries
searchHandle: {},
lang: {},
//request a user data
mid() {
return Ant.OS.announcer.getMID();
},
post(p, d) {
return new Promise(function(resolve, reject) {
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, p);
return $.ajax({
type: 'POST',
url: p,
contentType: 'application/json',
data: JSON.stringify(d,
function(k, v) {
if (k === "domel") { return undefined; }
return v;
}
, 4),
dataType: 'json',
success: null
})
.done(function(data) {
Ant.OS.API.loaded(q, p, "OK");
return resolve(data);}).fail(function(j, s, e) {
Ant.OS.API.loaded(q, p, "FAIL");
if (e) {
return reject(__e(e));
} else {
return reject(Ant.OS.API.throwe(s));
}
});
});
},
blob(p) {
return new Promise(function(resolve, reject) {
const q = Ant.OS.announcer.getMID();
const r = new XMLHttpRequest();
r.open("GET", p, true);
r.responseType = "arraybuffer";
r.onload = function(e) {
if ((this.status === 200) && (this.readyState === 4)) {
Ant.OS.API.loaded(q, p, "OK");
return resolve(this.response);
} else {
Ant.OS.API.loaded(q, p, "FAIL");
return reject(Ant.OS.API.throwe(__("Unable to get blob: {0}", p)));
}
};
Ant.OS.API.loading(q, p);
return r.send();
});
},
upload(p, d) {
return new Promise(function(resolve, reject) {
const q = Ant.OS.announcer.getMID();
//insert a temporal file selector
const o = ($('<input>')).attr('type', 'file').css("display", "none");
o.change(function() {
Ant.OS.API.loading(q, p);
const formd = new FormData();
formd.append('path', d);
// TODO: only one file is selected at this time
formd.append('upload', o[0].files[0]);
return $.ajax({
url: p,
data: formd,
type: 'POST',
contentType: false,
processData: false,
})
.done(function(data) {
Ant.OS.API.loaded(q, p, "OK");
resolve(data);
return o.remove();}).fail(function(j, s, e) {
Ant.OS.API.loaded(q, p, "FAIL");
if (e) {
reject(__e(e));
} else {
reject(Ant.OS.API.throwe(s));
}
return o.remove();
});
});
return o.click();
});
},
saveblob(name, b) {
const url = window.URL.createObjectURL(b);
const o = ($('<a>'))
.attr("href", url)
.attr("download", name)
.css("display", "none")
.appendTo("body");
o[0].click();
window.URL.revokeObjectURL(url);
return o.remove();
},
loading(q, p) {
return Ant.OS.announcer.trigger("loading", { id: q, data: { m: `${p}`, s: true }, name: "OS" });
},
loaded(q, p, m ) {
return Ant.OS.announcer.trigger("loaded", {
id: q, data: { m: `${m}: ${p}`, s: false }, name: "OS" });
},
get(p, t) {
return new Promise(function(resolve, reject) {
const conf = {
type: 'GET',
url: p
};
if (t) { conf.dataType = t; }
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, p);
return $.ajax(conf)
.done(function(data) {
Ant.OS.API.loaded(q, p, "OK");
return resolve(data);}).fail(function(j, s, e) {
Ant.OS.API.loaded(q, p, "FAIL");
if (e) {
return reject(__e(e));
} else {
return reject(Ant.OS.API.throwe(s));
}
});
});
},
script(p) {
return Ant.OS.API.get(p, "script");
},
resource(r) {
const path = `resources/${r}`;
return Ant.OS.API.get(path);
},
libready(l) {
return Ant.OS.API.shared[l] || false;
},
requires(l) {
return new Promise(function(resolve, reject) {
if (!Ant.OS.API.shared[l]) {
const libfp = l.asFileHandle();
switch (libfp.ext) {
case "css":
return libfp.onready()
.then(function() {
$('<link>', {
rel: 'stylesheet',
type: 'text/css',
'href': `${libfp.getlink()}`
})
.appendTo('head');
Ant.OS.API.shared[l] = true;
console.log("Loaded :", l);
Ant.OS.announcer.trigger("sharedlibraryloaded", l);
return resolve(undefined);}).catch(e => reject(__e(e)));
case "js":
return Ant.OS.API.script(libfp.getlink())
.then(function(data) {
Ant.OS.API.shared[l] = true;
console.log("Loaded :", l);
Ant.OS.announcer.trigger("sharedlibraryloaded", l);
return resolve(data);}).catch(e => reject(__e(e)));
default:
return reject(Ant.OS.API.throwe(__("Invalid library: {0}", l)));
}
} else {
console.log(l, "Library exist, no need to load");
Ant.OS.announcer.trigger("sharedlibraryloaded", l);
return resolve();
}
});
},
require(libs) {
return new Promise(function(resolve, reject) {
if (!(libs.length > 0)) { return resolve(); }
Ant.OS.announcer.observable.one("sharedlibraryloaded", function(l) {
libs.splice(0, 1);
return Ant.OS.API.require(libs)
.catch(e => reject(__e(e)))
.then(r => resolve(r));
});
return Ant.OS.API.requires(libs[0])
.catch(e => reject(__e(e)));
});
},
packages: {
fetch() {
return Ant.OS.API.handle.packages({
command: "list", args: { paths: ((() => {
const result = [];
for (let k in Ant.OS.setting.system.pkgpaths) {
const v = Ant.OS.setting.system.pkgpaths[k];
result.push(v);
}
return result;
})()) }
});
},
cache() {
return Ant.OS.API.handle.packages({
command: "cache", args: { paths: ((() => {
const result = [];
for (let k in Ant.OS.setting.system.pkgpaths) {
const v = Ant.OS.setting.system.pkgpaths[k];
result.push(v);
}
return result;
})()) }
});
}
},
setting(f) {
return Ant.OS.API.handle.setting(f);
},
apigateway(d, ws) {
return Ant.OS.API.handle.apigateway(d, ws);
},
search(text) {
let r = [];
for (let k in Ant.OS.API.searchHandle) {
const v = Ant.OS.API.searchHandle[k];
const ret = Ant.OS.API.searchHandle[k](text);
if (ret.length > 0) {
ret.unshift({ text: k, class: "search-header", dataid: "header" });
r = r.concat(ret);
}
}
return r;
},
onsearch(name, fn) {
if (!Ant.OS.API.searchHandle[name]) { return Ant.OS.API.searchHandle[name] = fn; }
},
setLocale(name) {
return new Promise(function(resolve, reject) {
const path = `resources/languages/${name}.json`;
return Ant.OS.API.get(path, "json")
.then(function(d) {
Ant.OS.setting.system.locale = name;
Ant.OS.API.lang = d;
Ant.OS.announcer.trigger("systemlocalechange", name);
return resolve(d);}).catch(e => reject(__e(e)));
});
},
throwe(n) {
let err = undefined;
try {
throw new Error(n);
} catch (e) {
err = e;
}
if (!err) { return ""; }
return err;
},
setClipboard(v) {
const $el = $("#clipboard");
$el.val(v);
$el[0].select();
$el[0].setSelectionRange(0, 99999);
return document.execCommand("copy");
},
getClipboard() {
return new Promise(function(resolve, reject) {
const $el = $("#clipboard");
if (!navigator.clipboard) { return resolve($el.val()); }
return navigator.clipboard.readText().then(d => resolve(d)).catch(e => reject(__e(e)));
});
},
// utilities functioncs
switcher() {
let k, v;
const o = {};
const p = {};
for (let i = 0, end = arguments.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) { p[arguments[i]] = false; }
Object.defineProperty(o, "__p", {
enumerable: false,
value: p
});
const fn = function(o, v) {
return Object.defineProperty(o, v, {
enumerable: true,
set(value) {
for (let k in this.__p) {
const l = this.__p[k];
this.__p[k] = false;
}
return o.__p[v] = value;
}
, get() {
return o.__p[v];
}
});
};
for (k in o.__p) {
v = o.__p[k];
fn(o, k);
}
Object.defineProperty(o, "selected", {
configurable: true,
enumerable: false,
get() {
for (k in o.__p) {
v = o.__p[k];
if (v) { return k; }
}
}
});
return o;
}
};
function __range__(left, right, inclusive) {
let range = [];
let ascending = left < right;
let end = !inclusive ? right : ascending ? right + 1 : right - 1;
for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) {
range.push(i);
}
return range;
}

View File

@ -1,60 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class DB
constructor: (@table) ->
save: (d) ->
return new Promise (resolve, reject) =>
Ant.OS.API.handle.dbquery "save", { table: @table, data: d }
.then (r) ->
return reject(Ant.OS.API.throwe(r.error)) if r.error
resolve(r.result)
.catch (e) -> reject __e e
delete: (c) ->
return new Promise (resolve, reject) =>
rq = { table: @table }
reject(Ant.OS.API.throwe("OS.DB: unkown condition")) unless c and c isnt ""
if isNaN c
rq.cond = c
else
rq.id = c
Ant.OS.API.handle.dbquery "delete", rq
.then (r) ->
return reject(Ant.OS.API.throwe(r.error)) if r.error
resolve(r.result)
.catch (e) -> reject __e e
get: (id) ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.dbquery "get", { table: @table, id: id }
.then (r) ->
return reject(Ant.OS.API.throwe(r.error)) if r.error
resolve(r.result)
.catch (e) -> reject __e e
find: (cond) ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.dbquery "select", { table: @table, cond: cond }
.then (r) ->
return reject(Ant.OS.API.throwe(r.error)) if r.error
resolve(r.result)
.catch (e) -> reject __e e
Ant.OS.API.DB = DB

73
src/core/db.js Normal file
View File

@ -0,0 +1,73 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class DB {
constructor(table) {
this.table = table;
}
save(d) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.dbquery("save", { table: this.table, data: d })
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
delete(c) {
return new Promise((resolve, reject) => {
const rq = { table: this.table };
if (!c || (c === "")) { reject(Ant.OS.API.throwe("OS.DB: unkown condition")); }
if (isNaN(c)) {
rq.cond = c;
} else {
rq.id = c;
}
return Ant.OS.API.handle.dbquery("delete", rq)
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
get(id) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.dbquery("get", { table: this.table, id })
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
find(cond) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.dbquery("select", { table: this.table, cond })
.then(function(r) {
if (r.error) { return reject(Ant.OS.API.throwe(r.error)); }
return resolve(r.result);}).catch(e => reject(__e(e)));
});
}
}
Ant.OS.API.DB = DB;

View File

@ -1,503 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
Ant.OS.GUI =
subwindows: new Object()
dialog: undefined
fullscreen: false
workspace: "#desktop"
shortcut:
ALT: {}
CTRL: {}
SHIFT: {}
META: {}
htmlToScheme: (html, app, parent) ->
scheme = $.parseHTML html
$(app.scheme).remove() if app.scheme
($ parent).append scheme
app.scheme = scheme[0]
scheme[0].uify app.observable
app.main()
app.show()
loadScheme: (path, app, parent) ->
path.asFileHandle().read()
.then (x) ->
return null unless x
Ant.OS.GUI.htmlToScheme x, app, parent
.catch (e) ->
Ant.OS.announcer.oserror __("Cannot load scheme: {0}", path), e
clearTheme: () ->
$ "head link#ostheme"
.attr "href", ""
loadTheme: (name, force) ->
Ant.OS.GUI.clearTheme() if force
path = "resources/themes/#{name}/#{name}.css"
$ "head link#ostheme"
.attr "href", path
pushServices: (srvs) ->
new Promise (resolve, reject) ->
return resolve() unless srvs.length > 0
srv = srvs.splice(0, 1)[0]
Ant.OS.GUI.pushService srv
.then (d) ->
Ant.OS.GUI.pushServices srvs
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) ->
Ant.OS.announcer.osfail __("Unable to load: {0}", srv), e
Ant.OS.GUI.pushServices srvs
.then () -> resolve()
.catch (e) -> reject __e e
openDialog: (d, data) ->
new Promise (resolve, reject) ->
if Ant.OS.GUI.dialog
Ant.OS.GUI.dialog.show()
return resolve()
if not Ant.OS.GUI.subwindows[d]
ex = Ant.OS.API.throwe "Dialog"
return reject(ex)
Ant.OS.GUI.dialog = new Ant.OS.GUI.subwindows[d]()
Ant.OS.GUI.dialog.parent = Ant.OS.GUI
Ant.OS.GUI.dialog.handle = resolve
Ant.OS.GUI.dialog.reject = reject
Ant.OS.GUI.dialog.pid = -1
Ant.OS.GUI.dialog.data = data
Ant.OS.GUI.dialog.init()
pushService: (ph) ->
new Promise (resolve, reject) ->
arr = ph.split "/"
srv = arr[1]
app = arr[0]
if Ant.OS.APP[srv]
Ant.OS.PM.createProcess srv, Ant.OS.APP[srv]
.then (d) -> resolve d
.catch (e) -> reject __e e
else
Ant.OS.GUI.loadApp app
.then (a) ->
if not Ant.OS.APP[srv]
return reject Ant.OS.API.throwe __("Service not found: {0}", ph)
Ant.OS.PM.createProcess srv, Ant.OS.APP[srv]
.then (d) -> resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
appsByMime: (mime) ->
metas = ( v for k, v of Ant.OS.setting.system.packages when v and v.app )
mimes = ( m.mimes for m in metas when m)
apps = []
# search app by mimes
f = ( arr, idx ) ->
try
arr.filter (m, i) ->
if mime.match (new RegExp m, "g")
return false if (apps.indexOf metas[idx]) >= 0
apps.push metas[idx]
return false
return false
catch e
Ant.OS.announcer.osfail __("Error find app by mimes {0}", mime), e
( f m, i if m ) for m, i in mimes
return apps
appsWithServices: () ->
o = {}
o[k] = v for k, v of Ant.OS.setting.system.packages when v and v.services and v.services.length > 0
o
openWith: (it) ->
return unless it
return Ant.OS.GUI.launch it.app if it.type is "app" and it.app
return Ant.OS.announcer.osinfo __("Application {0} is not executable", it.text) if it.type is "app"
apps = Ant.OS.GUI.appsByMime ( if it.type is "dir" then "dir" else it.mime )
return Ant.OS.announcer.osinfo __("No application available to open {0}", it.filename) if apps.length is 0
return Ant.OS.GUI.launch apps[0].app, [it] if apps.length is 1
list = ( { text: e.app, icon: e.icon, iconclass: e.iconclass } for e in apps )
Ant.OS.GUI.openDialog("SelectionDialog", {
title: __("Open with"),
data: list
}).then ( d ) ->
Ant.OS.GUI.launch d.text, [ { path: it.path, type: it.type }]
forceLaunch: (app, args) ->
console.warn "This method is used for developing only, please use the launch method instead"
Ant.OS.GUI.unloadApp app
Ant.OS.GUI.launch app, args
unloadApp: (app) ->
Ant.OS.PM.killAll app, true
($ Ant.OS.APP[app].style).remove() if Ant.OS.APP[app] and Ant.OS.APP[app].style
delete Ant.OS.APP[app]
loadApp: (app) ->
new Promise (resolve, reject) ->
path = Ant.OS.setting.system.packages[app].path if Ant.OS.setting.system.packages[app].path
js = path + "/main.js"
js.asFileHandle().read("script")
.then (d) ->
# load app meta data
"#{path}/package.json".asFileHandle().read("json")
.then (data) ->
data.path = path
Ant.OS.APP[app].meta = data if Ant.OS.APP[app]
Ant.OS.APP[v].meta = data for v in data.services if data.services
#load css file
css = "#{path}/main.css"
css.asFileHandle().onready()
.then (d) ->
stamp = (new Date).timestamp()
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{Ant.OS.API.handle.get}/#{css}?stamp=#{stamp}" }
.appendTo 'head'
Ant.OS.APP[app].style = el[0] if Ant.OS.APP[app]
resolve app
.catch (e) -> resolve app
.catch (e) -> reject __e e
#ok app
.catch (e) -> reject __e e
launch: (app, args) ->
if not Ant.OS.APP[app]
# first load it
Ant.OS.GUI.loadApp(app).then (a) ->
Ant.OS.PM.createProcess a, Ant.OS.APP[a], args
.catch (e) ->
Ant.OS.announcer.osfail __("Unable to launch: {0}", app), e
else
# now launch it
if Ant.OS.APP[app]
Ant.OS.PM.createProcess app, Ant.OS.APP[app], args
.catch (e) ->
Ant.OS.announcer.osfail __("Unable to launch: {0}", app), e
dock: (app, meta) ->
# dock an application to a dock
# create a data object
data =
icon: null
iconclass: meta.iconclass || ""
app: app
onbtclick: () -> app.toggle()
# TODO: this path is not good, need to create a blob of it
data.icon = "#{meta.path}/#{meta.icon}" if meta.icon
# TODO: add default app icon class in system setting
# so that it can be themed
data.iconclass = "fa fa-cogs" if (not meta.icon) and (not meta.iconclass)
dock = $ "#sysdock"
app.init()
app.one "rendered", () ->
dock.get(0).newapp data
app.sysdock = dock.get(0)
app.appmenu = ($ "[data-id = 'appmenu']", "#syspanel")[0]
app.subscribe "systemlocalechange", (name) ->
app.updateLocale(name)
app.update()
app.subscribe "appregistry", ( m ) ->
app.applySetting m.data.m if (m.name is app.name)
toggleFullscreen: () ->
el = document.documentElement
if Ant.OS.GUI.fullscreen
return document.exitFullscreen() if document.exitFullscreen
return document.mozCancelFullScreen() if document.mozCancelFullScreen
return document.webkitExitFullscreen() if document.webkitExitFullscreen
return document.cancelFullScreen() if document.cancelFullScreen
else
return el.requestFullscreen() if el.requestFullscreen
return el.mozRequestFullScreen() if el.mozRequestFullScreen
return el.webkitRequestFullscreen() if el.webkitRequestFullscreen
return el.msRequestFullscreen() if el.msRequestFullscreen
undock: (app) ->
($ "#sysdock").get(0).removeapp app
attachservice: (srv) ->
($ "#syspanel")[0].attachservice srv
srv.init()
srv.subscribe "systemlocalechange", (name) -> srv.update()
detachservice: (srv) ->
($ "#syspanel")[0].detachservice srv
bindContextMenu: (event) ->
handle = (e) ->
if e.contextmenuHandle
e.contextmenuHandle event, ($ "#contextmenu")[0]
else
p = $(e).parent().get(0)
handle p if p isnt ($ "#workspace").get(0)
handle event.target
event.preventDefault()
bindKey: (k, f) ->
arr = k.split "-"
return unless arr.length is 2
fnk = arr[0].toUpperCase()
c = arr[1].toUpperCase()
return unless Ant.OS.GUI.shortcut[fnk]
Ant.OS.GUI.shortcut[fnk][c] = f
wallpaper: (obj) ->
if obj
Ant.OS.setting.appearance.wp = obj
wp = Ant.OS.setting.appearance.wp
$("body").css("background-image", "url(#{Ant.OS.API.handle.get}/#{wp.url})" )
.css("background-size", wp.size)
.css("background-repeat", wp.repeat)
showTooltip: (el, text, e) ->
el = el[0]
label = ($ "#systooltip")[0]
cb = (ev) ->
if $(ev.target).closest(el).length is 0
$(label).hide()
$(document).off "mousemove", cb
$(document).on "mousemove", cb
arr = text.split /:(.+)/
tip = text
tip = arr[1] if arr.length > 1
offset = $(el).offset()
w = $(el).width()
h = $(el).height()
label.set "text", tip
$(label).show()
switch arr[0]
when "cr" # center right of the element
left = offset.left + w + 5
top = offset.top + h / 2 - $(label).height() / 2
when "ct" #ceter top
left = offset.left + w / 2 - $(label).width() / 2
top = offset.top - $(label).height() - 5
else
return unless e
top = e.clientY + 5
left = e.clientX + 5
$(label).css "top", top + "px"
.css "left", left + "px"
initDM: ->
scheme = $.parseHTML Ant.OS.GUI.schemes.ws
($ "#wrapper").append scheme
Ant.OS.announcer.observable.one "sysdockloaded", () ->
($ window).bind 'keydown', (event) ->
dock = ($ "#sysdock")[0]
return unless dock
app = dock.get "selectedApp"
#return true unless app
c = String.fromCharCode(event.which).toUpperCase()
fnk = undefined
if event.ctrlKey
fnk = "CTRL"
else if event.metaKey
fnk = "META"
else if event.shiftKey
fnk = "SHIFT"
else if event.altKey
fnk = "ALT"
return unless fnk
r = if app then app.shortcut fnk, c, event else true
return event.preventDefault() if not r
return unless Ant.OS.GUI.shortcut[fnk]
return unless Ant.OS.GUI.shortcut[fnk][c]
Ant.OS.GUI.shortcut[fnk][c](event)
event.preventDefault()
# system menu and dock
$("#syspanel")[0].uify()
$("#sysdock")[0].uify()
$("#systooltip")[0].uify()
$("#contextmenu")[0].uify()
($ "#workspace").contextmenu (e) -> Ant.OS.GUI.bindContextMenu e
# tooltip
($ document).mouseover (e) ->
el = $(e.target).closest "[tooltip]"
return unless el.length > 0
Ant.OS.GUI.showTooltip el, ($(el).attr "tooltip"), e
fp = Ant.OS.setting.desktop.path.asFileHandle()
# desktop default file manager
desktop = $ Ant.OS.GUI.workspace
desktop[0].fetch = () ->
file = Ant.OS.setting.desktop.path.asFileHandle()
fn = () ->
file.read().then (d) ->
return Ant.OS.announcer.osfail d.error, (Ant.OS.API.throwe "OS.VFS"), d.error if d.error
items = []
$.each d.result, (i, v) ->
return if v.filename[0] is '.' and not Ant.OS.setting.desktop.showhidden
v.text = v.filename
#v.text = v.text.substring(0,9) + "..." ifv.text.length > 10
v.iconclass = v.type
items.push(v)
desktop[0].set "data", items
desktop[0].refresh()
file.onready()
.then () -> fn()
.catch ( e ) -> # try to create the path
console.log "#{file.path} not found"
name = file.basename
file.parent().asFileHandle().mk(name).then (r) ->
ex = Ant.OS.API.throwe "OS.VFS"
.catch (e) ->
Ant.OS.announcer.osfail e.toString(), e
desktop[0].ready = (e) ->
e.observable = Ant.OS.announcer
window.onresize = () ->
Ant.OS.announcer.trigger "desktopresize"
e.refresh()
desktop[0].set "onlistselect", (d) ->
($ "#sysdock").get(0).set "selectedApp", null
desktop[0].set "onlistdbclick", ( d ) ->
($ "#sysdock").get(0).set "selectedApp", null
it = desktop[0].get "selectedItem"
Ant.OS.GUI.openWith it.get("data")
#($ "#workingenv").on "click", (e) ->
# desktop[0].set "selected", -1
desktop.on "click", (e) ->
el = $(e.target).parent()
return unless el.length > 0
el = el.parent()
return unless el.length > 0
return unless el[0] is desktop[0]
desktop[0].unselect()
($ "#sysdock").get(0).set "selectedApp", null
desktop[0].contextmenuHandle = (e, m) ->
desktop[0].unselect() if e.target.tagName.toUpperCase() is "UL"
($ "#sysdock").get(0).set "selectedApp", null
menu = [
{ text: __("Open"), dataid: "desktop-open" },
{ text: __("Refresh"), dataid: "desktop-refresh" }
]
menu = menu.concat ( v for k, v of Ant.OS.setting.desktop.menu)
m.set "items", menu
m.set "onmenuselect", (evt) ->
item = evt.data.item.get("data")
switch item.dataid
when "desktop-open"
it = desktop[0].get "selectedItem"
return Ant.OS.GUI.openWith it.get("data") if it
it = Ant.OS.setting.desktop.path.asFileHandle()
it.mime = "dir"
it.type = "dir"
Ant.OS.GUI.openWith it
when "desktop-refresh"
desktop[0].fetch()
else
Ant.OS.GUI.launch item.app, item.args if item.app
m.show(e)
desktop[0].fetch()
Ant.OS.announcer.observable.on "VFS", (d) ->
return if ["read", "publish", "download"].includes d.data.m
desktop[0].fetch() if d.data.file.hash() is fp.hash() or d.data.file.parent().hash() is fp.hash()
Ant.OS.announcer.ostrigger "desktoploaded"
# mount it
desktop[0].uify()
refreshDesktop: () ->
($ Ant.OS.GUI.workspace)[0].fetch()
login: () ->
scheme = $.parseHTML Ant.OS.GUI.schemes.login
($ "#wrapper").append scheme
($ "#btlogin").click () ->
data =
username: ($ "#txtuser").val(),
password: ($ "#txtpass").val()
Ant.OS.API.handle.login data
.then (d) ->
return ($ "#login_error").html d.error if d.error
Ant.OS.GUI.startAntOS d.result
.catch (e) ->
($ "#login_error").html "Login: server error"
($ "#txtpass").keyup (e) ->
($ "#btlogin").click() if e.which is 13
($ "#txtuser").keyup (e) ->
($ "#btlogin").click() if e.which is 13
startAntOS: (conf) ->
# clean up things
Ant.OS.cleanup()
# get setting from conf
Ant.OS.systemSetting conf
#console.log Ant.OS.setting
# load theme
Ant.OS.GUI.loadTheme Ant.OS.setting.appearance.theme
Ant.OS.GUI.wallpaper()
Ant.OS.announcer.observable.one "syspanelloaded", () ->
# TODO load packages list then build system menu
Ant.OS.announcer.observable.on "systemlocalechange", (name) ->
($ "#syspanel")[0].update()
Ant.OS.API.packages.cache().then (ret) ->
if ret.result
Ant.OS.API.packages.fetch().then (r) ->
if r.result
for k, v of r.result
v.text = v.name
v.filename = k
v.type = "app"
v.mime = "antos/app"
v.icon = "#{v.path}/#{v.icon}" if v.icon
v.iconclass = "fa fa-adn" unless v.iconclass or v.icon
Ant.OS.setting.system.packages = if r.result then r.result else
# Ant.OS.GUI.refreshSystemMenu()
# Ant.OS.GUI.buildSystemMenu()
# push startup services
# TODO: get services list from user setting
Ant.OS.GUI.pushServices (v for v in Ant.OS.setting.system.startup.services)
(Ant.OS.GUI.launch a) for a in Ant.OS.setting.system.startup.apps
#Ant.OS.GUI.launch "DummyApp"
# initDM
Ant.OS.API.setLocale Ant.OS.setting.system.locale
.then () ->
Ant.OS.GUI.initDM()
Ant.OS.GUI.schemes = {}
Ant.OS.GUI.schemes.ws = """
<afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-float-list id = "desktop" dir="vertical" ></afx-float-list>
</div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu>
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
<textarea id="clipboard"></textarea>
"""
Ant.OS.GUI.schemes.login = """
<div id = "login_form">
<p>Welcome to AntOS, please login</p>
<input id = "txtuser" type = "text" value = "demo" />
<input id = "txtpass" type = "password" value = "demo" />
<button id = "btlogin">Login</button>
<div id = "login_error"></div>
</div>
"""

611
src/core/gui.js Normal file
View File

@ -0,0 +1,611 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
Ant.OS.GUI = {
subwindows: new Object(),
dialog: undefined,
fullscreen: false,
workspace: "#desktop",
shortcut: {
ALT: {},
CTRL: {},
SHIFT: {},
META: {}
},
htmlToScheme(html, app, parent) {
const scheme = $.parseHTML(html);
if (app.scheme) { $(app.scheme).remove(); }
($(parent)).append(scheme);
app.scheme = scheme[0];
scheme[0].uify(app.observable);
app.main();
return app.show();
},
loadScheme(path, app, parent) {
return path.asFileHandle().read()
.then(function(x) {
if (!x) { return null; }
return Ant.OS.GUI.htmlToScheme(x, app, parent);})
.catch(
(e) =>{
Ant.OS.announcer.oserror(__("Cannot load scheme: {0}", path), e);
console.log(e);
});
},
clearTheme() {
return $("head link#ostheme")
.attr("href", "");
},
loadTheme(name, force) {
if (force) { Ant.OS.GUI.clearTheme(); }
const path = `resources/themes/${name}/${name}.css`;
return $("head link#ostheme")
.attr("href", path);
},
pushServices(srvs) {
return new Promise(function(resolve, reject) {
if (!(srvs.length > 0)) { return resolve(); }
const srv = srvs.splice(0, 1)[0];
return Ant.OS.GUI.pushService(srv)
.then(d => Ant.OS.GUI.pushServices(srvs)
.then(() => resolve())
.catch(e => reject(__e(e)))).catch(function(e) {
Ant.OS.announcer.osfail(__("Unable to load: {0}", srv), e);
return Ant.OS.GUI.pushServices(srvs)
.then(() => resolve())
.catch(e => reject(__e(e)));
});
});
},
openDialog(d, data) {
return new Promise(function(resolve, reject) {
if (Ant.OS.GUI.dialog) {
Ant.OS.GUI.dialog.show();
return resolve();
}
if (!Ant.OS.GUI.subwindows[d]) {
const ex = Ant.OS.API.throwe("Dialog");
return reject(ex);
}
Ant.OS.GUI.dialog = new (Ant.OS.GUI.subwindows[d])();
Ant.OS.GUI.dialog.parent = Ant.OS.GUI;
Ant.OS.GUI.dialog.handle = resolve;
Ant.OS.GUI.dialog.reject = reject;
Ant.OS.GUI.dialog.pid = -1;
Ant.OS.GUI.dialog.data = data;
return Ant.OS.GUI.dialog.init();
});
},
pushService(ph) {
return new Promise(function(resolve, reject) {
const arr = ph.split("/");
const srv = arr[1];
const app = arr[0];
if (Ant.OS.APP[srv]) {
return Ant.OS.PM.createProcess(srv, Ant.OS.APP[srv])
.then(d => resolve(d))
.catch(e => reject(__e(e)));
} else {
return Ant.OS.GUI.loadApp(app)
.then(function(a) {
if (!Ant.OS.APP[srv]) {
return reject(Ant.OS.API.throwe(__("Service not found: {0}", ph)));
}
return Ant.OS.PM.createProcess(srv, Ant.OS.APP[srv])
.then(d => resolve(d))
.catch(e => reject(__e(e)));}).catch(e => reject(__e(e)));
}
});
},
appsByMime(mime) {
let m;
const metas = ((() => {
const result = [];
for (let k in Ant.OS.setting.system.packages) {
const v = Ant.OS.setting.system.packages[k];
if (v && v.app) {
result.push(v);
}
}
return result;
})());
const mimes = ((() => {
const result1 = [];
for (m of Array.from(metas)) { if (m) {
result1.push(m.mimes);
}
}
return result1;
})());
const apps = [];
// search app by mimes
const f = function( arr, idx ) {
try {
return arr.filter(function(m, i) {
if (mime.match((new RegExp(m, "g")))) {
if ((apps.indexOf(metas[idx])) >= 0) { return false; }
apps.push(metas[idx]);
return false;
}
return false;
});
} catch (e) {
return Ant.OS.announcer.osfail(__("Error find app by mimes {0}", mime), e);
}
};
for (let i = 0; i < mimes.length; i++) { m = mimes[i]; if (m) { f(m, i); } }
return apps;
},
appsWithServices() {
const o = {};
for (let k in Ant.OS.setting.system.packages) { const v = Ant.OS.setting.system.packages[k]; if (v && v.services && (v.services.length > 0)) { o[k] = v; } }
return o;
},
openWith(it) {
if (!it) { return; }
if ((it.type === "app") && it.app) { return Ant.OS.GUI.launch(it.app); }
if (it.type === "app") { return Ant.OS.announcer.osinfo(__("Application {0} is not executable", it.text)); }
const apps = Ant.OS.GUI.appsByMime(( it.type === "dir" ? "dir" : it.mime ));
if (apps.length === 0) { return Ant.OS.announcer.osinfo(__("No application available to open {0}", it.filename)); }
if (apps.length === 1) { return Ant.OS.GUI.launch(apps[0].app, [it]); }
const list = ( Array.from(apps).map((e) => ({ text: e.app, icon: e.icon, iconclass: e.iconclass })) );
return Ant.OS.GUI.openDialog("SelectionDialog", {
title: __("Open with"),
data: list
}).then(d => Ant.OS.GUI.launch(d.text, [ { path: it.path, type: it.type }]));
},
forceLaunch(app, args) {
console.warn("This method is used for developing only, please use the launch method instead");
Ant.OS.GUI.unloadApp(app);
return Ant.OS.GUI.launch(app, args);
},
unloadApp(app) {
Ant.OS.PM.killAll(app, true);
if (Ant.OS.APP[app] && Ant.OS.APP[app].style) { ($(Ant.OS.APP[app].style)).remove(); }
return delete Ant.OS.APP[app];
},
loadApp(app) {
return new Promise(function(resolve, reject) {
let path;
if (Ant.OS.setting.system.packages[app].path) { ({
path
} = Ant.OS.setting.system.packages[app]); }
const js = path + "/main.js";
return js.asFileHandle().read("script")
.then(d => // load app meta data
`${path}/package.json`.asFileHandle().read("json")
.then(function(data) {
data.path = path;
if (Ant.OS.APP[app]) { Ant.OS.APP[app].meta = data; }
if (data.services) { for (let v of Array.from(data.services)) { Ant.OS.APP[v].meta = data; } }
//load css file
const css = `${path}/main.css`;
return css.asFileHandle().onready()
.then(function(d) {
const stamp = (new Date).timestamp();
const el = $('<link>', { rel: 'stylesheet', type: 'text/css', 'href': `${Ant.OS.API.handle.get}/${css}?stamp=${stamp}` })
.appendTo('head');
if (Ant.OS.APP[app]) { Ant.OS.APP[app].style = el[0]; }
return resolve(app);}).catch(e => resolve(app));}).catch(e => reject(__e(e)))).catch(e => reject(__e(e)));
});
},
launch(app, args) {
if (!Ant.OS.APP[app]) {
// first load it
return Ant.OS.GUI.loadApp(app).then(a => Ant.OS.PM.createProcess(a, Ant.OS.APP[a], args)).catch(e => Ant.OS.announcer.osfail(__("Unable to launch: {0}", app), e));
} else {
// now launch it
if (Ant.OS.APP[app]) {
return Ant.OS.PM.createProcess(app, Ant.OS.APP[app], args)
.catch(e => Ant.OS.announcer.osfail(__("Unable to launch: {0}", app), e));
}
}
},
dock(app, meta) {
// dock an application to a dock
// create a data object
const data = {
icon: null,
iconclass: meta.iconclass || "",
app,
onbtclick() { return app.toggle(); }
};
// TODO: this path is not good, need to create a blob of it
if (meta.icon) { data.icon = `${meta.path}/${meta.icon}`; }
// TODO: add default app icon class in system setting
// so that it can be themed
if ((!meta.icon) && (!meta.iconclass)) { data.iconclass = "fa fa-cogs"; }
const dock = $("#sysdock");
app.init();
return app.one("rendered", function() {
dock.get(0).newapp(data);
app.sysdock = dock.get(0);
app.appmenu = ($("[data-id = 'appmenu']", "#syspanel"))[0];
app.subscribe("systemlocalechange", function(name) {
app.updateLocale(name);
return app.update();
});
return app.subscribe("appregistry", function( m ) {
if (m.name === app.name) { return app.applySetting(m.data.m); }
});
});
},
toggleFullscreen() {
const el = document.documentElement;
if (Ant.OS.GUI.fullscreen) {
if (document.exitFullscreen) { return document.exitFullscreen(); }
if (document.mozCancelFullScreen) { return document.mozCancelFullScreen(); }
if (document.webkitExitFullscreen) { return document.webkitExitFullscreen(); }
if (document.cancelFullScreen) { return document.cancelFullScreen(); }
} else {
if (el.requestFullscreen) { return el.requestFullscreen(); }
if (el.mozRequestFullScreen) { return el.mozRequestFullScreen(); }
if (el.webkitRequestFullscreen) { return el.webkitRequestFullscreen(); }
if (el.msRequestFullscreen) { return el.msRequestFullscreen(); }
}
},
undock(app) {
return ($("#sysdock")).get(0).removeapp(app);
},
attachservice(srv) {
($("#syspanel"))[0].attachservice(srv);
srv.init();
return srv.subscribe("systemlocalechange", name => srv.update());
},
detachservice(srv) {
return ($("#syspanel"))[0].detachservice(srv);
},
bindContextMenu(event) {
var handle = function(e) {
if (e.contextmenuHandle) {
return e.contextmenuHandle(event, ($("#contextmenu"))[0]);
} else {
const p = $(e).parent().get(0);
if (p !== ($("#workspace")).get(0)) { return handle(p); }
}
};
handle(event.target);
return event.preventDefault();
},
bindKey(k, f) {
const arr = k.split("-");
if (arr.length !== 2) { return; }
const fnk = arr[0].toUpperCase();
const c = arr[1].toUpperCase();
if (!Ant.OS.GUI.shortcut[fnk]) { return; }
return Ant.OS.GUI.shortcut[fnk][c] = f;
},
wallpaper(obj) {
if (obj) {
Ant.OS.setting.appearance.wp = obj;
}
const {
wp
} = Ant.OS.setting.appearance;
return $("body").css("background-image", `url(${Ant.OS.API.handle.get}/${wp.url})` )
.css("background-size", wp.size)
.css("background-repeat", wp.repeat);
},
showTooltip(el, text, e) {
let left, top;
el = el[0];
const label = ($("#systooltip"))[0];
var cb = function(ev) {
if ($(ev.target).closest(el).length === 0) {
$(label).hide();
return $(document).off("mousemove", cb);
}
};
$(document).on("mousemove", cb);
const arr = text.split(/:(.+)/);
let tip = text;
if (arr.length > 1) { tip = arr[1]; }
const offset = $(el).offset();
const w = $(el).width();
const h = $(el).height();
label.set("text", tip);
$(label).show();
switch (arr[0]) {
case "cr": // center right of the element
left = offset.left + w + 5;
top = (offset.top + (h / 2)) - ($(label).height() / 2);
break;
case "ct": //ceter top
left = (offset.left + (w / 2)) - ($(label).width() / 2);
top = offset.top - $(label).height() - 5;
break;
default:
if (!e) { return; }
top = e.clientY + 5;
left = e.clientX + 5;
}
return $(label).css("top", top + "px")
.css("left", left + "px");
},
initDM() {
const scheme = $.parseHTML(Ant.OS.GUI.schemes.ws);
($("#wrapper")).append(scheme);
Ant.OS.announcer.observable.one("sysdockloaded", () => ($(window)).bind('keydown', function(event) {
const dock = ($("#sysdock"))[0];
if (!dock) { return; }
const app = dock.get("selectedApp");
//return true unless app
const c = String.fromCharCode(event.which).toUpperCase();
let fnk = undefined;
if (event.ctrlKey) {
fnk = "CTRL";
} else if (event.metaKey) {
fnk = "META";
} else if (event.shiftKey) {
fnk = "SHIFT";
} else if (event.altKey) {
fnk = "ALT";
}
if (!fnk) { return; }
const r = app ? app.shortcut(fnk, c, event) : true;
if (!r) { return event.preventDefault(); }
if (!Ant.OS.GUI.shortcut[fnk]) { return; }
if (!Ant.OS.GUI.shortcut[fnk][c]) { return; }
Ant.OS.GUI.shortcut[fnk][c](event);
return event.preventDefault();
}));
// system menu and dock
$("#syspanel")[0].uify();
$("#sysdock")[0].uify();
$("#systooltip")[0].uify();
$("#contextmenu")[0].uify();
($("#workspace")).contextmenu(e => Ant.OS.GUI.bindContextMenu(e));
// tooltip
($(document)).mouseover(function(e) {
const el = $(e.target).closest("[tooltip]");
if (!(el.length > 0)) { return; }
return Ant.OS.GUI.showTooltip(el, ($(el).attr("tooltip")), e);
});
const fp = Ant.OS.setting.desktop.path.asFileHandle();
// desktop default file manager
const desktop = $(Ant.OS.GUI.workspace);
desktop[0].fetch = function() {
const file = Ant.OS.setting.desktop.path.asFileHandle();
const fn = () => file.read().then(function(d) {
if (d.error) { return Ant.OS.announcer.osfail(d.error, (Ant.OS.API.throwe("OS.VFS")), d.error); }
const items = [];
$.each(d.result, function(i, v) {
if ((v.filename[0] === '.') && !Ant.OS.setting.desktop.showhidden) { return; }
v.text = v.filename;
//v.text = v.text.substring(0,9) + "..." ifv.text.length > 10
v.iconclass = v.type;
return items.push(v);
});
desktop[0].set("data", items);
return desktop[0].refresh();
});
return file.onready()
.then(() => fn())
.catch(function( e ) { // try to create the path
console.log(`${file.path} not found`);
const name = file.basename;
return file.parent().asFileHandle().mk(name).then(function(r) {
let ex;
return ex = Ant.OS.API.throwe("OS.VFS");}).catch(e => Ant.OS.announcer.osfail(e.toString(), e));
});
};
desktop[0].ready = function(e) {
e.observable = Ant.OS.announcer;
window.onresize = function() {
Ant.OS.announcer.trigger("desktopresize");
return e.refresh();
};
desktop[0].set("onlistselect", d => ($("#sysdock")).get(0).set("selectedApp", null));
desktop[0].set("onlistdbclick", function( d ) {
($("#sysdock")).get(0).set("selectedApp", null);
const it = desktop[0].get("selectedItem");
return Ant.OS.GUI.openWith(it.get("data"));
});
//($ "#workingenv").on "click", (e) ->
// desktop[0].set "selected", -1
desktop.on("click", function(e) {
let el = $(e.target).parent();
if (!(el.length > 0)) { return; }
el = el.parent();
if (!(el.length > 0)) { return; }
if (el[0] !== desktop[0]) { return; }
desktop[0].unselect();
return ($("#sysdock")).get(0).set("selectedApp", null);
});
desktop[0].contextmenuHandle = function(e, m) {
if (e.target.tagName.toUpperCase() === "UL") { desktop[0].unselect(); }
($("#sysdock")).get(0).set("selectedApp", null);
let menu = [
{ text: __("Open"), dataid: "desktop-open" },
{ text: __("Refresh"), dataid: "desktop-refresh" }
];
menu = menu.concat(((() => {
const result = [];
for (let k in Ant.OS.setting.desktop.menu) {
const v = Ant.OS.setting.desktop.menu[k];
result.push(v);
}
return result;
})()));
m.set("items", menu);
m.set("onmenuselect", function(evt) {
const item = evt.data.item.get("data");
switch (item.dataid) {
case "desktop-open":
var it = desktop[0].get("selectedItem");
if (it) { return Ant.OS.GUI.openWith(it.get("data")); }
it = Ant.OS.setting.desktop.path.asFileHandle();
it.mime = "dir";
it.type = "dir";
return Ant.OS.GUI.openWith(it);
case "desktop-refresh":
return desktop[0].fetch();
default:
if (item.app) { return Ant.OS.GUI.launch(item.app, item.args); }
}
});
return m.show(e);
};
desktop[0].fetch();
Ant.OS.announcer.observable.on("VFS", function(d) {
if (["read", "publish", "download"].includes(d.data.m)) { return; }
if ((d.data.file.hash() === fp.hash()) || (d.data.file.parent().hash() === fp.hash())) { return desktop[0].fetch(); }
});
return Ant.OS.announcer.ostrigger("desktoploaded");
};
// mount it
return desktop[0].uify();
},
refreshDesktop() {
return ($(Ant.OS.GUI.workspace))[0].fetch();
},
login() {
const scheme = $.parseHTML(Ant.OS.GUI.schemes.login);
($("#wrapper")).append(scheme);
($("#btlogin")).click(function() {
const data = {
username: ($("#txtuser")).val(),
password: ($("#txtpass")).val()
};
return Ant.OS.API.handle.login(data)
.then(function(d) {
if (d.error) { return ($("#login_error")).html(d.error); }
return Ant.OS.GUI.startAntOS(d.result);}).catch(e => ($("#login_error")).html("Login: server error"));
});
($("#txtpass")).keyup(function(e) {
if (e.which === 13) { return ($("#btlogin")).click(); }
});
return ($("#txtuser")).keyup(function(e) {
if (e.which === 13) { return ($("#btlogin")).click(); }
});
},
startAntOS(conf) {
// clean up things
Ant.OS.cleanup();
// get setting from conf
Ant.OS.systemSetting(conf);
//console.log Ant.OS.setting
// load theme
Ant.OS.GUI.loadTheme(Ant.OS.setting.appearance.theme);
Ant.OS.GUI.wallpaper();
Ant.OS.announcer.observable.one("syspanelloaded", function() {
// TODO load packages list then build system menu
Ant.OS.announcer.observable.on("systemlocalechange", name => ($("#syspanel"))[0].update());
return Ant.OS.API.packages.cache().then(function(ret) {
if (ret.result) {
return Ant.OS.API.packages.fetch().then(function(r) {
let v;
if (r.result) {
for (let k in r.result) {
v = r.result[k];
v.text = v.name;
v.filename = k;
v.type = "app";
v.mime = "antos/app";
if (v.icon) { v.icon = `${v.path}/${v.icon}`; }
if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; }
}
}
Ant.OS.setting.system.packages = r.result ? r.result : undefined;
// Ant.OS.GUI.refreshSystemMenu()
// Ant.OS.GUI.buildSystemMenu()
// push startup services
// TODO: get services list from user setting
Ant.OS.GUI.pushServices(((() => {
const result = [];
for (v of Array.from(Ant.OS.setting.system.startup.services)) { result.push(v);
}
return result;
})()));
return Array.from(Ant.OS.setting.system.startup.apps).map((a) => (Ant.OS.GUI.launch(a)));
});
}
});
});
//Ant.OS.GUI.launch "DummyApp"
// initDM
return Ant.OS.API.setLocale(Ant.OS.setting.system.locale)
.then(() => Ant.OS.GUI.initDM());
}
};
Ant.OS.GUI.schemes = {};
Ant.OS.GUI.schemes.ws = `\
<afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-float-list id = "desktop" dir="vertical" ></afx-float-list>
</div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu>
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
<textarea id="clipboard"></textarea>\
`;
Ant.OS.GUI.schemes.login = `\
<div id = "login_form">
<p>Welcome to AntOS, please login</p>
<input id = "txtuser" type = "text" value = "demo" />
<input id = "txtpass" type = "password" value = "demo" />
<button id = "btlogin">Login</button>
<div id = "login_error"></div>
</div>\
`;

View File

@ -1,105 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
Ant.OS.API.HOST = Ant.location.hostname + (if Ant.location.port then":#{Ant.location.port}" else "")
Ant.OS.API.REST = "#{Ant.location.protocol}//#{Ant.OS.API.HOST}"
Ant.OS.API.handle =
# get file, require authentification
get: "#{Ant.OS.API.REST}/VFS/get"
# get shared file with publish
shared: "#{Ant.OS.API.REST}/VFS/shared"
scandir: (p) ->
path = "#{Ant.OS.API.REST}/VFS/scandir"
Ant.OS.API.post path, { path: p }
mkdir: (p) ->
path = "#{Ant.OS.API.REST}/VFS/mkdir"
Ant.OS.API.post path, { path: p }
sharefile: (p, pub) ->
path = "#{Ant.OS.API.REST}/VFS/publish"
Ant.OS.API.post path, { path: p , publish: pub }
fileinfo: (p) ->
path = "#{Ant.OS.API.REST}/VFS/fileinfo"
Ant.OS.API.post path, { path: p }
readfile: (p, t) ->
path = "#{Ant.OS.API.REST}/VFS/get/"
Ant.OS.API.get path + p, t
move: (s, d) ->
path = "#{Ant.OS.API.REST}/VFS/move"
Ant.OS.API.post path, { src: s, dest: d }
delete: (p) ->
path = "#{Ant.OS.API.REST}/VFS/delete"
Ant.OS.API.post path, { path: p }
fileblob: (p) ->
path = "#{Ant.OS.API.REST}/VFS/get/"
Ant.OS.API.blob path + p
packages: (d) ->
path = "#{Ant.OS.API.REST}/system/packages"
Ant.OS.API.post path, d
upload: (d) ->
path = "#{Ant.OS.API.REST}/VFS/upload"
Ant.OS.API.upload path, d
write: (p, d) ->
path = "#{Ant.OS.API.REST}/VFS/write"
Ant.OS.API.post path, { path: p, data: d }
scanapp: (p, c ) ->
path = "#{Ant.OS.API.REST}/system/application"
apigateway: (d, ws) ->
if ws
new Promise (resolve, reject) ->
try
path = "#{Ant.OS.API.HOST}/system/apigateway?ws=1"
proto = if window.location.protocol is "https:" then "wss://" else "ws://"
socket = new WebSocket proto + path
resolve(socket)
catch e
reject __e e
else
path = "#{Ant.OS.API.REST}/system/apigateway?ws=0"
Ant.OS.API.post path, d
auth: () ->
p = "#{Ant.OS.API.REST}/user/auth"
Ant.OS.API.post p, {}
login: (d) ->
p = "#{Ant.OS.API.REST}/user/login"
Ant.OS.API.post p, d
logout: () ->
p = "#{Ant.OS.API.REST}/user/logout"
Ant.OS.API.post p, {}
setting: () ->
p = "#{Ant.OS.API.REST}/system/settings"
Ant.OS.API.post p, Ant.OS.setting
dbquery: (cmd, d) ->
path = "#{Ant.OS.API.REST}/VDB/#{cmd}"
Ant.OS.API.post path, d

View File

@ -0,0 +1,133 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
Ant.OS.API.HOST = Ant.location.hostname + (Ant.location.port ?`:${Ant.location.port}` : "");
Ant.OS.API.REST = `${Ant.location.protocol}//${Ant.OS.API.HOST}`;
Ant.OS.API.handle = {
// get file, require authentification
get: `${Ant.OS.API.REST}/VFS/get`,
// get shared file with publish
shared: `${Ant.OS.API.REST}/VFS/shared`,
scandir(p) {
const path = `${Ant.OS.API.REST}/VFS/scandir`;
return Ant.OS.API.post(path, { path: p });
},
mkdir(p) {
const path = `${Ant.OS.API.REST}/VFS/mkdir`;
return Ant.OS.API.post(path, { path: p });
},
sharefile(p, pub) {
const path = `${Ant.OS.API.REST}/VFS/publish`;
return Ant.OS.API.post(path, { path: p , publish: pub });
},
fileinfo(p) {
const path = `${Ant.OS.API.REST}/VFS/fileinfo`;
return Ant.OS.API.post(path, { path: p });
},
readfile(p, t) {
const path = `${Ant.OS.API.REST}/VFS/get/`;
return Ant.OS.API.get(path + p, t);
},
move(s, d) {
const path = `${Ant.OS.API.REST}/VFS/move`;
return Ant.OS.API.post(path, { src: s, dest: d });
},
delete(p) {
const path = `${Ant.OS.API.REST}/VFS/delete`;
return Ant.OS.API.post(path, { path: p });
},
fileblob(p) {
const path = `${Ant.OS.API.REST}/VFS/get/`;
return Ant.OS.API.blob(path + p);
},
packages(d) {
const path = `${Ant.OS.API.REST}/system/packages`;
return Ant.OS.API.post(path, d);
},
upload(d) {
const path = `${Ant.OS.API.REST}/VFS/upload`;
return Ant.OS.API.upload(path, d);
},
write(p, d) {
const path = `${Ant.OS.API.REST}/VFS/write`;
return Ant.OS.API.post(path, { path: p, data: d });
},
scanapp(p, c ) {
let path;
return path = `${Ant.OS.API.REST}/system/application`;
},
apigateway(d, ws) {
if (ws) {
return new Promise(function(resolve, reject) {
try {
const path = `${Ant.OS.API.HOST}/system/apigateway?ws=1`;
const proto = window.location.protocol === "https:" ? "wss://" : "ws://";
const socket = new WebSocket(proto + path);
return resolve(socket);
} catch (e) {
return reject(__e(e));
}
});
} else {
const path = `${Ant.OS.API.REST}/system/apigateway?ws=0`;
return Ant.OS.API.post(path, d);
}
},
auth() {
const p = `${Ant.OS.API.REST}/user/auth`;
return Ant.OS.API.post(p, {});
},
login(d) {
const p = `${Ant.OS.API.REST}/user/login`;
return Ant.OS.API.post(p, d);
},
logout() {
const p = `${Ant.OS.API.REST}/user/logout`;
return Ant.OS.API.post(p, {});
},
setting() {
const p = `${Ant.OS.API.REST}/system/settings`;
return Ant.OS.API.post(p, Ant.OS.setting);
},
dbquery(cmd, d) {
const path = `${Ant.OS.API.REST}/VDB/${cmd}`;
return Ant.OS.API.post(path, d);
}
};

View File

@ -1,23 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
Ant.OS.API.handle =
scandir: (p, c ) ->
path = 'resources/jsons/scandir.json'
Ant.OS.API.get path , c, (e, s) ->
Ant.OS.announcer.osfail "System fall: Cannot read #{path}", e, s

View File

@ -0,0 +1,29 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
Ant.OS.API.handle = {
scandir(p, c ) {
const path = 'resources/jsons/scandir.json';
return Ant.OS.API.get(path , c, (e, s) => Ant.OS.announcer.osfail(`System fall: Cannot read ${path}`, e, s));
}
};

View File

@ -1,100 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
self.OS.systemSetting = (conf) ->
Ant.OS.setting.desktop = conf.desktop if conf.desktop
Ant.OS.setting.applications = conf.applications if conf.applications
Ant.OS.setting.appearance = conf.appearance if conf.appearance
Ant.OS.setting.appearance.wp = {
url: "os://resources/themes/system/wp/wp3.jpg",
size: "cover",
repeat: "repeat"
} unless Ant.OS.setting.appearance.wp
Ant.OS.setting.appearance.wps = [] unless Ant.OS.setting.appearance.wps
Ant.OS.setting.applications = {} unless Ant.OS.setting.applications
Ant.OS.setting.user = conf.user
Ant.OS.setting.VFS = conf.VFS if conf.VFS
Ant.OS.setting.desktop.path = "home://.desktop" unless Ant.OS.setting.desktop.path
Ant.OS.setting.desktop.menu = {} unless Ant.OS.setting.desktop.menu
Ant.OS.setting.VFS.mountpoints = [
#TODO: multi app try to write to this object, it neet to be cloned
{ text: "__(Applications)", path: 'app://', iconclass: "fa fa-adn", type: "app" },
{ text: "__(Home)", path: 'home://', iconclass: "fa fa-home", type: "fs" },
{ text: "__(Desktop)", path: Ant.OS.setting.desktop.path , iconclass: "fa fa-desktop", type: "fs" },
{ text: "__(OS)", path: 'os://', iconclass: "fa fa-inbox", type: "fs" },
{ text: "__(Google Drive)", path: 'gdv://', iconclass: "fa fa-inbox", type: "fs" },
{ text: "__(Shared)", path: 'shared://' , iconclass: "fa fa-share-square", type: "fs" }
] if not Ant.OS.setting.VFS.mountpoints
Ant.OS.setting.system = conf.system if conf.system
Ant.OS.setting.system.startup = {
services: [
"Syslog/PushNotification",
"Syslog/Calendar"
],
apps: []
} if not Ant.OS.setting.system.startup
if not Ant.OS.setting.system.error_report
Ant.OS.setting.system.error_report = "https://os.iohub.dev/report"
Ant.OS.setting.system.pkgpaths = {
user: "home://.packages",
system: "os://packages"
} unless Ant.OS.setting.system.pkgpaths
Ant.OS.setting.system.locale = "en_GB" unless Ant.OS.setting.system.locale
Ant.OS.setting.system.menu = {} unless Ant.OS.setting.system.menu
Ant.OS.setting.system.repositories = [] unless Ant.OS.setting.system.repositories
Ant.OS.setting.appearance.theme = "antos_dark" unless Ant.OS.setting.appearance.theme
if not Ant.OS.setting.appearance.themes
Ant.OS.setting.appearance.themes = [
{
text: "AntOS light",
name: "antos_light"
},
{
text: "AntOS dark",
name: "antos_dark"
}
]
Ant.OS.setting.VFS.gdrive = {
CLIENT_ID: ""
API_KEY: ""
apilink: "https://apis.google.com/js/api.js"
DISCOVERY_DOCS: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"]
SCOPES: 'https://www.googleapis.com/auth/drive'
} unless Ant.OS.setting.VFS.gdrive
#search for app
Ant.OS.API.onsearch "__(Applications)", (t) ->
ar = []
term = new RegExp t, "i"
for k, v of Ant.OS.setting.system.packages when v.app
if (v.name.match term) or (v.description and v.description.match term)
v1 = {}
v1[k1] = e for k1, e of v when k1 isnt "selected"
v1.detail = [{ text: v1.path }]
v1.complex = true
ar.push v1
else if v.mimes
for m in v.mimes
if t.match (new RegExp m, "g")
v1 = {}
v1[k1] = v[k1] for k1, e of v when k1 isnt "selected"
v1.detail = [{ text: v1.path }]
v1.complex = true
ar.push v1
break
return ar

118
src/core/settings.js Normal file
View File

@ -0,0 +1,118 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
self.OS.systemSetting = function(conf) {
if (conf.desktop) { Ant.OS.setting.desktop = conf.desktop; }
if (conf.applications) { Ant.OS.setting.applications = conf.applications; }
if (conf.appearance) { Ant.OS.setting.appearance = conf.appearance; }
if (!Ant.OS.setting.appearance.wp) { Ant.OS.setting.appearance.wp = {
url: "os://resources/themes/system/wp/wp3.jpg",
size: "cover",
repeat: "repeat"
}; }
if (!Ant.OS.setting.appearance.wps) { Ant.OS.setting.appearance.wps = []; }
if (!Ant.OS.setting.applications) { Ant.OS.setting.applications = {}; }
Ant.OS.setting.user = conf.user;
if (conf.VFS) { Ant.OS.setting.VFS = conf.VFS; }
if (!Ant.OS.setting.desktop.path) { Ant.OS.setting.desktop.path = "home://.desktop"; }
if (!Ant.OS.setting.desktop.menu) { Ant.OS.setting.desktop.menu = {}; }
if (!Ant.OS.setting.VFS.mountpoints) { Ant.OS.setting.VFS.mountpoints = [
//TODO: multi app try to write to this object, it neet to be cloned
{ text: "__(Applications)", path: 'app://', iconclass: "fa fa-adn", type: "app" },
{ text: "__(Home)", path: 'home://', iconclass: "fa fa-home", type: "fs" },
{ text: "__(Desktop)", path: Ant.OS.setting.desktop.path , iconclass: "fa fa-desktop", type: "fs" },
{ text: "__(OS)", path: 'os://', iconclass: "fa fa-inbox", type: "fs" },
{ text: "__(Google Drive)", path: 'gdv://', iconclass: "fa fa-inbox", type: "fs" },
{ text: "__(Shared)", path: 'shared://' , iconclass: "fa fa-share-square", type: "fs" }
]; }
if (conf.system) { Ant.OS.setting.system = conf.system; }
if (!Ant.OS.setting.system.startup) { Ant.OS.setting.system.startup = {
services: [
"Syslog/PushNotification",
"Syslog/Calendar"
],
apps: []
}; }
if (!Ant.OS.setting.system.error_report) {
Ant.OS.setting.system.error_report = "https://os.iohub.dev/report";
}
if (!Ant.OS.setting.system.pkgpaths) { Ant.OS.setting.system.pkgpaths = {
user: "home://.packages",
system: "os://packages"
}; }
if (!Ant.OS.setting.system.locale) { Ant.OS.setting.system.locale = "en_GB"; }
if (!Ant.OS.setting.system.menu) { Ant.OS.setting.system.menu = {}; }
if (!Ant.OS.setting.system.repositories) { Ant.OS.setting.system.repositories = []; }
if (!Ant.OS.setting.appearance.theme) { Ant.OS.setting.appearance.theme = "antos_dark"; }
if (!Ant.OS.setting.appearance.themes) {
Ant.OS.setting.appearance.themes = [
{
text: "AntOS light",
name: "antos_light"
},
{
text: "AntOS dark",
name: "antos_dark"
}
];
}
if (!Ant.OS.setting.VFS.gdrive) { Ant.OS.setting.VFS.gdrive = {
CLIENT_ID: "",
API_KEY: "",
apilink: "https://apis.google.com/js/api.js",
DISCOVERY_DOCS: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"],
SCOPES: 'https://www.googleapis.com/auth/drive'
}; }
//search for app
return Ant.OS.API.onsearch("__(Applications)", function(t) {
const ar = [];
const term = new RegExp(t, "i");
for (let k in Ant.OS.setting.system.packages) {
const v = Ant.OS.setting.system.packages[k];
if (v.app) {var e, k1, v1;
if ((v.name.match(term)) || (v.description && v.description.match(term))) {
v1 = {};
for (k1 in v) { e = v[k1]; if (k1 !== "selected") { v1[k1] = e; } }
v1.detail = [{ text: v1.path }];
v1.complex = true;
ar.push(v1);
} else if (v.mimes) {
for (let m of Array.from(v.mimes)) {
if (t.match((new RegExp(m, "g")))) {
v1 = {};
for (k1 in v) { e = v[k1]; if (k1 !== "selected") { v1[k1] = v[k1]; } }
v1.detail = [{ text: v1.path }];
v1.complex = true;
ar.push(v1);
break;
}
}
}
}
}
return ar;
});
};

View File

@ -1,64 +0,0 @@
class AppDockTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "onappselect", (e) ->
@setopt "items", []
@setopt "selectedApp", undefined
@root.newapp = (a) => @addApp a
@root.removeapp = (a) => @removeApp a
__selectedApp__: (v) ->
el = undefined
for it in @get("items")
it.app.blur()
$(it.domel).removeClass()
el = it.domel if v and v is it.app
return unless el
$(el).addClass "selected"
$(Ant.OS.GUI.workspace)[0].unselect()
addApp: (item) ->
@get("items").push item
el = $("<afx-button>")
el.appendTo @root
el[0].uify @observable
el[0].set "*", item
el.attr "tooltip", "cr:#{item.app.title()}"
item.domel = el[0]
el[0].set "onbtclick", (e) =>
e.id = @aid()
e.data.app = item
item.app.show()
@set "selectedApp", item.app
removeApp: (a) ->
i = -1
for v, k in @get "items"
if v.app.pid == a.pid
i = k
break
if i != -1
items = @get("items")
delete items[i].app
items.splice(i, 1)
$($(@root).children()[i]).remove()
mount: () ->
@root.contextmenuHandle = (e, m) =>
return if e.target is @root
bt = $(e.target).closest "afx-button"
app = bt[0].get "app"
m.set "items", [
{ text: "__(Show)", dataid: "show" },
{ text: "__(Hide)", dataid: "hide" },
{ text: "__(Close)", dataid: "quit" }
]
m.set "onmenuselect", (evt) ->
item = evt.data.item.get("data")
if(app[item.dataid])
app[item.dataid]()
m.show(e)
Ant.OS.announcer.trigger "sysdockloaded"
Ant.OS.GUI.define "afx-apps-dock", AppDockTag

View File

@ -0,0 +1,86 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class AppDockTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("onappselect", function(e) {});
this.setopt("items", []);
this.setopt("selectedApp", undefined);
this.root.newapp = a => this.addApp(a);
this.root.removeapp = a => this.removeApp(a);
}
__selectedApp__(v) {
let el = undefined;
for (let it of Array.from(this.get("items"))) {
it.app.blur();
$(it.domel).removeClass();
if (v && (v === it.app)) { el = it.domel; }
}
if (!el) { return; }
$(el).addClass("selected");
return $(Ant.OS.GUI.workspace)[0].unselect();
}
addApp(item) {
this.get("items").push(item);
const el = $("<afx-button>");
el.appendTo(this.root);
el[0].uify(this.observable);
el[0].set("*", item);
el.attr("tooltip", `cr:${item.app.title()}`);
item.domel = el[0];
el[0].set("onbtclick", e => {
e.id = this.aid();
e.data.app = item;
return item.app.show();
});
return this.set("selectedApp", item.app);
}
removeApp(a) {
let i = -1;
const iterable = this.get("items");
for (let k = 0; k < iterable.length; k++) {
const v = iterable[k];
if (v.app.pid === a.pid) {
i = k;
break;
}
}
if (i !== -1) {
const items = this.get("items");
delete items[i].app;
items.splice(i, 1);
return $($(this.root).children()[i]).remove();
}
}
mount() {
this.root.contextmenuHandle = (e, m) => {
if (e.target === this.root) { return; }
const bt = $(e.target).closest("afx-button");
const app = bt[0].get("app");
m.set("items", [
{ text: "__(Show)", dataid: "show" },
{ text: "__(Hide)", dataid: "hide" },
{ text: "__(Close)", dataid: "quit" }
]);
m.set("onmenuselect", function(evt) {
const item = evt.data.item.get("data");
if(app[item.dataid]) {
return app[item.dataid]();
}
});
return m.show(e);
};
return Ant.OS.announcer.trigger("sysdockloaded");
}
}
Ant.OS.GUI.define("afx-apps-dock", AppDockTag);

View File

@ -1,58 +0,0 @@
class ButtonTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "color", undefined
@setopt "icon", undefined
@setopt "iconclass", undefined
@setopt "text", ""
@setopt "enable", true
@setopt "selected", false
@setopt "toggle", false
@setopt "onbtclick", () ->
__color__: (v) ->
@refs.label.set "color", v
__icon__: (v) ->
@refs.label.set "icon", v
__iconclass__: (v) ->
@refs.label.set "iconclass", v
__text__: (v) ->
@refs.label.set "text", v
__enable__: (v) ->
$(@refs.button).prop "disabled", !(@get "enable")
__selected__: (v) ->
$(@button).removeClass()
$(@button).addClass "selected" if v
mount: () ->
@root.trigger = () =>
$(@refs.button).trigger "click"
$(@refs.button).click (e) =>
@btclickhd e
btclickhd: (e) ->
hd = @get "onbtclick"
if typeof hd is "string"
eval hd
else if hd
hd { id: @aid(), data: e }
@observable.trigger "btclick", { id: @aid(), data: e }
if @toggle
@set "selected", !@get "selected"
layout: () ->
[{
el: "Button", ref: "button", children: [
{ el: "afx-label", ref: "label" }
]
}]
Ant.OS.GUI.define "afx-button", ButtonTag

View File

@ -0,0 +1,78 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class ButtonTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("color", undefined);
this.setopt("icon", undefined);
this.setopt("iconclass", undefined);
this.setopt("text", "");
this.setopt("enable", true);
this.setopt("selected", false);
this.setopt("toggle", false);
this.setopt("onbtclick", function() {});
}
__color__(v) {
return this.refs.label.set("color", v);
}
__icon__(v) {
return this.refs.label.set("icon", v);
}
__iconclass__(v) {
return this.refs.label.set("iconclass", v);
}
__text__(v) {
return this.refs.label.set("text", v);
}
__enable__(v) {
return $(this.refs.button).prop("disabled", !(this.get("enable")));
}
__selected__(v) {
$(this.button).removeClass();
if (v) { return $(this.button).addClass("selected"); }
}
mount() {
this.root.trigger = () => {
return $(this.refs.button).trigger("click");
};
return $(this.refs.button).click(e => {
return this.btclickhd(e);
});
}
btclickhd(e) {
const hd = this.get("onbtclick");
if (typeof hd === "string") {
eval(hd);
} else if (hd) {
hd({ id: this.aid(), data: e });
}
this.observable.trigger("btclick", { id: this.aid(), data: e });
if (this.toggle) {
return this.set("selected", !this.get("selected"));
}
}
layout() {
return [{
el: "Button", ref: "button", children: [
{ el: "afx-label", ref: "label" }
]
}];
}
}
Ant.OS.GUI.define("afx-button", ButtonTag);

View File

@ -1,126 +0,0 @@
class CalendarTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "day", 0
@setopt "ondateselect", () ->
@setopt "selectedDate", undefined
@day = 0
@month = 0
@year = 0
mount: () ->
$(@root).css "height", "100%"
$(@refs.grid).css "width", "100%"
$(@refs.prev).click (e) => @prevmonth()
$(@refs.next).click (e) => @nextmonth()
@refs.grid.set "header", [
{ text: "__(Sun)" },
{ text: "__(Mon)" },
{ text: "__(Tue)" },
{ text: "__(Wed)" },
{ text: "__(Thu)" },
{ text: "__(Fri)" },
{ text: "__(Sat)" }
]
@refs.grid.set "oncellselect", (e) =>
@dateselect(e)
@observable.on "resize", (e) => @calibrate()
@calibrate()
@calendar null
dateselect: (e) ->
return unless e.data.item
value = e.data.item.get("data").text
return if value is ""
evt = { id: @aid() , data: new Date(@year, @month, value) }
@get("ondateselect") evt
@set "selectedDate", evt.data
@observable.trigger "dateselect", evt
calibrate: () ->
$(@refs.grid)
.css "height", "#{$(@root).height() - $(@refs.ctrl).height()}px"
prevmonth: () ->
@set "selectedDate", undefined
@month--
if @month < 0
@month = 11
@year--
@calendar(new Date(@year, @month, 1))
nextmonth: () ->
@set "selectedDate", undefined
@month++
if @month > 11
@month = 0
@year++
@calendar(new Date(this.year, this.month, 1))
calendar: (date) ->
date = new Date() unless date
@day = date.getDate()
@month = date.getMonth()
@year = date.getFullYear()
now = {
d: (new Date()).getDate(),
m: (new Date()).getMonth(),
y: (new Date()).getFullYear()
}
months = [
__("January"),
__("February"),
__("March"),
__("April"),
__("May"),
__("June"),
__("July"),
__("August"),
__("September"),
__("October"),
__("November"),
__("December")
]
this_month = new Date(@year, @month, 1)
next_month = new Date(@year, @month + 1, 1)
# Find out when this month starts and ends.
first_week_day = this_month.getDay()
days_in_this_month = Math.round(
(next_month.getTime() - this_month.getTime()) / (1000 * 60 * 60 * 24))
#self.mtext = months[self.month]
rows = []
row = []
# Fill the first week of the month with the appropriate number of blanks.
row.push { text: "" } for week_day in [ 0..first_week_day - 1 ]
week_day = first_week_day
for day_counter in [ 1..days_in_this_month ]
week_day %= 7
if week_day == 0
rows.push(row)
row = []
# Do something different for the current day.
if now.d is day_counter and @month is now.m and @year is now.y
row.push { text: day_counter, selected: true }
else
row.push { text: day_counter }
week_day++
for i in [ 0..7 - row.length ]
row.push { text: "" }
rows.push(row)
@refs.grid.set "rows", rows
@refs.mlbl.set "text", "#{months[@month]} #{@year}"
layout: () ->
[{
el: "div", ref: "ctrl", children: [
{ el: "i", class: "prevmonth", ref: "prev" },
{ el: "afx-label", ref: "mlbl" },
{ el: "i", class: "nextmonth", ref: "next" }
]
},
{ el: "afx-grid-view", ref: "grid" }
]
Ant.OS.GUI.define "afx-calendar-view", CalendarTag

View File

@ -0,0 +1,150 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS202: Simplify dynamic range loops
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class CalendarTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("day", 0);
this.setopt("ondateselect", function() {});
this.setopt("selectedDate", undefined);
this.day = 0;
this.month = 0;
this.year = 0;
}
mount() {
$(this.root).css("height", "100%");
$(this.refs.grid).css("width", "100%");
$(this.refs.prev).click(e => this.prevmonth());
$(this.refs.next).click(e => this.nextmonth());
this.refs.grid.set("header", [
{ text: "__(Sun)" },
{ text: "__(Mon)" },
{ text: "__(Tue)" },
{ text: "__(Wed)" },
{ text: "__(Thu)" },
{ text: "__(Fri)" },
{ text: "__(Sat)" }
]);
this.refs.grid.set("oncellselect", e => {
return this.dateselect(e);
});
this.observable.on("resize", e => this.calibrate());
this.calibrate();
return this.calendar(null);
}
dateselect(e) {
if (!e.data.item) { return; }
const value = e.data.item.get("data").text;
if (value === "") { return; }
const evt = { id: this.aid() , data: new Date(this.year, this.month, value) };
this.get("ondateselect")(evt);
this.set("selectedDate", evt.data);
return this.observable.trigger("dateselect", evt);
}
calibrate() {
return $(this.refs.grid)
.css("height", `${$(this.root).height() - $(this.refs.ctrl).height()}px`);
}
prevmonth() {
this.set("selectedDate", undefined);
this.month--;
if (this.month < 0) {
this.month = 11;
this.year--;
}
return this.calendar(new Date(this.year, this.month, 1));
}
nextmonth() {
this.set("selectedDate", undefined);
this.month++;
if (this.month > 11) {
this.month = 0;
this.year++;
}
return this.calendar(new Date(this.year, this.month, 1));
}
calendar(date) {
let week_day;
let asc, end;
if (!date) { date = new Date(); }
this.day = date.getDate();
this.month = date.getMonth();
this.year = date.getFullYear();
const now = {
d: (new Date()).getDate(),
m: (new Date()).getMonth(),
y: (new Date()).getFullYear()
};
const months = [
__("January"),
__("February"),
__("March"),
__("April"),
__("May"),
__("June"),
__("July"),
__("August"),
__("September"),
__("October"),
__("November"),
__("December")
];
const this_month = new Date(this.year, this.month, 1);
const next_month = new Date(this.year, this.month + 1, 1);
// Find out when this month starts and ends.
const first_week_day = this_month.getDay();
const days_in_this_month = Math.round(
(next_month.getTime() - this_month.getTime()) / (1000 * 60 * 60 * 24));
//self.mtext = months[self.month]
const rows = [];
let row = [];
// Fill the first week of the month with the appropriate number of blanks.
for (week_day = 0, end = first_week_day - 1, asc = 0 <= end; asc ? week_day <= end : week_day >= end; asc ? week_day++ : week_day--) { row.push({ text: "" }); }
week_day = first_week_day;
for (let day_counter = 1, end1 = days_in_this_month, asc1 = 1 <= end1; asc1 ? day_counter <= end1 : day_counter >= end1; asc1 ? day_counter++ : day_counter--) {
week_day %= 7;
if (week_day === 0) {
rows.push(row);
row = [];
}
// Do something different for the current day.
if ((now.d === day_counter) && (this.month === now.m) && (this.year === now.y)) {
row.push({ text: day_counter, selected: true });
} else {
row.push({ text: day_counter });
}
week_day++;
}
for (let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2; asc2 ? i <= end2 : i >= end2; asc2 ? i++ : i--) {
row.push({ text: "" });
}
rows.push(row);
this.refs.grid.set("rows", rows);
return this.refs.mlbl.set("text", `${months[this.month]} ${this.year}`);
}
layout() {
return [{
el: "div", ref: "ctrl", children: [
{ el: "i", class: "prevmonth", ref: "prev" },
{ el: "afx-label", ref: "mlbl" },
{ el: "i", class: "nextmonth", ref: "next" }
]
},
{ el: "afx-grid-view", ref: "grid" }
];
}
}
Ant.OS.GUI.define("afx-calendar-view", CalendarTag);

View File

@ -1,116 +0,0 @@
class ColorPickerTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@colorctx = undefined
@setopt "oncolorselect", (e) ->
@setopt "selectedColor", undefined
mount: () ->
$(@refs.wrapper)
.css "width", "310px"
.css "height", "190px"
.css "display", "block"
.css "padding", "3px"
$(@refs.palette)
.css "width", "284px"
.css "height", "155px"
.css "float", "left"
$(@refs.colorval)
.css "width", "15px"
.css "height", "155px"
.css "text-align", "center"
.css "margin-left", "3px"
.css "display", "block"
.css "float", "left"
$(@refs.inputwrp)
.css "margin-top", "3px"
$(@refs.hextext)
.css "width", "70px"
.css "margin-left", "3px"
.css "margin-right", "5px"
@build_palette()
build_palette: () ->
colorctx = $(@refs.palette).get(0).getContext('2d')
gradient = colorctx.createLinearGradient(0, 0, $(@refs.palette).width(), 0)
# fill color
gradient.addColorStop(0, "rgb(255, 0, 0)")
gradient.addColorStop(0.15, "rgb(255, 0, 255)")
gradient.addColorStop(0.33, "rgb(0, 0, 255)")
gradient.addColorStop(0.49, "rgb(0, 255, 255)")
gradient.addColorStop(0.67, "rgb(0, 255, 0)")
gradient.addColorStop(0.84, "rgb(255, 255, 0)")
gradient.addColorStop(1, "rgb(255, 0, 0)")
gradient.addColorStop(0, "rgb(0, 0, 0)")
# Apply gradient to canvas
colorctx.fillStyle = gradient
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height)
# Create semi transparent gradient (white -> trans. -> black)
gradient = colorctx.createLinearGradient(0, 0, 0, $(@refs.palette).width())
gradient.addColorStop(0, "rgba(255, 255, 255, 1)")
gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)")
gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)")
gradient.addColorStop(1, "rgba(0, 0, 0, 1)")
# Apply gradient to canvas
colorctx.fillStyle = gradient
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height)
# now add mouse move event
getHex = (c) ->
s = c.toString(16)
s = "0" + s if s.length is 1
s
pick_color = (e) =>
$(@refs.palette).css("cursor", "crosshair")
offset = $(@refs.palette).offset()
x = e.pageX - offset.left
y = e.pageY - offset.top
color = colorctx.getImageData(x, y, 1, 1)
data = {
r: color.data[0],
g: color.data[1],
b: color.data[2],
text: 'rgb(' + color.data[0] + ', ' + color.data[1] + ', ' + color.data[2] + ')',
hex: '#' + getHex(color.data[0]) + getHex(color.data[1]) + getHex(color.data[2])
}
data
mouse_move_h = (e) =>
data = pick_color(e)
$(@refs.colorval).css("background-color", data.text)
$(@refs.palette).mouseenter (e) =>
$(@refs.palette).on("mousemove", mouse_move_h)
$(@refs.palette).mouseout (e) =>
$(@refs.palette).unbind("mousemove", mouse_move_h)
if @get "selectedColor"
$(@refs.colorval).css("background-color", @get("selectedColor").text)
$(@refs.palette).on "click", (e) =>
data = pick_color(e)
$(@refs.rgbtext).html(data.text)
$(@refs.hextext).val(data.hex)
@set "selectedColor", data
evt = { id: @aid(), data: data }
@get("oncolorselect") evt
@observable.trigger "colorselect", data
layout: () ->
[{
el: "div", ref: "wrapper", children: [
{ el: "canvas", class: "color-palette", ref: "palette" },
{ el: "color-sample", ref: "colorval" },
{ el: "div", class: "afx-clear" },
{ el: "div", ref: "inputwrp", children: [
{ el: "input", ref: "hextext" },
{ el: "span", ref: "rgbtext" }
] }
]
}]
Ant.OS.GUI.define "afx-color-picker", ColorPickerTag

View File

@ -0,0 +1,133 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class ColorPickerTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.colorctx = undefined;
this.setopt("oncolorselect", function(e) {});
this.setopt("selectedColor", undefined);
}
mount() {
$(this.refs.wrapper)
.css("width", "310px")
.css("height", "190px")
.css("display", "block")
.css("padding", "3px");
$(this.refs.palette)
.css("width", "284px")
.css("height", "155px")
.css("float", "left");
$(this.refs.colorval)
.css("width", "15px")
.css("height", "155px")
.css("text-align", "center")
.css("margin-left", "3px")
.css("display", "block")
.css("float", "left");
$(this.refs.inputwrp)
.css("margin-top", "3px");
$(this.refs.hextext)
.css("width", "70px")
.css("margin-left", "3px")
.css("margin-right", "5px");
return this.build_palette();
}
build_palette() {
const colorctx = $(this.refs.palette).get(0).getContext('2d');
let gradient = colorctx.createLinearGradient(0, 0, $(this.refs.palette).width(), 0);
// fill color
gradient.addColorStop(0, "rgb(255, 0, 0)");
gradient.addColorStop(0.15, "rgb(255, 0, 255)");
gradient.addColorStop(0.33, "rgb(0, 0, 255)");
gradient.addColorStop(0.49, "rgb(0, 255, 255)");
gradient.addColorStop(0.67, "rgb(0, 255, 0)");
gradient.addColorStop(0.84, "rgb(255, 255, 0)");
gradient.addColorStop(1, "rgb(255, 0, 0)");
gradient.addColorStop(0, "rgb(0, 0, 0)");
// Apply gradient to canvas
colorctx.fillStyle = gradient;
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height);
// Create semi transparent gradient (white -> trans. -> black)
gradient = colorctx.createLinearGradient(0, 0, 0, $(this.refs.palette).width());
gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)");
gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)");
gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
// Apply gradient to canvas
colorctx.fillStyle = gradient;
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height);
// now add mouse move event
const getHex = function(c) {
let s = c.toString(16);
if (s.length === 1) { s = "0" + s; }
return s;
};
const pick_color = e => {
$(this.refs.palette).css("cursor", "crosshair");
const offset = $(this.refs.palette).offset();
const x = e.pageX - offset.left;
const y = e.pageY - offset.top;
const color = colorctx.getImageData(x, y, 1, 1);
const data = {
r: color.data[0],
g: color.data[1],
b: color.data[2],
text: 'rgb(' + color.data[0] + ', ' + color.data[1] + ', ' + color.data[2] + ')',
hex: '#' + getHex(color.data[0]) + getHex(color.data[1]) + getHex(color.data[2])
};
return data;
};
const mouse_move_h = e => {
const data = pick_color(e);
return $(this.refs.colorval).css("background-color", data.text);
};
$(this.refs.palette).mouseenter(e => {
return $(this.refs.palette).on("mousemove", mouse_move_h);
});
$(this.refs.palette).mouseout(e => {
$(this.refs.palette).unbind("mousemove", mouse_move_h);
if (this.get("selectedColor")) {
return $(this.refs.colorval).css("background-color", this.get("selectedColor").text);
}
});
return $(this.refs.palette).on("click", e => {
const data = pick_color(e);
$(this.refs.rgbtext).html(data.text);
$(this.refs.hextext).val(data.hex);
this.set("selectedColor", data);
const evt = { id: this.aid(), data };
this.get("oncolorselect")(evt);
return this.observable.trigger("colorselect", data);
});
}
layout() {
return [{
el: "div", ref: "wrapper", children: [
{ el: "canvas", class: "color-palette", ref: "palette" },
{ el: "color-sample", ref: "colorval" },
{ el: "div", class: "afx-clear" },
{ el: "div", ref: "inputwrp", children: [
{ el: "input", ref: "hextext" },
{ el: "span", ref: "rgbtext" }
] }
]
}];
}
}
Ant.OS.GUI.define("afx-color-picker", ColorPickerTag);

View File

@ -1,218 +0,0 @@
class FileViewTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "onfileselect", ()->
@setopt "onfileopen", () ->
@setopt "ondragndrop", () ->
@setopt "selectedFile", undefined
@setopt "data", []
@setopt "status", true
@setopt "showhidden", false
@setopt "fetch", undefined
@setopt "path", undefined
@setopt "chdir", true
@setopt "view", "list"
@preventUpdate = false
@header = [
{ text: "__(File name)" },
{ text: "__(Type)", width: 150 },
{ text: "__(Size)", width: 70 }
]
view: () -> @get "view"
__view__: (v) ->
@switchView()
__status__: (v) ->
return $(@refs.status).show() if v
$(@refs.status).hide()
__showhidden__: (v) ->
return unless @get "data"
@switchView()
__path__: (v) ->
return unless v
return unless @get "fetch"
@get("fetch")(v)
.then (data) =>
return unless data
@set "data", data
@refs.status.set("text", " ") if @get "status"
.catch (e) ->
# this should be handled by the OS
Ant.OS.announcer.oserror e.toString(), e
__data__: (v) ->
return unless v
@refreshData()
__ondragndrop__: (v) ->
@refs.treeview.set "ondragndrop", v
@refs.listview.set "ondragndrop", v
sortByType: (a, b) ->
if a.type < b.type
-1
else if a.type > b.type
1
else
0
calibrate: () ->
h = $(@root).outerHeight()
w = $(@root).width()
h -= ($(@refs.status).height() + 10) if @get("status")
$(@refs.listview).css("height", h + "px")
$(@refs.gridview).css("height", h + "px")
$(@refs.treecontainer).css("height", h + "px")
$(@refs.listview).css("width", w + "px")
$(@refs.gridview).css("width", w + "px")
$(@refs.treecontainer).css("width", w + "px")
refreshList: () ->
items = []
$.each @get("data"), (i, v) =>
return if v.filename[0] is '.' and not @get("showhidden")
v.text = v.filename
v.text = v.text.substring(0, 9) + "..." if v.text.length > 10
v.iconclass = if v.iconclass then v.iconclass else v.type
v.icon = v.icon
items.push(v)
@refs.listview.set "data", items
refreshGrid: () ->
rows = []
$.each @get("data"), (i, v) =>
return if v.filename[0] is '.' and not @get("showhidden")
v.text = v.filename
v.iconclass = if v.iconclass then v.iconclass else v.type
row = [
v,
{
text: v.mime,
data: v
},
{
text: v.size,
data: v
}
]
rows.push(row)
@refs.gridview.set "rows", rows
refreshTree: () ->
#@treeview.root.set("selectedItem", null)
tdata = {}
tdata.name = @get "path"
tdata.path = tdata.name
tdata.open = true
tdata.nodes = @getTreeData( @get("data"))
@refs.treeview.set("data", tdata)
getTreeData: (data) ->
nodes = []
me = @
$.each data, (i, v) =>
return if v.filename[0] is '.' and not @get("showhidden")
v.name = v.filename
if v.type is 'dir'
v.nodes = []
v.open = false
v.iconclass = if v.iconclass then v.iconclass else v.type
v.icon = v.icon
nodes.push(v)
return nodes
refreshData: () ->
return unless @get("data")
@get("data").sort(@sortByType)
switch @get("view")
when "icon"
@refreshList()
when "list"
@refreshGrid()
else
@refreshTree()
switchView: () ->
$(@refs.listview).hide()
$(@refs.gridview).hide()
$(@refs.treecontainer).hide()
@set "selectedFile", undefined
switch @get "view"
when 'icon'
$(@refs.listview).show()
when 'list'
$(@refs.gridview).show()
else
$(@refs.treecontainer).show()
@refreshData()
@calibrate()
@refs.status.set("text", " ") if @get "status"
fileselect: (e) ->
if e.path is @get "path"
e.type = "dir"
e.mime = "dir"
if @get "status"
@refs.status.set "text", __(
"Selected: {0} ({1} bytes)",
e.filename,
if e.size then e.size else "0" )
evt = { id: @aid(), data: e }
@set "selectedFile", e
@get("onfileselect") evt
@observable.trigger "fileselect", evt
filedbclick: (e) ->
if e.path is @get "path"
e.type = "dir"
e.mime = "dir"
if e.type is "dir" and @get "chdir"
@set "path", e.path
else
evt = { id: @aid(), data: e }
@get("onfileopen") evt
@observable.trigger "fileopen", evt
mount: () ->
@observable.on "resize", (e) => @calibrate()
@refs.treeview.set "fetch", (v) =>
new Promise (resolve, reject) =>
return resolve undefined unless @get("fetch")
return resolve undefined unless v.get("data").path
@get("fetch")(v.get("data").path)
.then (d) => resolve @getTreeData(d.sort @sortByType)
.catch (e) -> reject __e e
@refs.gridview.set "header", @header
@refs.treeview.set "dragndrop", true
@refs.listview.set "dragndrop", true
# even handles
@refs.listview.set "onlistselect", (e) =>
@fileselect e.data.item.get("data")
@refs.gridview.set "onrowselect", (e) =>
@fileselect $(e.data.item).children()[0].get("data")
@refs.treeview.set "ontreeselect", (e) =>
@fileselect e.data.item.get("data")
# dblclick
@refs.listview.set "onlistdbclick", (e) =>
@filedbclick e.data.item.get("data")
@refs.gridview.set "oncelldbclick", (e) =>
@filedbclick e.data.item.get("data")
@refs.treeview.set "ontreedbclick", (e) =>
@filedbclick e.data.item.get("data")
@switchView()
layout: () ->
[
{ el: "afx-list-view", ref: "listview" },
{ el: "div", class: "treecontainer", ref: "treecontainer", children: [
{ el: "afx-tree-view", ref: "treeview" }
] },
{ el: "afx-grid-view", ref: "gridview" },
{ el: "afx-label", class: "status", ref: "status" }
]
Ant.OS.GUI.define "afx-file-view", FileViewTag

View File

@ -0,0 +1,264 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class FileViewTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("onfileselect", function(){});
this.setopt("onfileopen", function() {});
this.setopt("ondragndrop", function() {});
this.setopt("selectedFile", undefined);
this.setopt("data", []);
this.setopt("status", true);
this.setopt("showhidden", false);
this.setopt("fetch", undefined);
this.setopt("path", undefined);
this.setopt("chdir", true);
this.setopt("view", "list");
this.preventUpdate = false;
this.header = [
{ text: "__(File name)" },
{ text: "__(Type)", width: 150 },
{ text: "__(Size)", width: 70 }
];
}
view() { return this.get("view"); }
__view__(v) {
return this.switchView();
}
__status__(v) {
if (v) { return $(this.refs.status).show(); }
return $(this.refs.status).hide();
}
__showhidden__(v) {
if (!this.get("data")) { return; }
return this.switchView();
}
__path__(v) {
if (!v) { return; }
if (!this.get("fetch")) { return; }
return this.get("fetch")(v)
.then(data => {
if (!data) { return; }
this.set("data", data);
if (this.get("status")) { return this.refs.status.set("text", " "); }
}).catch(e => // this should be handled by the OS
Ant.OS.announcer.oserror(e.toString(), e));
}
__data__(v) {
if (!v) { return; }
return this.refreshData();
}
__ondragndrop__(v) {
this.refs.treeview.set("ondragndrop", v);
return this.refs.listview.set("ondragndrop", v);
}
sortByType(a, b) {
if (a.type < b.type) {
return -1;
} else if (a.type > b.type) {
return 1;
} else {
return 0;
}
}
calibrate() {
let h = $(this.root).outerHeight();
const w = $(this.root).width();
if (this.get("status")) { h -= ($(this.refs.status).height() + 10); }
$(this.refs.listview).css("height", h + "px");
$(this.refs.gridview).css("height", h + "px");
$(this.refs.treecontainer).css("height", h + "px");
$(this.refs.listview).css("width", w + "px");
$(this.refs.gridview).css("width", w + "px");
return $(this.refs.treecontainer).css("width", w + "px");
}
refreshList() {
const items = [];
$.each(this.get("data"), (i, v) => {
if ((v.filename[0] === '.') && !this.get("showhidden")) { return; }
v.text = v.filename;
if (v.text.length > 10) { v.text = v.text.substring(0, 9) + "..."; }
v.iconclass = v.iconclass ? v.iconclass : v.type;
v.icon = v.icon;
return items.push(v);
});
return this.refs.listview.set("data", items);
}
refreshGrid() {
const rows = [];
$.each(this.get("data"), (i, v) => {
if ((v.filename[0] === '.') && !this.get("showhidden")) { return; }
v.text = v.filename;
v.iconclass = v.iconclass ? v.iconclass : v.type;
const row = [
v,
{
text: v.mime,
data: v
},
{
text: v.size,
data: v
}
];
return rows.push(row);
});
return this.refs.gridview.set("rows", rows);
}
refreshTree() {
//@treeview.root.set("selectedItem", null)
const tdata = {};
tdata.name = this.get("path");
tdata.path = tdata.name;
tdata.open = true;
tdata.nodes = this.getTreeData( this.get("data"));
return this.refs.treeview.set("data", tdata);
}
getTreeData(data) {
const nodes = [];
const me = this;
$.each(data, (i, v) => {
if ((v.filename[0] === '.') && !this.get("showhidden")) { return; }
v.name = v.filename;
if (v.type === 'dir') {
v.nodes = [];
v.open = false;
}
v.iconclass = v.iconclass ? v.iconclass : v.type;
v.icon = v.icon;
return nodes.push(v);
});
return nodes;
}
refreshData() {
if (!this.get("data")) { return; }
this.get("data").sort(this.sortByType);
switch (this.get("view")) {
case "icon":
return this.refreshList();
case "list":
return this.refreshGrid();
default:
return this.refreshTree();
}
}
switchView() {
$(this.refs.listview).hide();
$(this.refs.gridview).hide();
$(this.refs.treecontainer).hide();
this.set("selectedFile", undefined);
switch (this.get("view")) {
case 'icon':
$(this.refs.listview).show();
break;
case 'list':
$(this.refs.gridview).show();
break;
default:
$(this.refs.treecontainer).show();
}
this.refreshData();
this.calibrate();
if (this.get("status")) { return this.refs.status.set("text", " "); }
}
fileselect(e) {
if (e.path === this.get("path")) {
e.type = "dir";
e.mime = "dir";
}
if (this.get("status")) {
this.refs.status.set("text", __(
"Selected: {0} ({1} bytes)",
e.filename,
e.size ? e.size : "0" )
);
}
const evt = { id: this.aid(), data: e };
this.set("selectedFile", e);
this.get("onfileselect")(evt);
return this.observable.trigger("fileselect", evt);
}
filedbclick(e) {
if (e.path === this.get("path")) {
e.type = "dir";
e.mime = "dir";
}
if ((e.type === "dir") && this.get("chdir")) {
return this.set("path", e.path);
} else {
const evt = { id: this.aid(), data: e };
this.get("onfileopen")(evt);
return this.observable.trigger("fileopen", evt);
}
}
mount() {
this.observable.on("resize", e => this.calibrate());
this.refs.treeview.set("fetch", v => {
return new Promise((resolve, reject) => {
if (!this.get("fetch")) { return resolve(undefined); }
if (!v.get("data").path) { return resolve(undefined); }
return this.get("fetch")(v.get("data").path)
.then(d => resolve(this.getTreeData(d.sort(this.sortByType))))
.catch(e => reject(__e(e)));
});
});
this.refs.gridview.set("header", this.header);
this.refs.treeview.set("dragndrop", true);
this.refs.listview.set("dragndrop", true);
// even handles
this.refs.listview.set("onlistselect", e => {
return this.fileselect(e.data.item.get("data"));
});
this.refs.gridview.set("onrowselect", e => {
return this.fileselect($(e.data.item).children()[0].get("data"));
});
this.refs.treeview.set("ontreeselect", e => {
return this.fileselect(e.data.item.get("data"));
});
// dblclick
this.refs.listview.set("onlistdbclick", e => {
return this.filedbclick(e.data.item.get("data"));
});
this.refs.gridview.set("oncelldbclick", e => {
return this.filedbclick(e.data.item.get("data"));
});
this.refs.treeview.set("ontreedbclick", e => {
return this.filedbclick(e.data.item.get("data"));
});
return this.switchView();
}
layout() {
return [
{ el: "afx-list-view", ref: "listview" },
{ el: "div", class: "treecontainer", ref: "treecontainer", children: [
{ el: "afx-tree-view", ref: "treeview" }
] },
{ el: "afx-grid-view", ref: "gridview" },
{ el: "afx-label", class: "status", ref: "status" }
];
}
}
Ant.OS.GUI.define("afx-file-view", FileViewTag);

View File

@ -1,90 +0,0 @@
class FloatListTag extends ListViewTag
constructor: (r, o) ->
super r, o
@setopt "dir", "horizontal"
@root.refresh = () => @calibrate()
@root.push = (e) => @refs.mlist.push(e)
@root.unshift = (e) => @refs.mlist.unshift(e)
@root.remove = (e) => @refs.mlist.remove(e)
# disable some uneccessary functions
__dropdown__: (v) -> @set "dropdown", false if v
__buttons__: (v) ->
showlist: (e) ->
dropoff: (e) ->
__data__: (v) ->
super.__data__(v)
@calibrate()
__dir__: (v) ->
@calibrate()
mount: () ->
$(@refs.container)
.css "width", "100%"
.css "height", "100%"
$(@refs.mlist)
.css "position", "absolute"
.css "display", "block"
.css "width", "100%"
@observable.on "resize", (e) => @calibrate()
@root.ready(@root) if @root.ready
push: (v) ->
el = super.push(v)
@enable_drag el
el
enable_drag: (el) ->
$(el)
.css "user-select", "none"
.css "cursor", "default"
.css "display", "block"
.css "position", "absolute"
.on "mousedown", (evt) =>
globalof = $(@refs.mlist).offset()
evt.preventDefault()
offset = $(el).offset()
offset.top = evt.clientY - offset.top
offset.left = evt.clientX - offset.left
mouse_move = (e) ->
top = e.clientY - offset.top - globalof.top
left = e.clientX - globalof.left - offset.left
left = if left < 0 then 0 else left
top = if top < 0 then 0 else top
$(el)
.css "top", "#{top}px"
.css "left", "#{left}px"
mouse_up = (e) ->
$(window).unbind "mousemove", mouse_move
$(window).unbind "mouseup", mouse_up
$(window).on "mousemove", mouse_move
$(window).on "mouseup", mouse_up
calibrate: () ->
ctop = 20
cleft = 20
$(@refs.mlist)
.css "height", "#{$(@refs.container).height()}px"
gw = $(@refs.mlist).width()
gh = $(@refs.mlist).height()
$(@refs.mlist).children().each (i, e) =>
$(e)
.css "top", "#{ctop}px"
.css "left", "#{cleft}px"
w = $(e).width()
h = $(e).height()
if @get("dir") is "vertical"
ctop += h + 20
if ctop > gh
ctop = 20
cleft += w + 20
else
cleft += w + 20
if cleft > gw
cleft = 20
ctop += h + 20
Ant.OS.GUI.define "afx-float-list", FloatListTag

View File

@ -0,0 +1,110 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class FloatListTag extends ListViewTag {
constructor(r, o) {
super(r, o);
this.setopt("dir", "horizontal");
this.root.refresh = () => this.calibrate();
this.root.push = e => this.refs.mlist.push(e);
this.root.unshift = e => this.refs.mlist.unshift(e);
this.root.remove = e => this.refs.mlist.remove(e);
}
// disable some uneccessary functions
__dropdown__(v) { if (v) { return this.set("dropdown", false); } }
__buttons__(v) {}
showlist(e) {}
dropoff(e) {}
__data__(v) {
super.__data__(v);
return this.calibrate();
}
__dir__(v) {
return this.calibrate();
}
mount() {
$(this.refs.container)
.css("width", "100%")
.css("height", "100%");
$(this.refs.mlist)
.css("position", "absolute")
.css("display", "block")
.css("width", "100%");
this.observable.on("resize", e => this.calibrate());
if (this.root.ready) { return this.root.ready(this.root); }
}
push(v) {
const el = super.push(v);
this.enable_drag(el);
return el;
}
enable_drag(el) {
return $(el)
.css("user-select", "none")
.css("cursor", "default")
.css("display", "block")
.css("position", "absolute")
.on("mousedown", evt => {
const globalof = $(this.refs.mlist).offset();
evt.preventDefault();
const offset = $(el).offset();
offset.top = evt.clientY - offset.top;
offset.left = evt.clientX - offset.left;
const mouse_move = function(e) {
let top = e.clientY - offset.top - globalof.top;
let left = e.clientX - globalof.left - offset.left;
left = left < 0 ? 0 : left;
top = top < 0 ? 0 : top;
return $(el)
.css("top", `${top}px`)
.css("left", `${left}px`);
};
var mouse_up = function(e) {
$(window).unbind("mousemove", mouse_move);
return $(window).unbind("mouseup", mouse_up);
};
$(window).on("mousemove", mouse_move);
return $(window).on("mouseup", mouse_up);
});
}
calibrate() {
let ctop = 20;
let cleft = 20;
$(this.refs.mlist)
.css("height", `${$(this.refs.container).height()}px`);
const gw = $(this.refs.mlist).width();
const gh = $(this.refs.mlist).height();
return $(this.refs.mlist).children().each((i, e) => {
$(e)
.css("top", `${ctop}px`)
.css("left", `${cleft}px`);
const w = $(e).width();
const h = $(e).height();
if (this.get("dir") === "vertical") {
ctop += h + 20;
if (ctop > gh) {
ctop = 20;
return cleft += w + 20;
}
} else {
cleft += w + 20;
if (cleft > gw) {
cleft = 20;
return ctop += h + 20;
}
}
});
}
}
Ant.OS.GUI.define("afx-float-list", FloatListTag);

View File

@ -1,231 +0,0 @@
class GridRowTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "data", []
@refs.yield = @root
class GridCellPrototype extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "class", "afx-grid-cell"
@setopt "oncellselect", (e) ->
@setopt "oncelldbclick", (e) ->
@setopt "data", {}
@setopt "selected", false
__data__: (v) ->
return unless v.selected
@set "selected", v.selected
__selected__: (v) ->
@get("data").selected = v
return unless v
@cellseleck {}, false
update: () ->
@set "data", @get("data")
mount: () ->
$(@root).css "display", "block"
$(@root).click (e) =>
@cellseleck e, false
$(@root).dblclick (e) =>
@cellseleck e, true
cellseleck: (e, flag) ->
e.item = @root
evt = { id: @aid(), data: e }
return @get("oncellselect") evt unless flag
@get("oncelldbclick") evt
__class__: (v) ->
$(@root).removeClass().addClass @get("class")
class SimpleGridCellTag extends GridCellPrototype
constructor: (r, o) ->
super r, o
@setopt "header", false
__header__: (v) ->
__data: (d) ->
@refs.cell.set k, v for k, v of d
layout: () ->
[{
el: "afx-label", ref: "cell"
}]
class GridViewTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "header", []
@setopt "headeritem", "afx-grid-cell"
@setopt "cellitem", "afx-grid-cell"
@setopt "selectedCell", undefined
@setopt "selectedRows", []
@setopt "selectedRow", undefined
@setopt "rows", []
@setopt "oncellselect", (e) ->
@setopt "onrowselect", (e) ->
@setopt "oncelldbclick", (e) ->
@setopt "multiselect", false
@root.push = (r) => @push r, false
@root.unshift = (r) => @unshift r
@root.remove = (r) => @remove r
__header__: (v) ->
return $(@refs.header).hide() if not v or v.length is 0
$(@refs.header).empty()
for item in v
el = $("<#{@get("headeritem")}>").appendTo @refs.header
el[0].uify undefined
el[0].set "data", item
item.domel = el[0]
@calibrate()
__rows__: (rows) ->
$(@refs.grid).empty()
for row in rows
@push row, false
remove: (row) ->
return unless row
rowdata = row.get "data"
data = @get "rows"
@set "selectedRow", undefined if @get("selectedRow") is row
@set "selectedCell", undefined if $(@get("selectedCell")).parent()[0] is row
list = @get("selectedRows")
list.splice(list.indexOf(row), 1) if list.includes(row)
if data.includes rowdata
data.splice data.indexOf(rowdata), 1
$(row).remove()
push: (row, flag) ->
rowel = $("<afx-grid-row>")
.css "display", "contents"
rowel[0].uify undefined
rowel[0].set "data", row
row.domel = rowel[0]
for cell in row
tag = @get "cellitem"
tag = cell.tag if cell.tag
el = $("<#{tag}>").appendTo rowel
cell.domel = el[0]
el[0].uify undefined
el[0].set "oncellselect", (e) => @cellselect e, false
el[0].set "oncelldbclick", (e) => @cellselect e, true
el[0].set "data", cell
if flag
$(@refs.grid).prepend rowel[0]
else
rowel.appendTo @refs.grid
unshift: (row) ->
@push row, true
multiselect: () ->
@get "multiselect"
cellselect: (e, flag) ->
e.id = @aid()
selectedCell = @get "selectedCell"
# return if e.data.item is selectedCell and not flag
selectedCell.set "class", "afx-grid-cell" if selectedCell
@set "selectedCell", e.data.item
$(e.data.item).addClass "afx-grid-cell-selected"
if flag
@observable.trigger "celldbclick", e
@get("oncelldbclick") e
else
@observable.trigger "cellselect", e
@get("oncellselect") e
@rowselect e
rowselect: (e) ->
return unless e.data.item
selectedRow = @get "selectedRow"
selectedRows = @get "selectedRows"
evt = { id: @aid(), data: {} }
row = $(e.data.item).parent()[0]
if @multiselect()
if selectedRows.includes row
selectedRows.splice selectedRows.indexOf(row) , 1
$(row).removeClass()
else
selectedRows.push row
$(row).removeClass().addClass("afx-grid-row-selected")
evt.data.items = @get "selectedRows"
else
return if selectedRow is row
$(selectedRow).removeClass()
@set "selectedRow", row
@set "selectedRows", [row]
evt.data.item = row
evt.data.items = [ row ]
$(row).removeClass().addClass("afx-grid-row-selected")
@get("onrowselect") evt
@observable.trigger "rowselect", evt
has_header: () ->
h = @get("header")
return h and h.length > 0
calibrate: () ->
@calibrate_header()
if @has_header()
$(@refs.container).css "height", $(@root).height() - $(@refs.header).height() + "px"
else
$(@refs.container).css "height", $(@root).height() + "px"
calibrate_header: () ->
header = @get "header"
return if not header or header.length is 0
colssize = []
ocw = 0
nauto = 0
totalw = $(@root).parent().width()
$.each header, (i, item) ->
if item.width
colssize.push item.width
ocw += item.width
else
colssize.push -1
nauto++
if nauto > 0
cellw = parseInt((totalw - ocw) / nauto)
$.each colssize, (i, e) ->
return unless e is -1
colssize[i] = cellw
template = ""
template += "#{v}px " for v in colssize
$(@refs.grid).css "grid-template-columns", template
$(@refs.header).css "grid-template-columns", template
mount: () ->
$(@root)
.css "overflow", "hidden"
$(@refs.grid).css "display", "grid"
$(@refs.header).css "display", "grid"
@observable.on "resize", (e) => @calibrate()
$(@refs.container)
.css "width", "100%"
.css "overflow-x", "hidden"
.css "overflow-y", "auto"
@calibrate()
layout: () ->
[
{ el: "div", ref: "header", class: "grid_row_header" },
{ el: "div", ref: "container", children: [
{ el: "div", ref: "grid" }
] }
]
Ant.OS.GUI.define "afx-grid-view", GridViewTag
Ant.OS.GUI.define "afx-grid-cell", SimpleGridCellTag
Ant.OS.GUI.define "afx-grid-row", GridRowTag
Ant.OS.GUI.define "afx-grid-cell-proto", GridCellPrototype

View File

@ -0,0 +1,290 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class GridRowTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("data", []);
this.refs.yield = this.root;
}
}
class GridCellPrototype extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("class", "afx-grid-cell");
this.setopt("oncellselect", function(e) {});
this.setopt("oncelldbclick", function(e) {});
this.setopt("data", {});
this.setopt("selected", false);
}
__data__(v) {
if (!v.selected) { return; }
return this.set("selected", v.selected);
}
__selected__(v) {
this.get("data").selected = v;
if (!v) { return; }
return this.cellseleck({}, false);
}
update() {
return this.set("data", this.get("data"));
}
mount() {
$(this.root).css("display", "block");
$(this.root).click(e => {
return this.cellseleck(e, false);
});
return $(this.root).dblclick(e => {
return this.cellseleck(e, true);
});
}
cellseleck(e, flag) {
e.item = this.root;
const evt = { id: this.aid(), data: e };
if (!flag) { return this.get("oncellselect")(evt); }
return this.get("oncelldbclick")(evt);
}
__class__(v) {
return $(this.root).removeClass().addClass(this.get("class"));
}
}
class SimpleGridCellTag extends GridCellPrototype {
constructor(r, o) {
super(r, o);
this.setopt("header", false);
}
__header__(v) {}
__data(d) {
return (() => {
const result = [];
for (let k in d) {
const v = d[k];
result.push(this.refs.cell.set(k, v));
}
return result;
})();
}
layout() {
return [{
el: "afx-label", ref: "cell"
}];
}
}
class GridViewTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("header", []);
this.setopt("headeritem", "afx-grid-cell");
this.setopt("cellitem", "afx-grid-cell");
this.setopt("selectedCell", undefined);
this.setopt("selectedRows", []);
this.setopt("selectedRow", undefined);
this.setopt("rows", []);
this.setopt("oncellselect", function(e) {});
this.setopt("onrowselect", function(e) {});
this.setopt("oncelldbclick", function(e) {});
this.setopt("multiselect", false);
this.root.push = r => this.push(r, false);
this.root.unshift = r => this.unshift(r);
this.root.remove = r => this.remove(r);
}
__header__(v) {
if (!v || (v.length === 0)) { return $(this.refs.header).hide(); }
$(this.refs.header).empty();
for (let item of Array.from(v)) {
const el = $(`<${this.get("headeritem")}>`).appendTo(this.refs.header);
el[0].uify(undefined);
el[0].set("data", item);
item.domel = el[0];
}
return this.calibrate();
}
__rows__(rows) {
$(this.refs.grid).empty();
return Array.from(rows).map((row) =>
this.push(row, false));
}
remove(row) {
if (!row) { return; }
const rowdata = row.get("data");
const data = this.get("rows");
if (this.get("selectedRow") === row) { this.set("selectedRow", undefined); }
if ($(this.get("selectedCell")).parent()[0] === row) { this.set("selectedCell", undefined); }
const list = this.get("selectedRows");
if (list.includes(row)) { list.splice(list.indexOf(row), 1); }
if (data.includes(rowdata)) {
data.splice(data.indexOf(rowdata), 1);
}
return $(row).remove();
}
push(row, flag) {
const rowel = $("<afx-grid-row>")
.css("display", "contents");
rowel[0].uify(undefined);
rowel[0].set("data", row);
row.domel = rowel[0];
for (let cell of Array.from(row)) {
let tag = this.get("cellitem");
if (cell.tag) { ({
tag
} = cell); }
const el = $(`<${tag}>`).appendTo(rowel);
cell.domel = el[0];
el[0].uify(undefined);
el[0].set("oncellselect", e => this.cellselect(e, false));
el[0].set("oncelldbclick", e => this.cellselect(e, true));
el[0].set("data", cell);
}
if (flag) {
return $(this.refs.grid).prepend(rowel[0]);
} else {
return rowel.appendTo(this.refs.grid);
}
}
unshift(row) {
return this.push(row, true);
}
multiselect() {
return this.get("multiselect");
}
cellselect(e, flag) {
e.id = this.aid();
const selectedCell = this.get("selectedCell");
// return if e.data.item is selectedCell and not flag
if (selectedCell) { selectedCell.set("class", "afx-grid-cell"); }
this.set("selectedCell", e.data.item);
$(e.data.item).addClass("afx-grid-cell-selected");
if (flag) {
this.observable.trigger("celldbclick", e);
return this.get("oncelldbclick")(e);
} else {
this.observable.trigger("cellselect", e);
this.get("oncellselect")(e);
return this.rowselect(e);
}
}
rowselect(e) {
if (!e.data.item) { return; }
const selectedRow = this.get("selectedRow");
const selectedRows = this.get("selectedRows");
const evt = { id: this.aid(), data: {} };
const row = $(e.data.item).parent()[0];
if (this.multiselect()) {
if (selectedRows.includes(row)) {
selectedRows.splice(selectedRows.indexOf(row) , 1);
$(row).removeClass();
} else {
selectedRows.push(row);
$(row).removeClass().addClass("afx-grid-row-selected");
}
evt.data.items = this.get("selectedRows");
} else {
if (selectedRow === row) { return; }
$(selectedRow).removeClass();
this.set("selectedRow", row);
this.set("selectedRows", [row]);
evt.data.item = row;
evt.data.items = [ row ];
$(row).removeClass().addClass("afx-grid-row-selected");
}
this.get("onrowselect")(evt);
return this.observable.trigger("rowselect", evt);
}
has_header() {
const h = this.get("header");
return h && (h.length > 0);
}
calibrate() {
this.calibrate_header();
if (this.has_header()) {
return $(this.refs.container).css("height", ($(this.root).height() - $(this.refs.header).height()) + "px");
} else {
return $(this.refs.container).css("height", $(this.root).height() + "px");
}
}
calibrate_header() {
const header = this.get("header");
if (!header || (header.length === 0)) { return; }
const colssize = [];
let ocw = 0;
let nauto = 0;
const totalw = $(this.root).parent().width();
$.each(header, function(i, item) {
if (item.width) {
colssize.push(item.width);
return ocw += item.width;
} else {
colssize.push(-1);
return nauto++;
}
});
if (nauto > 0) {
const cellw = parseInt((totalw - ocw) / nauto);
$.each(colssize, function(i, e) {
if (e !== -1) { return; }
return colssize[i] = cellw;
});
}
let template = "";
for (let v of Array.from(colssize)) { template += `${v}px `; }
$(this.refs.grid).css("grid-template-columns", template);
return $(this.refs.header).css("grid-template-columns", template);
}
mount() {
$(this.root)
.css("overflow", "hidden");
$(this.refs.grid).css("display", "grid");
$(this.refs.header).css("display", "grid");
this.observable.on("resize", e => this.calibrate());
$(this.refs.container)
.css("width", "100%")
.css("overflow-x", "hidden")
.css("overflow-y", "auto");
return this.calibrate();
}
layout() {
return [
{ el: "div", ref: "header", class: "grid_row_header" },
{ el: "div", ref: "container", children: [
{ el: "div", ref: "grid" }
] }
];
}
}
Ant.OS.GUI.define("afx-grid-view", GridViewTag);
Ant.OS.GUI.define("afx-grid-cell", SimpleGridCellTag);
Ant.OS.GUI.define("afx-grid-row", GridRowTag);
Ant.OS.GUI.define("afx-grid-cell-proto", GridCellPrototype);

View File

@ -1,61 +0,0 @@
class LabelTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "color", undefined
@setopt "icon", undefined
@setopt "iconclass", undefined
@setopt "class", undefined
@setopt "text", undefined
mount: () ->
update: () ->
@set "text", @get("text")
__class__: (v) ->
$(@root).removeClass()
$(@root).addClass v if v
__color__: (v) ->
return unless v
$(@refs.container).css "color", v
__icon__: (v) ->
$(@refs.i).attr "style", ""
if v
$(@refs.i)
.css "background", "url(#{Ant.OS.API.handle.get}/#{v})"
.css "background-size", "100% 100%"
.css "background-repeat", "no-repeat"
$(@refs.i).show()
else
$(@refs.i).hide()
__iconclass__: (v) ->
$(@refs.iclass).removeClass()
if v
$(@refs.iclass).addClass v
$(@refs.iclass).show()
else
$(@refs.iclass).hide()
__text__: (v) ->
if v and v isnt ""
$(@refs.text).show()
$(@refs.text).html v.__()
else
$(@refs.text).hide()
layout: () ->
[{
el: "span", ref: "container", children: [
{ el: "i", ref: "iclass" },
{ el: "i", ref: "i", class: "icon-style" },
{ el: "i", ref: "text", class: "label-text" }
]
}]
Ant.OS.GUI.define "afx-label", LabelTag

78
src/core/tags/LabelTag.js Normal file
View File

@ -0,0 +1,78 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class LabelTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("color", undefined);
this.setopt("icon", undefined);
this.setopt("iconclass", undefined);
this.setopt("class", undefined);
this.setopt("text", undefined);
}
mount() {}
update() {
return this.set("text", this.get("text"));
}
__class__(v) {
$(this.root).removeClass();
if (v) { return $(this.root).addClass(v); }
}
__color__(v) {
if (!v) { return; }
return $(this.refs.container).css("color", v);
}
__icon__(v) {
$(this.refs.i).attr("style", "");
if (v) {
$(this.refs.i)
.css("background", `url(${Ant.OS.API.handle.get}/${v})`)
.css("background-size", "100% 100%")
.css("background-repeat", "no-repeat");
return $(this.refs.i).show();
} else {
return $(this.refs.i).hide();
}
}
__iconclass__(v) {
$(this.refs.iclass).removeClass();
if (v) {
$(this.refs.iclass).addClass(v);
return $(this.refs.iclass).show();
} else {
return $(this.refs.iclass).hide();
}
}
__text__(v) {
if (v && (v !== "")) {
$(this.refs.text).show();
return $(this.refs.text).html(v.__());
} else {
return $(this.refs.text).hide();
}
}
layout() {
return [{
el: "span", ref: "container", children: [
{ el: "i", ref: "iclass" },
{ el: "i", ref: "i", class: "icon-style" },
{ el: "i", ref: "text", class: "label-text" }
]
}];
}
}
Ant.OS.GUI.define("afx-label", LabelTag);

View File

@ -1,350 +0,0 @@
class ListViewItemTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "data", {}
@setopt "oncontextmenu", (e) ->
@setopt "onclick", (e) ->
@setopt "onselect", (e) ->
@setopt "ondbclick", (e) ->
@setopt "onclose", (e) ->
@setopt "index", 0
@setopt "closable", false
@setopt "selected", false
__closable__: (v) ->
if v then $(@refs.btcl).show() else $(@refs.btcl).hide()
__selected__: (v) ->
$(@refs.item).removeClass()
return unless v
$(@refs.item).addClass "selected"
@get("onselect")({ item: @root })
mount: () ->
$(@refs.item).attr "dataref", "afx-list-item"
$(@refs.item).contextmenu (e) =>
e.item = @root
@get("oncontextmenu")(e)
$(@refs.item).click (e) =>
e.item = @root
@get("onclick")(e)
$(@refs.item).dblclick (e) =>
e.item = @root
@get("ondbclick")(e)
$(@refs.btcl).click (e) =>
e.item = @root
@get("onclose")(e)
layout: () ->
[{
el: "li", ref: "item", children: [
@itemlayout(),
{ el: "i", class: "closable", ref: "btcl" }
]
}]
itemlayout: () ->
class SimpleListItemTag extends ListViewItemTag
constructor: (r, o) ->
super r, o
__data__: (v) ->
return unless v
@refs.label.set "class", v.class if v.class
@refs.label.set "color", v.color if v.color
@refs.label.set "iconclass", v.iconclass if v.iconclass
@refs.label.set "icon", v.icon if v.icon
@refs.label.set "text", v.text if v.text
@set "selected", v.selected if v.selected
@set "closable", v.closable if v.closable
__selected: (v) ->
@get("data").selected = v
update: () ->
@set "data", @get("data")
itemlayout: () ->
{ el: "afx-label", ref: "label" }
class ListViewTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "onlistselect", () ->
@setopt "onlistdbclick", () ->
@setopt "ondragndrop", () ->
@setopt "onitemclose", () -> true
@setopt "buttons", []
@setopt "data", []
@setopt "dropdown", false
@setopt "itemtag", "afx-list-item"
@setopt "multiselect", false
@setopt "selectedItem", undefined
@setopt "selectedItems", []
@setopt "selected", -1
@setopt "dragndrop", false
$(@root)
.css "display", "flex"
.css "flex-direction", "column"
@root.push = (e) => @push e
@root.remove = (e) => @remove e
@root.unshift = (e) => @unshift e
@root.unselect = () => @unselect()
@root.selectNext = () => @selectNext()
@root.selectPrev = () => @selectPrev()
multiselect: () ->
return false if @get "dropdown"
@get "multiselect"
unshift: (item) ->
@push item, true
has_data: (v) ->
@get("data").includes v
push: (item, flag) ->
tag = @get "itemtag"
tag = item.tag if item.tag
el = $("<#{tag}>")
if flag
@get("data").unshift item if not @has_data item
$(@refs.mlist).prepend el[0]
else
@get("data").push item if not @has_data item
el.appendTo @refs.mlist
el[0].uify @observable
el[0]
.set "oncontextmenu", (e) =>
@iclick e, true
.set "ondbclick", (e) =>
@idbclick e, false
.set "onclick", (e) =>
@iclick e, false
.set "onselect", (e) =>
@iselect e
.set "onclose", (e) =>
@iclose e
.set "data", item
item.domel = el[0]
el[0]
remove: (item) ->
el = item.get "data"
data = @get "data"
@set "selectedItem", undefined if @get("selectedItem") is item
list = @get("selectedItems")
list.splice(list.indexOf(item), 1) if list.includes(item)
if data.includes el
data.splice data.indexOf(el), 1
$(item).remove()
selectNext: () ->
return if @multiselect()
el = @get "selectedItem"
idx = 0
idx = $(el).index() + 1 if el
@set "selected", idx
selectPrev: () ->
return if @multiselect()
el = @get "selectedItem"
idx = 0
idx = $(el).index() - 1 if el
@set "selected", idx
__selected__: (idx) ->
return @unselect() if idx < 0
data = @get "data"
return if idx >= data.length
data[idx].domel.set "selected", true
__buttons__: (v) ->
return if @get "dropdown"
return unless v.length > 0
for item in v
$(@refs.btlist).show()
bt = $("<afx-button>").appendTo @refs.btlist
bt[0].uify @observable
bt[0].set "*", item
item.domel = bt[0]
__data__: (data) ->
$( @refs.mlist).empty()
for item in data
@push item, false
$(@refs.container).off "mousedown", @onmousedown
if @__("dragndrop") and not @__("dropdown")
$(@refs.container).on "mousedown", @onmousedown
unselect: () ->
v.set "selected", false for v in @get("selectedItems")
@set "selectedItems", []
@set "selectedItem", undefined
iclick: (e, flag) ->
return if not e.item
list = @get("selectedItems")
if @multiselect() and list.includes(e.item) and not flag
list.splice(list.indexOf(e.item), 1)
return e.item.set "selected", false
e.item.set "selected", true
idbclick: (e) ->
evt = { id: @aid(), data: e }
@get("onlistdbclick") evt
@observable.trigger "listdbclick", evt
iselect: (e) ->
return unless e.item
if @multiselect()
return if @get("selectedItems").includes e.item
@set "selectedItem", e.item
@get("selectedItems").push e.item
e.items = @get("selectedItems")
else
return if @get("selectedItem") is e.item
@get("selectedItem").set "selected", false if @get("selectedItem")
@set "selectedItem", e.item
@set "selectedItems", [e.item]
e.items = [e.item]
#scroll element
li = $(e.item).children()[0]
offset = $(@refs.container).offset()
top = $(@refs.container).scrollTop()
if ($(li).offset().top + $(li).height() > $(@refs.container).height() + offset.top)
$(@refs.container).scrollTop(top + $(@refs.container).height() - $(li).height())
else if ($(li).offset().top < offset.top)
$(@refs.container).scrollTop(top - $(@refs.container).height() + $(li).height())
if @get "dropdown"
@refs.drlabel.set "*", e.item.get "data"
$(@refs.mlist).hide()
evt = { id: @aid(), data: e }
@get("onlistselect") evt
@observable.trigger "listselect", evt
mount: () ->
@dnd = {}
@onmousedown = (e) =>
el = $(e.target).closest("li[dataref='afx-list-item']")
return if el.length is 0
el = el.parent()[0]
@dnd.from = el
@dnd.to = undefined
$(window).on "mouseup", @onmouseup
$(window).on "mousemove", @onmousemove
@onmouseup = (e) =>
$(window).off "mouseup", @onmouseup
$(window).off "mousemove", @onmousemove
($ "#systooltip").hide()
el = $(e.target).closest("li[dataref='afx-list-item']")
return if el.length is 0
el = el.parent()[0]
return if el is @dnd.from
@dnd.to = el
@__("ondragndrop") { id: @aid(), data: @dnd }
@dnd = {}
@onmousemove = (e) =>
return unless e
return unless @dnd.from
data = @dnd.from.get("data")
$label = $("#systooltip")
top = e.clientY + 5
left = e.clientX + 5
$label.show()
$label[0].set "*", data
$label
.css "top", top + "px"
.css "left", left + "px"
$(@refs.btlist).hide()
@observable.on "resize", (e) => @calibrate()
@calibrate()
iclose: (e) ->
return unless e.item
evt = { id: @aid(), data: e }
r = @get("onitemclose") evt
return unless r
@observable.trigger "itemclose", evt
@remove(e.item)
__dropdown__: (v) ->
$(@refs.container).removeAttr "style"
$(@refs.mlist).removeAttr "style"
$(@refs.container).css "flex", 1
$(@root).removeClass()
drop = (e) =>
@dropoff e
show = (e) =>
@showlist e
if v
$(@root).addClass "dropdown"
$(@refs.current).show()
$(document).on "click", drop
$(@refs.current).on "click", show
$(@refs.container)
.css "position", "absolute"
.css "display", "inline-block"
$(@refs.mlist)
.css "position", "absolute"
.css "display", "none"
.css "top", "100%"
.css "left", "0"
@calibrate()
else
$(@refs.current).hide()
$(document).off "click", drop
$(@refs.current).off "click", show
showlist: (e) ->
return unless @get "dropdown"
desktoph = $(Ant.OS.GUI.workspace).height()
offset = $(@root).offset().top + $(@refs.mlist).height()
if offset > desktoph
$(@refs.mlist)
.css "top", "-#{$(@refs.mlist).outerHeight()}px"
else
$(@mlist).css "top", "100%"
$(@refs.mlist).show()
dropoff: (e) ->
$(@refs.mlist).hide() if $(e.target).closest(@refs.container).length is 0
calibrate: (e) ->
return unless @get "dropdown"
w = "#{$(@root).width()}px"
$(@refs.container).css "width", w
$(@refs.current).css "width", w
$(@refs.mlist).css "width", w
layout: () ->
[
{
el: "div",
class: "list-container",
ref: "container",
children: [
{
el: "div", ref: "current", children: [
{ el: "afx-label", ref: "drlabel" }
]
},
{ el: "ul", ref: "mlist" }
]
},
{ el: "div", class: "button_container", ref: "btlist" }
]
Ant.OS.GUI.define "afx-list-view", ListViewTag
Ant.OS.GUI.define "afx-list-item-proto", ListViewItemTag
Ant.OS.GUI.define "afx-list-item", SimpleListItemTag

View File

@ -0,0 +1,418 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class ListViewItemTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("data", {});
this.setopt("oncontextmenu", function(e) {});
this.setopt("onclick", function(e) {});
this.setopt("onselect", function(e) {});
this.setopt("ondbclick", function(e) {});
this.setopt("onclose", function(e) {});
this.setopt("index", 0);
this.setopt("closable", false);
this.setopt("selected", false);
}
__closable__(v) {
if (v) { return $(this.refs.btcl).show(); } else { return $(this.refs.btcl).hide(); }
}
__selected__(v) {
$(this.refs.item).removeClass();
if (!v) { return; }
$(this.refs.item).addClass("selected");
return this.get("onselect")({ item: this.root });
}
mount() {
$(this.refs.item).attr("dataref", "afx-list-item");
$(this.refs.item).contextmenu(e => {
e.item = this.root;
return this.get("oncontextmenu")(e);
});
$(this.refs.item).click(e => {
e.item = this.root;
return this.get("onclick")(e);
});
$(this.refs.item).dblclick(e => {
e.item = this.root;
return this.get("ondbclick")(e);
});
return $(this.refs.btcl).click(e => {
e.item = this.root;
return this.get("onclose")(e);
});
}
layout() {
return [{
el: "li", ref: "item", children: [
this.itemlayout(),
{ el: "i", class: "closable", ref: "btcl" }
]
}];
}
itemlayout() {}
}
class SimpleListItemTag extends ListViewItemTag {
constructor(r, o) {
super(r, o);
}
__data__(v) {
if (!v) { return; }
if (v.class) { this.refs.label.set("class", v.class); }
if (v.color) { this.refs.label.set("color", v.color); }
if (v.iconclass) { this.refs.label.set("iconclass", v.iconclass); }
if (v.icon) { this.refs.label.set("icon", v.icon); }
if (v.text) { this.refs.label.set("text", v.text); }
if (v.selected) { this.set("selected", v.selected); }
if (v.closable) { return this.set("closable", v.closable); }
}
__selected(v) {
return this.get("data").selected = v;
}
update() {
return this.set("data", this.get("data"));
}
itemlayout() {
return { el: "afx-label", ref: "label" };
}
}
class ListViewTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("onlistselect", function() {});
this.setopt("onlistdbclick", function() {});
this.setopt("ondragndrop", function() {});
this.setopt("onitemclose", () => true);
this.setopt("buttons", []);
this.setopt("data", []);
this.setopt("dropdown", false);
this.setopt("itemtag", "afx-list-item");
this.setopt("multiselect", false);
this.setopt("selectedItem", undefined);
this.setopt("selectedItems", []);
this.setopt("selected", -1);
this.setopt("dragndrop", false);
$(this.root)
.css("display", "flex")
.css("flex-direction", "column");
this.root.push = e => this.push(e);
this.root.remove = e => this.remove(e);
this.root.unshift = e => this.unshift(e);
this.root.unselect = () => this.unselect();
this.root.selectNext = () => this.selectNext();
this.root.selectPrev = () => this.selectPrev();
}
multiselect() {
if (this.get("dropdown")) { return false; }
return this.get("multiselect");
}
unshift(item) {
return this.push(item, true);
}
has_data(v) {
return this.get("data").includes(v);
}
push(item, flag) {
let tag = this.get("itemtag");
if (item.tag) { ({
tag
} = item); }
const el = $(`<${tag}>`);
if (flag) {
if (!this.has_data(item)) { this.get("data").unshift(item); }
$(this.refs.mlist).prepend(el[0]);
} else {
if (!this.has_data(item)) { this.get("data").push(item); }
el.appendTo(this.refs.mlist);
}
el[0].uify(this.observable);
el[0]
.set("oncontextmenu", e => {
return this.iclick(e, true);
}).set("ondbclick", e => {
return this.idbclick(e, false);
}).set("onclick", e => {
return this.iclick(e, false);
}).set("onselect", e => {
return this.iselect(e);
}).set("onclose", e => {
return this.iclose(e);
}).set("data", item);
item.domel = el[0];
return el[0];
}
remove(item) {
const el = item.get("data");
const data = this.get("data");
if (this.get("selectedItem") === item) { this.set("selectedItem", undefined); }
const list = this.get("selectedItems");
if (list.includes(item)) { list.splice(list.indexOf(item), 1); }
if (data.includes(el)) {
data.splice(data.indexOf(el), 1);
}
return $(item).remove();
}
selectNext() {
if (this.multiselect()) { return; }
const el = this.get("selectedItem");
let idx = 0;
if (el) { idx = $(el).index() + 1; }
return this.set("selected", idx);
}
selectPrev() {
if (this.multiselect()) { return; }
const el = this.get("selectedItem");
let idx = 0;
if (el) { idx = $(el).index() - 1; }
return this.set("selected", idx);
}
__selected__(idx) {
if (idx < 0) { return this.unselect(); }
const data = this.get("data");
if (idx >= data.length) { return; }
return data[idx].domel.set("selected", true);
}
__buttons__(v) {
if (this.get("dropdown")) { return; }
if (!(v.length > 0)) { return; }
return (() => {
const result = [];
for (let item of Array.from(v)) {
$(this.refs.btlist).show();
const bt = $("<afx-button>").appendTo(this.refs.btlist);
bt[0].uify(this.observable);
bt[0].set("*", item);
result.push(item.domel = bt[0]);
}
return result;
})();
}
__data__(data) {
$( this.refs.mlist).empty();
for (let item of Array.from(data)) {
this.push(item, false);
}
$(this.refs.container).off("mousedown", this.onmousedown);
if (this.__("dragndrop") && !this.__("dropdown")) {
return $(this.refs.container).on("mousedown", this.onmousedown);
}
}
unselect() {
for (let v of Array.from(this.get("selectedItems"))) { v.set("selected", false); }
this.set("selectedItems", []);
return this.set("selectedItem", undefined);
}
iclick(e, flag) {
if (!e.item) { return; }
const list = this.get("selectedItems");
if (this.multiselect() && list.includes(e.item) && !flag) {
list.splice(list.indexOf(e.item), 1);
return e.item.set("selected", false);
}
return e.item.set("selected", true);
}
idbclick(e) {
const evt = { id: this.aid(), data: e };
this.get("onlistdbclick")(evt);
return this.observable.trigger("listdbclick", evt);
}
iselect(e) {
if (!e.item) { return; }
if (this.multiselect()) {
if (this.get("selectedItems").includes(e.item)) { return; }
this.set("selectedItem", e.item);
this.get("selectedItems").push(e.item);
e.items = this.get("selectedItems");
} else {
if (this.get("selectedItem") === e.item) { return; }
if (this.get("selectedItem")) { this.get("selectedItem").set("selected", false); }
this.set("selectedItem", e.item);
this.set("selectedItems", [e.item]);
e.items = [e.item];
//scroll element
const li = $(e.item).children()[0];
const offset = $(this.refs.container).offset();
const top = $(this.refs.container).scrollTop();
if (($(li).offset().top + $(li).height()) > ($(this.refs.container).height() + offset.top)) {
$(this.refs.container).scrollTop((top + $(this.refs.container).height()) - $(li).height());
} else if ($(li).offset().top < offset.top) {
$(this.refs.container).scrollTop((top - $(this.refs.container).height()) + $(li).height());
}
}
if (this.get("dropdown")) {
this.refs.drlabel.set("*", e.item.get("data"));
$(this.refs.mlist).hide();
}
const evt = { id: this.aid(), data: e };
this.get("onlistselect")(evt);
return this.observable.trigger("listselect", evt);
}
mount() {
this.dnd = {};
this.onmousedown = e => {
let el = $(e.target).closest("li[dataref='afx-list-item']");
if (el.length === 0) { return; }
el = el.parent()[0];
this.dnd.from = el;
this.dnd.to = undefined;
$(window).on("mouseup", this.onmouseup);
return $(window).on("mousemove", this.onmousemove);
};
this.onmouseup = e => {
$(window).off("mouseup", this.onmouseup);
$(window).off("mousemove", this.onmousemove);
($("#systooltip")).hide();
let el = $(e.target).closest("li[dataref='afx-list-item']");
if (el.length === 0) { return; }
el = el.parent()[0];
if (el === this.dnd.from) { return; }
this.dnd.to = el;
this.__("ondragndrop")({ id: this.aid(), data: this.dnd });
return this.dnd = {};
};
this.onmousemove = e => {
if (!e) { return; }
if (!this.dnd.from) { return; }
const data = this.dnd.from.get("data");
const $label = $("#systooltip");
const top = e.clientY + 5;
const left = e.clientX + 5;
$label.show();
$label[0].set("*", data);
return $label
.css("top", top + "px")
.css("left", left + "px");
};
$(this.refs.btlist).hide();
this.observable.on("resize", e => this.calibrate());
return this.calibrate();
}
iclose(e) {
if (!e.item) { return; }
const evt = { id: this.aid(), data: e };
const r = this.get("onitemclose")(evt);
if (!r) { return; }
this.observable.trigger("itemclose", evt);
return this.remove(e.item);
}
__dropdown__(v) {
$(this.refs.container).removeAttr("style");
$(this.refs.mlist).removeAttr("style");
$(this.refs.container).css("flex", 1);
$(this.root).removeClass();
const drop = e => {
return this.dropoff(e);
};
const show = e => {
return this.showlist(e);
};
if (v) {
$(this.root).addClass("dropdown");
$(this.refs.current).show();
$(document).on("click", drop);
$(this.refs.current).on("click", show);
$(this.refs.container)
.css("position", "absolute")
.css("display", "inline-block");
$(this.refs.mlist)
.css("position", "absolute")
.css("display", "none")
.css("top", "100%")
.css("left", "0");
return this.calibrate();
} else {
$(this.refs.current).hide();
$(document).off("click", drop);
return $(this.refs.current).off("click", show);
}
}
showlist(e) {
if (!this.get("dropdown")) { return; }
const desktoph = $(Ant.OS.GUI.workspace).height();
const offset = $(this.root).offset().top + $(this.refs.mlist).height();
if (offset > desktoph) {
$(this.refs.mlist)
.css("top", `-${$(this.refs.mlist).outerHeight()}px`);
} else {
$(this.mlist).css("top", "100%");
}
return $(this.refs.mlist).show();
}
dropoff(e) {
if ($(e.target).closest(this.refs.container).length === 0) { return $(this.refs.mlist).hide(); }
}
calibrate(e) {
if (!this.get("dropdown")) { return; }
const w = `${$(this.root).width()}px`;
$(this.refs.container).css("width", w);
$(this.refs.current).css("width", w);
return $(this.refs.mlist).css("width", w);
}
layout() {
return [
{
el: "div",
class: "list-container",
ref: "container",
children: [
{
el: "div", ref: "current", children: [
{ el: "afx-label", ref: "drlabel" }
]
},
{ el: "ul", ref: "mlist" }
]
},
{ el: "div", class: "button_container", ref: "btlist" }
];
}
}
Ant.OS.GUI.define("afx-list-view", ListViewTag);
Ant.OS.GUI.define("afx-list-item-proto", ListViewItemTag);
Ant.OS.GUI.define("afx-list-item", SimpleListItemTag);

View File

@ -1,242 +0,0 @@
class MenuEntryTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "data", {}
@setopt "onmenuselect", () ->
@setopt "onchildselect", () ->
@setopt "children", undefined
@setopt "child", undefined
@setopt "parent", undefined
@setopt "root", undefined
__data__: (data) ->
@set k, v for k, v of data
__child__: (v) ->
@set "children", v
has_children: () ->
ch = @get "children"
return ch and ch.length > 0
is_root: () ->
return if @get "parent" then false else true
layout: () ->
[{
el: "li", ref: "container", children: [
{
el: "a", ref: "entry", children: @itemlayout()
},
{ el: "afx-menu", ref: "submenu" }
]
}]
__children__: (v) ->
$(@refs.container).removeClass("afx_submenu")
return $(@refs.submenu).hide() unless v and v.length > 0
$(@refs.container).addClass("afx_submenu")
$(@refs.submenu)
.show()
.attr("style", "")
@refs.submenu.set "parent", @
@refs.submenu.set "root", @get("root")
@refs.submenu.set "items", v
if @is_root()
$(@refs.container).mouseleave (e) =>
$(@refs.submenu).attr("style", "")
mount: () ->
$(@refs.entry).click (e) => @select e
submenuoff: () ->
p = @get "parent"
return $(@refs.submenu).attr("style", "") unless p
p.submenuoff()
select: (e) ->
e.item = @root
evt = { id: @aid(), data: e }
e.preventDefault()
if @is_root() and @has_children() and not @get "context"
$(@refs.submenu).show()
else
@submenuoff()
@get("onmenuselect") evt
if @get("parent")
@get("parent").get("onchildselect") evt
if @get("root")
@get("root").get("onmenuitemselect") evt
itemlayout: () ->
class SimpleMenuEntryTag extends MenuEntryTag
constructor: (r, o) ->
super r, o
@setopt "switch", false
@setopt "radio", false
@setopt "color", undefined
@setopt "icon", undefined
@setopt "iconclass", undefined
@setopt "text", ""
@setopt "shortcut", undefined
@setopt "checked", false
__switch__: (v) ->
if @get("radio") or v
$(@refs.switch).show()
else
$(@refs.switch).hide()
__radio__: (v) ->
if @get("switch") or v
$(@refs.switch).show()
else
$(@refs.switch).hide()
__checked__: (v) ->
@get("data").checked = v
return unless @get("radio") or @get("switch")
@refs.switch.set "swon", v
__color__: (v) ->
return unless v
@refs.label.set "color", v
__icon__: (v) ->
$(@refs.container).removeClass("fix_padding")
return unless v
@refs.label.set "icon", v
$(@refs.container).addClass("fix_padding")
__iconclass__: (v) ->
return unless v
@refs.label.set "iconclass", v
__text__: (v) ->
return unless v isnt undefined
@refs.label.set "text", v
__shortcut__: (v) ->
$(@refs.shortcut).hide()
return unless v
$(@refs.shortcut).show()
$(@refs.shortcut).text v
reset_radio: () ->
return unless @has_children()
for v in @get "children"
return unless v.domel.get "radio"
v.domel.set "checked", false
mount: () ->
super.mount()
@refs.switch.set "enable", false
select: (e) ->
if @get "switch"
@set "checked", !@get "checked"
else if @get "radio"
p = @get "parent"
p.reset_radio() if p
@set "checked", !@get "checked"
super.select(e)
itemlayout: () ->
[
{ el: "afx-switch", ref: "switch" },
{ el: "afx-label", ref: "label" },
{ el: "span", class: "shortcut", ref: "shortcut" }
]
class MenuTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "context", false
@setopt "parent", undefined
@setopt "root", undefined
@setopt "contentag", "afx-menu-entry"
@setopt "onmenuitemselect", (e) => @handleselect e
@setopt "onmenuselect", (e) ->
@setopt "items", []
@root.show = (e) =>
@showctxmenu e
@root.push = (e) => @push e
@root.remove = (e) => @remove e
@root.unshift = (e) => @unshift e
handleselect: (e) ->
$(@root).hide() if @isctxmenu()
e.id = @aid()
@get("onmenuselect") e
@observable.trigger "menuselect", e
showctxmenu: (e) ->
return unless @get "context"
$(@root)
.css("top", e.clientY - 15 + "px")
.css("left", e.clientX - 5 + "px")
.show()
isctxmenu: () ->
return @get "context"
is_root: () ->
return @get("root") is undefined
mount: () ->
$(@refs.container).css "display", "contents"
return unless @isctxmenu()
$(@refs.wrapper).mouseleave (e) =>
return unless @is_root()
$(@root).hide()
__context__: (v) ->
$(@refs.wrapper).removeClass("context")
return unless v
$(@refs.wrapper).addClass("context")
$(@root).hide()
unshift: (item) ->
@push item, true
remove: (item) ->
el = item.get "data"
data = @get "items"
if data.includes el
data.splice data.indexOf(el), 1
$(item).remove()
push: (item, flag) ->
tag = @get "contentag"
tag = item.tag if item.tag
items = @get "items"
el = $("<#{tag}>")
if flag
$(@refs.container).prepend el[0]
@get("items").unshift item if not items.includes item
else
el.appendTo @refs.container
@get("items").push item if not items.includes item
el[0].uify undefined
el[0].set "parent", @get("parent")
el[0].set "root", if @get("parent") then @get("parent").get("root") else @
el[0].set "data", item
item.domel = el[0]
el[0]
__items__: (data) ->
$(@refs.container).empty()
for item in data
@push item, false
layout: () ->
[{ el: "ul", ref: "wrapper", children: [
{ el: "li", class: "afx-corner-fix" },
{ el: "div", ref: "container" },
{ el: "li", class: "afx-corner-fix" }
] }]
Ant.OS.GUI.define "afx-menu", MenuTag
Ant.OS.GUI.define "afx-menu-entry-proto", MenuEntryTag
Ant.OS.GUI.define "afx-menu-entry", SimpleMenuEntryTag

309
src/core/tags/MenuTag.js Normal file
View File

@ -0,0 +1,309 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class MenuEntryTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("data", {});
this.setopt("onmenuselect", function() {});
this.setopt("onchildselect", function() {});
this.setopt("children", undefined);
this.setopt("child", undefined);
this.setopt("parent", undefined);
this.setopt("root", undefined);
}
__data__(data) {
return (() => {
const result = [];
for (let k in data) {
const v = data[k];
result.push(this.set(k, v));
}
return result;
})();
}
__child__(v) {
return this.set("children", v);
}
has_children() {
const ch = this.get("children");
return ch && (ch.length > 0);
}
is_root() {
if (this.get("parent")) { return false; } else { return true; }
}
layout() {
return [{
el: "li", ref: "container", children: [
{
el: "a", ref: "entry", children: this.itemlayout()
},
{ el: "afx-menu", ref: "submenu" }
]
}];
}
__children__(v) {
$(this.refs.container).removeClass("afx_submenu");
if (!v || !(v.length > 0)) { return $(this.refs.submenu).hide(); }
$(this.refs.container).addClass("afx_submenu");
$(this.refs.submenu)
.show()
.attr("style", "");
this.refs.submenu.set("parent", this);
this.refs.submenu.set("root", this.get("root"));
this.refs.submenu.set("items", v);
if (this.is_root()) {
return $(this.refs.container).mouseleave(e => {
return $(this.refs.submenu).attr("style", "");
});
}
}
mount() {
return $(this.refs.entry).click(e => this.select(e));
}
submenuoff() {
const p = this.get("parent");
if (!p) { return $(this.refs.submenu).attr("style", ""); }
return p.submenuoff();
}
select(e) {
e.item = this.root;
const evt = { id: this.aid(), data: e };
e.preventDefault();
if (this.is_root() && this.has_children() && !this.get("context")) {
$(this.refs.submenu).show();
} else {
this.submenuoff();
}
this.get("onmenuselect")(evt);
if (this.get("parent")) {
this.get("parent").get("onchildselect")(evt);
}
if (this.get("root")) {
return this.get("root").get("onmenuitemselect")(evt);
}
}
itemlayout() {}
}
class SimpleMenuEntryTag extends MenuEntryTag {
constructor(r, o) {
super(r, o);
this.setopt("switch", false);
this.setopt("radio", false);
this.setopt("color", undefined);
this.setopt("icon", undefined);
this.setopt("iconclass", undefined);
this.setopt("text", "");
this.setopt("shortcut", undefined);
this.setopt("checked", false);
}
__switch__(v) {
if (this.get("radio") || v) {
return $(this.refs.switch).show();
} else {
return $(this.refs.switch).hide();
}
}
__radio__(v) {
if (this.get("switch") || v) {
return $(this.refs.switch).show();
} else {
return $(this.refs.switch).hide();
}
}
__checked__(v) {
this.get("data").checked = v;
if (!this.get("radio") && !this.get("switch")) { return; }
return this.refs.switch.set("swon", v);
}
__color__(v) {
if (!v) { return; }
return this.refs.label.set("color", v);
}
__icon__(v) {
$(this.refs.container).removeClass("fix_padding");
if (!v) { return; }
this.refs.label.set("icon", v);
return $(this.refs.container).addClass("fix_padding");
}
__iconclass__(v) {
if (!v) { return; }
return this.refs.label.set("iconclass", v);
}
__text__(v) {
if (v === undefined) { return; }
return this.refs.label.set("text", v);
}
__shortcut__(v) {
$(this.refs.shortcut).hide();
if (!v) { return; }
$(this.refs.shortcut).show();
return $(this.refs.shortcut).text(v);
}
reset_radio() {
if (!this.has_children()) { return; }
for (let v of Array.from(this.get("children"))) {
if (!v.domel.get("radio")) { return; }
v.domel.set("checked", false);
}
}
mount() {
super.mount();
return this.refs.switch.set("enable", false);
}
select(e) {
if (this.get("switch")) {
this.set("checked", !this.get("checked"));
} else if (this.get("radio")) {
const p = this.get("parent");
if (p) { p.reset_radio(); }
this.set("checked", !this.get("checked"));
}
return super.select(e);
}
itemlayout() {
return [
{ el: "afx-switch", ref: "switch" },
{ el: "afx-label", ref: "label" },
{ el: "span", class: "shortcut", ref: "shortcut" }
];
}
}
class MenuTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("context", false);
this.setopt("parent", undefined);
this.setopt("root", undefined);
this.setopt("contentag", "afx-menu-entry");
this.setopt("onmenuitemselect", e => this.handleselect(e));
this.setopt("onmenuselect", function(e) {});
this.setopt("items", []);
this.root.show = e => {
return this.showctxmenu(e);
};
this.root.push = e => this.push(e);
this.root.remove = e => this.remove(e);
this.root.unshift = e => this.unshift(e);
}
handleselect(e) {
if (this.isctxmenu()) { $(this.root).hide(); }
e.id = this.aid();
this.get("onmenuselect")(e);
return this.observable.trigger("menuselect", e);
}
showctxmenu(e) {
if (!this.get("context")) { return; }
return $(this.root)
.css("top", (e.clientY - 15) + "px")
.css("left", (e.clientX - 5) + "px")
.show();
}
isctxmenu() {
return this.get("context");
}
is_root() {
return this.get("root") === undefined;
}
mount() {
$(this.refs.container).css("display", "contents");
if (!this.isctxmenu()) { return; }
return $(this.refs.wrapper).mouseleave(e => {
if (!this.is_root()) { return; }
return $(this.root).hide();
});
}
__context__(v) {
$(this.refs.wrapper).removeClass("context");
if (!v) { return; }
$(this.refs.wrapper).addClass("context");
return $(this.root).hide();
}
unshift(item) {
return this.push(item, true);
}
remove(item) {
const el = item.get("data");
const data = this.get("items");
if (data.includes(el)) {
data.splice(data.indexOf(el), 1);
}
return $(item).remove();
}
push(item, flag) {
let tag = this.get("contentag");
if (item.tag) { ({
tag
} = item); }
const items = this.get("items");
const el = $(`<${tag}>`);
if (flag) {
$(this.refs.container).prepend(el[0]);
if (!items.includes(item)) { this.get("items").unshift(item); }
} else {
el.appendTo(this.refs.container);
if (!items.includes(item)) { this.get("items").push(item); }
}
el[0].uify(undefined);
el[0].set("parent", this.get("parent"));
el[0].set("root", this.get("parent") ? this.get("parent").get("root") : this);
el[0].set("data", item);
item.domel = el[0];
return el[0];
}
__items__(data) {
$(this.refs.container).empty();
return Array.from(data).map((item) =>
this.push(item, false));
}
layout() {
return [{ el: "ul", ref: "wrapper", children: [
{ el: "li", class: "afx-corner-fix" },
{ el: "div", ref: "container" },
{ el: "li", class: "afx-corner-fix" }
] }];
}
}
Ant.OS.GUI.define("afx-menu", MenuTag);
Ant.OS.GUI.define("afx-menu-entry-proto", MenuEntryTag);
Ant.OS.GUI.define("afx-menu-entry", SimpleMenuEntryTag);

View File

@ -1,78 +0,0 @@
class NSpinnerTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "onchange", (e) ->
@setopt "value", 0
@setopt "step", 1
mount: () ->
$(@refs.holder).attr "type", "text"
$(@refs.incr).click (e) =>
@set "value", (@get("value") + @get("step") )
$(@refs.decr).click (e) =>
@set "value", (@get("value") - @get("step") )
# @observable.on "calibrate", () -> @calibrate()
@observable.on "resize", () => @calibrate()
$(@refs.holder).on 'keyup', (e) =>
if e.keyCode is 13
val = @refs.holder.value
if not isNaN(val)
val = parseInt(val)
val = @value if val < 0
@set "value", val
@calibrate()
calibrate: () ->
$(@refs.holder).css "width", $(@root).width() - 20 + "px"
$(@refs.holder).css "height", $(@root).height() + "px"
$(@refs.spinner)
.css "width", "20px"
.css "height", $(@root).height() + "px"
$(@refs.incr)
.css "height", $(@root).height() / 2 - 2 + "px"
.css "position", "relative"
$(@refs.decr).css "height", $(@root).height() / 2 - 2 + "px"
.css "position", "relative"
$(@refs.spinner).find("li")
.css "display", "block"
.css "text-align", "center"
.css "vertical-align", "middle"
$(@refs.spinner).find("i")
.css "font-size", "16px"
.css "position", "absolute"
fn = (ie, pos) ->
el = $(ie).find("i")
el
.css pos, ($(ie).height() - el.height()) / 2 + "px"
.css "left", ($(ie).width() - el.width()) / 2 + "px"
fn @refs.decr, "bottom"
fn @refs.incr, "top"
__value__: (v) ->
$(@refs.holder).val @get("value")
evt = { id: @aid(), data: v }
@get("onchange")(evt)
@observable.trigger "nspin", evt
layout: () ->
[
{
el: "input", ref: "holder"
},
{
el: "ul", ref: "spinner", children: [
{ el: "li", class: "incr", ref: "incr", children: [
{ el: "i" }
] },
{ el: "li", class: "decr", ref: "decr", children: [
{ el: "i" }
] }
]
}
]
Ant.OS.GUI.define "afx-nspinner", NSpinnerTag

View File

@ -0,0 +1,95 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class NSpinnerTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("onchange", function(e) {});
this.setopt("value", 0);
this.setopt("step", 1);
}
mount() {
$(this.refs.holder).attr("type", "text");
$(this.refs.incr).click(e => {
return this.set("value", (this.get("value") + this.get("step") ));
});
$(this.refs.decr).click(e => {
return this.set("value", (this.get("value") - this.get("step") ));
});
// @observable.on "calibrate", () -> @calibrate()
this.observable.on("resize", () => this.calibrate());
$(this.refs.holder).on('keyup', e => {
if (e.keyCode === 13) {
let val = this.refs.holder.value;
if (!isNaN(val)) {
val = parseInt(val);
if (val < 0) { val = this.value; }
return this.set("value", val);
}
}
});
return this.calibrate();
}
calibrate() {
$(this.refs.holder).css("width", ($(this.root).width() - 20) + "px");
$(this.refs.holder).css("height", $(this.root).height() + "px");
$(this.refs.spinner)
.css("width", "20px")
.css("height", $(this.root).height() + "px");
$(this.refs.incr)
.css("height", (($(this.root).height() / 2) - 2) + "px")
.css("position", "relative");
$(this.refs.decr).css("height", (($(this.root).height() / 2) - 2) + "px")
.css("position", "relative");
$(this.refs.spinner).find("li")
.css("display", "block")
.css("text-align", "center")
.css("vertical-align", "middle");
$(this.refs.spinner).find("i")
.css("font-size", "16px")
.css("position", "absolute");
const fn = function(ie, pos) {
const el = $(ie).find("i");
return el
.css(pos, (($(ie).height() - el.height()) / 2) + "px")
.css("left", (($(ie).width() - el.width()) / 2) + "px");
};
fn(this.refs.decr, "bottom");
return fn(this.refs.incr, "top");
}
__value__(v) {
$(this.refs.holder).val(this.get("value"));
const evt = { id: this.aid(), data: v };
this.get("onchange")(evt);
return this.observable.trigger("nspin", evt);
}
layout() {
return [
{
el: "input", ref: "holder"
},
{
el: "ul", ref: "spinner", children: [
{ el: "li", class: "incr", ref: "incr", children: [
{ el: "i" }
] },
{ el: "li", class: "decr", ref: "decr", children: [
{ el: "i" }
] }
]
}
];
}
}
Ant.OS.GUI.define("afx-nspinner", NSpinnerTag);

View File

@ -1,44 +0,0 @@
class OverlayTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "width", undefined
@setopt "height", undefined
$(@refs.yield)
.css("position", "relative")
.css("width", "100%" )
.css("height", "100%")
$(@root)
.css("position", "absolute")
.css "z-index", 1000000
#.css "display", "flex"
#.css "flex-direction", "column"
#$(@refs.yield).css "flex", "1"
__width__: (v) ->
return unless v
@calibrate()
__height__: (v) ->
return unless v
@calibrate()
mount: () ->
@calibrate()
calibrate: () ->
$(@root)
.css("width", @get("width") )
.css("height", @get("height"))
@observable.trigger "resize", {
id: @aid(),
data: {
w: @get("width"),
h: @get("height")
}
}
layout: () ->
[{
el: "afx-vbox", ref: "yield"
}]
Ant.OS.GUI.define "afx-overlay", OverlayTag

View File

@ -0,0 +1,56 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class OverlayTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("width", undefined);
this.setopt("height", undefined);
$(this.refs.yield)
.css("position", "relative")
.css("width", "100%" )
.css("height", "100%");
$(this.root)
.css("position", "absolute")
.css("z-index", 1000000);
}
//.css "display", "flex"
//.css "flex-direction", "column"
//$(@refs.yield).css "flex", "1"
__width__(v) {
if (!v) { return; }
return this.calibrate();
}
__height__(v) {
if (!v) { return; }
return this.calibrate();
}
mount() {
return this.calibrate();
}
calibrate() {
$(this.root)
.css("width", this.get("width") )
.css("height", this.get("height"));
return this.observable.trigger("resize", {
id: this.aid(),
data: {
w: this.get("width"),
h: this.get("height")
}
});
}
layout() {
return [{
el: "afx-vbox", ref: "yield"
}];
}
}
Ant.OS.GUI.define("afx-overlay", OverlayTag);

View File

@ -1,69 +0,0 @@
class ResizerTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@dir = "hz"
@resizable_el = undefined
@parent = $(@root).parent().parent()
@minsize = 0
mount: () ->
$(@root).css " display", "block"
tagname = $(@parent).prop("tagName")
@resizable_el = if $(@root).prev().length is 1 then $(@root).prev()[0] else undefined
if tagname is "AFX-HBOX"
@dir = "hz"
$(@root).css "cursor", "col-resize"
$(@root).addClass "horizontal"
if @resizable_el
att = $(@resizable_el).attr "min-width"
@minsize = parseInt(att) if att
else if tagname is "AFX-VBOX"
@dir = "ve"
$(@root).css "cursor", "row-resize"
$(@root).addClass "vertical"
if @resizable_el
att = $(@resizable_el).attr "min-height"
@minsize = parseInt(att) if att
else
@dir = "none"
@minsize = 10 if @minsize is 0
@draggable()
draggable: () ->
$(@root).css "user-select", "none"
$(@root).on "mousedown", (e) =>
e.preventDefault()
$(window).on "mousemove", (evt) =>
return unless @resizable_el
if @dir is "hz"
@horizontalResize evt
else if @dir is "ve"
@verticalResize evt
$(window).on "mouseup", (evt) ->
$(window).unbind "mousemove", null
$(window).unbind "mouseup", null
$(window).unbind "mouseup", null
horizontalResize: (e) ->
return unless @resizable_el
offset = $(@resizable_el).offset()
w = Math.round(e.clientX - offset.left)
w = @minsize if w < @minsize
$(@resizable_el).attr "data-width", w.toString()
@observable.trigger "resize", { id: @aid(), data: { w: w } }
verticalResize: (e) ->
return unless @resizable_el
offset = $(@resizable_el).offset()
h = Math.round(e.clientY - offset.top)
h = @minsize if h < @minsize
$(@resizable_el).attr "data-height", h.toString()
@observable.trigger "resize", { id: @aid(), data: { w: w } }
layout: () ->
[]
Ant.OS.GUI.define "afx-resizer", ResizerTag

View File

@ -0,0 +1,89 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class ResizerTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.dir = "hz";
this.resizable_el = undefined;
this.parent = $(this.root).parent().parent();
this.minsize = 0;
}
mount() {
let att;
$(this.root).css(" display", "block");
const tagname = $(this.parent).prop("tagName");
this.resizable_el = $(this.root).prev().length === 1 ? $(this.root).prev()[0] : undefined;
if (tagname === "AFX-HBOX") {
this.dir = "hz";
$(this.root).css("cursor", "col-resize");
$(this.root).addClass("horizontal");
if (this.resizable_el) {
att = $(this.resizable_el).attr("min-width");
if (att) { this.minsize = parseInt(att); }
}
} else if (tagname === "AFX-VBOX") {
this.dir = "ve";
$(this.root).css("cursor", "row-resize");
$(this.root).addClass("vertical");
if (this.resizable_el) {
att = $(this.resizable_el).attr("min-height");
if (att) { this.minsize = parseInt(att); }
}
} else {
this.dir = "none";
}
if (this.minsize === 0) { this.minsize = 10; }
return this.draggable();
}
draggable() {
$(this.root).css("user-select", "none");
return $(this.root).on("mousedown", e => {
e.preventDefault();
$(window).on("mousemove", evt => {
if (!this.resizable_el) { return; }
if (this.dir === "hz") {
return this.horizontalResize(evt);
} else if (this.dir === "ve") {
return this.verticalResize(evt);
}
});
return $(window).on("mouseup", function(evt) {
$(window).unbind("mousemove", null);
$(window).unbind("mouseup", null);
return $(window).unbind("mouseup", null);
});
});
}
horizontalResize(e) {
if (!this.resizable_el) { return; }
const offset = $(this.resizable_el).offset();
let w = Math.round(e.clientX - offset.left);
if (w < this.minsize) { w = this.minsize; }
$(this.resizable_el).attr("data-width", w.toString());
return this.observable.trigger("resize", { id: this.aid(), data: { w } });
}
verticalResize(e) {
if (!this.resizable_el) { return; }
const offset = $(this.resizable_el).offset();
let h = Math.round(e.clientY - offset.top);
if (h < this.minsize) { h = this.minsize; }
$(this.resizable_el).attr("data-height", h.toString());
return this.observable.trigger("resize", { id: this.aid(), data: { w } });
}
layout() {
return [];
}
}
Ant.OS.GUI.define("afx-resizer", ResizerTag);

View File

@ -1,90 +0,0 @@
class SliderTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "dragable", true
@setopt "max", 100
@setopt "value", 0
@setopt "onchanging", (e) ->
@setopt "onchange", (e) ->
__value__: () ->
@calibrate()
__max__: () ->
@calibrate()
__dragable__: (v) ->
if v
$(@root)
.mouseover () =>
$(@refs.point).show()
.mouseout () =>
$(@refs.point).hide()
else
$(@refs.point).hide()
$(@root)
.unbind("mouseover")
.ubbind("mouseout")
mount: () ->
@enable_dragging()
$(@refs.point).css "position", "absolute"
$(@refs.point).hide()
@observable.on "resize", (e) =>
@calibrate()
$(@refs.container).click (e) =>
offset = $(@refs.container).offset()
left = e.clientX - offset.left
maxw = $(@refs.container).width()
@set "value", left * @get("max") / maxw
@calibrate()
evt = { id: @aid(), data: @get("value") }
@get("onchange") evt
@get("onchanging") evt
@calibrate()
calibrate: () ->
@set "value", @get("max") if @get("value") > @get("max")
$(@refs.container).css "width", $(@root).width() + "px"
w = $(@refs.container).width() * @get("value") / @get("max")
$(@refs.prg)
.css "width", w + "px"
.css "height", $(@refs.container).height() + "px"
if @get("dragable")
ow = w - $(@refs.point).width() / 2
top = Math.floor(($(@refs.prg).height() - $(@refs.point).height()) / 2)
$(@refs.point)
.css "left", ow + "px"
.css "top", top + "px"
enable_dragging: () ->
$(@refs.point)
.css "user-select", "none"
.css "cursor", "default"
$(@refs.point).on "mousedown", (e) =>
e.preventDefault()
offset = $(@refs.container).offset()
$(window).on "mousemove", (e) =>
left = e.clientX - offset.left
left = if left < 0 then 0 else left
maxw = $(@refs.container).width()
left = if left > maxw then maxw else left
@set "value", left * @get("max") / maxw
@calibrate()
@get("onchanging") { id: @aid(), data: @get("value") }
$(window).on "mouseup", (e) =>
@get("onchange") { id: @aid(), data: @get("value") }
$(window).unbind("mousemove", null)
$(window).unbind("mouseup", null)
layout: () ->
[{
el: "div", class: "container", ref: "container", children: [
{ el: "div", class: "progress", ref: "prg" },
{ el: "div", class: "dragpoint", ref: "point" }
]
}]
Ant.OS.GUI.define "afx-slider", SliderTag

112
src/core/tags/SliderTag.js Normal file
View File

@ -0,0 +1,112 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class SliderTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("dragable", true);
this.setopt("max", 100);
this.setopt("value", 0);
this.setopt("onchanging", function(e) {});
this.setopt("onchange", function(e) {});
}
__value__() {
return this.calibrate();
}
__max__() {
return this.calibrate();
}
__dragable__(v) {
if (v) {
return $(this.root)
.mouseover(() => {
return $(this.refs.point).show();
}).mouseout(() => {
return $(this.refs.point).hide();
});
} else {
$(this.refs.point).hide();
return $(this.root)
.unbind("mouseover")
.ubbind("mouseout");
}
}
mount() {
this.enable_dragging();
$(this.refs.point).css("position", "absolute");
$(this.refs.point).hide();
this.observable.on("resize", e => {
return this.calibrate();
});
$(this.refs.container).click(e => {
const offset = $(this.refs.container).offset();
const left = e.clientX - offset.left;
const maxw = $(this.refs.container).width();
this.set("value", (left * this.get("max")) / maxw);
this.calibrate();
const evt = { id: this.aid(), data: this.get("value") };
this.get("onchange")(evt);
return this.get("onchanging")(evt);
});
return this.calibrate();
}
calibrate() {
if (this.get("value") > this.get("max")) { this.set("value", this.get("max")); }
$(this.refs.container).css("width", $(this.root).width() + "px");
const w = ($(this.refs.container).width() * this.get("value")) / this.get("max");
$(this.refs.prg)
.css("width", w + "px")
.css("height", $(this.refs.container).height() + "px");
if (this.get("dragable")) {
const ow = w - ($(this.refs.point).width() / 2);
const top = Math.floor(($(this.refs.prg).height() - $(this.refs.point).height()) / 2);
return $(this.refs.point)
.css("left", ow + "px")
.css("top", top + "px");
}
}
enable_dragging() {
$(this.refs.point)
.css("user-select", "none")
.css("cursor", "default");
return $(this.refs.point).on("mousedown", e => {
e.preventDefault();
const offset = $(this.refs.container).offset();
$(window).on("mousemove", e => {
let left = e.clientX - offset.left;
left = left < 0 ? 0 : left;
const maxw = $(this.refs.container).width();
left = left > maxw ? maxw : left;
this.set("value", (left * this.get("max")) / maxw);
this.calibrate();
return this.get("onchanging")({ id: this.aid(), data: this.get("value") });
});
return $(window).on("mouseup", e => {
this.get("onchange")({ id: this.aid(), data: this.get("value") });
$(window).unbind("mousemove", null);
return $(window).unbind("mouseup", null);
});
});
}
layout() {
return [{
el: "div", class: "container", ref: "container", children: [
{ el: "div", class: "progress", ref: "prg" },
{ el: "div", class: "dragpoint", ref: "point" }
]
}];
}
}
Ant.OS.GUI.define("afx-slider", SliderTag);

View File

@ -1,29 +0,0 @@
class SwitchTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "swon", false
@setopt "enable", true
@setopt "onchange", (e) ->
mount: () ->
$(@refs.switch).click (e) =>
@onchange(e)
onchange: (e) ->
return unless @get "enable"
@setopt "swon", !@get("swon")
evt = { id: @aid(), data: @get "swon" }
@get("onchange") evt
@observable.trigger "switch", evt
__swon__: (v) ->
$(@refs.switch).removeClass()
$(@refs.switch).addClass "swon" if v
layout: () ->
[{
el: "span", ref: "switch"
}]
Ant.OS.GUI.define "afx-switch", SwitchTag

View File

@ -0,0 +1,41 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class SwitchTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("swon", false);
this.setopt("enable", true);
this.setopt("onchange", function(e) {});
}
mount() {
return $(this.refs.switch).click(e => {
return this.onchange(e);
});
}
onchange(e) {
if (!this.get("enable")) { return; }
this.setopt("swon", !this.get("swon"));
const evt = { id: this.aid(), data: this.get("swon") };
this.get("onchange")(evt);
return this.observable.trigger("switch", evt);
}
__swon__(v) {
$(this.refs.switch).removeClass();
if (v) { return $(this.refs.switch).addClass("swon"); }
}
layout() {
return [{
el: "span", ref: "switch"
}];
}
}
Ant.OS.GUI.define("afx-switch", SwitchTag);

View File

@ -1,177 +0,0 @@
class SystemPanelTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "osmenu", {
text: __("Start"),
iconclass: "fa fa-circle"
}
@setopt "appmenu", []
@setopt "systray", []
@root.attachservice = (s) => @attachservice s
@root.detachservice = (s) => @detachservice s
@view = false
__osmenu__: (v) ->
@refs.osmenu.set "items", [v]
__appmenu__: (v) ->
@refs.appmenu.set "items", v
__systray__: (v) ->
@refs.systray.set "items", v
attachservice: (s) ->
@refs.systray.unshift s
s.attach @refs.systray
open: () ->
el = @refs.applist.get "selectedItem"
return unless el
data = el.get("data")
return if not data or data.dataid is "header"
@toggle false
# launch the app or open the file
Ant.OS.GUI.openWith data
@refs.applist.unselect()
search: (e) ->
switch e.which
when 27
# escape key
@toggle false
when 37
e.preventDefault()
when 38
@refs.applist.selectPrev()
e.preventDefault()
when 39
e.preventDefault()
when 40
@refs.applist.selectNext()
e.preventDefault()
when 13
e.preventDefault()
@open()
else
text = @refs.search.value
return @refreshAppList() unless text.length >= 3
result = Ant.OS.API.search text
return if result.length is 0
@refs.applist.set "data", result
detachservice: (s) ->
@refs.systray.remove s.domel
layout: () ->
[
{
el: "div", ref: "panel", children: [
{ el: "afx-menu", ref: "osmenu", class: "afx-panel-os-menu" },
{ el: "afx-menu", id: "appmenu", ref: "appmenu", class: "afx-panel-os-app" },
{ el: "afx-menu", id: "systray", ref: "systray", class: "afx-panel-os-stray" }
]
},
{
el: "afx-overlay", id: "start-panel", ref: "overlay", children: [
{
el: "afx-hbox", height: 30, children: [
{ el: "div", width: 30, id: "searchicon" },
{ el: "input", ref: "search" }
]
},
{ el: "afx-list-view", id: "applist", ref: "applist" },
{
el: "afx-hbox", id: "btlist", height: 30, children: [
{
el: "afx-button",
ref: "btscreen",
tooltip: __("ct:Toggle fullscreen")
},
{
el: "afx-button",
ref: "btuser",
tooltip: __("ct:User: {0}", Ant.OS.setting.user.username)
},
{ el: "afx-button", ref: "btlogout", tooltip: __("ct:Logout") }
]
}
]
}
]
refreshAppList: () ->
list = []
list.push v for k, v of Ant.OS.setting.system.packages when (v and v.app)
list.push v for k, v of Ant.OS.setting.system.menu
list.sort (a, b) ->
if a.text < b.text
-1
else if a.text > b.text
1
else
0
@refs.applist.set "data", list
toggle: (flag) ->
@view = flag
if flag
@refreshAppList()
$(@refs.overlay).show()
@calibrate()
$(document).on "click", @cb
@refs.search.value = ""
$(@refs.search).focus()
else
$(@refs.overlay).hide()
$(document).unbind "click", @cb
calibrate: () ->
@refs.overlay.set "height", "#{$(window).height() - $(@refs.panel).height()}px"
mount: () ->
@cb = (e) =>
if not ($ e.target).closest($ @refs.overlay).length and not ($ e.target).closest(@refs.osmenu).length
@toggle false
else
$(@refs.search).focus()
$(@refs.appmenu).css("z-index", 1000000)
$(@refs.systray).css("z-index", 1000000)
@refs.btscreen.set "*", {
iconclass: "fa fa-tv",
onbtclick: (e) =>
@toggle false
Ant.OS.GUI.toggleFullscreen()
}
@refs.btuser.set "*", {
iconclass: "fa fa-user-circle-o",
onbtclick: (e) =>
@toggle false
Ant.OS.GUI.openDialog("InfoDialog", Ant.OS.setting.user)
}
@refs.btlogout.set "*", {
iconclass: "fa fa-power-off",
onbtclick: (e) =>
@toggle false
Ant.OS.exit()
}
@refs.osmenu.set "onmenuselect", (e) =>
@toggle true
($ @refs.overlay).css "left", 0
.css "top", "#{$(@refs.panel).height()}px"
.css "bottom", "0"
.hide()
($ @refs.search).keyup (e) =>
@search e
$(@refs.applist).click (e) =>
@open()
Ant.OS.GUI.bindKey "CTRL- ", (e) =>
if @view is false
@toggle true
else
@toggle false
Ant.OS.announcer.trigger("syspanelloaded")
Ant.OS.GUI.define "afx-sys-panel", SystemPanelTag

View File

@ -0,0 +1,211 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class SystemPanelTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("osmenu", {
text: __("Start"),
iconclass: "fa fa-circle"
});
this.setopt("appmenu", []);
this.setopt("systray", []);
this.root.attachservice = s => this.attachservice(s);
this.root.detachservice = s => this.detachservice(s);
this.view = false;
}
__osmenu__(v) {
return this.refs.osmenu.set("items", [v]);
}
__appmenu__(v) {
return this.refs.appmenu.set("items", v);
}
__systray__(v) {
return this.refs.systray.set("items", v);
}
attachservice(s) {
this.refs.systray.unshift(s);
return s.attach(this.refs.systray);
}
open() {
const el = this.refs.applist.get("selectedItem");
if (!el) { return; }
const data = el.get("data");
if (!data || (data.dataid === "header")) { return; }
this.toggle(false);
// launch the app or open the file
Ant.OS.GUI.openWith(data);
return this.refs.applist.unselect();
}
search(e) {
switch (e.which) {
case 27:
// escape key
return this.toggle(false);
case 37:
return e.preventDefault();
case 38:
this.refs.applist.selectPrev();
return e.preventDefault();
case 39:
return e.preventDefault();
case 40:
this.refs.applist.selectNext();
return e.preventDefault();
case 13:
e.preventDefault();
return this.open();
default:
var text = this.refs.search.value;
if (!(text.length >= 3)) { return this.refreshAppList(); }
var result = Ant.OS.API.search(text);
if (result.length === 0) { return; }
return this.refs.applist.set("data", result);
}
}
detachservice(s) {
return this.refs.systray.remove(s.domel);
}
layout() {
return [
{
el: "div", ref: "panel", children: [
{ el: "afx-menu", ref: "osmenu", class: "afx-panel-os-menu" },
{ el: "afx-menu", id: "appmenu", ref: "appmenu", class: "afx-panel-os-app" },
{ el: "afx-menu", id: "systray", ref: "systray", class: "afx-panel-os-stray" }
]
},
{
el: "afx-overlay", id: "start-panel", ref: "overlay", children: [
{
el: "afx-hbox", height: 30, children: [
{ el: "div", width: 30, id: "searchicon" },
{ el: "input", ref: "search" }
]
},
{ el: "afx-list-view", id: "applist", ref: "applist" },
{
el: "afx-hbox", id: "btlist", height: 30, children: [
{
el: "afx-button",
ref: "btscreen",
tooltip: __("ct:Toggle fullscreen")
},
{
el: "afx-button",
ref: "btuser",
tooltip: __("ct:User: {0}", Ant.OS.setting.user.username)
},
{ el: "afx-button", ref: "btlogout", tooltip: __("ct:Logout") }
]
}
]
}
];
}
refreshAppList() {
let k, v;
const list = [];
for (k in Ant.OS.setting.system.packages) { v = Ant.OS.setting.system.packages[k]; if (v && v.app) { list.push(v); } }
for (k in Ant.OS.setting.system.menu) { v = Ant.OS.setting.system.menu[k]; list.push(v); }
list.sort(function(a, b) {
if (a.text < b.text) {
return -1;
} else if (a.text > b.text) {
return 1;
} else {
return 0;
}
});
return this.refs.applist.set("data", list);
}
toggle(flag) {
this.view = flag;
if (flag) {
this.refreshAppList();
$(this.refs.overlay).show();
this.calibrate();
$(document).on("click", this.cb);
this.refs.search.value = "";
return $(this.refs.search).focus();
} else {
$(this.refs.overlay).hide();
return $(document).unbind("click", this.cb);
}
}
calibrate() {
return this.refs.overlay.set("height", `${$(window).height() - $(this.refs.panel).height()}px`);
}
mount() {
this.cb = e => {
if (!($(e.target)).closest($(this.refs.overlay)).length && !($(e.target)).closest(this.refs.osmenu).length) {
return this.toggle(false);
} else {
return $(this.refs.search).focus();
}
};
$(this.refs.appmenu).css("z-index", 1000000);
$(this.refs.systray).css("z-index", 1000000);
this.refs.btscreen.set("*", {
iconclass: "fa fa-tv",
onbtclick: e => {
this.toggle(false);
return Ant.OS.GUI.toggleFullscreen();
}
});
this.refs.btuser.set("*", {
iconclass: "fa fa-user-circle-o",
onbtclick: e => {
this.toggle(false);
return Ant.OS.GUI.openDialog("InfoDialog", Ant.OS.setting.user);
}
});
this.refs.btlogout.set("*", {
iconclass: "fa fa-power-off",
onbtclick: e => {
this.toggle(false);
return Ant.OS.exit();
}
});
this.refs.osmenu.set("onmenuselect", e => {
return this.toggle(true);
});
($(this.refs.overlay)).css("left", 0)
.css("top", `${$(this.refs.panel).height()}px`)
.css("bottom", "0")
.hide();
($(this.refs.search)).keyup(e => {
return this.search(e);
});
$(this.refs.applist).click(e => {
return this.open();
});
Ant.OS.GUI.bindKey("CTRL- ", e => {
if (this.view === false) {
return this.toggle(true);
} else {
return this.toggle(false);
}
});
return Ant.OS.announcer.trigger("syspanelloaded");
}
}
Ant.OS.GUI.define("afx-sys-panel", SystemPanelTag);

View File

@ -1,36 +0,0 @@
class TabBarTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "closable", false
@setopt "ontabselect", (e) ->
@setopt "ontabclose", (e) ->
@setopt "items", []
@setopt "selected", -1
@root.push = (e) =>
e.closable = @get "closable"
@refs.list.push e
@root.remove = (e) => @refs.list.remove e
@root.unshift = (e) => @refs.list.unshift e
@refs.list.set "onlistselect", (e) =>
@get("ontabselect") e
@observable.trigger "tabselect", e
__items__: (v) ->
i.closable = @get "closable" for i in v
@refs.list.set "data", v
__selected__: (v) ->
@refs.list.set "selected", v
mount: () ->
$(@refs.list).css "height", "100%"
@refs.list.set "onitemclose", (e) =>
e.id = @aid()
@get("ontabclose") e
layout: () ->
[{
el: "afx-list-view", ref: "list"
}]
Ant.OS.GUI.define "afx-tab-bar", TabBarTag

View File

@ -0,0 +1,51 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class TabBarTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("closable", false);
this.setopt("ontabselect", function(e) {});
this.setopt("ontabclose", function(e) {});
this.setopt("items", []);
this.setopt("selected", -1);
this.root.push = e => {
e.closable = this.get("closable");
return this.refs.list.push(e);
};
this.root.remove = e => this.refs.list.remove(e);
this.root.unshift = e => this.refs.list.unshift(e);
this.refs.list.set("onlistselect", e => {
this.get("ontabselect")(e);
return this.observable.trigger("tabselect", e);
});
}
__items__(v) {
for (let i of Array.from(v)) { i.closable = this.get("closable"); }
return this.refs.list.set("data", v);
}
__selected__(v) {
return this.refs.list.set("selected", v);
}
mount() {
$(this.refs.list).css("height", "100%");
return this.refs.list.set("onitemclose", e => {
e.id = this.aid();
return this.get("ontabclose")(e);
});
}
layout() {
return [{
el: "afx-list-view", ref: "list"
}];
}
}
Ant.OS.GUI.define("afx-tab-bar", TabBarTag);

View File

@ -1,61 +0,0 @@
class TabContainerTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "dir", "column" # or row
@setopt "selectedTab", undefined
@setopt "tabbarwidth", undefined
@setopt "tabbarheight", undefined
@setopt "ontabselect", () ->
@refs.bar.set "ontabselect", (e) =>
data = e.data.item.get "data"
@set "selectedTab", data
@get("ontabselect") { data: data, id: @aid() }
__selectedTab: (v) ->
return unless v
selected = @get("selectedTab")
$(selected.container).hide() if selected
$(v.container).show()
@observable.trigger "resize"
__tabbarwidth__: (v) ->
return unless v
$(@refs.bar).attr "data-width", "#{@get("tabbarwidth")}"
@refs.wrapper.calibrate()
__tabbarheight__: (v) ->
$(@refs.bar).attr "data-height", "#{@get("tabbarheight")}"
@refs.wrapper.calibrate()
__dir__: (v) ->
return unless v
@refs.wrapper.set "dir", v
@set "tabsize", @get("tabsize")
mount: () ->
$(@children).each (i, e) =>
item = {}
item.text = $(e).attr "tabname" if $(e).attr "tabname"
item.icon = $(e).attr "icon" if $(e).attr "icon"
item.iconclass = $(e).attr "iconclass" if $(e).attr "iconclass"
item.container = e
$(e)
.css "width", "100%"
.css "height", "100%"
el = @refs.bar.push item
el.set "selected", true
@observable.on "resize", (e) => @calibrate()
@calibrate()
calibrate: () ->
$(@refs.wrapper).css "height", "#{$(@root).height()}px"
layout: () ->
[{
el: "afx-tile", ref: "wrapper", children: [
{ el: "afx-tab-bar", ref: "bar" },
{ el: "div", ref: "yield" }
]
}]
Ant.OS.GUI.define "afx-tab-container", TabContainerTag

View File

@ -0,0 +1,77 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class TabContainerTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("dir", "column"); // or row
this.setopt("selectedTab", undefined);
this.setopt("tabbarwidth", undefined);
this.setopt("tabbarheight", undefined);
this.setopt("ontabselect", function() {});
this.refs.bar.set("ontabselect", e => {
const data = e.data.item.get("data");
this.set("selectedTab", data);
return this.get("ontabselect")({ data, id: this.aid() });
});
}
__selectedTab(v) {
if (!v) { return; }
const selected = this.get("selectedTab");
if (selected) { $(selected.container).hide(); }
$(v.container).show();
return this.observable.trigger("resize");
}
__tabbarwidth__(v) {
if (!v) { return; }
$(this.refs.bar).attr("data-width", `${this.get("tabbarwidth")}`);
return this.refs.wrapper.calibrate();
}
__tabbarheight__(v) {
$(this.refs.bar).attr("data-height", `${this.get("tabbarheight")}`);
return this.refs.wrapper.calibrate();
}
__dir__(v) {
if (!v) { return; }
this.refs.wrapper.set("dir", v);
return this.set("tabsize", this.get("tabsize"));
}
mount() {
$(this.children).each((i, e) => {
const item = {};
if ($(e).attr("tabname")) { item.text = $(e).attr("tabname"); }
if ($(e).attr("icon")) { item.icon = $(e).attr("icon"); }
if ($(e).attr("iconclass")) { item.iconclass = $(e).attr("iconclass"); }
item.container = e;
$(e)
.css("width", "100%")
.css("height", "100%");
const el = this.refs.bar.push(item);
return el.set("selected", true);
});
this.observable.on("resize", e => this.calibrate());
return this.calibrate();
}
calibrate() {
return $(this.refs.wrapper).css("height", `${$(this.root).height()}px`);
}
layout() {
return [{
el: "afx-tile", ref: "wrapper", children: [
{ el: "afx-tab-bar", ref: "bar" },
{ el: "div", ref: "yield" }
]
}];
}
}
Ant.OS.GUI.define("afx-tab-container", TabContainerTag);

View File

@ -1,103 +0,0 @@
class TileLayoutTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "name", undefined
@setopt "dir", undefined
$(@root).css("display", "block")
$(@refs.yield)
.css("display", "flex")
.css("width", "100%")
# @setopt @conf.opt, "grow"
__name__: (v) ->
return unless v
$(@refs.yield)
.removeClass()
.addClass("afx-#{v}-container")
@calibrate()
__dir__: (v) ->
return unless v
$(@refs.yield)
.css("flex-direction", v)
@calibrate()
mount: () ->
@observable.on "resize", (e) => @calibrate()
@calibrate()
calibrate: () ->
return @hcalibrate() if @get("dir") is "row"
@vcalibrate() if @get("dir") is "column"
hcalibrate: () ->
auto_width = []
ocwidth = 0
avaiheight = $(@root).height()
avaiWidth = $(@root).width()
$(@refs.yield).css "height", "#{avaiheight}px"
$(@refs.yield)
.children()
.each (e) ->
dw = $(@).attr "data-width"
if dw and dw isnt "grow"
dw = Number(dw.slice(0, -1)) * avaiWidth / 100 if dw[dw.length - 1] is "%"
$(@).css "width", "#{dw}px"
ocwidth += Number dw
else
$(@).css "flex-grow", "1"
auto_width.push(@)
csize = (avaiWidth - ocwidth) / auto_width.length
if csize > 0
$.each auto_width, (i, v) ->
$(v).css "width", "#{csize}px"
@observable.trigger "hboxchange", { id: @aid(), data: { w: avaiWidth, h: avaiheight } }
vcalibrate: () ->
auto_height = []
ocheight = 0
avaiheight = $(@root).height()
avaiwidth = $(@root).width()
$(@refs.yield).css "height", "#{avaiheight}px"
$(@refs.yield)
.children()
.each (e) ->
dh = $(@).attr "data-height"
if dh and dh isnt "grow"
dh = Number(dh.slice(0, -1)) * avaiheight / 100 if dh[dh.length - 1] is "%"
$(@).css "height", "#{dh}px"
ocheight += Number(dh)
else
$(@).css "flex-grow", "1"
auto_height.push @
csize = (avaiheight - ocheight) / auto_height.length
if csize > 0
$.each auto_height, (i, v) ->
$(v).css "height", "#{csize}px"
@observable.trigger "vboxchange", { id: @aid(), data: { w: avaiwidth, h: avaiheight } }
layout: () ->
[{
el: "div", ref: "yield"
}]
class HBoxTag extends TileLayoutTag
constructor: (r, o) ->
super r, o
@set "dir", "row"
@set "name", "hbox"
class VBoxTag extends TileLayoutTag
constructor: (r, o) ->
super r, o
@set "dir", "column"
@set "name", "vbox"
Ant.OS.GUI.define "afx-tile", TileLayoutTag
Ant.OS.GUI.define "afx-hbox", HBoxTag
Ant.OS.GUI.define "afx-vbox", VBoxTag

View File

@ -0,0 +1,125 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class TileLayoutTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("name", undefined);
this.setopt("dir", undefined);
$(this.root).css("display", "block");
$(this.refs.yield)
.css("display", "flex")
.css("width", "100%");
}
// @setopt @conf.opt, "grow"
__name__(v) {
if (!v) { return; }
$(this.refs.yield)
.removeClass()
.addClass(`afx-${v}-container`);
return this.calibrate();
}
__dir__(v) {
if (!v) { return; }
$(this.refs.yield)
.css("flex-direction", v);
return this.calibrate();
}
mount() {
this.observable.on("resize", e => this.calibrate());
return this.calibrate();
}
calibrate() {
if (this.get("dir") === "row") { return this.hcalibrate(); }
if (this.get("dir") === "column") { return this.vcalibrate(); }
}
hcalibrate() {
const auto_width = [];
let ocwidth = 0;
const avaiheight = $(this.root).height();
const avaiWidth = $(this.root).width();
$(this.refs.yield).css("height", `${avaiheight}px`);
$(this.refs.yield)
.children()
.each(function(e) {
let dw = $(this).attr("data-width");
if (dw && (dw !== "grow")) {
if (dw[dw.length - 1] === "%") { dw = (Number(dw.slice(0, -1)) * avaiWidth) / 100; }
$(this).css("width", `${dw}px`);
return ocwidth += Number(dw);
} else {
$(this).css("flex-grow", "1");
return auto_width.push(this);
}
});
const csize = (avaiWidth - ocwidth) / auto_width.length;
if (csize > 0) {
$.each(auto_width, (i, v) => $(v).css("width", `${csize}px`));
}
return this.observable.trigger("hboxchange", { id: this.aid(), data: { w: avaiWidth, h: avaiheight } });
}
vcalibrate() {
const auto_height = [];
let ocheight = 0;
const avaiheight = $(this.root).height();
const avaiwidth = $(this.root).width();
$(this.refs.yield).css("height", `${avaiheight}px`);
$(this.refs.yield)
.children()
.each(function(e) {
let dh = $(this).attr("data-height");
if (dh && (dh !== "grow")) {
if (dh[dh.length - 1] === "%") { dh = (Number(dh.slice(0, -1)) * avaiheight) / 100; }
$(this).css("height", `${dh}px`);
return ocheight += Number(dh);
} else {
$(this).css("flex-grow", "1");
return auto_height.push(this);
}
});
const csize = (avaiheight - ocheight) / auto_height.length;
if (csize > 0) {
$.each(auto_height, (i, v) => $(v).css("height", `${csize}px`));
}
return this.observable.trigger("vboxchange", { id: this.aid(), data: { w: avaiwidth, h: avaiheight } });
}
layout() {
return [{
el: "div", ref: "yield"
}];
}
}
class HBoxTag extends TileLayoutTag {
constructor(r, o) {
super(r, o);
this.set("dir", "row");
this.set("name", "hbox");
}
}
class VBoxTag extends TileLayoutTag {
constructor(r, o) {
super(r, o);
this.set("dir", "column");
this.set("name", "vbox");
}
}
Ant.OS.GUI.define("afx-tile", TileLayoutTag);
Ant.OS.GUI.define("afx-hbox", HBoxTag);
Ant.OS.GUI.define("afx-vbox", VBoxTag);

View File

@ -1,285 +0,0 @@
class TreeViewItemPrototype extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "data", undefined
@setopt "nodes", undefined
@setopt "treeroot", undefined
@setopt "indent", 0
@setopt "toggle", false
@setopt "fetch", undefined
@setopt "open", true
@setopt "itemindex", 0
@setopt "parent", undefined
@setopt "selected", false
@setopt "treepath", @aid()
update: (p) ->
return unless p
switch p
when "expand"
@set "open", true
when "collapse"
@set "open", false
else
return unless p is @get("treepath")
@set "open", true
__data__: (v) ->
return unless v
@set "nodes", v.nodes if v.nodes
@set "open", v.open
@set "treepath", v.path if v.path
@set "selected", v.selected
__selected__: (v) ->
return unless @opts.data
$(@refs.wrapper).removeClass()
@opts.data.selected = v
if v
@get("treeroot").unselect()
# set selectedItem but not trigger the update
@get("treeroot").set "selectedItem", @root, true
return $(@refs.wrapper).addClass("afx_tree_item_selected")
__open__: (v) ->
return unless @is_folder()
$(@refs.toggle)
.removeClass()
if(v)
if @get("fetch")
@get("fetch")(@root)
.then (d) =>
return unless d
@.set "nodes", d
.catch (e) ->
Ant.OS.announcer.oserror e.toString(), e
else
@.set "nodes", @__("nodes")
$(@refs.childnodes).show()
else
$(@refs.childnodes).hide()
return $(@refs.toggle).addClass "afx-tree-view-folder-open" if v
$(@refs.toggle).addClass "afx-tree-view-folder-close"
__itemindex__: (v) ->
return unless v
$(@refs.wrapper).addClass "afx_tree_item_odd" if v % 2 isnt 0
__indent__: (v) ->
return unless v
$(@refs.padding)
.css("display", "inline-block")
.css("height", "1px")
.css("padding", 0)
.css("margin", 0)
.css("background-color", "transparent")
.css("width", v * 15 + "px" )
is_folder: () ->
if @get("nodes") then true else false
__nodes__: (nodes) ->
return unless nodes
# return unless @get("nodes") and @get("nodes").length > 0
$(@refs.childnodes).empty()
$(@refs.wrapper).addClass("afx_folder_item")
root = @get("treeroot")
for v in nodes
el = $("<afx-tree-view>").appendTo @refs.childnodes
el[0].uify undefined
el[0].set "treeroot", root
el[0].set "indent", (@get("indent") + 1)
root.indexcounter++
el[0].set "parent", @get("parent")
el[0].set "itemindex", root.indexcounter
el[0].set "treepath", "#{@get("treepath")}/#{el[0].aid()}"
el[0].set "fetch", @get("fetch")
el[0].set "data", v
mount: () ->
super.mount()
$(@refs.container)
.css "padding", 0
.css "margin", 0
.css "white-space", "nowrap"
$(@refs.itemholder)
.css "display", "inline-block"
$(@refs.wrapper)
.click (e) =>
e.item = @root
@get("treeroot").itemclick e, false
$(@refs.wrapper)
.dblclick (e) =>
e.item = @root
@get("treeroot").itemclick e, true
$(@refs.toggle)
.css "display", "inline-block"
.css "width", "15px"
.addClass "afx-tree-view-item"
.click (e) =>
@set "open", not @get("open")
e.preventDefault()
e.stopPropagation()
layout: () ->
[ {
el: "div", ref: "wrapper", children: [
{
el: "ul", ref: "container", children: [
{ el: "li", ref: "padding" },
{ el: "li", ref: "toggle" }
{ el: "li", ref: "itemholder", class: "itemname", children: @itemlayout() }
]
}
] },
{
el: "ul", ref: "childnodes"
}
]
itemlayout: () ->
class SimpleTreeViewItem extends TreeViewItemPrototype
constructor: (r, o) ->
super r, o
__data__: (v) ->
return unless v
super.__data__(v)
@refs.label.set "color", v.color if v.color
@refs.label.set "text", v.name if v.name
@refs.label.set "icon", v.icon if v.icon
@refs.label.set "iconclass", v.iconclass if v.iconclass
itemlayout: () ->
[{ el: "afx-label", ref: "label" }]
class TreeViewTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "itemtag", "afx-tree-view-item"
@setopt "data", undefined
@setopt "treeroot", undefined
@setopt "parent", undefined
@setopt "indent", 0
@setopt "open", true
@setopt "itemindex", 0
@setopt "ontreeselect", () ->
@setopt "ontreedbclick", () ->
@setopt "ondragndrop", () ->
@setopt "selectedItem", undefined
@setopt "fetch", undefined
@setopt "dragndrop", false
@setopt "treepath", @aid()
@root.is_leaf = () => @is_leaf()
@root.expandAll = () => @expandAll()
@root.collapseAll = () => @collapseAll()
@root.unselect = () => @unselect()
@indexcounter = 0
unselect: () ->
@get("selectedItem").set "selected", false if @get("selectedItem")
__selectedItem: (v) ->
return unless v
return if v is @get("selectedItem")
v.set "selected", true
expandAll: () ->
return if @is_leaf()
@root.update "expand"
collapseAll: () ->
return if @is_leaf()
@root.update "collapse"
itemclick: (e, flag) ->
return unless e and e.item
return if e.item is @get("selectedItem") and not flag
@set "selectedItem", e.item
evt = { id: @aid(), data: e }
if flag
@get("ontreedbclick") evt
@observable.trigger "treedbclick", evt
else
@get("ontreeselect") evt
@observable.trigger "treeselect", evt
is_root: () ->
return @get("treeroot") is undefined
is_leaf: () ->
data = @get "data"
return true unless data
return if data.nodes then false else true
__data__: (v) ->
return unless v
$(@root).empty()
@set "treepath", v.path if v.path
tag = @get "itemtag"
tag = v.tag if v.tag
el = $("<#{tag}>").appendTo @root
el[0].uify undefined
el[0].set "treeroot", if @is_root() then @ else @get "treeroot"
el[0].set "indent", @get("indent")
el[0].set "itemindex", @get "itemindex"
el[0].set "treepath", @get("treepath")
el[0].set "open", @get("open")
el[0].set "fetch", @get("fetch")
el[0].set "parent", @root
el[0].set "data", v
if @is_root()
$(@root).off "mousedown", @treemousedown
$(@root).on "mousedown", @treemousedown if @get("dragndrop")
mount: () ->
@dnd = {}
@treemousedown = (e) =>
el = $(e.target).closest("afx-tree-view")
return if el.length is 0
el = el[0]
return if el is @root
@dnd.from = el
@dnd.to = undefined
$(window).on "mouseup", @treemouseup
$(window).on "mousemove", @treemousemove
@treemouseup = (e) =>
$(window).off "mouseup", @treemouseup
$(window).off "mousemove", @treemousemove
($ "#systooltip").hide()
el = $(e.target).closest("afx-tree-view")
return if el.length is 0
el = el[0]
el = el.get("parent") if el.is_leaf()
return if el is @dnd.from or el is @dnd.from.get("parent")
@dnd.to = el
@__("ondragndrop") { id: @aid(), data: @dnd }
@dnd = {}
@treemousemove = (e) =>
return unless e
return unless @dnd.from
data = @dnd.from.get("data")
$label = $("#systooltip")
top = e.clientY + 5
left = e.clientX + 5
$label.show()
$label[0].set "text", data.name
$label[0].set "icon", data.icon if data.icon
$label[0].set "iconclass", data.iconclass if data.iconclass
$label
.css "top", top + "px"
.css "left", left + "px"
Ant.OS.GUI.define "afx-tree-view", TreeViewTag
Ant.OS.GUI.define "afx-tree-view-item-proto", TreeViewItemPrototype
Ant.OS.GUI.define "afx-tree-view-item", SimpleTreeViewItem

View File

@ -0,0 +1,337 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class TreeViewItemPrototype extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("data", undefined);
this.setopt("nodes", undefined);
this.setopt("treeroot", undefined);
this.setopt("indent", 0);
this.setopt("toggle", false);
this.setopt("fetch", undefined);
this.setopt("open", true);
this.setopt("itemindex", 0);
this.setopt("parent", undefined);
this.setopt("selected", false);
this.setopt("treepath", this.aid());
}
update(p) {
if (!p) { return; }
switch (p) {
case "expand":
return this.set("open", true);
case "collapse":
return this.set("open", false);
default:
if (p !== this.get("treepath")) { return; }
return this.set("open", true);
}
}
__data__(v) {
if (!v) { return; }
if (v.nodes) { this.set("nodes", v.nodes); }
this.set("open", v.open);
if (v.path) { this.set("treepath", v.path); }
return this.set("selected", v.selected);
}
__selected__(v) {
if (!this.opts.data) { return; }
$(this.refs.wrapper).removeClass();
this.opts.data.selected = v;
if (v) {
this.get("treeroot").unselect();
// set selectedItem but not trigger the update
this.get("treeroot").set("selectedItem", this.root, true);
return $(this.refs.wrapper).addClass("afx_tree_item_selected");
}
}
__open__(v) {
if (!this.is_folder()) { return; }
$(this.refs.toggle)
.removeClass();
if(v) {
if (this.get("fetch")) {
this.get("fetch")(this.root)
.then(d => {
if (!d) { return; }
return this.set("nodes", d);
}).catch(e => Ant.OS.announcer.oserror(e.toString(), e));
} else {
this.set("nodes", this.__("nodes"));
}
$(this.refs.childnodes).show();
} else {
$(this.refs.childnodes).hide();
}
if (v) { return $(this.refs.toggle).addClass("afx-tree-view-folder-open"); }
return $(this.refs.toggle).addClass("afx-tree-view-folder-close");
}
__itemindex__(v) {
if (!v) { return; }
if ((v % 2) !== 0) { return $(this.refs.wrapper).addClass("afx_tree_item_odd"); }
}
__indent__(v) {
if (!v) { return; }
return $(this.refs.padding)
.css("display", "inline-block")
.css("height", "1px")
.css("padding", 0)
.css("margin", 0)
.css("background-color", "transparent")
.css("width", (v * 15) + "px" );
}
is_folder() {
if (this.get("nodes")) { return true; } else { return false; }
}
__nodes__(nodes) {
if (!nodes) { return; }
// return unless @get("nodes") and @get("nodes").length > 0
$(this.refs.childnodes).empty();
$(this.refs.wrapper).addClass("afx_folder_item");
const root = this.get("treeroot");
return (() => {
const result = [];
for (let v of Array.from(nodes)) {
const el = $("<afx-tree-view>").appendTo(this.refs.childnodes);
el[0].uify(undefined);
el[0].set("treeroot", root);
el[0].set("indent", (this.get("indent") + 1));
root.indexcounter++;
el[0].set("parent", this.get("parent"));
el[0].set("itemindex", root.indexcounter);
el[0].set("treepath", `${this.get("treepath")}/${el[0].aid()}`);
el[0].set("fetch", this.get("fetch"));
result.push(el[0].set("data", v));
}
return result;
})();
}
mount() {
super.mount();
$(this.refs.container)
.css("padding", 0)
.css("margin", 0)
.css("white-space", "nowrap");
$(this.refs.itemholder)
.css("display", "inline-block");
$(this.refs.wrapper)
.click(e => {
e.item = this.root;
return this.get("treeroot").itemclick(e, false);
});
$(this.refs.wrapper)
.dblclick(e => {
e.item = this.root;
return this.get("treeroot").itemclick(e, true);
});
return $(this.refs.toggle)
.css("display", "inline-block")
.css("width", "15px")
.addClass("afx-tree-view-item")
.click(e => {
this.set("open", !this.get("open"));
e.preventDefault();
return e.stopPropagation();
});
}
layout() {
return [ {
el: "div", ref: "wrapper", children: [
{
el: "ul", ref: "container", children: [
{ el: "li", ref: "padding" },
{ el: "li", ref: "toggle" },
{ el: "li", ref: "itemholder", class: "itemname", children: this.itemlayout() }
]
}
] },
{
el: "ul", ref: "childnodes"
}
];
}
itemlayout() {}
}
class SimpleTreeViewItem extends TreeViewItemPrototype {
constructor(r, o) {
super(r, o);
}
__data__(v) {
if (!v) { return; }
super.__data__(v);
if (v.color) { this.refs.label.set("color", v.color); }
if (v.name) { this.refs.label.set("text", v.name); }
if (v.icon) { this.refs.label.set("icon", v.icon); }
if (v.iconclass) { return this.refs.label.set("iconclass", v.iconclass); }
}
itemlayout() {
return [{ el: "afx-label", ref: "label" }];
}
}
class TreeViewTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("itemtag", "afx-tree-view-item");
this.setopt("data", undefined);
this.setopt("treeroot", undefined);
this.setopt("parent", undefined);
this.setopt("indent", 0);
this.setopt("open", true);
this.setopt("itemindex", 0);
this.setopt("ontreeselect", function() {});
this.setopt("ontreedbclick", function() {});
this.setopt("ondragndrop", function() {});
this.setopt("selectedItem", undefined);
this.setopt("fetch", undefined);
this.setopt("dragndrop", false);
this.setopt("treepath", this.aid());
this.root.is_leaf = () => this.is_leaf();
this.root.expandAll = () => this.expandAll();
this.root.collapseAll = () => this.collapseAll();
this.root.unselect = () => this.unselect();
this.indexcounter = 0;
}
unselect() {
if (this.get("selectedItem")) { return this.get("selectedItem").set("selected", false); }
}
__selectedItem(v) {
if (!v) { return; }
if (v === this.get("selectedItem")) { return; }
return v.set("selected", true);
}
expandAll() {
if (this.is_leaf()) { return; }
return this.root.update("expand");
}
collapseAll() {
if (this.is_leaf()) { return; }
return this.root.update("collapse");
}
itemclick(e, flag) {
if (!e || !e.item) { return; }
if ((e.item === this.get("selectedItem")) && !flag) { return; }
this.set("selectedItem", e.item);
const evt = { id: this.aid(), data: e };
if (flag) {
this.get("ontreedbclick")(evt);
return this.observable.trigger("treedbclick", evt);
} else {
this.get("ontreeselect")(evt);
return this.observable.trigger("treeselect", evt);
}
}
is_root() {
return this.get("treeroot") === undefined;
}
is_leaf() {
const data = this.get("data");
if (!data) { return true; }
if (data.nodes) { return false; } else { return true; }
}
__data__(v) {
if (!v) { return; }
$(this.root).empty();
if (v.path) { this.set("treepath", v.path); }
let tag = this.get("itemtag");
if (v.tag) { ({
tag
} = v); }
const el = $(`<${tag}>`).appendTo(this.root);
el[0].uify(undefined);
el[0].set("treeroot", this.is_root() ? this : this.get("treeroot"));
el[0].set("indent", this.get("indent"));
el[0].set("itemindex", this.get("itemindex"));
el[0].set("treepath", this.get("treepath"));
el[0].set("open", this.get("open"));
el[0].set("fetch", this.get("fetch"));
el[0].set("parent", this.root);
el[0].set("data", v);
if (this.is_root()) {
$(this.root).off("mousedown", this.treemousedown);
if (this.get("dragndrop")) { return $(this.root).on("mousedown", this.treemousedown); }
}
}
mount() {
this.dnd = {};
this.treemousedown = e => {
let el = $(e.target).closest("afx-tree-view");
if (el.length === 0) { return; }
el = el[0];
if (el === this.root) { return; }
this.dnd.from = el;
this.dnd.to = undefined;
$(window).on("mouseup", this.treemouseup);
return $(window).on("mousemove", this.treemousemove);
};
this.treemouseup = e => {
$(window).off("mouseup", this.treemouseup);
$(window).off("mousemove", this.treemousemove);
($("#systooltip")).hide();
let el = $(e.target).closest("afx-tree-view");
if (el.length === 0) { return; }
el = el[0];
if (el.is_leaf()) { el = el.get("parent"); }
if ((el === this.dnd.from) || (el === this.dnd.from.get("parent"))) { return; }
this.dnd.to = el;
this.__("ondragndrop")({ id: this.aid(), data: this.dnd });
return this.dnd = {};
};
return this.treemousemove = e => {
if (!e) { return; }
if (!this.dnd.from) { return; }
const data = this.dnd.from.get("data");
const $label = $("#systooltip");
const top = e.clientY + 5;
const left = e.clientX + 5;
$label.show();
$label[0].set("text", data.name);
if (data.icon) { $label[0].set("icon", data.icon); }
if (data.iconclass) { $label[0].set("iconclass", data.iconclass); }
return $label
.css("top", top + "px")
.css("left", left + "px");
};
}
}
Ant.OS.GUI.define("afx-tree-view", TreeViewTag);
Ant.OS.GUI.define("afx-tree-view-item-proto", TreeViewItemPrototype);
Ant.OS.GUI.define("afx-tree-view-item", SimpleTreeViewItem);

View File

@ -1,204 +0,0 @@
class WindowTag extends Ant.OS.GUI.BaseTag
constructor: (r, o) ->
super r, o
@setopt "minimizable", true
@setopt "resizable", true
@setopt "apptitle", "Untitled"
@setopt "desktop", Ant.OS.GUI.workspace
@setopt "width", 400
@setopt "height", 300
@shown = false
@isMaxi = false
@history = {}
@desktop = $(@get "desktop")
@desktop_pos = @desktop.offset()
resize: () ->
ch = $(@refs["yield"]).height() / $(@refs["yield"]).children().length
$(@refs["yield"]).children().each (e) ->
$(this).css "height", "#{ch}px"
mount: () ->
@root.contextmenuHandle = (e) ->
$(@refs["minbt"]).click (e) =>
@observable.trigger "hide", { id: @aid() }
$(@refs["maxbt"]).click (e) =>
@toggle_window()
$(@refs["closebt"]).click (e) =>
@observable.trigger("exit", { id: @aid() })
left = ($(@desktop).width() - (@get "width")) / 2
top = ($(@desktop).height() - (@get "height")) / 2
$(@root)
.css("position", 'absolute')
.css("left", "#{left}px")
.css("top", "#{top}px")
.css("z-index", Ant.OS.GUI.zindex++)
$(@root).on "mousedown", (e) =>
return if @shown
@observable.trigger "focus", { id: @aid() }
$(@refs["dragger"]).dblclick (e) =>
@toggle_window()
@observable.on "resize", (e) => @resize()
@observable.on "focus", () =>
Ant.OS.GUI.zindex++
$(@root)
.show()
.css("z-index", Ant.OS.GUI.zindex)
.removeClass("unactive")
@shown = true
@observable.on "blur", () =>
@shown = false
$(@root)
.addClass("unactive")
@observable.on "hide", () =>
$(@root).hide()
@shown = false
@observable.on "toggle", () =>
if @shown
@observable.trigger "hide", { id: @aid() }
else
@observable.trigger "focus", { id: @aid() }
@enable_dragging()
@enable_resize()
@setsize { w: (@get "width"), h: (@get "height") }
@observable.trigger "rendered", { id: @aid() }
__minimizable__: (value) ->
if value then $(@refs["minbt"]).show() else $(@refs["minbt"]).hide()
__width__: (v) ->
return unless v
@setsize { w: v, h: @get("height") }
__height__: (v) ->
return unless v
@setsize { w: @get("width"), h: v }
setsize: (o) ->
return unless o
@opts.width = o.w
@opts.height = o.h
$(@root)
.css("width", "#{o.w}px")
.css("height", "#{o.h}px")
@observable.trigger "resize", { id: @aid(), data: o }
__resizable__: (value) ->
if value
$(@refs["maxbt"]).show()
$(@refs["grip"]).show()
else
$(@refs["maxbt"]).hide()
$(@refs["grip"]).hide()
__apptitle__: (value) ->
@refs["txtTitle"].set "text", value if value
enable_dragging: () ->
$(@refs["dragger"])
.css("user-select", "none")
.css("cursor", "default")
$(@refs["dragger"]).on "mousedown", (e) =>
e.preventDefault()
offset = $(@root).offset()
offset.top = e.clientY - offset.top
offset.left = e.clientX - offset.left
$(window).on "mousemove", (e) =>
if @isMaxi
@toggle_window()
top = 0
letf = e.clientX - $(@root).width() / 2
offset.top = 10
offset.left = $(@root).width() / 2
else
top = e.clientY - offset.top - @desktop_pos.top
left = e.clientX - @desktop_pos.top - offset.left
left = if left < 0 then 0 else left
top = if top < 0 then 0 else top
$(@root)
.css("top", "#{top}px")
.css("left", "#{left}px")
$(window).on "mouseup", (e) ->
$(window).unbind "mousemove", null
$(window).unbind "mouseup", null
enable_resize: () ->
$(@refs["grip"])
.css("user-select", "none")
.css("cursor", "default")
.css("position", "absolute")
.css("bottom", "0")
.css("right", "0")
.css("cursor", "nwse-resize")
$(@refs["grip"]).on "mousedown", (e) =>
e.preventDefault()
offset = { top: 0, left: 0 }
offset.top = e.clientY
offset.left = e.clientX
$(window).on "mousemove", (e) =>
w = $(@root).width() + e.clientX - offset.left
h = $(@root).height() + e.clientY - offset.top
w = if w < 100 then 100 else w
h = if h < 100 then 100 else h
offset.top = e.clientY
offset.left = e.clientX
@isMaxi = false
@setsize { w: w, h: h }
$(window).on "mouseup", (e) ->
$(window).unbind "mousemove", null
$(window).unbind "mouseup", null
toggle_window: () ->
return unless @get "resizable"
if @isMaxi is false
@history = {
top: $(@root).css("top"),
left: $(@root).css("left"),
width: $(@root).css("width"),
height: $(@root).css("height")
}
w = $(@desktop).width()
h = $(@desktop).height()
$(@root)
.css("top", "0")
.css("left", "0")
@setsize { w: w, h: h }
@isMaxi = true
else
@isMaxi = false
$(@root)
.css("top", @history.top)
.css("left", @history.left)
@setsize { w: parseInt(@history.width), h: parseInt(@history.height) }
layout: () ->
[{
el: "div", class: "afx-window-wrapper", children: [
{
el: "ul", class: "afx-window-top", children: [
{ el: "li", class: "afx-window-close", ref: "closebt" },
{ el: "li", class: "afx-window-minimize", ref: "minbt" },
{ el: "li", class: "afx-window-maximize", ref: "maxbt" },
{ el: "li", class: "afx-window-title", ref: "dragger", children: [{
el: "afx-label", ref: "txtTitle"
}] }
]
},
{ el: "div", class: "afx-clear" },
{ el: "div", ref: "yield", class: "afx-window-content" },
{ el: "div", ref: "grip", class: "afx-window-grip" }
]
}]
Ant.OS.GUI.define "afx-app-window", WindowTag

245
src/core/tags/WindowTag.js Normal file
View File

@ -0,0 +1,245 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class WindowTag extends Ant.OS.GUI.BaseTag {
constructor(r, o) {
super(r, o);
this.setopt("minimizable", true);
this.setopt("resizable", true);
this.setopt("apptitle", "Untitled");
this.setopt("desktop", Ant.OS.GUI.workspace);
this.setopt("width", 400);
this.setopt("height", 300);
this.shown = false;
this.isMaxi = false;
this.history = {};
this.desktop = $(this.get("desktop"));
this.desktop_pos = this.desktop.offset();
}
resize() {
const ch = $(this.refs["yield"]).height() / $(this.refs["yield"]).children().length;
return $(this.refs["yield"]).children().each(function(e) {
return $(this).css("height", `${ch}px`);
});
}
mount() {
this.root.contextmenuHandle = function(e) {};
$(this.refs["minbt"]).click(e => {
return this.observable.trigger("hide", { id: this.aid() });
});
$(this.refs["maxbt"]).click(e => {
return this.toggle_window();
});
$(this.refs["closebt"]).click(e => {
return this.observable.trigger("exit", { id: this.aid() });
});
const left = ($(this.desktop).width() - (this.get("width"))) / 2;
const top = ($(this.desktop).height() - (this.get("height"))) / 2;
$(this.root)
.css("position", 'absolute')
.css("left", `${left}px`)
.css("top", `${top}px`)
.css("z-index", Ant.OS.GUI.zindex++);
$(this.root).on("mousedown", e => {
if (this.shown) { return; }
return this.observable.trigger("focus", { id: this.aid() });
});
$(this.refs["dragger"]).dblclick(e => {
return this.toggle_window();
});
this.observable.on("resize", e => this.resize());
this.observable.on("focus", () => {
Ant.OS.GUI.zindex++;
$(this.root)
.show()
.css("z-index", Ant.OS.GUI.zindex)
.removeClass("unactive");
return this.shown = true;
});
this.observable.on("blur", () => {
this.shown = false;
return $(this.root)
.addClass("unactive");
});
this.observable.on("hide", () => {
$(this.root).hide();
return this.shown = false;
});
this.observable.on("toggle", () => {
if (this.shown) {
return this.observable.trigger("hide", { id: this.aid() });
} else {
return this.observable.trigger("focus", { id: this.aid() });
}
});
this.enable_dragging();
this.enable_resize();
this.setsize({ w: (this.get("width")), h: (this.get("height")) });
return this.observable.trigger("rendered", { id: this.aid() });
}
__minimizable__(value) {
if (value) { return $(this.refs["minbt"]).show(); } else { return $(this.refs["minbt"]).hide(); }
}
__width__(v) {
if (!v) { return; }
return this.setsize({ w: v, h: this.get("height") });
}
__height__(v) {
if (!v) { return; }
return this.setsize({ w: this.get("width"), h: v });
}
setsize(o) {
if (!o) { return; }
this.opts.width = o.w;
this.opts.height = o.h;
$(this.root)
.css("width", `${o.w}px`)
.css("height", `${o.h}px`);
return this.observable.trigger("resize", { id: this.aid(), data: o });
}
__resizable__(value) {
if (value) {
$(this.refs["maxbt"]).show();
return $(this.refs["grip"]).show();
} else {
$(this.refs["maxbt"]).hide();
return $(this.refs["grip"]).hide();
}
}
__apptitle__(value) {
if (value) { return this.refs["txtTitle"].set("text", value); }
}
enable_dragging() {
$(this.refs["dragger"])
.css("user-select", "none")
.css("cursor", "default");
return $(this.refs["dragger"]).on("mousedown", e => {
e.preventDefault();
const offset = $(this.root).offset();
offset.top = e.clientY - offset.top;
offset.left = e.clientX - offset.left;
$(window).on("mousemove", e => {
let left, top;
if (this.isMaxi) {
this.toggle_window();
top = 0;
const letf = e.clientX - ($(this.root).width() / 2);
offset.top = 10;
offset.left = $(this.root).width() / 2;
} else {
top = e.clientY - offset.top - this.desktop_pos.top;
left = e.clientX - this.desktop_pos.top - offset.left;
left = left < 0 ? 0 : left;
top = top < 0 ? 0 : top;
}
return $(this.root)
.css("top", `${top}px`)
.css("left", `${left}px`);
});
return $(window).on("mouseup", function(e) {
$(window).unbind("mousemove", null);
return $(window).unbind("mouseup", null);
});
});
}
enable_resize() {
$(this.refs["grip"])
.css("user-select", "none")
.css("cursor", "default")
.css("position", "absolute")
.css("bottom", "0")
.css("right", "0")
.css("cursor", "nwse-resize");
return $(this.refs["grip"]).on("mousedown", e => {
e.preventDefault();
const offset = { top: 0, left: 0 };
offset.top = e.clientY;
offset.left = e.clientX;
$(window).on("mousemove", e => {
let w = ($(this.root).width() + e.clientX) - offset.left;
let h = ($(this.root).height() + e.clientY) - offset.top;
w = w < 100 ? 100 : w;
h = h < 100 ? 100 : h;
offset.top = e.clientY;
offset.left = e.clientX;
this.isMaxi = false;
return this.setsize({ w, h });
});
return $(window).on("mouseup", function(e) {
$(window).unbind("mousemove", null);
return $(window).unbind("mouseup", null);
});
});
}
toggle_window() {
let h, w;
if (!this.get("resizable")) { return; }
if (this.isMaxi === false) {
this.history = {
top: $(this.root).css("top"),
left: $(this.root).css("left"),
width: $(this.root).css("width"),
height: $(this.root).css("height")
};
w = $(this.desktop).width();
h = $(this.desktop).height();
$(this.root)
.css("top", "0")
.css("left", "0");
this.setsize({ w, h });
return this.isMaxi = true;
} else {
this.isMaxi = false;
$(this.root)
.css("top", this.history.top)
.css("left", this.history.left);
return this.setsize({ w: parseInt(this.history.width), h: parseInt(this.history.height) });
}
}
layout() {
return [{
el: "div", class: "afx-window-wrapper", children: [
{
el: "ul", class: "afx-window-top", children: [
{ el: "li", class: "afx-window-close", ref: "closebt" },
{ el: "li", class: "afx-window-minimize", ref: "minbt" },
{ el: "li", class: "afx-window-maximize", ref: "maxbt" },
{ el: "li", class: "afx-window-title", ref: "dragger", children: [{
el: "afx-label", ref: "txtTitle"
}] }
]
},
{ el: "div", class: "afx-clear" },
{ el: "div", ref: "yield", class: "afx-window-content" },
{ el: "div", ref: "grip", class: "afx-window-grip" }
]
}];
}
}
Ant.OS.GUI.define("afx-app-window", WindowTag);

View File

@ -1,130 +0,0 @@
Ant.OS.GUI.tag = {}
Ant.OS.GUI.zindex = 10
class Ant.OS.GUI.BaseTag
constructor: (@root, @observable) ->
@opts = {}
@observable = new Ant.OS.API.Announcer() unless @observable
# export to rootnode
@root.observable = @observable
@root.set = (k, v) => @set k, v
@root.get = (k) => @get k
@root.aid = () => @aid()
@root.calibrate = () => @calibrate()
@root.sync = (d) => @sync(d)
@mounted = false
@root.setup = () => @setup()
@refs = {}
@setopt "data-id", (Math.floor(Math.random() * 100000) + 1).toString()
@setopt "tooltip", undefined
#$(@root).attr "data-id", @get("data-id")
@children = $(@root).children()
for obj in @layout()
dom = @mkui obj
if dom
$(dom).appendTo(@root)
if @refs.yield
$(v).detach().appendTo @refs.yield for v in @children
else
@children = []
$(@root).children().each (i, e) => e.mkui @observable
__: (k, v) ->
@set k, v if v
@get k
__tooltip__: (v) ->
return unless v
$(@root).attr "tooltip", v
setopt: (name, val) ->
value = val
if ($(@root).attr name)
v = $(@root).attr name
try
value = JSON.parse(v)
catch e
value = v
@set name, value
set: (opt, value, flag) ->
if opt is "*"
@set k, v for k, v of value
else
@["__#{opt}"](value) if @["__#{opt}"] and not flag
@opts[opt] = value
@["__#{opt}__"](value) if @["__#{opt}__"] and not flag
@
aid: () ->
@get "data-id"
calibrate: () ->
update: () ->
get: (opt) ->
return @opts if opt is "*"
@opts[opt]
sync: (d) ->
@update(d)
$(@root).children().each () -> @update(d)
@root
setup: () ->
return if @mounted
@mounted = true
@mount()
$(@root).children().each () -> @.mount()
@root
mount: () ->
layout: () ->
[]
# should be defined by subclasses
mkui: (tag) ->
return undefined unless tag
dom = $("<#{tag.el}>")
$(dom).addClass tag.class if tag.class
$(dom).attr "data-id", tag.id if tag.id
$(dom).attr "data-height", tag.height if tag.height
$(dom).attr "data-width", tag.width if tag.width
$(dom).attr "tooltip", tag.tooltip if tag.tooltip
if tag.children
$(@mkui(v)).appendTo(dom) for v in tag.children
if tag.ref
@refs[tag.ref] = dom[0]
# dom.mount @observable
dom[0] #.uify(@observable)
Element.prototype.mkui = (observable) ->
tag = @tagName.toLowerCase()
if RegExp("afx-*", "i" ).test(tag) and Ant.OS.GUI.tag[tag]
o = new Ant.OS.GUI.tag[tag](@, observable)
return o.root
else
$(@).children().each () ->
@mkui(observable)
return @
Element.prototype.mount = () ->
return @setup() if @setup
$(@).children().each () -> @mount()
@
Element.prototype.update = (d) ->
return @sync(d) if @sync
$(@).children().each () -> @update(d)
@
Element.prototype.uify = (observable) ->
@mkui(observable)
@mount()
Ant.OS.GUI.define = (name, cls) ->
Ant.OS.GUI.tag[name] = cls

163
src/core/tags/tag.js Normal file
View File

@ -0,0 +1,163 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
Ant.OS.GUI.tag = {};
Ant.OS.GUI.zindex = 10;
Ant.OS.GUI.BaseTag = class BaseTag {
constructor(root, observable) {
this.root = root;
this.observable = observable;
this.opts = {};
if (!this.observable) { this.observable = new Ant.OS.API.Announcer(); }
// export to rootnode
this.root.observable = this.observable;
this.root.set = (k, v) => this.set(k, v);
this.root.get = k => this.get(k);
this.root.aid = () => this.aid();
this.root.calibrate = () => this.calibrate();
this.root.sync = d => this.sync(d);
this.mounted = false;
this.root.setup = () => this.setup();
this.refs = {};
this.setopt("data-id", (Math.floor(Math.random() * 100000) + 1).toString());
this.setopt("tooltip", undefined);
//$(@root).attr "data-id", @get("data-id")
this.children = $(this.root).children();
for (let obj of Array.from(this.layout())) {
const dom = this.mkui(obj);
if (dom) {
$(dom).appendTo(this.root);
}
}
if (this.refs.yield) {
for (let v of Array.from(this.children)) { $(v).detach().appendTo(this.refs.yield); }
} else {
this.children = [];
}
$(this.root).children().each((i, e) => e.mkui(this.observable));
}
__(k, v) {
if (v) { this.set(k, v); }
return this.get(k);
}
__tooltip__(v) {
if (!v) { return; }
return $(this.root).attr("tooltip", v);
}
setopt(name, val) {
let value = val;
if ($(this.root).attr(name)) {
const v = $(this.root).attr(name);
try {
value = JSON.parse(v);
} catch (e) {
value = v;
}
}
return this.set(name, value);
}
set(opt, value, flag) {
if (opt === "*") {
for (let k in value) { const v = value[k]; this.set(k, v); }
} else {
if (this[`__${opt}`] && !flag) { this[`__${opt}`](value); }
this.opts[opt] = value;
if (this[`__${opt}__`] && !flag) { this[`__${opt}__`](value); }
}
return this;
}
aid() {
return this.get("data-id");
}
calibrate() {}
update() {}
get(opt) {
if (opt === "*") { return this.opts; }
return this.opts[opt];
}
sync(d) {
this.update(d);
$(this.root).children().each(function() { return this.update(d); });
return this.root;
}
setup() {
if (this.mounted) { return; }
this.mounted = true;
this.mount();
$(this.root).children().each(function() { return this.mount(); });
return this.root;
}
mount() {}
layout() {
return [];
}
// should be defined by subclasses
mkui(tag) {
if (!tag) { return undefined; }
const dom = $(`<${tag.el}>`);
if (tag.class) { $(dom).addClass(tag.class); }
if (tag.id) { $(dom).attr("data-id", tag.id); }
if (tag.height) { $(dom).attr("data-height", tag.height); }
if (tag.width) { $(dom).attr("data-width", tag.width); }
if (tag.tooltip) { $(dom).attr("tooltip", tag.tooltip); }
if (tag.children) {
for (let v of Array.from(tag.children)) { $(this.mkui(v)).appendTo(dom); }
}
if (tag.ref) {
this.refs[tag.ref] = dom[0];
}
// dom.mount @observable
return dom[0]; //.uify(@observable)
}
};
Element.prototype.mkui = function(observable) {
const tag = this.tagName.toLowerCase();
if (RegExp("afx-*", "i" ).test(tag) && Ant.OS.GUI.tag[tag]) {
const o = new (Ant.OS.GUI.tag[tag])(this, observable);
return o.root;
} else {
$(this).children().each(function() {
return this.mkui(observable);
});
}
return this;
};
Element.prototype.mount = function() {
if (this.setup) { return this.setup(); }
$(this).children().each(function() { return this.mount(); });
return this;
};
Element.prototype.update = function(d) {
if (this.sync) { return this.sync(d); }
$(this).children().each(function() { return this.update(d); });
return this;
};
Element.prototype.uify = function(observable) {
this.mkui(observable);
return this.mount();
};
Ant.OS.GUI.define = (name, cls) => Ant.OS.GUI.tag[name] = cls;

View File

@ -1,446 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
String.prototype.asFileHandle = () ->
list = @split "://"
handles = Ant.OS.API.VFS.findHandles list[0]
if not handles or handles.length is 0
Ant.OS.announcer.osfail __("VFS unknown handle: {0}", @), (Ant.OS.API.throwe "OS.VFS"), @
return null
return new handles[0](@)
this.OS.API.VFS =
handles: { }
register: ( protos, cls ) ->
return Ant.OS.API.VFS.handles[protos] = cls # if typeof protos is "string"
#Ant.OS.API.VFS.handles[v] = cls for v in protos
findHandles: (proto) ->
l = (v for k, v of Ant.OS.API.VFS.handles when proto.trim().match (new RegExp k , "g"))
return l
class BaseFileHandle
constructor: (path) ->
@dirty = false
@cache = undefined
@setPath path
setPath: (p) ->
@ready = false
return unless p
@path = p.toString()
list = @path.split "://"
@protocol = list[0]
return unless list.length > 1
re = list[1].replace(/^\/+|\/+$/g, '')
return if re is ""
@genealogy = re.split("/")
@basename = @genealogy[@genealogy.length - 1] unless @isRoot()
@ext = @basename.split( "." ).pop() unless @basename.lastIndexOf(".") is 0 or @basename.indexOf( "." ) is -1
filename: () ->
return "Untitled" unless @basename
@basename
setCache: (v) ->
@cache = v
@
asFileHandle: () -> @
isRoot: () -> (not @genealogy) or (@genealogy.size is 0)
child: (name) ->
if @isRoot()
return @path + name
else
return @path + "/" + name
isHidden: () ->
return false if not @basename
@basename[0] is "."
hash: () ->
return -1 unless @path
return @path.hash()
b64: (t) ->
# t is object or mime type
new Promise (resolve, reject) =>
m = if t is "object" then "text/plain" else t
return resolve "" unless @cache
if t is "object" or typeof @cache is "string"
if t is "object"
b64 = JSON.stringify(@cache, undefined, 4).asBase64()
else
b64 = @cache.asBase64()
b64 = "data:#{m};base64,#{b64}"
resolve(b64)
else
reader = new FileReader()
reader.readAsDataURL(@cache)
reader.onload = () ->
resolve reader.result
reader.onerror = (e) ->
reject e
parent: () ->
return @ if @isRoot()
return (@protocol + "://" + (@genealogy.slice 0 , @genealogy.length - 1).join "/")
.asFileHandle()
onready: () ->
# read meta data
new Promise (resolve, reject) =>
return resolve(@info) if @ready
@meta()
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.errors
@info = d.result
@ready = true
resolve(d.result)
.catch (e) -> reject __e e
read: (t) ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_rd(t)
.then (d) ->
# Ant.OS.announcer.ostrigger "VFS", { m: "read", file: me }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
write: (t) ->
new Promise (resolve, reject) =>
@_wr(t)
.then (r) =>
Ant.OS.announcer.ostrigger "VFS", { m: "write", file: @ }
resolve r
.catch (e) -> reject __e e
mk: (d) ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_mk(d)
.then (d) =>
Ant.OS.announcer.ostrigger "VFS", { m: "mk", file: @ }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
remove: () ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_rm()
.then (d) =>
Ant.OS.announcer.ostrigger "VFS", { m: "remove", file: @ }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
upload: () ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_up()
.then (d) =>
Ant.OS.announcer.ostrigger "VFS", { m: "upload", file: @ }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
publish: () ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_pub()
.then (d) =>
Ant.OS.announcer.ostrigger "VFS", { m: "publish", file: @ }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
download: () ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_down()
.then (d) =>
Ant.OS.announcer.ostrigger "VFS", { m: "download", file: @ }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
move: (d) ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_mv(d)
.then (data) =>
Ant.OS.announcer.ostrigger "VFS", { m: "move", file: d.asFileHandle() }
resolve data
.catch (e) -> reject __e e
.catch (e) -> reject __e e
execute: () ->
new Promise (resolve, reject) =>
@onready()
.then (r) =>
@_exec()
.then (d) =>
Ant.OS.announcer.ostrigger "VFS", { m: "execute", file: @ }
resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
getlink: () -> @path
unsupported: (t) ->
new Promise (resolve, reject) =>
reject Ant.OS.API.throwe __("Action {0} is unsupported on: {1}", t, @path)
# actions must be implemented by subclasses
_rd: (t) -> @unsupported "read"
_wr: (d, t) -> @unsupported "write"
_mk: (d) -> @unsupported "mk"
_rm: () -> @unsupported "remove"
_mv: (d) -> @unsupported "move"
_up: () -> @unsupported "upload"
_down: () -> @unsupported "download"
_exec: () -> @unsupported "execute"
_pub: () -> @unsupported "publish"
# now export the class
Ant.OS.API.VFS.BaseFileHandle = BaseFileHandle
# Remote file handle
class RemoteFileHandle extends Ant.OS.API.VFS.BaseFileHandle
constructor: (path) ->
super path
meta: () ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.fileinfo @path
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
getlink: () ->
Ant.OS.API.handle.get + "/" + @path
_rd: (t) ->
# t: binary, text, any type
if not @info
return new Promise (resolve, reject) =>
reject Ant.OS.API.throwe __(
"file meta-data not found: {0}", @path)
return Ant.OS.API.handle.scandir @path if @info.type is "dir"
#read the file
return Ant.OS.API.handle.fileblob @path if t is "binary"
Ant.OS.API.handle.readfile @path, if t then t else "text"
_wr: (t) ->
# t is base64 or undefined
new Promise (resolve, reject) =>
if t is "base64"
Ant.OS.API.handle.write(@path, @cache).then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
else
@b64(t)
.then (r) =>
Ant.OS.API.handle.write @path, r
.then (result) =>
if result.error
return reject Ant.OS.API.throwe __(
"{0}: {1}", result.error, @path)
resolve result
.catch (e) -> reject __e e
.catch (e) -> reject __e e
_mk: (d) ->
new Promise (resolve, reject) =>
if not @info
return reject Ant.OS.API.throwe __(
"file meta-data not found: {0}", @path)
if @info.type is "file"
return reject Ant.OS.API.throwe __("{0} is not a directory", @path)
Ant.OS.API.handle.mkdir "#{@path}/#{d}"
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
_rm: () ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.delete @path
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
_mv: (d) ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.move @path, d
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
_up: () ->
new Promise (resolve, reject) =>
if @info.type isnt "dir"
return reject Ant.OS.API.throwe __("{0} is not a file", @path)
Ant.OS.API.handle.upload @path
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
_down: () ->
new Promise (resolve, reject) =>
if @info.type is "dir"
return Ant.OS.API.throwe __("{0} is not a file", @path)
Ant.OS.API.handle.fileblob(@path)
.then (d) =>
blob = new Blob [d], { type: "octet/stream" }
Ant.OS.API.saveblob @basename, blob
resolve()
.catch (e) ->
reject __e e
_pub: () ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.sharefile @path, true
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
Ant.OS.API.VFS.register "^(home|desktop|os|Untitled)$", RemoteFileHandle
# Application Handle
class ApplicationHandle extends Ant.OS.API.VFS.BaseFileHandle
constructor: (path) ->
super path
@info = Ant.OS.setting.system.packages[@basename] if @basename
@ready = true
_rd: (t) ->
new Promise (resolve, reject) =>
return resolve { result: @info } if @info
return reject Ant.OS.API.throwe(__("Application meta data isnt found")) unless @isRoot()
resolve { result: ( v for k, v of Ant.OS.setting.system.packages ) }
Ant.OS.API.VFS.register "^app$", ApplicationHandle
class BufferFileHandle extends Ant.OS.API.VFS.BaseFileHandle
constructor: (path, mime, data) ->
super path
@cache = data if data
@info =
mime: mime
path: path
size: if data then data.length else 0
name: @basename
type: "file"
_rd: (t) ->
new Promise (resolve, reject) =>
resolve { result: @cache }
_wr: (d, t) ->
@cache = d
@onchange @ if @onchange
new Promise (resolve, reject) ->
resolve { result: true }
_down: () ->
new Promise (resolve, reject) =>
blob = new Blob [@cache], { type: "octet/stream" }
Ant.OS.API.saveblob @basename, blob
resolve()
onchange: (f) ->
@onchange = f
Ant.OS.API.VFS.register "^mem$", BufferFileHandle
class URLFileHandle extends Ant.OS.API.VFS.BaseFileHandle
constructor: (path) ->
super path
@ready = true
_rd: (t) ->
Ant.OS.API.get @path, if t then t else "text"
Ant.OS.API.VFS.register "^(http|https|ftp)$", URLFileHandle
class SharedFileHandle extends Ant.OS.API.VFS.BaseFileHandle
constructor: (path) ->
super path
@ready = true if @isRoot()
meta: () ->
Ant.OS.API.handle.fileinfo @path
_rd: (t) ->
return Ant.OS.API.get "#{Ant.OS.API.handle.shared}/all", t if @isRoot()
#read the file
return Ant.OS.API.handle.fileblob @path if t is "binary"
Ant.OS.API.handle.readfile @path, if t then t else "text"
_wr: (d, t) ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.write @path, d
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
_rm: () ->
new Promise (resolve, reject) =>
Ant.OS.API.handle.sharefile @basename, false
.then (d) =>
return reject Ant.OS.API.throwe __("{0}: {1}", d.error, @path) if d.error
resolve d
.catch (e) -> reject __e e
_down: () ->
new Promise (resolve, reject) =>
if @info.type is "dir"
return reject Ant.OS.API.throwe __("{0} is not a file", @path)
Ant.OS.API.handle.fileblob @path
.then (data) =>
blob = new Blob [data], { type: "octet/stream" }
Ant.OS.API.saveblob @basename, blob
resolve()
.catch (e) -> reject __e e
_pub: () ->
return new Promise (resolve, reject) => resolve { result: @basename }
Ant.OS.API.VFS.register "^shared$", SharedFileHandle

567
src/core/vfs.js Normal file
View File

@ -0,0 +1,567 @@
/*
* decaffeinate suggestions:
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
String.prototype.asFileHandle = function() {
const list = this.split("://");
const handles = Ant.OS.API.VFS.findHandles(list[0]);
if (!handles || (handles.length === 0)) {
Ant.OS.announcer.osfail(__("VFS unknown handle: {0}", this), (Ant.OS.API.throwe("OS.VFS")), this);
return null;
}
return new (handles[0])(this);
};
this.OS.API.VFS = {
handles: { },
register( protos, cls ) {
return Ant.OS.API.VFS.handles[protos] = cls;
}, // if typeof protos is "string"
//Ant.OS.API.VFS.handles[v] = cls for v in protos
findHandles(proto) {
const l = ((() => {
const result = [];
for (let k in Ant.OS.API.VFS.handles) {
const v = Ant.OS.API.VFS.handles[k];
if (proto.trim().match((new RegExp(k , "g")))) {
result.push(v);
}
}
return result;
})());
return l;
}
};
class BaseFileHandle {
constructor(path) {
this.dirty = false;
this.cache = undefined;
this.setPath(path);
}
setPath(p) {
this.ready = false;
if (!p) { return; }
this.path = p.toString();
const list = this.path.split("://");
this.protocol = list[0];
if (!(list.length > 1)) { return; }
const re = list[1].replace(/^\/+|\/+$/g, '');
if (re === "") { return; }
this.genealogy = re.split("/");
if (!this.isRoot()) { this.basename = this.genealogy[this.genealogy.length - 1]; }
if ((this.basename.lastIndexOf(".") !== 0) && (this.basename.indexOf( "." ) !== -1)) { return this.ext = this.basename.split( "." ).pop(); }
}
filename() {
if (!this.basename) { return "Untitled"; }
return this.basename;
}
setCache(v) {
this.cache = v;
return this;
}
asFileHandle() { return this; }
isRoot() { return (!this.genealogy) || (this.genealogy.size === 0); }
child(name) {
if (this.isRoot()) {
return this.path + name;
} else {
return this.path + "/" + name;
}
}
isHidden() {
if (!this.basename) { return false; }
return this.basename[0] === ".";
}
hash() {
if (!this.path) { return -1; }
return this.path.hash();
}
b64(t) {
// t is object or mime type
return new Promise((resolve, reject) => {
const m = t === "object" ? "text/plain" : t;
if (!this.cache) { return resolve(""); }
if ((t === "object") || (typeof this.cache === "string")) {
let b64;
if (t === "object") {
b64 = JSON.stringify(this.cache, undefined, 4).asBase64();
} else {
b64 = this.cache.asBase64();
}
b64 = `data:${m};base64,${b64}`;
return resolve(b64);
} else {
const reader = new FileReader();
reader.readAsDataURL(this.cache);
reader.onload = () => resolve(reader.result);
return reader.onerror = e => reject(e);
}
});
}
parent() {
if (this.isRoot()) { return this; }
return (this.protocol + "://" + (this.genealogy.slice(0 , this.genealogy.length - 1)).join("/"))
.asFileHandle();
}
onready() {
// read meta data
return new Promise((resolve, reject) => {
if (this.ready) { return resolve(this.info); }
return this.meta()
.then(d => {
if (d.errors) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
this.info = d.result;
this.ready = true;
return resolve(d.result);
}).catch(e => reject(__e(e)));
});
}
read(t) {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._rd(t)
.then(d => // Ant.OS.announcer.ostrigger "VFS", { m: "read", file: me }
resolve(d)).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
write(t) {
return new Promise((resolve, reject) => {
return this._wr(t)
.then(r => {
Ant.OS.announcer.ostrigger("VFS", { m: "write", file: this });
return resolve(r);
}).catch(e => reject(__e(e)));
});
}
mk(d) {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._mk(d)
.then(d => {
Ant.OS.announcer.ostrigger("VFS", { m: "mk", file: this });
return resolve(d);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
remove() {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._rm()
.then(d => {
Ant.OS.announcer.ostrigger("VFS", { m: "remove", file: this });
return resolve(d);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
upload() {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._up()
.then(d => {
Ant.OS.announcer.ostrigger("VFS", { m: "upload", file: this });
return resolve(d);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
publish() {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._pub()
.then(d => {
Ant.OS.announcer.ostrigger("VFS", { m: "publish", file: this });
return resolve(d);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
download() {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._down()
.then(d => {
Ant.OS.announcer.ostrigger("VFS", { m: "download", file: this });
return resolve(d);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
move(d) {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._mv(d)
.then(data => {
Ant.OS.announcer.ostrigger("VFS", { m: "move", file: d.asFileHandle() });
return resolve(data);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
execute() {
return new Promise((resolve, reject) => {
return this.onready()
.then(r => {
return this._exec()
.then(d => {
Ant.OS.announcer.ostrigger("VFS", { m: "execute", file: this });
return resolve(d);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
getlink() { return this.path; }
unsupported(t) {
return new Promise((resolve, reject) => {
return reject(Ant.OS.API.throwe(__("Action {0} is unsupported on: {1}", t, this.path)));
});
}
// actions must be implemented by subclasses
_rd(t) { return this.unsupported("read"); }
_wr(d, t) { return this.unsupported("write"); }
_mk(d) { return this.unsupported("mk"); }
_rm() { return this.unsupported("remove"); }
_mv(d) { return this.unsupported("move"); }
_up() { return this.unsupported("upload"); }
_down() { return this.unsupported("download"); }
_exec() { return this.unsupported("execute"); }
_pub() { return this.unsupported("publish"); }
}
// now export the class
Ant.OS.API.VFS.BaseFileHandle = BaseFileHandle;
// Remote file handle
class RemoteFileHandle extends Ant.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
}
meta() {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.fileinfo(this.path)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
getlink() {
return Ant.OS.API.handle.get + "/" + this.path;
}
_rd(t) {
// t: binary, text, any type
if (!this.info) {
return new Promise((resolve, reject) => {
return reject(Ant.OS.API.throwe(__(
"file meta-data not found: {0}", this.path)
)
);
});
}
if (this.info.type === "dir") { return Ant.OS.API.handle.scandir(this.path); }
//read the file
if (t === "binary") { return Ant.OS.API.handle.fileblob(this.path); }
return Ant.OS.API.handle.readfile(this.path, t ? t : "text");
}
_wr(t) {
// t is base64 or undefined
return new Promise((resolve, reject) => {
if (t === "base64") {
return Ant.OS.API.handle.write(this.path, this.cache).then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
} else {
return this.b64(t)
.then(r => {
return Ant.OS.API.handle.write(this.path, r)
.then(result => {
if (result.error) {
return reject(Ant.OS.API.throwe(__(
"{0}: {1}", result.error, this.path)
)
);
}
return resolve(result);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}
});
}
_mk(d) {
return new Promise((resolve, reject) => {
if (!this.info) {
return reject(Ant.OS.API.throwe(__(
"file meta-data not found: {0}", this.path)
)
);
}
if (this.info.type === "file") {
return reject(Ant.OS.API.throwe(__("{0} is not a directory", this.path)));
}
return Ant.OS.API.handle.mkdir(`${this.path}/${d}`)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
_rm() {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.delete(this.path)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
_mv(d) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.move(this.path, d)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
_up() {
return new Promise((resolve, reject) => {
if (this.info.type !== "dir") {
return reject(Ant.OS.API.throwe(__("{0} is not a file", this.path)));
}
return Ant.OS.API.handle.upload(this.path)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
_down() {
return new Promise((resolve, reject) => {
if (this.info.type === "dir") {
return Ant.OS.API.throwe(__("{0} is not a file", this.path));
}
return Ant.OS.API.handle.fileblob(this.path)
.then(d => {
const blob = new Blob([d], { type: "octet/stream" });
Ant.OS.API.saveblob(this.basename, blob);
return resolve();
}).catch(e => reject(__e(e)));
});
}
_pub() {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.sharefile(this.path, true)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
}
Ant.OS.API.VFS.register("^(home|desktop|os|Untitled)$", RemoteFileHandle);
// Application Handle
class ApplicationHandle extends Ant.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
if (this.basename) { this.info = Ant.OS.setting.system.packages[this.basename]; }
this.ready = true;
}
_rd(t) {
return new Promise((resolve, reject) => {
if (this.info) { return resolve({ result: this.info }); }
if (!this.isRoot()) { return reject(Ant.OS.API.throwe(__("Application meta data isnt found"))); }
return resolve({ result: ((() => {
const result = [];
for (let k in Ant.OS.setting.system.packages) {
const v = Ant.OS.setting.system.packages[k];
result.push(v);
}
return result;
})()) });
});
}
}
Ant.OS.API.VFS.register("^app$", ApplicationHandle);
class BufferFileHandle extends Ant.OS.API.VFS.BaseFileHandle {
constructor(path, mime, data) {
super(path);
if (data) { this.cache = data; }
this.info = {
mime,
path,
size: data ? data.length : 0,
name: this.basename,
type: "file"
};
}
_rd(t) {
return new Promise((resolve, reject) => {
return resolve({ result: this.cache });
});
}
_wr(d, t) {
this.cache = d;
if (this.onchange) { this.onchange(this); }
return new Promise((resolve, reject) => resolve({ result: true }));
}
_down() {
return new Promise((resolve, reject) => {
const blob = new Blob([this.cache], { type: "octet/stream" });
Ant.OS.API.saveblob(this.basename, blob);
return resolve();
});
}
onchange(f) {
return this.onchange = f;
}
}
Ant.OS.API.VFS.register("^mem$", BufferFileHandle);
class URLFileHandle extends Ant.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
this.ready = true;
}
_rd(t) {
return Ant.OS.API.get(this.path, t ? t : "text");
}
}
Ant.OS.API.VFS.register("^(http|https|ftp)$", URLFileHandle);
class SharedFileHandle extends Ant.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
if (this.isRoot()) { this.ready = true; }
}
meta() {
return Ant.OS.API.handle.fileinfo(this.path);
}
_rd(t) {
if (this.isRoot()) { return Ant.OS.API.get(`${Ant.OS.API.handle.shared}/all`, t); }
//read the file
if (t === "binary") { return Ant.OS.API.handle.fileblob(this.path); }
return Ant.OS.API.handle.readfile(this.path, t ? t : "text");
}
_wr(d, t) {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.write(this.path, d)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
_rm() {
return new Promise((resolve, reject) => {
return Ant.OS.API.handle.sharefile(this.basename, false)
.then(d => {
if (d.error) { return reject(Ant.OS.API.throwe(__("{0}: {1}", d.error, this.path))); }
return resolve(d);
}).catch(e => reject(__e(e)));
});
}
_down() {
return new Promise((resolve, reject) => {
if (this.info.type === "dir") {
return reject(Ant.OS.API.throwe(__("{0} is not a file", this.path)));
}
return Ant.OS.API.handle.fileblob(this.path)
.then(data => {
const blob = new Blob([data], { type: "octet/stream" });
Ant.OS.API.saveblob(this.basename, blob);
return resolve();
}).catch(e => reject(__e(e)));
});
}
_pub() {
return new Promise((resolve, reject) => resolve({ result: this.basename }));
}
}
Ant.OS.API.VFS.register("^shared$", SharedFileHandle);

View File

@ -1,355 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
# GoogleDrive File Handle
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } }
class GoogleDriveHandle extends this.OS.API.VFS.BaseFileHandle
constructor: (path) ->
super path
me = @
@setting = Ant.OS.setting.VFS.gdrive
return Ant.OS.announcer.oserror __("Unknown API setting for {0}", "GAPI"), (Ant.OS.API.throwe "OS.VFS"), null unless @setting
@gid = 'root' if @isRoot()
@cache = ""
oninit: (f) ->
me = @
return unless @setting
fn = (r) ->
return f() if r
# perform the login
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } }
gapi.auth2.getAuthInstance().signIn()
if Ant.OS.API.libready @setting.apilink
gapi.auth2.getAuthInstance().isSignedIn.listen (r) ->
fn(r)
fn(gapi.auth2.getAuthInstance().isSignedIn.get())
else
Ant.OS.API.require @setting.apilink, () ->
# avoid popup block
q = Ant.OS.announcer.getMID()
gapi.load "client:auth2", () ->
Ant.OS.API.loading q, "GAPI"
gapi.client.init {
apiKey: me.setting.API_KEY,
clientId: me.setting.CLIENT_ID,
discoveryDocs: me.setting.DISCOVERY_DOCS,
scope: me.setting.SCOPES
}
.then () ->
Ant.OS.API.loaded q, "OK"
gapi.auth2.getAuthInstance().isSignedIn.listen (r) ->
fn(r)
_GUI.openDialog "YesNoDialog", (d) ->
return Ant.OS.announcer.osinfo __("User abort the authentication") unless d
fn(gapi.auth2.getAuthInstance().isSignedIn.get())
, __("Authentication")
, { text: __("Would you like to login to {0}?", "Google Drive") }
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot init {0}: {1}", "GAPI",err.error), (Ant.OS.API.throwe "OS.VFS"), err
meta: (f) ->
me = @
@oninit () ->
q = Ant.OS.announcer.getMID()
me.gid = G_CACHE[me.path].id if G_CACHE[me.path]
if me.gid
#console.log "Gid exists ", me.gid
Ant.OS.API.loading q, "GAPI"
gapi.client.drive.files.get {
fileId: me.gid,
fields: me.fields()
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
return unless r.result
r.result.mime = r.result.mimeType
f(r)
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot get meta data for {0}", me.gid), (Ant.OS.API.throwe "OS.VFS"), err
else
#console.log "Find file in ", me.parent()
fp = me.parent().asFileHandle()
fp.meta (d) ->
file = d.result
q1 = Ant.OS.announcer.getMID()
Ant.OS.API.loading q1, "GAPI"
G_CACHE[fp.path] = { id: file.id, mime: file.mimeType }
gapi.client.drive.files.list {
q: "name = '#{me.basename}' and '#{file.id}' in parents and trashed = false",
fields: "files(#{me.fields()})"
}
.then (r) ->
#console.log r
Ant.OS.API.loaded q1, "OK"
return unless r.result.files and r.result.files.length > 0
G_CACHE[me.path] = { id: r.result.files[0].id, mime: r.result.files[0].mimeType }
r.result.files[0].mime = r.result.files[0].mimeType
f { result: r.result.files[0] }
.catch (err) ->
Ant.OS.API.loaded q1, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot get meta data for {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), err
fields: () ->
return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size"
isFolder: () ->
return @info.mimeType is "application/vnd.google-apps.folder"
save: (id, m, f) ->
me = @
user = gapi.auth2.getAuthInstance().currentUser.get()
oauthToken = user.getAuthResponse().access_token
q = Ant.OS.announcer.getMID()
xhr = new XMLHttpRequest()
url = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=media'
xhr.open('PATCH', url)
xhr.setRequestHeader 'Authorization', 'Bearer ' + oauthToken
xhr.setRequestHeader 'Content-Type', m
xhr.setRequestHeader 'Content-Encoding', 'base64'
xhr.setRequestHeader 'Content-Transfer-Encoding', 'base64'
Ant.OS.API.loading q, "GAPI"
error = (e, s) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot save : {0}", me.path), e, s
xhr.onreadystatechange = () ->
if ( xhr.readyState == 4 )
if ( xhr.status == 200 )
Ant.OS.API.loaded q, "OK"
f { result: JSON.parse(xhr.responseText) }
else
error xhr, xhr.status
xhr.onerror = () ->
error xhr, xhr.status
return xhr.send me.cache.replace /^data:[^;]+;base64,/g, "" if m is "base64"
@sendB64 m, (data) ->
xhr.send data.replace /^data:[^;]+;base64,/g, ""
getlink: () ->
return @info.webContentLink if @ready
return undefined
action: (n, p, f) ->
me = @
q = Ant.OS.announcer.getMID()
Ant.OS.API.loading q, "GAPI"
switch n
when "read"
return unless @info.id
if @isFolder()
gapi.client.drive.files.list {
q: "'#{me.info.id}' in parents and trashed = false",
fields: "files(#{me.fields()})"
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
return unless r.result.files
for file in r.result.files
file.path = me.child file.name
file.mime = file.mimeType
file.filename = file.name
file.type = "file"
file.gid = file.id
if file.mimeType is "application/vnd.google-apps.folder"
file.mime = "dir"
file.type = "dir"
file.size = 0
G_CACHE[file.path] = { id: file.gid, mime: file.mime }
f { result: r.result.files }
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), err
else
gapi.client.drive.files.get {
fileId: me.info.id,
alt: 'media'
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
return f r.body unless p is "binary"
f r.body.asUint8Array()
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), err
when "mk"
return f { error: __("{0} is not a directory", @path) } unless @isFolder()
meta =
name: p,
parents: [@info.id],
mimeType: 'application/vnd.google-apps.folder'
gapi.client.drive.files.create {
resource: meta,
fields: 'id'
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
#console.log r
return Ant.OS.announcer.oserror __("VFS cannot create : {0}", p), (Ant.OS.API.throwe "OS.VFS"), r unless r and r.result
G_CACHE[me.child p] = { id: r.result.id, mime: "dir" }
f r
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot create : {0}", p), (Ant.OS.API.throwe "OS.VFS"), err
return
when "write"
gid = undefined
gid = G_CACHE[me.path].id if G_CACHE[me.path]
if gid
Ant.OS.API.loaded q, "OK"
@save gid, p, f
else
dir = @parent().asFileHandle()
dir.onready () ->
meta =
name: me.basename,
mimeType: p,
parents: [dir.info.id]
gapi.client.drive.files.create {
resource: meta,
fields: 'id'
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
return Ant.OS.announcer.oserror __("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), r unless r and r.result
G_CACHE[me.path] = { id: r.result.id, mime: p }
me.save r.result.id, p, f
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), err
when "upload"
return unless @isFolder()
#insert a temporal file selector
o = ($ '<input>').attr('type', 'file').css("display", "none")
Ant.OS.API.loaded q, "OK"
o.change () ->
#Ant.OS.API.loading q, p
fo = o[0].files[0]
file = (me.child fo.name).asFileHandle()
file.cache = fo
file.write fo.type, f
o.remove()
#Ant.OS.API.loaded q, p, "OK"
#Ant.OS.API.loaded q, p, "FAIL"
o.click()
when "remove"
return unless @info.id
gapi.client.drive.files.delete {
fileId: me.info.id
}
.then (r) ->
#console.log r
Ant.OS.API.loaded q, "OK"
return Ant.OS.announcer.oserror __("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), r unless r
G_CACHE[me.path] = null
f { result: true }
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), err
when "publish"
Ant.OS.API.loaded q, "OK"
return
when "download"
gapi.client.drive.files.get {
fileId: me.info.id,
alt: 'media'
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
return Ant.OS.announcer.oserror __("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), r unless r.body
bytes = []
for i in [0..(r.body.length - 1)]
bytes.push r.body.charCodeAt i
bytes = new Uint8Array(bytes)
blob = new Blob [bytes], { type: "octet/stream" }
Ant.OS.API.saveblob me.basename, blob
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), err
when "move"
dest = p.asFileHandle().parent().asFileHandle()
dest.onready () ->
previousParents = me.info.parents.join ','
gapi.client.drive.files.update {
fileId: me.info.id,
addParents: dest.info.id,
removeParents: previousParents,
fields: "id"
}
.then (r) ->
Ant.OS.API.loaded q, "OK"
return Ant.OS.announcer.oserror __("VFS cannot move : {0}", me.path), (Ant.OS.API.throwe "OS.VFS"), r unless r
f r
.catch (err) ->
Ant.OS.API.loaded q, "FAIL"
Ant.OS.announcer.oserror __("VFS cannot move : {0}", me.gid), (Ant.OS.API.throwe "OS.VFS"), err
, (err) ->
Ant.OS.API.loaded q, "FAIL"
else
Ant.OS.API.loaded q, "FAIL"
return Ant.OS.announcer.osfail __("VFS unknown action: {0}", n), (Ant.OS.API.throwe "OS.VFS"), n
self.OS.API.VFS.register "^gdv$", GoogleDriveHandle
# search the cache for file
self.OS.API.onsearch "Google Drive", (t) ->
arr = []
term = new RegExp t, "i"
for k, v of G_CACHE
if (k.match term) or (v and v.mime.match term)
file = k.asFileHandle()
file.text = file.basename
file.mime = v.mime
file.iconclass = "fa fa-file"
file.iconclass = "fa fa-folder" if file.mime is "dir"
file.complex = true
file.detail = [{ text: file.path }]
arr.push file
return arr
self.OS.onexit "cleanUpGoogleDrive", () ->
G_CACHE = { "gdv://": { id: "root", mime: 'dir' } }
return unless Ant.OS.setting.VFS.gdrive and Ant.OS.API.libready Ant.OS.setting.VFS.gdrive.apilink
auth2 = gapi.auth2.getAuthInstance()
return unless auth2
if auth2.isSignedIn.get()
el = $ '<iframe/>', {
src: 'https://www.google.com/accounts/Logout',
frameborder: 0,
onload: () ->
#console.log("disconnect")
auth2.disconnect()
#$(this).remove()
}
#($ "body").append(el)

View File

@ -0,0 +1,401 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS202: Simplify dynamic range loops
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
// GoogleDrive File Handle
let G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
class GoogleDriveHandle extends this.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
const me = this;
this.setting = Ant.OS.setting.VFS.gdrive;
if (!this.setting) { return Ant.OS.announcer.oserror(__("Unknown API setting for {0}", "GAPI"), (Ant.OS.API.throwe("OS.VFS")), null); }
if (this.isRoot()) { this.gid = 'root'; }
this.cache = "";
}
oninit(f) {
const me = this;
if (!this.setting) { return; }
const fn = function(r) {
if (r) { return f(); }
// perform the login
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
return gapi.auth2.getAuthInstance().signIn();
};
if (Ant.OS.API.libready(this.setting.apilink)) {
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
} else {
return Ant.OS.API.require(this.setting.apilink, function() {
// avoid popup block
const q = Ant.OS.announcer.getMID();
return gapi.load("client:auth2", function() {
Ant.OS.API.loading(q, "GAPI");
return gapi.client.init({
apiKey: me.setting.API_KEY,
clientId: me.setting.CLIENT_ID,
discoveryDocs: me.setting.DISCOVERY_DOCS,
scope: me.setting.SCOPES
})
.then(function() {
Ant.OS.API.loaded(q, "OK");
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
return _GUI.openDialog("YesNoDialog", function(d) {
if (!d) { return Ant.OS.announcer.osinfo(__("User abort the authentication")); }
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
}
, __("Authentication")
, { text: __("Would you like to login to {0}?", "Google Drive") });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot init {0}: {1}", "GAPI",err.error), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
});
}
}
meta(f) {
const me = this;
return this.oninit(function() {
const q = Ant.OS.announcer.getMID();
if (G_CACHE[me.path]) { me.gid = G_CACHE[me.path].id; }
if (me.gid) {
//console.log "Gid exists ", me.gid
Ant.OS.API.loading(q, "GAPI");
return gapi.client.drive.files.get({
fileId: me.gid,
fields: me.fields()
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.result) { return; }
r.result.mime = r.result.mimeType;
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
});
} else {
//console.log "Find file in ", me.parent()
const fp = me.parent().asFileHandle();
return fp.meta(function(d) {
const file = d.result;
const q1 = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q1, "GAPI");
G_CACHE[fp.path] = { id: file.id, mime: file.mimeType };
return gapi.client.drive.files.list({
q: `name = '${me.basename}' and '${file.id}' in parents and trashed = false`,
fields: `files(${me.fields()})`
})
.then(function(r) {
//console.log r
Ant.OS.API.loaded(q1, "OK");
if (!r.result.files || !(r.result.files.length > 0)) { return; }
G_CACHE[me.path] = { id: r.result.files[0].id, mime: r.result.files[0].mimeType };
r.result.files[0].mime = r.result.files[0].mimeType;
return f({ result: r.result.files[0] });})
.catch(function(err) {
Ant.OS.API.loaded(q1, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
}
});
}
fields() {
return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size";
}
isFolder() {
return this.info.mimeType === "application/vnd.google-apps.folder";
}
save(id, m, f) {
const me = this;
const user = gapi.auth2.getAuthInstance().currentUser.get();
const oauthToken = user.getAuthResponse().access_token;
const q = Ant.OS.announcer.getMID();
const xhr = new XMLHttpRequest();
const url = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=media';
xhr.open('PATCH', url);
xhr.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
xhr.setRequestHeader('Content-Type', m);
xhr.setRequestHeader('Content-Encoding', 'base64');
xhr.setRequestHeader('Content-Transfer-Encoding', 'base64');
Ant.OS.API.loading(q, "GAPI");
const error = function(e, s) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot save : {0}", me.path), e, s);
};
xhr.onreadystatechange = function() {
if ( xhr.readyState === 4 ) {
if ( xhr.status === 200 ) {
Ant.OS.API.loaded(q, "OK");
return f({ result: JSON.parse(xhr.responseText) });
} else {
return error(xhr, xhr.status);
}
}
};
xhr.onerror = () => error(xhr, xhr.status);
if (m === "base64") { return xhr.send(me.cache.replace(/^data:[^;]+;base64,/g, "")); }
return this.sendB64(m, data => xhr.send(data.replace(/^data:[^;]+;base64,/g, "")));
}
getlink() {
if (this.ready) { return this.info.webContentLink; }
return undefined;
}
action(n, p, f) {
const me = this;
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, "GAPI");
switch (n) {
case "read":
if (!this.info.id) { return; }
if (this.isFolder()) {
return gapi.client.drive.files.list({
q: `'${me.info.id}' in parents and trashed = false`,
fields: `files(${me.fields()})`
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.result.files) { return; }
for (let file of Array.from(r.result.files)) {
file.path = me.child(file.name);
file.mime = file.mimeType;
file.filename = file.name;
file.type = "file";
file.gid = file.id;
if (file.mimeType === "application/vnd.google-apps.folder") {
file.mime = "dir";
file.type = "dir";
file.size = 0;
}
G_CACHE[file.path] = { id: file.gid, mime: file.mime };
}
return f({ result: r.result.files });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
} else {
return gapi.client.drive.files.get({
fileId: me.info.id,
alt: 'media'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (p !== "binary") { return f(r.body); }
return f(r.body.asUint8Array());}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
}
case "mk":
if (!this.isFolder()) { return f({ error: __("{0} is not a directory", this.path) }); }
var meta = {
name: p,
parents: [this.info.id],
mimeType: 'application/vnd.google-apps.folder'
};
gapi.client.drive.files.create({
resource: meta,
fields: 'id'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
//console.log r
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.child(p)] = { id: r.result.id, mime: "dir" };
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), err);
});
return;
case "write":
var gid = undefined;
if (G_CACHE[me.path]) { gid = G_CACHE[me.path].id; }
if (gid) {
Ant.OS.API.loaded(q, "OK");
return this.save(gid, p, f);
} else {
const dir = this.parent().asFileHandle();
return dir.onready(function() {
meta = {
name: me.basename,
mimeType: p,
parents: [dir.info.id]
};
return gapi.client.drive.files.create({
resource: meta,
fields: 'id'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.path] = { id: r.result.id, mime: p };
return me.save(r.result.id, p, f);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
}
case "upload":
if (!this.isFolder()) { return; }
//insert a temporal file selector
var o = ($('<input>')).attr('type', 'file').css("display", "none");
Ant.OS.API.loaded(q, "OK");
o.change(function() {
//Ant.OS.API.loading q, p
const fo = o[0].files[0];
const file = (me.child(fo.name)).asFileHandle();
file.cache = fo;
file.write(fo.type, f);
return o.remove();
});
//Ant.OS.API.loaded q, p, "OK"
//Ant.OS.API.loaded q, p, "FAIL"
return o.click();
case "remove":
if (!this.info.id) { return; }
return gapi.client.drive.files.delete({
fileId: me.info.id
})
.then(function(r) {
//console.log r
Ant.OS.API.loaded(q, "OK");
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.path] = null;
return f({ result: true });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
case "publish":
Ant.OS.API.loaded(q, "OK");
return;
case "download":
return gapi.client.drive.files.get({
fileId: me.info.id,
alt: 'media'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.body) { return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
let bytes = [];
for (let i = 0, end = r.body.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
bytes.push(r.body.charCodeAt(i));
}
bytes = new Uint8Array(bytes);
const blob = new Blob([bytes], { type: "octet/stream" });
return Ant.OS.API.saveblob(me.basename, blob);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
case "move":
var dest = p.asFileHandle().parent().asFileHandle();
return dest.onready(function() {
const previousParents = me.info.parents.join(',');
return gapi.client.drive.files.update({
fileId: me.info.id,
addParents: dest.info.id,
removeParents: previousParents,
fields: "id"
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
});
}
, err => Ant.OS.API.loaded(q, "FAIL"));
default:
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.osfail(__("VFS unknown action: {0}", n), (Ant.OS.API.throwe("OS.VFS")), n);
}
}
}
self.OS.API.VFS.register("^gdv$", GoogleDriveHandle);
// search the cache for file
self.OS.API.onsearch("Google Drive", function(t) {
const arr = [];
const term = new RegExp(t, "i");
for (let k in G_CACHE) {
const v = G_CACHE[k];
if ((k.match(term)) || (v && v.mime.match(term))) {
const file = k.asFileHandle();
file.text = file.basename;
file.mime = v.mime;
file.iconclass = "fa fa-file";
if (file.mime === "dir") { file.iconclass = "fa fa-folder"; }
file.complex = true;
file.detail = [{ text: file.path }];
arr.push(file);
}
}
return arr;
});
self.OS.onexit("cleanUpGoogleDrive", function() {
G_CACHE = { "gdv://": { id: "root", mime: 'dir' } };
if (!Ant.OS.setting.VFS.gdrive || !Ant.OS.API.libready(Ant.OS.setting.VFS.gdrive.apilink)) { return; }
const auth2 = gapi.auth2.getAuthInstance();
if (!auth2) { return; }
if (auth2.isSignedIn.get()) {
let el;
return el = $('<iframe/>', {
src: 'https://www.google.com/accounts/Logout',
frameborder: 0,
onload() {
//console.log("disconnect")
return auth2.disconnect();
}
//$(this).remove()
});
}
});
//($ "body").append(el)

View File

@ -0,0 +1,195 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
CodePad.BaseExtension = class BaseExtension {
constructor(app) {
this.app = app;
}
preload() {
return Ant.OS.API.require(this.dependencies());
}
import(libs) {
return Ant.OS.API.require(libs);
}
basedir() {
return `${this.app.meta().path}/extensions`;
}
notify(m) {
return this.app.notify(m);
}
error(m, e) {
return this.app.error(m, e);
}
dependencies() {
return [];
}
cat(list, data) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(data); }
const file = (list.splice(0, 1))[0].asFileHandle();
return file
.read()
.then(text => {
data = data + "\n" + text;
return this.cat(list, data)
.then(d => resolve(d))
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
copy(files, to) {
return new Promise((resolve, reject) => {
if (files.length === 0) { return resolve(); }
const file = (files.splice(0, 1))[0].asFileHandle();
const tof = `${to}/${file.basename}`.asFileHandle();
return file.onready().then(meta => {
if (meta.type === "dir") {
// copy directory
const desdir = to.asFileHandle();
return desdir.mk(file.basename).then(() => {
// read the dir content
return file.read().then(data => {
const list = (Array.from(data.result).map((v) => v.path));
return this.copy(list, `${desdir.path}/${file.basename}`)
.then(() => {
return this.copy(files, to)
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
} else {
// copy file
return file.read("binary")
.then(data => {
return tof.setCache(new Blob([data], { type: file.info.mime }))
.write(file.info.mime)
.then(d => {
return this.copy(files, to)
.then(() => resolve())
.catch(e => reject(__e(e)));
});
}).catch(e => reject(__e(e)));
}
}).catch(e => reject(__e(e)));
});
}
aradd(list, zip, base) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(zip); }
const path = (list.splice(0, 1))[0];
const file = path.asFileHandle();
return file.onready().then(meta => {
if (meta.type === "dir") {
return file.read().then(d => {
const l = (Array.from(d.result).map((v) => v.path));
return this.aradd(l, zip, `${base}${file.basename}/`)
.then(() => {
return this.aradd(list, zip, base)
.then(() => resolve(zip))
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
} else {
return file.read("binary").then(d => {
const zpath = `${base}${file.basename}`.replace(/^\/+|\/+$/g, '');
zip.file(zpath, d, { binary: true });
return this.aradd(list, zip, base)
.then(() => resolve(zip))
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}
}).catch(e => reject(__e(e)));
});
}
mkar(src, dest) {
this.notify(__("Preparing for release"));
return new Promise((resolve, reject) => {
return new Promise((r, e) => {
return this.import(["os://scripts/jszip.min.js"]).then(() => src.asFileHandle()
.read().then(d => r(d.result)).catch(ex => e(__e(ex)))).catch(ex => e(__e(ex)));
}).then(files => {
return new Promise((r, e) => {
const zip = new JSZip();
return this.aradd((Array.from(files).map((v) => v.path)), zip, "/")
.then(z => r(z))
.catch(ex => e(__e(ex)));
});
}).then(zip => {
return zip.generateAsync({ type: "base64" }).then(data => {
return dest.asFileHandle()
.setCache('data:application/zip;base64,' + data)
.write("base64").then(r => {
return this.notify(__("Archive is generated at: {0}", dest));
}).catch(e => reject(__e(e)));
});
}).catch(e => reject(__e(e)));
});
}
mkdirAll(list) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(); }
const path = (list.splice(0, 1))[0].asFileHandle();
return path.parent().mk(path.basename)
.then(d => {
this.app.trigger("filechange", { file: path.parent(), type: "dir" });
return this.mkdirAll(list)
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
mkfileAll(list, path, name) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(); }
const item = (list.splice(0, 1))[0];
return `${this.basedir()}/${item[0]}`
.asFileHandle()
.read()
.then(data => {
const file = item[1].asFileHandle();
return file
.setCache(data.format(name, `${path}/${name}`))
.write("text/plain")
.then(() => {
this.app.trigger("filechange", { file, type: "file" });
return this.mkfileAll(list, path, name)
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
metadata(file) {
return new Promise((resolve, reject) => {
if (!this.app.currdir) {
return reject(this.app._api.throwe(__("Current folder is not found")));
}
return `${this.app.currdir.path}/${file}`
.asFileHandle()
.read("json")
.then(data => resolve(data)).catch(e => {
return reject(this.app._api.throwe(__("Unable to read meta-data")));
});
});
}
};
CodePad.extensions = {};

View File

@ -0,0 +1,98 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
class CommandPalette extends this.OS.GUI.BasicDialog {
constructor() {
super("CommandPalete", CommandPalette.scheme);
}
main() {
super.main();
const offset = $(".afx-window-content", this.parent.scheme).offset();
const pw = this.parent.scheme.get("width") / 5;
this.scheme.set("width", 3 * pw);
$(this.scheme).offset({ top: offset.top - 2, left: offset.left + pw });
var cb = e => {
if (($(e.target)).closest(this.scheme).length > 0) {
return $(this.find("searchbox")).focus();
} else {
$(document).unbind("mousedown", cb);
return this.quit();
}
};
$(document).on("mousedown", cb);
$(this.find("searchbox")).focus();
this.cmdlist = this.find("container");
if (this.data) { this.cmdlist.set("data", (Array.from(this.data.child))); }
$(this.cmdlist).click(e => {
return this.selectCommand();
});
this.searchbox = this.find("searchbox");
return ($(this.searchbox)).keyup(e => {
return this.search(e);
});
}
search(e) {
let v;
switch (e.which) {
case 27:
// escape key
this.quit();
if (this.data.parent && this.data.parent.run) { return this.data.parent.run(this.parent); }
break;
case 37:
return e.preventDefault();
case 38:
this.cmdlist.selectPrev();
return e.preventDefault();
case 39:
return e.preventDefault();
case 40:
this.cmdlist.selectNext();
return e.preventDefault();
case 13:
e.preventDefault();
return this.selectCommand();
default:
var text = this.searchbox.value;
if (text.length === 2) { this.cmdlist.set("data", ((() => {
const result1 = [];
for (v of Array.from(this.data.child)) { result1.push(v);
}
return result1;
})())); }
if (text.length < 3) { return; }
var result = [];
var term = new RegExp(text, 'i');
for (v of Array.from(this.data.child)) { if (v.text.match(term)) { result.push(v); } }
return this.cmdlist.set("data", result);
}
}
selectCommand() {
const el = this.cmdlist.get("selectedItem");
if (!el) { return; }
el.set("selected", false);
let result = false;
if (this.handle) { result = this.handle({ data: { item: el } }); }
if (!result) { return this.quit(); }
}
}
CommandPalette.scheme = `\
<afx-app-window data-id = "cmd-win"
apptitle="" minimizable="false"
resizable = "false" width="200" height="200">
<afx-vbox>
<input data-height="25" type = "text" data-id="searchbox"/>
<afx-list-view data-id="container"></afx-list-view>
</afx-vbox>
</afx-app-window>\
`;

View File

@ -1,13 +1,10 @@
coffee_files = coffees/CommandPalette.coffee coffees/main.coffee coffees/BaseExtension.coffee
module_files = CommandPalette.js main.js BaseExtension.js
module_dir = extensions
module_dir_src = coffees/extensions
jsfiles =
libfiles =
cssfiles = css/main.css
copyfiles = assets/scheme.html package.json extensions.json
copyfiles = assets/scheme.html package.json extensions.json extensions
PKG_NAME=CodePad

View File

@ -1,168 +0,0 @@
class CodePad.BaseExtension
constructor: (@app) ->
preload: () ->
Ant.OS.API.require @dependencies()
import: (libs) ->
Ant.OS.API.require libs
basedir: () ->
"#{@app.meta().path}/extensions"
notify: (m) ->
@app.notify m
error: (m, e) ->
@app.error m, e
dependencies: () ->
[]
cat: (list, data) ->
new Promise (resolve, reject) =>
return resolve data if list.length is 0
file = (list.splice 0, 1)[0].asFileHandle()
file
.read()
.then (text) =>
data = data + "\n" + text
@cat list, data
.then (d) -> resolve d
.catch (e) -> reject __e e
.catch (e) -> reject __e e
copy: (files, to) ->
new Promise (resolve, reject) =>
return resolve() if files.length is 0
file = (files.splice 0, 1)[0].asFileHandle()
tof = "#{to}/#{file.basename}".asFileHandle()
file.onready().then (meta) =>
if meta.type is "dir"
# copy directory
desdir = to.asFileHandle()
desdir.mk(file.basename).then () =>
# read the dir content
file.read().then (data) =>
list = (v.path for v in data.result)
@copy list, "#{desdir.path}/#{file.basename}"
.then () =>
@copy files, to
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) ->
reject __e e
.catch (e) -> reject __e e
.catch (e) ->
reject __e e
else
# copy file
file.read("binary")
.then (data) =>
tof.setCache(new Blob [data], { type: file.info.mime })
.write(file.info.mime)
.then (d) =>
@copy files, to
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) ->
reject __e e
aradd: (list, zip, base) ->
new Promise (resolve, reject) =>
return resolve(zip) if list.length is 0
path = (list.splice 0, 1)[0]
file = path.asFileHandle()
file.onready().then (meta) =>
if meta.type is "dir"
file.read().then (d) =>
l = (v.path for v in d.result)
@aradd l, zip, "#{base}#{file.basename}/"
.then () =>
@aradd list, zip, base
.then () -> resolve(zip)
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
else
file.read("binary").then (d) =>
zpath = "#{base}#{file.basename}".replace(/^\/+|\/+$/g, '')
zip.file zpath, d, { binary: true }
@aradd list, zip, base
.then () -> resolve(zip)
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
mkar: (src, dest) ->
@notify __("Preparing for release")
new Promise (resolve, reject) =>
new Promise (r, e) =>
@import(["os://scripts/jszip.min.js"]).then () ->
src.asFileHandle()
.read().then (d) ->
r d.result
.catch (ex) -> e __e ex
.catch (ex) -> e __e ex
.then (files) =>
new Promise (r, e) =>
zip = new JSZip()
@aradd (v.path for v in files), zip, "/"
.then (z) -> r(z)
.catch (ex) -> e __e ex
.then (zip) =>
zip.generateAsync({ type: "base64" }).then (data) =>
dest.asFileHandle()
.setCache('data:application/zip;base64,' + data)
.write("base64").then (r) =>
@notify __("Archive is generated at: {0}", dest)
.catch (e) -> reject __e e
.catch (e) -> reject __e e
mkdirAll: (list) ->
new Promise (resolve, reject) =>
return resolve() if list.length is 0
path = (list.splice 0, 1)[0].asFileHandle()
path.parent().mk path.basename
.then (d) =>
@app.trigger "filechange", { file: path.parent(), type: "dir" }
@mkdirAll list
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
mkfileAll: (list, path, name) ->
new Promise (resolve, reject) =>
return resolve() if list.length is 0
item = (list.splice 0, 1)[0]
"#{@basedir()}/#{item[0]}"
.asFileHandle()
.read()
.then (data) =>
file = item[1].asFileHandle()
file
.setCache(data.format name, "#{path}/#{name}")
.write "text/plain"
.then () =>
@app.trigger "filechange", { file: file, type: "file" }
@mkfileAll list, path, name
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
metadata: (file) ->
new Promise (resolve, reject) =>
if not @app.currdir
return reject @app._api.throwe __("Current folder is not found")
"#{@app.currdir.path}/#{file}"
.asFileHandle()
.read("json")
.then (data) ->
resolve data
.catch (e) =>
reject @app._api.throwe __("Unable to read meta-data")
CodePad.extensions = {}

View File

@ -1,74 +0,0 @@
class CommandPalette extends this.OS.GUI.BasicDialog
constructor: () ->
super "CommandPalete", CommandPalette.scheme
main: () ->
super.main()
offset = $(".afx-window-content", @parent.scheme).offset()
pw = @parent.scheme.get("width") / 5
@scheme.set "width", 3 * pw
$(@scheme).offset { top: offset.top - 2, left: offset.left + pw }
cb = (e) =>
if ($ e.target).closest(@scheme).length > 0
$(@find "searchbox").focus()
else
$(document).unbind "mousedown", cb
@quit()
$(document).on "mousedown", cb
$(@find "searchbox").focus()
@cmdlist = @find("container")
@cmdlist.set "data", (v for v in @data.child) if @data
$(@cmdlist).click (e) =>
@selectCommand()
@searchbox = @find "searchbox"
($ @searchbox).keyup (e) =>
@search e
search: (e) ->
switch e.which
when 27
# escape key
@quit()
@data.parent.run(@parent) if @data.parent and @data.parent.run
when 37
e.preventDefault()
when 38
@cmdlist.selectPrev()
e.preventDefault()
when 39
e.preventDefault()
when 40
@cmdlist.selectNext()
e.preventDefault()
when 13
e.preventDefault()
@selectCommand()
else
text = @searchbox.value
@cmdlist.set "data", (v for v in @data.child) if text.length is 2
return if text.length < 3
result = []
term = new RegExp text, 'i'
result.push v for v in @data.child when v.text.match term
@cmdlist.set "data", result
selectCommand: () ->
el = @cmdlist.get "selectedItem"
return unless el
el.set "selected", false
result = false
result = @handle { data: { item: el } } if @handle
return @quit() unless result
CommandPalette.scheme = """
<afx-app-window data-id = "cmd-win"
apptitle="" minimizable="false"
resizable = "false" width="200" height="200">
<afx-vbox>
<input data-height="25" type = "text" data-id="searchbox"/>
<afx-list-view data-id="container"></afx-list-view>
</afx-vbox>
</afx-app-window>
"""

View File

@ -1,176 +0,0 @@
# import the CodePad application module
App = this.OS.APP.CodePad
# define the extension
class App.extensions.AntOSDK extends App.BaseExtension
constructor: (app) ->
super app
# public functions
create: () ->
@app.openDialog("FileDialog", {
title: "__(New Project at)",
file: { basename: __("ProjectName") },
mimes: ["dir"]
}).then (d) =>
@mktpl d.file.path, d.name, true
init: () ->
dir = @app.currdir
return @create() unless dir and dir.basename
dir.read()
.then (d) =>
return @notify __("Cannot read folder: {0}", dir.path) if d.error
return @notify __("The folder is not empty: {0}", dir.path) unless d.result.length is 0
@mktpl dir.parent().path, dir.basename
buildnrun: () ->
@metadata("project.json").then (meta) =>
@build(meta, true).then () =>
@run(meta).catch (e) => @error __("Unable to run project"), e
.catch (e) =>
@error __("Unable to build project"), e
.catch (e) => @error __("Unable to read meta-data"), e
release: () ->
@metadata("project.json").then (meta) =>
@build(meta, false).then () =>
@mkar("#{meta.root}/build/debug", "#{meta.root}/build/release/#{meta.name}.zip")
.then () ->
.catch (e) => @error __("Unable to create package archive"), e
.catch (e) =>
@error __("Unable to build project"), e
.catch (e) => @error __("Unable to read meta-data"), e
# private functions
mktpl: (path, name, flag) ->
rpath = "#{path}/#{name}"
dirs = [
"#{rpath}/javascripts",
"#{rpath}/css",
"#{rpath}/coffees",
"#{rpath}/assets"
]
dirs.unshift rpath if flag
files = [
["templates/sdk-main.tpl", "#{rpath}/coffees/main.coffee"],
["templates/sdk-package.tpl", "#{rpath}/package.json"],
["templates/sdk-project.tpl", "#{rpath}/project.json"],
["templates/sdk-README.tpl", "#{rpath}/README.md"],
["templates/sdk-scheme.tpl", "#{rpath}/assets/scheme.html"]
]
@mkdirAll dirs
.then () =>
@mkfileAll(files, path, name)
.then () =>
@app.currdir = rpath.asFileHandle()
@app.initSideBar()
@app.openFile "#{rpath}/README.md".asFileHandle()
.catch (e) => @error __("Unable to create template files"), e
.catch (e) => @error __("Unable to create project directory"), e
verify: (list) ->
new Promise (resolve, reject) =>
return resolve() if list.length is 0
file = (list.splice 0, 1)[0].asFileHandle()
@notify __("Verifying: {0}", file.path)
file.read().then (data) =>
try
CoffeeScript.nodes data
@verify list
.then () -> resolve()
.catch (e) -> reject __e e
catch ex
reject __e ex
.catch (e) -> reject __e e
compile: (meta) ->
new Promise (resolve, reject) =>
@import([
"#{@basedir()}/coffeescript.js",
"#{@basedir()}/terser.min.js"
]).then () =>
list = ("#{meta.root}/#{v}" for v in meta.coffees)
@verify((f for f in list)).then () =>
@cat(list).then (code) =>
jsrc = CoffeeScript.compile code
@notify __("Compiled successful")
resolve jsrc
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
build: (meta, debug) ->
dirs = [
"#{meta.root}/build",
"#{meta.root}/build/debug",
"#{meta.root}/build/release"
]
new Promise (resolve, reject) =>
@mkdirAll(dirs).then =>
@compile(meta).then (src) =>
@cat ("#{meta.root}/#{v}" for v in meta.javascripts), src
.then (jsrc) ->
new Promise (r, e) ->
code = jsrc
if not debug
options = {
toplevel: true,
compress: {
passes: 3,
#pure_getters: true,
#unsafe: true,
},
mangle: true,
output: {
#beautify: true,
},
}
result = Terser.minify(jsrc, options)
if result.error
@notify __("Unable to minify code: {0}", result.error)
else
code = result.code
"#{meta.root}/build/debug/main.js"
.asFileHandle()
.setCache code
.write("text/plain")
.then (d) ->
r()
.catch (ex) -> e __e ex
.then () =>
new Promise (r, e) =>
@cat ("#{meta.root}/#{v}" for v in meta.css), ""
.then (txt) ->
return r() if txt is ""
"#{meta.root}/build/debug/main.css"
.asFileHandle()
.setCache txt
.write("text/plain")
.then (d) ->
r()
.catch (ex) -> e __e ex
.then () =>
@copy ("#{meta.root}/#{v}" for v in meta.copies), "#{meta.root}/build/debug"
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
run: (meta) ->
"#{meta.root}/build/debug/package.json"
.asFileHandle()
.read("json")
.then (v) =>
v.text = v.name
v.path = "#{meta.root}/build/debug"
v.filename = meta.name
v.type = "app"
v.mime = "antos/app"
v.icon = "#{v.path}/#{v.icon}" if v.icon
v.iconclass = "fa fa-adn" unless v.iconclass or v.icon
@notify __("Installing...")
@app.systemsetting.system.packages[meta.name] = v
@notify __("Running {0}...", meta.name)
@app._gui.forceLaunch meta.name

View File

@ -1,217 +0,0 @@
# import the CodePad application module
App = this.OS.APP.CodePad
# define the extension
class App.extensions.ExtensionMaker extends App.BaseExtension
constructor: (app) ->
super app
# public functions
create: () ->
@app.openDialog("FileDialog", {
title: "__(New CodePad extension at)",
file: { basename: __("ExtensionName") },
mimes: ["dir"]
}).then (d) =>
@mktpl d.file.path, d.name
buildnrun: () ->
@metadata("extension.json").then (meta) =>
@build(meta).then () =>
@run(meta).catch (e) => @error __("Unable to run extension"), e
.catch (e) =>
@error __("Unable to build extension"), e
.catch (e) => @error __("Unable to read meta-data"), e
release: () ->
@metadata("extension.json").then (meta) =>
@build(meta).then () =>
@mkar("#{meta.root}/build/debug",
"#{meta.root}/build/release/#{meta.meta.name}.zip")
.then () ->
.catch (e) => @error __("Unable to create archive"), e
.catch (e) =>
@error __("Unable to build extension"), e
.catch (e) => @error __("Unable to read meta-data"), e
install: () ->
@app.openDialog("FileDialog", {
title: "__(Select extension archive)",
mimes: [".*/zip"]
}).then (d) =>
@installZip d.file.path
.then () =>
@notify __("Extension installed")
@app.loadExtensionMetaData()
.catch (e) => @error __("Unable to install extension"), e
# private functions
mktpl: (path, name) ->
rpath = "#{path}/#{name}"
dirs = [
rpath,
"#{rpath}/build",
"#{rpath}/build/release",
"#{rpath}/build/debug"
]
files = [
["templates/ext-main.tpl", "#{rpath}/#{name}.coffee"],
["templates/ext-extension.tpl", "#{rpath}/extension.json"],
]
@mkdirAll dirs
.then () =>
@mkfileAll(files, path, name)
.then () =>
@app.currdir = rpath.asFileHandle()
@app.initSideBar()
@app.openFile "#{rpath}/#{name}.coffee".asFileHandle()
.catch (e) => @error __("Unable to create extension template"), e
.catch (e) => @error __("Unable to create extension directories"), e
verify: (list) ->
new Promise (resolve, reject) =>
return resolve() if list.length is 0
file = (list.splice 0, 1)[0].asFileHandle()
@notify __("Verifying: {0}", file.path)
file.read().then (data) =>
try
CoffeeScript.nodes data
@verify list
.then () -> resolve()
.catch (e) -> reject __e e
catch ex
reject __e ex
.catch (e) -> reject __e e
compile: (meta) ->
new Promise (resolve, reject) =>
@import(["#{@basedir()}/coffeescript.js"]).then () =>
list = ("#{meta.root}/#{v}" for v in meta.coffees)
@verify((f for f in list)).then () =>
@cat(list).then (code) =>
jsrc = CoffeeScript.compile code
@notify __("Compiled successful")
resolve jsrc
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
build: (meta) ->
new Promise (resolve, reject) =>
@compile(meta).then (src) =>
@cat ("#{meta.root}/#{v}" for v in meta.javascripts), src
.then (jsrc) ->
new Promise (r, e) ->
"#{meta.root}/build/debug/#{meta.meta.name}.js"
.asFileHandle()
.setCache jsrc
.write("text/plain")
.then (d) ->
r()
.catch (ex) -> e __e ex
.then () ->
new Promise (r, e) ->
"#{meta.root}/build/debug/extension.json"
.asFileHandle()
.setCache meta.meta
.write("object")
.then (data) ->
r data
.catch (ex) -> e __e ex
.then () =>
@copy ("#{meta.root}/#{v}" for v in meta.copies), "#{meta.root}/build/debug"
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
run: (meta) ->
new Promise (resolve, reject) =>
path = "#{meta.root}/build/debug/#{meta.meta.name}.js"
delete @app._api.shared[path] if @app._api.shared[path]
@app._api.requires path
.then () =>
if @app.extensions[meta.meta.name]
@app.extensions[meta.meta.name].child = []
@app.extensions[meta.meta.name].addAction v for v in meta.meta.actions
else
@app.extensions[meta.meta.name] = new App.CMDMenu meta.meta.text
@app.extensions[meta.meta.name].name = meta.meta.name
@app.extensions[meta.meta.name].addAction v for v in meta.meta.actions
@app.spotlight.addAction @app.extensions[meta.meta.name]
@app.extensions[meta.meta.name].onchildselect (e) =>
@app.loadAndRunExtensionAction e.data.item.get "data"
@app.spotlight.run @app
resolve()
.catch (e) -> reject __e e
installExtension: (files, zip) ->
new Promise (resolve, reject) =>
idx = files.indexOf "extension.json"
reject(@app._api.throwe __("No meta-data found")) if idx < 0
metafile = (files.splice idx, 1)[0]
# read the meta file
zip.file(metafile).async("uint8array").then (d) =>
meta = JSON.parse(new TextDecoder("utf-8").decode(d))
@installFiles files, zip, meta
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
installFiles: (files, zip, meta) ->
return @installMeta(meta) if files.length is 0
new Promise (resolve, reject) =>
file = (files.splice 0, 1)[0]
path = "#{@basedir()}/#{file}"
zip.file(file).async("uint8array").then (d) =>
path.asFileHandle()
.setCache(new Blob [d], { type: "octet/stream" })
.write("text/plain").then (r) =>
return reject r.error if r.error
@installFiles files, zip, meta
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e
installMeta: (meta) ->
new Promise (resolve, reject) =>
file = "#{@app.meta().path}/extensions.json".asFileHandle()
file.read("json").then (data) ->
names = (v.name) for v in data
idx = name.indexOf meta.name
data.splice idx, 1 if idx >= 0
data.push meta
file.setCache data
.write("object")
.then () -> resolve()
.catch (e) -> reject __e e
.catch (e) -> reject __e e
installZip: (path) ->
new Promise (resolve, reject) =>
@import(["os://scripts/jszip.min.js"]).then () =>
path.asFileHandle().read("binary").then (data) =>
JSZip.loadAsync(data).then (zip) =>
pth = @basedir()
dir = []
files = []
for name, file of zip.files
if file.dir
dir.push(pth + "/" + name)
else
files.push name
if dir.length > 0
@mkdirAll dir
.then () =>
@installExtension files, zip
.then () -> resolve()
.catch(e) -> reject(__e e)
.catch (e) -> reject __e e
else
@installExtension files, zip
.then () -> resolve()
.catch (e) -> reject(__e e)
.catch (e) -> reject __e e
.catch (e) -> reject __e e
.catch (e) -> reject __e e

View File

@ -1,502 +0,0 @@
Ant = this
class CodePad extends this.OS.GUI.BaseApplication
constructor: (args) ->
super "CodePad", args
@currfile = "Untitled".asFileHandle()
@currdir = undefined
if @args and @args.length > 0
if @args[0].type is "dir"
@currdir = @args[0].path.asFileHandle()
else
@currfile = @args[0].path.asFileHandle()
@currdir = @currfile.parent()
main: () ->
@extensions = {}
@fileview = @find("fileview")
@sidebar = @find("sidebar")
@tabbar = @find "tabbar"
@langstat = @find "langstat"
@editorstat = @find "editorstat"
@fileview.set "fetch", (path) ->
new Promise (resolve, reject) ->
dir = path
dir = path.asFileHandle() if typeof path is "string"
dir.read().then (d) ->
return reject d.error if d.error
resolve d.result
.catch (e) -> reject __e e
@setup()
setup: () ->
ace.config.set('basePath', '/scripts/ace')
ace.require "ace/ext/language_tools"
@editor = ace.edit @find("datarea")
@editor.setOptions {
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
highlightSelectedWord: true,
behavioursEnabled: true,
wrap: true,
fontSize: "11pt",
showInvisibles: true
}
#themes = ace.require "ace/ext/themelist"
@editor.setTheme "ace/theme/monokai"
@modes = ace.require "ace/ext/modelist"
@editor.completers.push { getCompletions: ( editor, session, pos, prefix, callback ) -> }
@editor.getSession().setUseWrapMode true
@editormux = false
@editor.on "input", () =>
if @editormux
@editormux = false
return false
if not @currfile.dirty
@currfile.dirty = true
@currfile.text += "*"
@tabbar.update()
@editor.getSession().selection.on "changeCursor", (e) =>
@updateStatus()
@tabbar.set "ontabselect", (e) =>
@selecteTab $(e.data.item).index()
@tabbar.set "ontabclose", (e) =>
it = e.data.item
return false unless it
return @closeTab it unless it.get("data").dirty
@openDialog("YesNoDialog", {
title: __("Close tab"),
text: __("Close without saving ?")
}).then (d) =>
return @closeTab it if d
@editor.focus()
return false
@fileview.set "onfileopen", (e) =>
return unless e.data and e.data.path
return if e.data.type is "dir"
@openFile e.data.path.asFileHandle()
@fileview.set "onfileselect", (e) =>
return unless e.data and e.data.path
return if e.data.type is "dir"
i = @findTabByFile e.data.path.asFileHandle()
return @tabbar.set "selected", i if i isnt -1
@on "resize", () => @editor.resize()
@on "focus", () => @editor.focus()
@spotlight = new CMDMenu __("Command palette")
@bindKey "ALT-P", () => @spotlight.run @
@find("datarea").contextmenuHandle = (e, m) =>
m.set "items", [{
text: __("Command palete"),
onmenuselect: (e) =>
@spotlight.run @
}]
m.show e
@fileview.contextmenuHandle = (e, m) =>
m.set "items", [
{ text: "__(New file)", id: "new" },
{ text: "__(New folder)", id: "newdir" },
{ text: "__(Rename)", id: "rename" },
{ text: "__(Delete)", id: "delete" }
]
m.set "onmenuselect", (e) =>
@ctxFileMenuHandle e
m.show e
@bindKey "ALT-N", () => @menuAction "new"
@bindKey "ALT-O", () => @menuAction "open"
@bindKey "ALT-F", () => @menuAction "opendir"
@bindKey "CTRL-S", () => @menuAction "save"
@bindKey "ALT-W", () => @menuAction "saveas"
@fileview.set "ondragndrop", (e) =>
src = e.data.from.get("data").path.asFileHandle()
des = e.data.to.get("data").path
src.move "#{des}/#{src.basename}"
.then (d) ->
e.data.to.update des
e.data.from.get("parent").update src.parent().path
.catch (e) => @error __("Unable to move file/folder"), e
@on "filechange", (data) =>
path = data.file.path
path = data.file.parent().path if data.type is "file"
@fileview.update path
@loadExtensionMetaData()
@initCommandPalete()
@initSideBar()
@openFile @currfile
openFile: (file) ->
#find tab
i = @findTabByFile file
return @tabbar.set "selected", i if i isnt -1
return @newTab file if file.path.toString() is "Untitled"
file.read()
.then (d) =>
file.cache = d or ""
@newTab file
.catch (e) =>
@error __("Unable to open: {0}", file.path), e
findTabByFile: (file) ->
lst = @tabbar.get "items"
its = ( i for d, i in lst when d.hash() is file.hash() )
return -1 if its.length is 0
return its[0]
newTab: (file) ->
file.text = if file.basename then file.basename else file.path
file.cache = "" unless file.cache
file.um = new ace.UndoManager()
@currfile.selected = false
file.selected = true
#console.log cnt
@tabbar.push file
closeTab: (it) ->
@tabbar.remove it
cnt = @tabbar.get("items").length
if cnt is 0
@openFile "Untitled".asFileHandle()
return false
@tabbar.set "selected", cnt - 1
return false
selecteTab: (i) ->
#return if i is @tabbar.get "selidx"
file = (@tabbar.get "items")[i]
return unless file
@scheme.set "apptitle", file.text.toString()
#return if file is @currfile
if @currfile isnt file
@currfile.cache = @editor.getValue()
@currfile.cursor = @editor.selection.getCursor()
@currfile.selected = false
@currfile = file
if not file.langmode
if file.path.toString() isnt "Untitled"
m = @modes.getModeForPath(file.path)
file.langmode = { caption: m.caption, mode: m.mode }
else
file.langmode = { caption: "Text", mode: "ace/mode/text" }
@editormux = true
@editor.getSession().setUndoManager new ace.UndoManager()
@editor.setValue file.cache, -1
@editor.getSession().setMode file.langmode.mode
if file.cursor
@editor.renderer.scrollCursorIntoView {
row: file.cursor.row, column: file.cursor.column
}, 0.5
@editor.selection.moveTo file.cursor.row, file.cursor.column
@editor.getSession().setUndoManager file.um
@updateStatus()
@editor.focus()
updateStatus: () ->
c = @editor.session.selection.getCursor()
l = @editor.session.getLength()
@editorstat.set "text", __("Row {0}, col {1}, lines: {2}", c.row + 1, c.column + 1, l)
@langstat.set "text", @currfile.langmode.caption
initSideBar: () ->
if @currdir
$(@sidebar).show()
@fileview.set "path", @currdir.path
else
$(@sidebar).hide()
@trigger "resize"
addAction: (action) ->
@spotlight.addAction action
@
addActions: (list) ->
@spotlight.addActions list
@
initCommandPalete: () ->
themes = ace.require "ace/ext/themelist"
cmdtheme = new CMDMenu __("Change theme")
cmdtheme.addAction { text: v.caption, theme: v.theme } for k, v of themes.themesByName
cmdtheme.onchildselect (d, r) ->
data = d.data.item.get("data")
r.editor.setTheme data.theme
r.editor.focus()
@spotlight.addAction cmdtheme
cmdmode = new CMDMenu __("Change language mode")
cmdmode.addAction { text: v.caption, mode: v.mode } for v in @modes.modes
cmdmode.onchildselect (d, r) ->
data = d.data.item.get("data")
r.editor.session.setMode data.mode
r.currfile.langmode = { caption: data.text, mode: data.mode }
r.updateStatus()
r.editor.focus()
@spotlight.addAction cmdmode
@addAction CMDMenu.fromMenu @fileMenu()
loadExtensionMetaData: () ->
"#{@meta().path}/extensions.json"
.asFileHandle()
.read("json")
.then (d) =>
for ext in d
if @extensions[ext.name]
@extensions[ext.name].child = []
@extensions[ext.name].addAction v for v in ext.actions
else
@extensions[ext.name] = new CMDMenu ext.text
@extensions[ext.name].name = ext.name
@extensions[ext.name].addAction v for v in ext.actions
@spotlight.addAction @extensions[ext.name]
@extensions[ext.name].onchildselect (e) =>
@loadAndRunExtensionAction e.data.item.get "data"
.catch (e) =>
@error __("Cannot load extension meta data"), e
runExtensionAction: (name, action) ->
return @error __("Unable to find extension: {0}", name) unless CodePad.extensions[name]
ext = new CodePad.extensions[name](@)
return @error __("Unable to find action: {0}", action) unless ext[action]
ext.preload()
.then () ->
ext[action]()
.catch (e) =>
@error __("Unable to preload extension"), e
loadAndRunExtensionAction: (data) ->
name = data.parent.name
action = data.name
#verify if the extension is load
if not CodePad.extensions[name]
#load the extension
path = "#{@meta().path}/extensions/#{name}.js"
@_api.requires path
.then () => @runExtensionAction name, action
.catch (e) =>
@error __("unable to load extension: {0}", name), e
else
@runExtensionAction name, action
fileMenu: () ->
{
text: __("File"),
child: [
{ text: __("New"), dataid: "new", shortcut: "A-N" },
{ text: __("Open"), dataid: "open", shortcut: "A-O" },
{ text: __("Open Folder"), dataid: "opendir", shortcut: "A-F" },
{ text: __("Save"), dataid: "save", shortcut: "C-S" },
{ text: __("Save as"), dataid: "saveas", shortcut: "A-W" }
],
onchildselect: (e, r) =>
@menuAction e.data.item.get("data").dataid, r
}
ctxFileMenuHandle: (e) ->
el = e.data.item
return unless el
data = el.get("data")
return unless data
file = @fileview.get "selectedFile"
dir = @currdir
dir = file.path.asFileHandle() if file and file.type is "dir"
dir = file.path.asFileHandle().parent() if file and file.type is "file"
switch data.id
when "new"
return unless dir
@openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)"
})
.then (d) =>
fp = "#{dir.path}/#{d}".asFileHandle()
fp.write("text/plain")
.then (r) =>
@fileview.update dir.path
.catch (e) =>
@error __("Fail to create: {0}", e.stack), e
when "newdir"
return unless dir
@openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)"
})
.then (d) =>
dir.mk(d)
.then (r) =>
@fileview.update dir.path
.catch (e) =>
@error __("Fail to create: {0}", dir.path), e
when "rename"
return unless file
@openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename
})
.then (d) =>
return if d is file.filename
file = file.path.asFileHandle()
dir = file.parent()
file.move "#{dir.path}/#{d}"
.then (r) =>
@fileview.update dir.path
.catch (e) =>
@error __("Fail to rename: {0}", file.path), e
when "delete"
return unless file
@openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __("Do you really want to delete: {0}?", file.filename)
})
.then (d) =>
return unless d
file = file.path.asFileHandle()
dir = file.parent()
file.remove()
.then (r) =>
@fileview.update dir.path
.catch (e) =>
@error __("Fail to delete: {0}", file.path), e
else
save: (file) ->
file.write("text/plain")
.then (d) =>
file.dirty = false
file.text = file.basename
@tabbar.update()
@scheme.set "apptitle", "#{@currfile.basename}"
.catch (e) => @error __("Unable to save file: {0}", file.path), e
saveAs: () ->
@openDialog("FileDialog", {
title: __("Save as"),
file: @currfile
})
.then (f) =>
d = f.file.path.asFileHandle()
d = d.parent() if f.file.type is "file"
@currfile.setPath "#{d.path}/#{f.name}"
@save @currfile
menuAction: (dataid, r) ->
me = @
me = r if r
switch dataid
when "new"
me.openFile "Untitled".asFileHandle()
when "open"
me.openDialog("FileDialog", {
title: __("Open file"),
mimes: (v for v in me.meta().mimes when v isnt "dir")
})
.then (f) ->
me.openFile f.file.path.asFileHandle()
when "opendir"
me.openDialog("FileDialog", {
title: __("Open folder"),
mimes: ["dir"]
})
.then (f) ->
me.currdir = f.file.path.asFileHandle()
me.initSideBar()
when "save"
me.currfile.cache = me.editor.getValue()
return me.save me.currfile if me.currfile.basename
me.saveAs()
when "saveas"
me.currfile.cache = me.editor.getValue()
me.saveAs()
else
console.log dataid
cleanup: (evt) ->
dirties = ( v for v in @tabbar.get "items" when v.dirty )
return if dirties.length is 0
evt.preventDefault()
@.openDialog("YesNoDialog", {
title: "__(Quit)",
text: __("Ignore all unsaved files: {0} ?", (v.filename() for v in dirties).join ", " )
}).then (d) =>
if d
v.dirty = false for v in dirties
@quit()
menu: () ->
menu = [
@fileMenu()
{
text: "__(View)",
child: [
{ text: "__(Command Palette)", dataid: "cmdpalette", shortcut: "A-P" }
],
onchildselect: (e, r) =>
@spotlight.run @
}
]
menu
class CMDMenu
constructor: (@text, @shortcut) ->
@child = []
@parent = undefined
@select = (e) ->
addAction: (v) ->
v.parent = @
@child.push v
@
addActions: (list) ->
@addAction v for v in list
onchildselect: (f) ->
@select = f
@
run: (root) ->
root.openDialog(new CommandPalette(), @)
.then (d) =>
data = d.data.item.get("data")
return data.run root if data.run
@select d, root
CMDMenu.fromMenu = (mn) ->
m = new CMDMenu mn.text, mn.shortcut
m.onchildselect mn.onchildselect
for v in mn.child
if v.child
m.addAction CMDMenu.fromMenu v
else
m.addAction v
m
CodePad.CMDMenu = CMDMenu
CodePad.dependencies = [
"os://scripts/ace/ace.js",
"os://scripts/ace/ext-language_tools.js",
"os://scripts/ace/ext-modelist.js",
"os://scripts/ace/ext-themelist.js"
]
this.OS.register "CodePad", CodePad

View File

@ -0,0 +1,222 @@
(function() {
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// import the CodePad application module
const App = this.OS.APP.CodePad;
// define the extension
App.extensions.AntOSDK = class AntOSDK extends App.BaseExtension {
constructor(app) {
super(app);
}
// public functions
create() {
return this.app.openDialog("FileDialog", {
title: "__(New Project at)",
file: { basename: __("ProjectName") },
mimes: ["dir"]
}).then(d => {
return this.mktpl(d.file.path, d.name, true);
});
}
init() {
const dir = this.app.currdir;
if (!dir || !dir.basename) { return this.create(); }
return dir.read()
.then(d => {
if (d.error) { return this.notify(__("Cannot read folder: {0}", dir.path)); }
if (d.result.length !== 0) { return this.notify(__("The folder is not empty: {0}", dir.path)); }
return this.mktpl(dir.parent().path, dir.basename);
});
}
buildnrun() {
return this.metadata("project.json").then(meta => {
return this.build(meta, true).then(() => {
return this.run(meta).catch(e => this.error(__("Unable to run project"), e));
}).catch(e => {
return this.error(__("Unable to build project"), e);
});
}).catch(e => this.error(__("Unable to read meta-data"), e));
}
release() {
return this.metadata("project.json").then(meta => {
return this.build(meta, false).then(() => {
return this.mkar(`${meta.root}/build/debug`, `${meta.root}/build/release/${meta.name}.zip`)
.catch(e => this.error(__("Unable to create package archive"), e));
}).catch(e => {},
this.error(__("Unable to build project"), e)
);
}).catch(e => this.error(__("Unable to read meta-data"), e));
}
// private functions
mktpl(path, name, flag) {
const rpath = `${path}/${name}`;
const dirs = [
`${rpath}/javascripts`,
`${rpath}/css`,
`${rpath}/coffees`,
`${rpath}/assets`
];
if (flag) { dirs.unshift(rpath); }
const files = [
["templates/sdk-main.tpl", `${rpath}/coffees/main.coffee`],
["templates/sdk-package.tpl", `${rpath}/package.json`],
["templates/sdk-project.tpl", `${rpath}/project.json`],
["templates/sdk-README.tpl", `${rpath}/README.md`],
["templates/sdk-scheme.tpl", `${rpath}/assets/scheme.html`]
];
return this.mkdirAll(dirs)
.then(() => {
return this.mkfileAll(files, path, name)
.then(() => {
this.app.currdir = rpath.asFileHandle();
this.app.initSideBar();
return this.app.openFile(`${rpath}/README.md`.asFileHandle());
}).catch(e => this.error(__("Unable to create template files"), e));
}).catch(e => this.error(__("Unable to create project directory"), e));
}
verify(list) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(); }
const file = (list.splice(0, 1))[0].asFileHandle();
this.notify(__("Verifying: {0}", file.path));
return file.read().then(data => {
try {
CoffeeScript.nodes(data);
return this.verify(list)
.then(() => resolve())
.catch(e => reject(__e(e)));
} catch (ex) {
return reject(__e(ex));
}
}).catch(e => reject(__e(e)));
});
}
compile(meta) {
return new Promise((resolve, reject) => {
return this.import([
`${this.basedir()}/coffeescript.js`,
`${this.basedir()}/terser.min.js`
]).then(() => {
const list = (Array.from(meta.coffees).map((v) => `${meta.root}/${v}`));
return this.verify((Array.from(list))).then(() => {
return this.cat(list).then(code => {
const jsrc = CoffeeScript.compile(code);
this.notify(__("Compiled successful"));
return resolve(jsrc);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
build(meta, debug) {
const dirs = [
`${meta.root}/build`,
`${meta.root}/build/debug`,
`${meta.root}/build/release`
];
return new Promise((resolve, reject) => {
return this.mkdirAll(dirs).then(() => {
return this.compile(meta).then(src => {
let v;
return this.cat(((() => {
const result = [];
for (v of Array.from(meta.javascripts)) { result.push(`${meta.root}/${v}`);
}
return result;
})()), src)
.then(jsrc => new Promise(function(r, e) {
let code = jsrc;
if (!debug) {
const options = {
toplevel: true,
compress: {
passes: 3,
//pure_getters: true,
//unsafe: true,
},
mangle: true,
output: {
//beautify: true,
},
};
const result = Terser.minify(jsrc, options);
if (result.error) {
this.notify(__("Unable to minify code: {0}", result.error));
} else {
({
code
} = result);
}
}
return `${meta.root}/build/debug/main.js`
.asFileHandle()
.setCache(code)
.write("text/plain")
.then(d => r()).catch(ex => e(__e(ex)));
})).then(() => {
return new Promise((r, e) => {
return this.cat(((() => {
const result1 = [];
for (v of Array.from(meta.css)) { result1.push(`${meta.root}/${v}`);
}
return result1;
})()), "")
.then(function(txt) {
if (txt === "") { return r(); }
return `${meta.root}/build/debug/main.css`
.asFileHandle()
.setCache(txt)
.write("text/plain")
.then(d => r()).catch(ex => e(__e(ex)));
});
});
}).then(() => {
return this.copy(((() => {
const result1 = [];
for (v of Array.from(meta.copies)) { result1.push(`${meta.root}/${v}`);
}
return result1;
})()), `${meta.root}/build/debug`);
}).then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
run(meta) {
return `${meta.root}/build/debug/package.json`
.asFileHandle()
.read("json")
.then(v => {
v.text = v.name;
v.path = `${meta.root}/build/debug`;
v.filename = meta.name;
v.type = "app";
v.mime = "antos/app";
if (v.icon) { v.icon = `${v.path}/${v.icon}`; }
if (!v.iconclass && !v.icon) { v.iconclass = "fa fa-adn"; }
this.notify(__("Installing..."));
this.app.systemsetting.system.packages[meta.name] = v;
this.notify(__("Running {0}...", meta.name));
return this.app._gui.forceLaunch(meta.name);
});
}
};
}).call(this);

View File

@ -0,0 +1,261 @@
(function() {
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// import the CodePad application module
const App = this.OS.APP.CodePad;
// define the extension
App.extensions.ExtensionMaker = class ExtensionMaker extends App.BaseExtension {
constructor(app) {
super(app);
}
// public functions
create() {
return this.app.openDialog("FileDialog", {
title: "__(New CodePad extension at)",
file: { basename: __("ExtensionName") },
mimes: ["dir"]
}).then(d => {
return this.mktpl(d.file.path, d.name);
});
}
buildnrun() {
return this.metadata("extension.json").then(meta => {
return this.build(meta).then(() => {
return this.run(meta).catch(e => this.error(__("Unable to run extension"), e));
}).catch(e => {
return this.error(__("Unable to build extension"), e);
});
}).catch(e => this.error(__("Unable to read meta-data"), e));
}
release() {
return this.metadata("extension.json").then(meta => {
return this.build(meta).then(() => {
return this.mkar(`${meta.root}/build/debug`,
`${meta.root}/build/release/${meta.meta.name}.zip`)
.catch(e => this.error(__("Unable to create archive"), e));
}).catch(e => {},
this.error(__("Unable to build extension"), e)
);
}).catch(e => this.error(__("Unable to read meta-data"), e));
}
install() {
return this.app.openDialog("FileDialog", {
title: "__(Select extension archive)",
mimes: [".*/zip"]
}).then(d => {
return this.installZip(d.file.path)
.then(() => {
this.notify(__("Extension installed"));
return this.app.loadExtensionMetaData();
}).catch(e => this.error(__("Unable to install extension"), e));
});
}
// private functions
mktpl(path, name) {
const rpath = `${path}/${name}`;
const dirs = [
rpath,
`${rpath}/build`,
`${rpath}/build/release`,
`${rpath}/build/debug`
];
const files = [
["templates/ext-main.tpl", `${rpath}/${name}.coffee`],
["templates/ext-extension.tpl", `${rpath}/extension.json`],
];
return this.mkdirAll(dirs)
.then(() => {
return this.mkfileAll(files, path, name)
.then(() => {
this.app.currdir = rpath.asFileHandle();
this.app.initSideBar();
return this.app.openFile(`${rpath}/${name}.coffee`.asFileHandle());
}).catch(e => this.error(__("Unable to create extension template"), e));
}).catch(e => this.error(__("Unable to create extension directories"), e));
}
verify(list) {
return new Promise((resolve, reject) => {
if (list.length === 0) { return resolve(); }
const file = (list.splice(0, 1))[0].asFileHandle();
this.notify(__("Verifying: {0}", file.path));
return file.read().then(data => {
try {
CoffeeScript.nodes(data);
return this.verify(list)
.then(() => resolve())
.catch(e => reject(__e(e)));
} catch (ex) {
return reject(__e(ex));
}
}).catch(e => reject(__e(e)));
});
}
compile(meta) {
return new Promise((resolve, reject) => {
return this.import([`${this.basedir()}/coffeescript.js`]).then(() => {
const list = (Array.from(meta.coffees).map((v) => `${meta.root}/${v}`));
return this.verify((Array.from(list))).then(() => {
return this.cat(list).then(code => {
const jsrc = CoffeeScript.compile(code);
this.notify(__("Compiled successful"));
return resolve(jsrc);
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
build(meta) {
return new Promise((resolve, reject) => {
return this.compile(meta).then(src => {
let v;
return this.cat(((() => {
const result = [];
for (v of Array.from(meta.javascripts)) { result.push(`${meta.root}/${v}`);
}
return result;
})()), src)
.then(jsrc => new Promise((r, e) => `${meta.root}/build/debug/${meta.meta.name}.js`
.asFileHandle()
.setCache(jsrc)
.write("text/plain")
.then(d => r()).catch(ex => e(__e(ex))))).then(() => new Promise((r, e) => `${meta.root}/build/debug/extension.json`
.asFileHandle()
.setCache(meta.meta)
.write("object")
.then(data => r(data)).catch(ex => e(__e(ex))))).then(() => {
return this.copy(((() => {
const result1 = [];
for (v of Array.from(meta.copies)) { result1.push(`${meta.root}/${v}`);
}
return result1;
})()), `${meta.root}/build/debug`);
}).then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
run(meta) {
return new Promise((resolve, reject) => {
const path = `${meta.root}/build/debug/${meta.meta.name}.js`;
if (this.app._api.shared[path]) { delete this.app._api.shared[path]; }
return this.app._api.requires(path)
.then(() => {
let v;
if (this.app.extensions[meta.meta.name]) {
this.app.extensions[meta.meta.name].child = [];
for (v of Array.from(meta.meta.actions)) { this.app.extensions[meta.meta.name].addAction(v); }
} else {
this.app.extensions[meta.meta.name] = new App.CMDMenu(meta.meta.text);
this.app.extensions[meta.meta.name].name = meta.meta.name;
for (v of Array.from(meta.meta.actions)) { this.app.extensions[meta.meta.name].addAction(v); }
this.app.spotlight.addAction(this.app.extensions[meta.meta.name]);
this.app.extensions[meta.meta.name].onchildselect(e => {
return this.app.loadAndRunExtensionAction(e.data.item.get("data"));
});
}
this.app.spotlight.run(this.app);
return resolve();
}).catch(e => reject(__e(e)));
});
}
installExtension(files, zip) {
return new Promise((resolve, reject) => {
const idx = files.indexOf("extension.json");
if (idx < 0) { reject(this.app._api.throwe(__("No meta-data found"))); }
const metafile = (files.splice(idx, 1))[0];
// read the meta file
return zip.file(metafile).async("uint8array").then(d => {
const meta = JSON.parse(new TextDecoder("utf-8").decode(d));
return this.installFiles(files, zip, meta)
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
installFiles(files, zip, meta) {
if (files.length === 0) { return this.installMeta(meta); }
return new Promise((resolve, reject) => {
const file = (files.splice(0, 1))[0];
const path = `${this.basedir()}/${file}`;
return zip.file(file).async("uint8array").then(d => {
return path.asFileHandle()
.setCache(new Blob([d], { type: "octet/stream" }))
.write("text/plain").then(r => {
if (r.error) { return reject(r.error); }
return this.installFiles(files, zip, meta)
.then(() => resolve())
.catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
installMeta(meta) {
return new Promise((resolve, reject) => {
const file = `${this.app.meta().path}/extensions.json`.asFileHandle();
return file.read("json").then(function(data) {
for (let v of Array.from(data)) { const names = (v.name); }
const idx = name.indexOf(meta.name);
if (idx >= 0) { data.splice(idx, 1); }
data.push(meta);
return file.setCache(data)
.write("object")
.then(() => resolve())
.catch(e => reject(__e(e)));}).catch(e => reject(__e(e)));
});
}
installZip(path) {
return new Promise((resolve, reject) => {
return this.import(["os://scripts/jszip.min.js"]).then(() => {
return path.asFileHandle().read("binary").then(data => {
return JSZip.loadAsync(data).then(zip => {
const pth = this.basedir();
const dir = [];
const files = [];
for (let name in zip.files) {
const file = zip.files[name];
if (file.dir) {
dir.push(pth + "/" + name);
} else {
files.push(name);
}
}
if (dir.length > 0) {
return this.mkdirAll(dir)
.then(() => {
return this.installExtension(files, zip)
.then(() => resolve())
.catch(e)(() => reject(__e(e)));
}).catch(e => reject(__e(e)));
} else {
return this.installExtension(files, zip)
.then(() => resolve())
.catch(e => reject(__e(e)));
}
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
}).catch(e => reject(__e(e)));
});
}
};
}).call(this);

View File

@ -0,0 +1,628 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS205: Consider reworking code to avoid use of IIFEs
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
const Ant = this;
class CodePad extends this.OS.GUI.BaseApplication {
constructor(args) {
super("CodePad", args);
this.currfile = "Untitled".asFileHandle();
this.currdir = undefined;
if (this.args && (this.args.length > 0)) {
if (this.args[0].type === "dir") {
this.currdir = this.args[0].path.asFileHandle();
} else {
this.currfile = this.args[0].path.asFileHandle();
this.currdir = this.currfile.parent();
}
}
}
main() {
this.extensions = {};
this.fileview = this.find("fileview");
this.sidebar = this.find("sidebar");
this.tabbar = this.find("tabbar");
this.langstat = this.find("langstat");
this.editorstat = this.find("editorstat");
this.fileview.set("fetch", path => new Promise(function(resolve, reject) {
let dir = path;
if (typeof path === "string") { dir = path.asFileHandle(); }
return dir.read().then(function(d) {
if (d.error) { return reject(d.error); }
return resolve(d.result);}).catch(e => reject(__e(e)));
}));
return this.setup();
}
setup() {
ace.config.set('basePath', '/scripts/ace');
ace.require("ace/ext/language_tools");
this.editor = ace.edit(this.find("datarea"));
this.editor.setOptions({
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
highlightActiveLine: true,
highlightSelectedWord: true,
behavioursEnabled: true,
wrap: true,
fontSize: "11pt",
showInvisibles: true
});
//themes = ace.require "ace/ext/themelist"
this.editor.setTheme("ace/theme/monokai");
this.modes = ace.require("ace/ext/modelist");
this.editor.completers.push({ getCompletions( editor, session, pos, prefix, callback ) {} });
this.editor.getSession().setUseWrapMode(true);
this.editormux = false;
this.editor.on("input", () => {
if (this.editormux) {
this.editormux = false;
return false;
}
if (!this.currfile.dirty) {
this.currfile.dirty = true;
this.currfile.text += "*";
return this.tabbar.update();
}
});
this.editor.getSession().selection.on("changeCursor", e => {
return this.updateStatus();
});
this.tabbar.set("ontabselect", e => {
return this.selecteTab($(e.data.item).index());
});
this.tabbar.set("ontabclose", e => {
const it = e.data.item;
if (!it) { return false; }
if (!it.get("data").dirty) { return this.closeTab(it); }
this.openDialog("YesNoDialog", {
title: __("Close tab"),
text: __("Close without saving ?")
}).then(d => {
if (d) { return this.closeTab(it); }
return this.editor.focus();
});
return false;
});
this.fileview.set("onfileopen", e => {
if (!e.data || !e.data.path) { return; }
if (e.data.type === "dir") { return; }
return this.openFile(e.data.path.asFileHandle());
});
this.fileview.set("onfileselect", e => {
if (!e.data || !e.data.path) { return; }
if (e.data.type === "dir") { return; }
const i = this.findTabByFile(e.data.path.asFileHandle());
if (i !== -1) { return this.tabbar.set("selected", i); }
});
this.on("resize", () => this.editor.resize());
this.on("focus", () => this.editor.focus());
this.spotlight = new CMDMenu(__("Command palette"));
this.bindKey("ALT-P", () => this.spotlight.run(this));
this.find("datarea").contextmenuHandle = (e, m) => {
m.set("items", [{
text: __("Command palete"),
onmenuselect: e => {
return this.spotlight.run(this);
}
}]);
return m.show(e);
};
this.fileview.contextmenuHandle = (e, m) => {
m.set("items", [
{ text: "__(New file)", id: "new" },
{ text: "__(New folder)", id: "newdir" },
{ text: "__(Rename)", id: "rename" },
{ text: "__(Delete)", id: "delete" }
]);
m.set("onmenuselect", e => {
return this.ctxFileMenuHandle(e);
});
return m.show(e);
};
this.bindKey("ALT-N", () => this.menuAction("new"));
this.bindKey("ALT-O", () => this.menuAction("open"));
this.bindKey("ALT-F", () => this.menuAction("opendir"));
this.bindKey("CTRL-S", () => this.menuAction("save"));
this.bindKey("ALT-W", () => this.menuAction("saveas"));
this.fileview.set("ondragndrop", e => {
const src = e.data.from.get("data").path.asFileHandle();
const des = e.data.to.get("data").path;
return src.move(`${des}/${src.basename}`)
.then(function(d) {
e.data.to.update(des);
return e.data.from.get("parent").update(src.parent().path);}).catch(e => this.error(__("Unable to move file/folder"), e));
});
this.on("filechange", data => {
let {
path
} = data.file;
if (data.type === "file") { ({
path
} = data.file.parent()); }
return this.fileview.update(path);
});
this.loadExtensionMetaData();
this.initCommandPalete();
this.initSideBar();
return this.openFile(this.currfile);
}
openFile(file) {
//find tab
const i = this.findTabByFile(file);
if (i !== -1) { return this.tabbar.set("selected", i); }
if (file.path.toString() === "Untitled") { return this.newTab(file); }
return file.read()
.then(d => {
file.cache = d || "";
return this.newTab(file);
}).catch(e => {
return this.error(__("Unable to open: {0}", file.path), e);
});
}
findTabByFile(file) {
const lst = this.tabbar.get("items");
const its = ((() => {
const result = [];
for (let i = 0; i < lst.length; i++) {
const d = lst[i];
if (d.hash() === file.hash()) {
result.push(i);
}
}
return result;
})());
if (its.length === 0) { return -1; }
return its[0];
}
newTab(file) {
file.text = file.basename ? file.basename : file.path;
if (!file.cache) { file.cache = ""; }
file.um = new ace.UndoManager();
this.currfile.selected = false;
file.selected = true;
//console.log cnt
return this.tabbar.push(file);
}
closeTab(it) {
this.tabbar.remove(it);
const cnt = this.tabbar.get("items").length;
if (cnt === 0) {
this.openFile("Untitled".asFileHandle());
return false;
}
this.tabbar.set("selected", cnt - 1);
return false;
}
selecteTab(i) {
//return if i is @tabbar.get "selidx"
const file = (this.tabbar.get("items"))[i];
if (!file) { return; }
this.scheme.set("apptitle", file.text.toString());
//return if file is @currfile
if (this.currfile !== file) {
this.currfile.cache = this.editor.getValue();
this.currfile.cursor = this.editor.selection.getCursor();
this.currfile.selected = false;
this.currfile = file;
}
if (!file.langmode) {
if (file.path.toString() !== "Untitled") {
const m = this.modes.getModeForPath(file.path);
file.langmode = { caption: m.caption, mode: m.mode };
} else {
file.langmode = { caption: "Text", mode: "ace/mode/text" };
}
}
this.editormux = true;
this.editor.getSession().setUndoManager(new ace.UndoManager());
this.editor.setValue(file.cache, -1);
this.editor.getSession().setMode(file.langmode.mode);
if (file.cursor) {
this.editor.renderer.scrollCursorIntoView({
row: file.cursor.row, column: file.cursor.column
}, 0.5);
this.editor.selection.moveTo(file.cursor.row, file.cursor.column);
}
this.editor.getSession().setUndoManager(file.um);
this.updateStatus();
return this.editor.focus();
}
updateStatus() {
const c = this.editor.session.selection.getCursor();
const l = this.editor.session.getLength();
this.editorstat.set("text", __("Row {0}, col {1}, lines: {2}", c.row + 1, c.column + 1, l));
return this.langstat.set("text", this.currfile.langmode.caption);
}
initSideBar() {
if (this.currdir) {
$(this.sidebar).show();
this.fileview.set("path", this.currdir.path);
} else {
$(this.sidebar).hide();
}
return this.trigger("resize");
}
addAction(action) {
this.spotlight.addAction(action);
return this;
}
addActions(list) {
this.spotlight.addActions(list);
return this;
}
initCommandPalete() {
let v;
const themes = ace.require("ace/ext/themelist");
const cmdtheme = new CMDMenu(__("Change theme"));
for (let k in themes.themesByName) { v = themes.themesByName[k]; cmdtheme.addAction({ text: v.caption, theme: v.theme }); }
cmdtheme.onchildselect(function(d, r) {
const data = d.data.item.get("data");
r.editor.setTheme(data.theme);
return r.editor.focus();
});
this.spotlight.addAction(cmdtheme);
const cmdmode = new CMDMenu(__("Change language mode"));
for (v of Array.from(this.modes.modes)) { cmdmode.addAction({ text: v.caption, mode: v.mode }); }
cmdmode.onchildselect(function(d, r) {
const data = d.data.item.get("data");
r.editor.session.setMode(data.mode);
r.currfile.langmode = { caption: data.text, mode: data.mode };
r.updateStatus();
return r.editor.focus();
});
this.spotlight.addAction(cmdmode);
return this.addAction(CMDMenu.fromMenu(this.fileMenu()));
}
loadExtensionMetaData() {
return `${this.meta().path}/extensions.json`
.asFileHandle()
.read("json")
.then(d => {
return (() => {
const result = [];
for (var ext of Array.from(d)) {
if (this.extensions[ext.name]) {
this.extensions[ext.name].child = [];
result.push((() => {
const result1 = [];
for (let v of Array.from(ext.actions)) { result1.push(this.extensions[ext.name].addAction(v));
}
return result1;
})());
} else {
this.extensions[ext.name] = new CMDMenu(ext.text);
this.extensions[ext.name].name = ext.name;
for (let v of Array.from(ext.actions)) { this.extensions[ext.name].addAction(v); }
this.spotlight.addAction(this.extensions[ext.name]);
result.push(this.extensions[ext.name].onchildselect(e => {
return this.loadAndRunExtensionAction(e.data.item.get("data"));
}));
}
}
return result;
})();
}).catch(e => {
return this.error(__("Cannot load extension meta data"), e);
});
}
runExtensionAction(name, action) {
if (!CodePad.extensions[name]) { return this.error(__("Unable to find extension: {0}", name)); }
const ext = new (CodePad.extensions[name])(this);
if (!ext[action]) { return this.error(__("Unable to find action: {0}", action)); }
return ext.preload()
.then(() => ext[action]()).catch(e => {
return this.error(__("Unable to preload extension"), e);
});
}
loadAndRunExtensionAction(data) {
const {
name
} = data.parent;
const action = data.name;
//verify if the extension is load
if (!CodePad.extensions[name]) {
//load the extension
const path = `${this.meta().path}/extensions/${name}.js`;
return this._api.requires(path)
.then(() => this.runExtensionAction(name, action))
.catch(e => {
return this.error(__("unable to load extension: {0}", name), e);
});
} else {
return this.runExtensionAction(name, action);
}
}
fileMenu() {
return {
text: __("File"),
child: [
{ text: __("New"), dataid: "new", shortcut: "A-N" },
{ text: __("Open"), dataid: "open", shortcut: "A-O" },
{ text: __("Open Folder"), dataid: "opendir", shortcut: "A-F" },
{ text: __("Save"), dataid: "save", shortcut: "C-S" },
{ text: __("Save as"), dataid: "saveas", shortcut: "A-W" }
],
onchildselect: (e, r) => {
return this.menuAction(e.data.item.get("data").dataid, r);
}
};
}
ctxFileMenuHandle(e) {
const el = e.data.item;
if (!el) { return; }
const data = el.get("data");
if (!data) { return; }
let file = this.fileview.get("selectedFile");
let dir = this.currdir;
if (file && (file.type === "dir")) { dir = file.path.asFileHandle(); }
if (file && (file.type === "file")) { dir = file.path.asFileHandle().parent(); }
switch (data.id) {
case "new":
if (!dir) { return; }
return this.openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)"
})
.then(d => {
const fp = `${dir.path}/${d}`.asFileHandle();
return fp.write("text/plain")
.then(r => {
return this.fileview.update(dir.path);
}).catch(e => {
return this.error(__("Fail to create: {0}", e.stack), e);
});
});
case "newdir":
if (!dir) { return; }
return this.openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)"
})
.then(d => {
return dir.mk(d)
.then(r => {
return this.fileview.update(dir.path);
}).catch(e => {
return this.error(__("Fail to create: {0}", dir.path), e);
});
});
case "rename":
if (!file) { return; }
return this.openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename
})
.then(d => {
if (d === file.filename) { return; }
file = file.path.asFileHandle();
dir = file.parent();
return file.move(`${dir.path}/${d}`)
.then(r => {
return this.fileview.update(dir.path);
}).catch(e => {
return this.error(__("Fail to rename: {0}", file.path), e);
});
});
case "delete":
if (!file) { return; }
return this.openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __("Do you really want to delete: {0}?", file.filename)
})
.then(d => {
if (!d) { return; }
file = file.path.asFileHandle();
dir = file.parent();
return file.remove()
.then(r => {
return this.fileview.update(dir.path);
}).catch(e => {
return this.error(__("Fail to delete: {0}", file.path), e);
});
});
default:
}
}
save(file) {
return file.write("text/plain")
.then(d => {
file.dirty = false;
file.text = file.basename;
this.tabbar.update();
return this.scheme.set("apptitle", `${this.currfile.basename}`);
}).catch(e => this.error(__("Unable to save file: {0}", file.path), e));
}
saveAs() {
return this.openDialog("FileDialog", {
title: __("Save as"),
file: this.currfile
})
.then(f => {
let d = f.file.path.asFileHandle();
if (f.file.type === "file") { d = d.parent(); }
this.currfile.setPath(`${d.path}/${f.name}`);
return this.save(this.currfile);
});
}
menuAction(dataid, r) {
let me = this;
if (r) { me = r; }
switch (dataid) {
case "new":
return me.openFile("Untitled".asFileHandle());
case "open":
return me.openDialog("FileDialog", {
title: __("Open file"),
mimes: (Array.from(me.meta().mimes).filter((v) => v !== "dir"))
})
.then(f => me.openFile(f.file.path.asFileHandle()));
case "opendir":
return me.openDialog("FileDialog", {
title: __("Open folder"),
mimes: ["dir"]
})
.then(function(f) {
me.currdir = f.file.path.asFileHandle();
return me.initSideBar();
});
case "save":
me.currfile.cache = me.editor.getValue();
if (me.currfile.basename) { return me.save(me.currfile); }
return me.saveAs();
case "saveas":
me.currfile.cache = me.editor.getValue();
return me.saveAs();
default:
return console.log(dataid);
}
}
cleanup(evt) {
let v;
const dirties = ((() => {
const result = [];
for (v of Array.from(this.tabbar.get("items"))) { if (v.dirty) {
result.push(v);
}
}
return result;
})());
if (dirties.length === 0) { return; }
evt.preventDefault();
return this.openDialog("YesNoDialog", {
title: "__(Quit)",
text: __("Ignore all unsaved files: {0} ?", ((() => {
const result1 = [];
for (v of Array.from(dirties)) { result1.push(v.filename());
}
return result1;
})()).join(", ") )
}).then(d => {
if (d) {
for (v of Array.from(dirties)) { v.dirty = false; }
return this.quit();
}
});
}
menu() {
const menu = [
this.fileMenu(),
{
text: "__(View)",
child: [
{ text: "__(Command Palette)", dataid: "cmdpalette", shortcut: "A-P" }
],
onchildselect: (e, r) => {
return this.spotlight.run(this);
}
}
];
return menu;
}
}
class CMDMenu {
constructor(text, shortcut) {
this.text = text;
this.shortcut = shortcut;
this.child = [];
this.parent = undefined;
this.select = function(e) {};
}
addAction(v) {
v.parent = this;
this.child.push(v);
return this;
}
addActions(list) {
return Array.from(list).map((v) => this.addAction(v));
}
onchildselect(f) {
this.select = f;
return this;
}
run(root) {
return root.openDialog(new CommandPalette(), this)
.then(d => {
const data = d.data.item.get("data");
if (data.run) { return data.run(root); }
return this.select(d, root);
});
}
}
CMDMenu.fromMenu = function(mn) {
const m = new CMDMenu(mn.text, mn.shortcut);
m.onchildselect(mn.onchildselect);
for (let v of Array.from(mn.child)) {
if (v.child) {
m.addAction(CMDMenu.fromMenu(v));
} else {
m.addAction(v);
}
}
return m;
};
CodePad.CMDMenu = CMDMenu;
CodePad.dependencies = [
"os://scripts/ace/ace.js",
"os://scripts/ace/ext-language_tools.js",
"os://scripts/ace/ext-modelist.js",
"os://scripts/ace/ext-themelist.js"
];
this.OS.register("CodePad", CodePad);

View File

@ -1,6 +1,6 @@
coffee_files = main.coffee
module_files = main.js
jsfiles =
libfiles =
cssfiles = main.css

View File

@ -1,363 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class Files extends this.OS.GUI.BaseApplication
constructor: (args) ->
super "Files", args
main: () ->
@scheme.set "apptitle", "Files manager"
@view = @find "fileview"
@navinput = @find "navinput"
@navbar = @find "nav-bar"
if @args and @args.length > 0
@currdir = @args[0].path.asFileHandle()
else
@currdir = "home://".asFileHandle()
@favo = @find "favouri"
@clipboard = undefined
@viewType = @_api.switcher "icon", "list", "tree"
@viewType.list = true
@view.contextmenuHandle = (e, m) =>
file = @view.get "selectedFile"
return unless file
apps = []
file.mime = "dir" if file.type is "dir"
for v in @_gui.appsByMime file.mime
apps.push {
text: v.text,
app: v.app,
icon: v.icon,
iconclass: v.iconclass
}
m.set "items", [
{
text: "__(Open with)",
child: apps,
onchildselect: (e) =>
return unless e
it = e.data.item.get("data")
@_gui.launch it.app, [file]
},
@mnFile(),
@mnEdit()
]
m.show e
@view.set "onfileopen", (e) =>
return unless e.data
return if e.data.type is "dir"
@_gui.openWith e.data
@favo.set "onlistselect", (e) =>
@view.set "path", e.data.item.get("data").path
($ @find "btback").click () =>
return if @currdir.isRoot()
p = @currdir.parent()
@favo.set "selected", -1
@view.set "path", p.path
($ @navinput).keyup (e) =>
@view.set "path", ($ @navinput).val() if e.keyCode is 13 #enter
@view.set "fetch", (path) =>
new Promise (resolve, reject) =>
dir = path
dir = path.asFileHandle() if typeof path is "string"
dir.read().then (d) =>
return reject d.error if d.error
if not dir.isRoot()
p = dir.parent()
p.filename = "[..]"
p.type = "dir"
d.result.unshift p
@currdir = dir
($ @navinput).val dir.path
resolve d.result
.catch (e) -> reject __e e
@vfs_event_flag = true
@view.set "ondragndrop", (e) =>
return unless e
src = e.data.from.get("data")
des = e.data.to.get("data")
return if des.type is "file"
file = src.path.asFileHandle()
# disable the vfs event on
# we update it manually
@vfs_event_flag = false
file.move "#{des.path}/#{file.basename}"
.then () =>
if @view.get("view") is "icon"
@view.set "path", @view.get("path")
else
@view.update file.parent().path
@view.update des.path
#reenable the vfs event
@vfs_event_flag = true
.catch (e) =>
# reenable the vfs event
@vfs_event_flag = true
@error __("Unable to move: {0} -> {1}", src.path, des.path), e
# application setting
@setting.sidebar = true if @setting.sidebar is undefined
@setting.nav = true if @setting.nav is undefined
@setting.showhidden = false if @setting.showhidden is undefined
@applyAllSetting()
# VFS mount point and event
mntpoints = @systemsetting.VFS.mountpoints
el.selected = false for el, i in mntpoints
@favo.set "data", mntpoints
#@favo.set "selected", -1
@view.set "view", @setting.view if @setting.view
@subscribe "VFS", (d) =>
return unless @vfs_event_flag
return if ["read", "publish", "download"].includes d.data.m
if (d.data.file.hash() is @currdir.hash() or
d.data.file.parent().hash() is @currdir.hash())
@view.set "path", @currdir
# bind keyboard shortcut
@bindKey "CTRL-F", () => @actionFile "#{@name}-mkf"
@bindKey "CTRL-D", () => @actionFile "#{@name}-mkdir"
@bindKey "CTRL-U", () => @actionFile "#{@name}-upload"
@bindKey "CTRL-S", () => @actionFile "#{@name}-share"
@bindKey "CTRL-I", () => @actionFile "#{@name}-info"
@bindKey "CTRL-R", () => @actionEdit "#{@name}-mv"
@bindKey "CTRL-M", () => @actionEdit "#{@name}-rm"
@bindKey "CTRL-X", () => @actionEdit "#{@name}-cut"
@bindKey "CTRL-C", () => @actionEdit "#{@name}-copy"
@bindKey "CTRL-P", () => @actionEdit "#{@name}-paste"
(@find "btgrid").set "onbtclick", (e) =>
@view.set 'view', "icon"
@viewType.icon = true
(@find "btlist").set "onbtclick", (e) =>
@view.set 'view', "list"
@viewType.list = true
@view.set "path", @currdir
applySetting: (k) ->
# view setting
switch k
when "showhidden" then @view.set "showhidden", @setting.showhidden
when "nav" then @toggleNav @setting.nav
when "sidebar" then @toggleSidebar @setting.sidebar
mnFile: () ->
#console.log file
arr = {
text: "__(File)",
child: [
{ text: "__(New file)", dataid: "#{@name}-mkf", shortcut: 'C-F' },
{ text: "__(New folder)", dataid: "#{@name}-mkdir", shortcut: 'C-D' },
{ text: "__(Upload)", dataid: "#{@name}-upload", shortcut: 'C-U' },
{ text: "__(Download)", dataid: "#{@name}-download" },
{ text: "__(Share file)", dataid: "#{@name}-share", shortcut: 'C-S' },
{ text: "__(Properties)", dataid: "#{@name}-info", shortcut: 'C-I' }
], onchildselect: (e) => @actionFile e.data.item.get("data").dataid
}
return arr
mnEdit: () ->
{
text: "__(Edit)",
child: [
{ text: "__(Rename)", dataid: "#{@name}-mv", shortcut: 'C-R' },
{ text: "__(Delete)", dataid: "#{@name}-rm", shortcut: 'C-M' },
{ text: "__(Cut)", dataid: "#{@name}-cut", shortcut: 'C-X' },
{ text: "__(Copy)", dataid: "#{@name}-copy", shortcut: 'C-C' },
{ text: "__(Paste)", dataid: "#{@name}-paste", shortcut: 'C-P' }
], onchildselect: (e) => @actionEdit e.data.item.get("data").dataid
}
menu: () ->
menu = [
@mnFile(),
@mnEdit(),
{
text: "__(View)",
child: [
{ text: "__(Refresh)", dataid: "#{@name}-refresh" },
{ text: "__(Sidebar)", switch: true, checked: @setting.sidebar, dataid: "#{@name}-side" },
{ text: "__(Navigation bar)", switch: true, checked: @setting.nav, dataid: "#{@name}-nav" },
{ text: "__(Hidden files)", switch: true, checked: @setting.showhidden, dataid: "#{@name}-hidden" },
{ text: "__(Type)", child: [
{ text: "__(Icon view)", radio: true, checked: @viewType.icon, dataid: "#{@name}-icon", type: 'icon' },
{ text: "__(List view)", radio:true, checked: @viewType.list, dataid: "#{@name}-list", type: 'list' },
{ text: "__(Tree view)", radio:true, checked: @viewType.tree, dataid: "#{@name}-tree", type: 'tree' }
], onchildselect: (e) =>
type = e.data.item.get("data").type
@view.set 'view', type
@viewType[type] = true
},
], onchildselect: (e) => @actionView e
},
]
menu
toggleSidebar: (b) ->
if b then ($ @favo).show() else ($ @favo).hide()
@trigger "resize"
toggleNav: (b) ->
if b then ($ @navbar).show() else ($ @navbar).hide()
@trigger "resize"
actionView: (e) ->
data = e.data.item.get("data")
switch data.dataid
when "#{@name}-hidden"
#@.view.set "showhidden", e.item.data.checked
@registry "showhidden", data.checked
#@.setting.showhidden = e.item.data.checked
when "#{@name}-refresh"
@.chdir null
when "#{@name}-side"
@registry "sidebar", data.checked
#@setting.sidebar = e.item.data.checked
#@toggleSidebar e.item.data.checked
when "#{@name}-nav"
@registry "nav", data.checked
#@setting.nav = e.item.data.checked
#@toggleNav e.item.data.checked
actionEdit: (e) ->
file = @view.get "selectedFile"
switch e
when "#{@name}-mv"
return unless file
@openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename
})
.then (d) =>
return if d is file.filename
file.path.asFileHandle().move "#{@currdir.path}/#{d}"
.catch (e) =>
@error __("Fail to rename: {0}", file.path), e
when "#{@name}-rm"
return unless file
@openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __("Do you really want to delete: {0}?", file.filename)
})
.then (d) =>
return unless d
file.path.asFileHandle().remove()
.catch (e) =>
@error __("Fail to delete: {0}", file.path), e
when "#{@name}-cut"
return unless file
@clipboard =
cut: true
file: file.path.asFileHandle()
@notify __("File {0} cut", file.filename)
when "#{@name}-copy"
return unless file or file.type is "dir"
@clipboard =
cut: false
file: file.path.asFileHandle()
@notify __("File {0} copied", file.filename)
when "#{@name}-paste"
return unless @clipboard
if @clipboard.cut
@clipboard.file.move "#{@currdir.path}/#{@clipboard.file.basename}"
.then (r) =>
@clipboard = undefined
.catch (e) =>
@error __("Fail to paste: {0}", @clipboard.file.path), e
else
@clipboard.file.read("binary")
.then (d) =>
blob = new Blob [d], { type: @clipboard.file.info.mime }
fp = "#{@currdir.path}/#{@clipboard.file.basename}".asFileHandle()
fp.cache = blob
fp.write(@clipboard.file.info.mime)
.then (r) =>
@clipboard = undefined
.catch (e) =>
@error __("Fail to paste: {0}", @clipboard.file.path), e
.catch (e) =>
@error __("Fail to read: {0}", @clipboard.file.path), e
else
@_api.handle.setting()
actionFile: (e) ->
file = @view.get "selectedFile"
switch e
when "#{@name}-mkdir"
@openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)"
})
.then (d) =>
@currdir.mk(d)
.catch (e) =>
@error __("Fail to create: {0}", d), e
when "#{@name}-mkf"
@openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)"
})
.then (d) =>
fp = "#{@currdir.path}/#{d}".asFileHandle()
fp.write("text/plain")
.catch (e) =>
@error __("Fail to create: {0}", fp.path)
when "#{@name}-info"
return unless file
@openDialog "InfoDialog", file
when "#{@name}-upload"
@currdir.upload()
.catch (e) =>
@error __("Fail to upload: {0}", e.toString()), e
when "#{@name}-share"
return unless file and file.type is "file"
file.path.asFileHandle().publish()
.then (r) =>
@notify __("Shared url: {0}", r.result)
.catch (e) =>
@error __("Fail to publish: {0}", file.path), e
when "#{@name}-download"
return unless file.type is "file"
file.path.asFileHandle().download()
.catch (e) =>
@error __("Fail to download: {0}", file.path), e
else
console.log e
this.OS.register "Files", Files

424
src/packages/Files/main.js Normal file
View File

@ -0,0 +1,424 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS208: Avoid top-level this
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
class Files extends this.OS.GUI.BaseApplication {
constructor(args) {
super("Files", args);
}
main() {
this.scheme.set("apptitle", "Files manager");
this.view = this.find("fileview");
this.navinput = this.find("navinput");
this.navbar = this.find("nav-bar");
if (this.args && (this.args.length > 0)) {
this.currdir = this.args[0].path.asFileHandle();
} else {
this.currdir = "home://".asFileHandle();
}
this.favo = this.find("favouri");
this.clipboard = undefined;
this.viewType = this._api.switcher("icon", "list", "tree");
this.viewType.list = true;
this.view.contextmenuHandle = (e, m) => {
const file = this.view.get("selectedFile");
if (!file) { return; }
const apps = [];
if (file.type === "dir") { file.mime = "dir"; }
for (let v of Array.from(this._gui.appsByMime(file.mime))) {
apps.push({
text: v.text,
app: v.app,
icon: v.icon,
iconclass: v.iconclass
});
}
m.set("items", [
{
text: "__(Open with)",
child: apps,
onchildselect: e => {
if (!e) { return; }
const it = e.data.item.get("data");
return this._gui.launch(it.app, [file]);
}
},
this.mnFile(),
this.mnEdit()
]);
return m.show(e);
};
this.view.set("onfileopen", e => {
if (!e.data) { return; }
if (e.data.type === "dir") { return; }
return this._gui.openWith(e.data);
});
this.favo.set("onlistselect", e => {
return this.view.set("path", e.data.item.get("data").path);
});
($(this.find("btback"))).click(() => {
if (this.currdir.isRoot()) { return; }
const p = this.currdir.parent();
this.favo.set("selected", -1);
return this.view.set("path", p.path);
});
($(this.navinput)).keyup(e => {
if (e.keyCode === 13) { return this.view.set("path", ($(this.navinput)).val()); }
}); //enter
this.view.set("fetch", path => {
return new Promise((resolve, reject) => {
let dir = path;
if (typeof path === "string") { dir = path.asFileHandle(); }
return dir.read().then(d => {
if (d.error) { return reject(d.error); }
if (!dir.isRoot()) {
const p = dir.parent();
p.filename = "[..]";
p.type = "dir";
d.result.unshift(p);
}
this.currdir = dir;
($(this.navinput)).val(dir.path);
return resolve(d.result);
}).catch(e => reject(__e(e)));
});
});
this.vfs_event_flag = true;
this.view.set("ondragndrop", e => {
if (!e) { return; }
const src = e.data.from.get("data");
const des = e.data.to.get("data");
if (des.type === "file") { return; }
const file = src.path.asFileHandle();
// disable the vfs event on
// we update it manually
this.vfs_event_flag = false;
return file.move(`${des.path}/${file.basename}`)
.then(() => {
if (this.view.get("view") === "icon") {
this.view.set("path", this.view.get("path"));
} else {
this.view.update(file.parent().path);
this.view.update(des.path);
}
//reenable the vfs event
return this.vfs_event_flag = true;
}).catch(e => {
// reenable the vfs event
this.vfs_event_flag = true;
return this.error(__("Unable to move: {0} -> {1}", src.path, des.path), e);
});
});
// application setting
if (this.setting.sidebar === undefined) { this.setting.sidebar = true; }
if (this.setting.nav === undefined) { this.setting.nav = true; }
if (this.setting.showhidden === undefined) { this.setting.showhidden = false; }
this.applyAllSetting();
// VFS mount point and event
const mntpoints = this.systemsetting.VFS.mountpoints;
for (let i = 0; i < mntpoints.length; i++) { const el = mntpoints[i]; el.selected = false; }
this.favo.set("data", mntpoints);
//@favo.set "selected", -1
if (this.setting.view) { this.view.set("view", this.setting.view); }
this.subscribe("VFS", d => {
if (!this.vfs_event_flag) { return; }
if (["read", "publish", "download"].includes(d.data.m)) { return; }
if ((d.data.file.hash() === this.currdir.hash()) ||
(d.data.file.parent().hash() === this.currdir.hash())) {
return this.view.set("path", this.currdir);
}
});
// bind keyboard shortcut
this.bindKey("CTRL-F", () => this.actionFile(`${this.name}-mkf`));
this.bindKey("CTRL-D", () => this.actionFile(`${this.name}-mkdir`));
this.bindKey("CTRL-U", () => this.actionFile(`${this.name}-upload`));
this.bindKey("CTRL-S", () => this.actionFile(`${this.name}-share`));
this.bindKey("CTRL-I", () => this.actionFile(`${this.name}-info`));
this.bindKey("CTRL-R", () => this.actionEdit(`${this.name}-mv`));
this.bindKey("CTRL-M", () => this.actionEdit(`${this.name}-rm`));
this.bindKey("CTRL-X", () => this.actionEdit(`${this.name}-cut`));
this.bindKey("CTRL-C", () => this.actionEdit(`${this.name}-copy`));
this.bindKey("CTRL-P", () => this.actionEdit(`${this.name}-paste`));
(this.find("btgrid")).set("onbtclick", e => {
this.view.set('view', "icon");
return this.viewType.icon = true;
});
(this.find("btlist")).set("onbtclick", e => {
this.view.set('view', "list");
return this.viewType.list = true;
});
return this.view.set("path", this.currdir);
}
applySetting(k) {
// view setting
switch (k) {
case "showhidden": return this.view.set("showhidden", this.setting.showhidden);
case "nav": return this.toggleNav(this.setting.nav);
case "sidebar": return this.toggleSidebar(this.setting.sidebar);
}
}
mnFile() {
//console.log file
const arr = {
text: "__(File)",
child: [
{ text: "__(New file)", dataid: `${this.name}-mkf`, shortcut: 'C-F' },
{ text: "__(New folder)", dataid: `${this.name}-mkdir`, shortcut: 'C-D' },
{ text: "__(Upload)", dataid: `${this.name}-upload`, shortcut: 'C-U' },
{ text: "__(Download)", dataid: `${this.name}-download` },
{ text: "__(Share file)", dataid: `${this.name}-share`, shortcut: 'C-S' },
{ text: "__(Properties)", dataid: `${this.name}-info`, shortcut: 'C-I' }
], onchildselect: e => this.actionFile(e.data.item.get("data").dataid)
};
return arr;
}
mnEdit() {
return {
text: "__(Edit)",
child: [
{ text: "__(Rename)", dataid: `${this.name}-mv`, shortcut: 'C-R' },
{ text: "__(Delete)", dataid: `${this.name}-rm`, shortcut: 'C-M' },
{ text: "__(Cut)", dataid: `${this.name}-cut`, shortcut: 'C-X' },
{ text: "__(Copy)", dataid: `${this.name}-copy`, shortcut: 'C-C' },
{ text: "__(Paste)", dataid: `${this.name}-paste`, shortcut: 'C-P' }
], onchildselect: e => this.actionEdit(e.data.item.get("data").dataid)
};
}
menu() {
const menu = [
this.mnFile(),
this.mnEdit(),
{
text: "__(View)",
child: [
{ text: "__(Refresh)", dataid: `${this.name}-refresh` },
{ text: "__(Sidebar)", switch: true, checked: this.setting.sidebar, dataid: `${this.name}-side` },
{ text: "__(Navigation bar)", switch: true, checked: this.setting.nav, dataid: `${this.name}-nav` },
{ text: "__(Hidden files)", switch: true, checked: this.setting.showhidden, dataid: `${this.name}-hidden` },
{ text: "__(Type)", child: [
{ text: "__(Icon view)", radio: true, checked: this.viewType.icon, dataid: `${this.name}-icon`, type: 'icon' },
{ text: "__(List view)", radio:true, checked: this.viewType.list, dataid: `${this.name}-list`, type: 'list' },
{ text: "__(Tree view)", radio:true, checked: this.viewType.tree, dataid: `${this.name}-tree`, type: 'tree' }
], onchildselect: e => {
const {
type
} = e.data.item.get("data");
this.view.set('view', type);
return this.viewType[type] = true;
}
},
], onchildselect: e => this.actionView(e)
},
];
return menu;
}
toggleSidebar(b) {
if (b) { ($(this.favo)).show(); } else { ($(this.favo)).hide(); }
return this.trigger("resize");
}
toggleNav(b) {
if (b) { ($(this.navbar)).show(); } else { ($(this.navbar)).hide(); }
return this.trigger("resize");
}
actionView(e) {
const data = e.data.item.get("data");
switch (data.dataid) {
case `${this.name}-hidden`:
//@.view.set "showhidden", e.item.data.checked
return this.registry("showhidden", data.checked);
//@.setting.showhidden = e.item.data.checked
case `${this.name}-refresh`:
return this.chdir(null);
case `${this.name}-side`:
return this.registry("sidebar", data.checked);
//@setting.sidebar = e.item.data.checked
//@toggleSidebar e.item.data.checked
case `${this.name}-nav`:
return this.registry("nav", data.checked);
}
}
//@setting.nav = e.item.data.checked
//@toggleNav e.item.data.checked
actionEdit(e) {
const file = this.view.get("selectedFile");
switch (e) {
case `${this.name}-mv`:
if (!file) { return; }
return this.openDialog("PromptDialog", {
title: "__(Rename)",
label: "__(File name)",
value: file.filename
})
.then(d => {
if (d === file.filename) { return; }
return file.path.asFileHandle().move(`${this.currdir.path}/${d}`)
.catch(e => {
return this.error(__("Fail to rename: {0}", file.path), e);
});
});
case `${this.name}-rm`:
if (!file) { return; }
return this.openDialog("YesNoDialog", {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __("Do you really want to delete: {0}?", file.filename)
})
.then(d => {
if (!d) { return; }
return file.path.asFileHandle().remove()
.catch(e => {
return this.error(__("Fail to delete: {0}", file.path), e);
});
});
case `${this.name}-cut`:
if (!file) { return; }
this.clipboard = {
cut: true,
file: file.path.asFileHandle()
};
return this.notify(__("File {0} cut", file.filename));
case `${this.name}-copy`:
if (!file && (file.type !== "dir")) { return; }
this.clipboard = {
cut: false,
file: file.path.asFileHandle()
};
return this.notify(__("File {0} copied", file.filename));
case `${this.name}-paste`:
if (!this.clipboard) { return; }
if (this.clipboard.cut) {
return this.clipboard.file.move(`${this.currdir.path}/${this.clipboard.file.basename}`)
.then(r => {
return this.clipboard = undefined;
}).catch(e => {
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
});
} else {
return this.clipboard.file.read("binary")
.then(d => {
const blob = new Blob([d], { type: this.clipboard.file.info.mime });
const fp = `${this.currdir.path}/${this.clipboard.file.basename}`.asFileHandle();
fp.cache = blob;
return fp.write(this.clipboard.file.info.mime)
.then(r => {
return this.clipboard = undefined;
}).catch(e => {
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
});
}).catch(e => {
return this.error(__("Fail to read: {0}", this.clipboard.file.path), e);
});
}
default:
return this._api.handle.setting();
}
}
actionFile(e) {
const file = this.view.get("selectedFile");
switch (e) {
case `${this.name}-mkdir`:
return this.openDialog("PromptDialog", {
title: "__(New folder)",
label: "__(Folder name)"
})
.then(d => {
return this.currdir.mk(d)
.catch(e => {
return this.error(__("Fail to create: {0}", d), e);
});
});
case `${this.name}-mkf`:
return this.openDialog("PromptDialog", {
title: "__(New file)",
label: "__(File name)"
})
.then(d => {
const fp = `${this.currdir.path}/${d}`.asFileHandle();
return fp.write("text/plain")
.catch(e => {
return this.error(__("Fail to create: {0}", fp.path));
});
});
case `${this.name}-info`:
if (!file) { return; }
return this.openDialog("InfoDialog", file);
case `${this.name}-upload`:
return this.currdir.upload()
.catch(e => {
return this.error(__("Fail to upload: {0}", e.toString()), e);
});
case `${this.name}-share`:
if (!file || (file.type !== "file")) { return; }
return file.path.asFileHandle().publish()
.then(r => {
return this.notify(__("Shared url: {0}", r.result));
}).catch(e => {
return this.error(__("Fail to publish: {0}", file.path), e);
});
case `${this.name}-download`:
if (file.type !== "file") { return; }
return file.path.asFileHandle().download()
.catch(e => {
return this.error(__("Fail to download: {0}", file.path), e);
});
default:
return console.log(e);
}
}
}
this.OS.register("Files", Files);

View File

@ -1,6 +1,6 @@
coffee_files = dialog.coffee main.coffee
module_files = dialog.js main.js
jsfiles =
libfiles =
cssfiles = main.css

View File

@ -1,84 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class RepositoryDialog extends this.OS.GUI.subwindows.SelectionDialog
constructor: () ->
super()
main: () ->
super.main()
@list = @find "list"
$((@find "btnOk")).hide()
@list.set "buttons", [
{
text: "+",
onbtclick: () =>
@openDialog("PromptDialog", {
title: __("Add repository"),
label: __("Format : [name] url")
}).then (e) =>
m = e.match /\[([^\]]*)\]\s*(.+)/
if not m or m.length isnt 3
return @error __("Wrong format: it should be [name] url")
repo = {
url: m[2],
text: m[1]
}
@systemsetting.system.repositories.push repo
@list.push repo
},
{
text: "-",
onbtclick: () =>
el = @list.get "selectedItem"
return unless el
selidx = $(el).index()
return unless selidx >= 0
@systemsetting.system.repositories.splice selidx, selidx
@list.remove el
},
{
iconclass: "fa fa-pencil",
onbtclick: () => @editRepo()
}
]
editRepo: () ->
el = @list.get "selectedItem"
return unless el
selidx = $(el).index()
return unless selidx >= 0
data = el.get "data"
sel = @systemsetting.system.repositories[selidx]
@openDialog("PromptDialog", {
title: __("Edit repository"),
label: __("Format : [name] url"),
value: "[#{data.text}] #{data.url}"
}).then (e) =>
m = e.match /\[([^\]]*)\]\s*(.+)/
if not m or m.length isnt 3
return @error __("Wrong format: it should be [name] url")
data.text = m[1]
data.url = m[2]
@list.update()
@list.unselect()
onexit: (e) ->
@parent.refreshRepoList()
super.onexit e

Some files were not shown because too many files have changed in this diff Show More