mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-04-08 09:36:45 +02:00
switch to es6 from coffeescript
This commit is contained in:
parent
f11509120e
commit
759cd1fc6f
87
Makefile
87
Makefile
@ -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:
|
||||
|
@ -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
27
src/antos.js
Normal 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();
|
||||
};
|
@ -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
133
src/core/Announcerment.js
Normal 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;
|
||||
}
|
||||
};
|
@ -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
197
src/core/BaseApplication.js
Normal 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;
|
@ -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
565
src/core/BaseDialog.js
Normal 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);
|
@ -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
35
src/core/BaseEvent.js
Normal 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;
|
@ -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
160
src/core/BaseModel.js
Normal 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;
|
@ -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
77
src/core/BaseService.js
Normal 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;
|
@ -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
715
src/core/core.js
Normal 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;
|
||||
}
|
@ -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
73
src/core/db.js
Normal 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;
|
@ -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
611
src/core/gui.js
Normal 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>\
|
||||
`;
|
@ -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
|
133
src/core/handles/RemoteHandle.js
Normal file
133
src/core/handles/RemoteHandle.js
Normal 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);
|
||||
}
|
||||
};
|
@ -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
|
||||
|
29
src/core/handles/TestHandle.js
Normal file
29
src/core/handles/TestHandle.js
Normal 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));
|
||||
}
|
||||
};
|
||||
|
@ -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
118
src/core/settings.js
Normal 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;
|
||||
});
|
||||
};
|
@ -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
|
86
src/core/tags/AppDockTag.js
Normal file
86
src/core/tags/AppDockTag.js
Normal 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);
|
@ -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
|
78
src/core/tags/ButtonTag.js
Normal file
78
src/core/tags/ButtonTag.js
Normal 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);
|
@ -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
|
150
src/core/tags/CalendarTag.js
Normal file
150
src/core/tags/CalendarTag.js
Normal 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);
|
@ -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
|
133
src/core/tags/ColorPickerTag.js
Normal file
133
src/core/tags/ColorPickerTag.js
Normal 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);
|
@ -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
|
264
src/core/tags/FileViewTag.js
Normal file
264
src/core/tags/FileViewTag.js
Normal 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);
|
@ -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
|
110
src/core/tags/FloatListTag.js
Normal file
110
src/core/tags/FloatListTag.js
Normal 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);
|
@ -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
|
290
src/core/tags/GridViewTag.js
Normal file
290
src/core/tags/GridViewTag.js
Normal 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);
|
@ -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
78
src/core/tags/LabelTag.js
Normal 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);
|
@ -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
|
418
src/core/tags/ListViewTag.js
Normal file
418
src/core/tags/ListViewTag.js
Normal 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);
|
@ -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
309
src/core/tags/MenuTag.js
Normal 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);
|
@ -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
|
95
src/core/tags/NSpinnerTag.js
Normal file
95
src/core/tags/NSpinnerTag.js
Normal 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);
|
@ -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
|
56
src/core/tags/OverlayTag.js
Normal file
56
src/core/tags/OverlayTag.js
Normal 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);
|
@ -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
|
89
src/core/tags/ResizerTag.js
Normal file
89
src/core/tags/ResizerTag.js
Normal 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);
|
@ -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
112
src/core/tags/SliderTag.js
Normal 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);
|
@ -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
|
41
src/core/tags/SwitchTag.js
Normal file
41
src/core/tags/SwitchTag.js
Normal 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);
|
@ -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
|
211
src/core/tags/SystemPanelTag.js
Normal file
211
src/core/tags/SystemPanelTag.js
Normal 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);
|
@ -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
|
51
src/core/tags/TabBarTag.js
Normal file
51
src/core/tags/TabBarTag.js
Normal 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);
|
@ -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
|
77
src/core/tags/TabContainerTag.js
Normal file
77
src/core/tags/TabContainerTag.js
Normal 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);
|
@ -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
|
125
src/core/tags/TileLayoutTags.js
Normal file
125
src/core/tags/TileLayoutTags.js
Normal 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);
|
@ -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
|
337
src/core/tags/TreeViewTag.js
Normal file
337
src/core/tags/TreeViewTag.js
Normal 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);
|
@ -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
245
src/core/tags/WindowTag.js
Normal 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);
|
@ -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
163
src/core/tags/tag.js
Normal 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;
|
||||
|
@ -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
567
src/core/vfs.js
Normal 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);
|
@ -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)
|
||||
|
401
src/core/vfs/GoogleDriveHandle.js
Normal file
401
src/core/vfs/GoogleDriveHandle.js
Normal 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)
|
||||
|
195
src/packages/CodePad/BaseExtension.js
Normal file
195
src/packages/CodePad/BaseExtension.js
Normal 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 = {};
|
98
src/packages/CodePad/CommandPalette.js
Normal file
98
src/packages/CodePad/CommandPalette.js
Normal 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>\
|
||||
`;
|
@ -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
|
||||
|
@ -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 = {}
|
@ -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>
|
||||
"""
|
@ -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
|
@ -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
|
@ -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
|
222
src/packages/CodePad/extensions/AntOSDK.js
Normal file
222
src/packages/CodePad/extensions/AntOSDK.js
Normal 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);
|
261
src/packages/CodePad/extensions/ExtensionMaker.js
Normal file
261
src/packages/CodePad/extensions/ExtensionMaker.js
Normal 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);
|
628
src/packages/CodePad/main.js
Normal file
628
src/packages/CodePad/main.js
Normal 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);
|
@ -1,6 +1,6 @@
|
||||
coffee_files = main.coffee
|
||||
module_files = main.js
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
|
@ -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
424
src/packages/Files/main.js
Normal 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);
|
@ -1,6 +1,6 @@
|
||||
coffee_files = dialog.coffee main.coffee
|
||||
module_files = dialog.js main.js
|
||||
|
||||
jsfiles =
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user