From 759cd1fc6fb4da3c67ca45cc9782507667c542e3 Mon Sep 17 00:00:00 2001 From: Xuan Sang LE Date: Sun, 24 May 2020 13:17:59 +0200 Subject: [PATCH] switch to es6 from coffeescript --- Makefile | 87 ++- src/antos.coffee | 21 - src/antos.js | 27 + src/core/Announcerment.coffee | 88 --- src/core/Announcerment.js | 133 ++++ src/core/BaseApplication.coffee | 149 ---- src/core/BaseApplication.js | 197 +++++ src/core/BaseDialog.coffee | 483 ------------ src/core/BaseDialog.js | 565 ++++++++++++++ src/core/BaseEvent.coffee | 24 - src/core/BaseEvent.js | 35 + src/core/BaseModel.coffee | 129 ---- src/core/BaseModel.js | 160 ++++ src/core/BaseService.coffee | 62 -- src/core/BaseService.js | 77 ++ src/core/core.coffee | 577 -------------- src/core/core.js | 715 ++++++++++++++++++ src/core/db.coffee | 60 -- src/core/db.js | 73 ++ src/core/gui.coffee | 503 ------------ src/core/gui.js | 611 +++++++++++++++ src/core/handles/RemoteHandle.coffee | 105 --- src/core/handles/RemoteHandle.js | 133 ++++ src/core/handles/TestHandle.coffee | 23 - src/core/handles/TestHandle.js | 29 + src/core/settings.coffee | 100 --- src/core/settings.js | 118 +++ src/core/tags/AppDockTag.coffee | 64 -- src/core/tags/AppDockTag.js | 86 +++ src/core/tags/ButtonTag.coffee | 58 -- src/core/tags/ButtonTag.js | 78 ++ src/core/tags/CalendarTag.coffee | 126 --- src/core/tags/CalendarTag.js | 150 ++++ src/core/tags/ColorPickerTag.coffee | 116 --- src/core/tags/ColorPickerTag.js | 133 ++++ src/core/tags/FileViewTag.coffee | 218 ------ src/core/tags/FileViewTag.js | 264 +++++++ src/core/tags/FloatListTag.coffee | 90 --- src/core/tags/FloatListTag.js | 110 +++ src/core/tags/GridViewTag.coffee | 231 ------ src/core/tags/GridViewTag.js | 290 +++++++ src/core/tags/LabelTag.coffee | 61 -- src/core/tags/LabelTag.js | 78 ++ src/core/tags/ListViewTag.coffee | 350 --------- src/core/tags/ListViewTag.js | 418 ++++++++++ src/core/tags/MenuTag.coffee | 242 ------ src/core/tags/MenuTag.js | 309 ++++++++ src/core/tags/NSpinnerTag.coffee | 78 -- src/core/tags/NSpinnerTag.js | 95 +++ src/core/tags/OverlayTag.coffee | 44 -- src/core/tags/OverlayTag.js | 56 ++ src/core/tags/ResizerTag.coffee | 69 -- src/core/tags/ResizerTag.js | 89 +++ src/core/tags/SliderTag.coffee | 90 --- src/core/tags/SliderTag.js | 112 +++ src/core/tags/SwitchTag.coffee | 29 - src/core/tags/SwitchTag.js | 41 + src/core/tags/SystemPanelTag.coffee | 177 ----- src/core/tags/SystemPanelTag.js | 211 ++++++ src/core/tags/TabBarTag.coffee | 36 - src/core/tags/TabBarTag.js | 51 ++ src/core/tags/TabContainerTag.coffee | 61 -- src/core/tags/TabContainerTag.js | 77 ++ src/core/tags/TileLayoutTags.coffee | 103 --- src/core/tags/TileLayoutTags.js | 125 +++ src/core/tags/TreeViewTag.coffee | 285 ------- src/core/tags/TreeViewTag.js | 337 +++++++++ src/core/tags/WindowTag.coffee | 204 ----- src/core/tags/WindowTag.js | 245 ++++++ src/core/tags/tag.coffee | 130 ---- src/core/tags/tag.js | 163 ++++ src/core/vfs.coffee | 446 ----------- src/core/vfs.js | 567 ++++++++++++++ src/core/vfs/GoogleDriveHandle.coffee | 355 --------- src/core/vfs/GoogleDriveHandle.js | 401 ++++++++++ src/packages/CodePad/BaseExtension.js | 195 +++++ src/packages/CodePad/CommandPalette.js | 98 +++ src/packages/CodePad/Makefile | 9 +- .../CodePad/coffees/BaseExtension.coffee | 168 ---- .../CodePad/coffees/CommandPalette.coffee | 74 -- .../CodePad/coffees/extensions/AntOSDK.coffee | 176 ----- .../coffees/extensions/ExtensionMaker.coffee | 217 ------ src/packages/CodePad/coffees/main.coffee | 502 ------------ src/packages/CodePad/extensions/AntOSDK.js | 222 ++++++ .../CodePad/extensions/ExtensionMaker.js | 261 +++++++ .../{coffees => }/extensions/coffeescript.js | 0 .../extensions/templates/ext-extension.tpl | 0 .../extensions/templates/ext-main.tpl | 0 .../extensions/templates/sdk-README.tpl | 0 .../extensions/templates/sdk-main.tpl | 0 .../extensions/templates/sdk-package.tpl | 0 .../extensions/templates/sdk-project.tpl | 0 .../extensions/templates/sdk-scheme.tpl | 0 .../{coffees => }/extensions/terser.min.js | 0 src/packages/CodePad/main.js | 628 +++++++++++++++ src/packages/Files/Makefile | 4 +- src/packages/Files/main.coffee | 363 --------- src/packages/Files/main.js | 424 +++++++++++ src/packages/MarketPlace/Makefile | 4 +- src/packages/MarketPlace/dialog.coffee | 84 -- src/packages/MarketPlace/dialog.js | 100 +++ src/packages/MarketPlace/main.coffee | 357 --------- src/packages/MarketPlace/main.js | 433 +++++++++++ src/packages/Setting/AppearanceHandle.coffee | 104 --- src/packages/Setting/AppearanceHandle.js | 126 +++ src/packages/Setting/LocaleHandle.coffee | 38 - src/packages/Setting/LocaleHandle.js | 49 ++ src/packages/Setting/Makefile | 4 +- src/packages/Setting/StartupHandle.coffee | 73 -- src/packages/Setting/StartupHandle.js | 110 +++ src/packages/Setting/VFSHandle.coffee | 148 ---- src/packages/Setting/VFSHandle.js | 173 +++++ src/packages/Setting/main.coffee | 46 -- src/packages/Setting/main.js | 61 ++ src/packages/Syslog/Calendar.coffee | 41 - src/packages/Syslog/Calendar.js | 52 ++ src/packages/Syslog/Makefile | 4 +- src/packages/Syslog/PushNotification.coffee | 149 ---- src/packages/Syslog/PushNotification.js | 175 +++++ src/packages/Syslog/Syslog.coffee | 105 --- src/packages/Syslog/Syslog.js | 132 ++++ src/packages/pkg.mk | 18 +- 122 files changed, 10658 insertions(+), 8702 deletions(-) delete mode 100644 src/antos.coffee create mode 100644 src/antos.js delete mode 100644 src/core/Announcerment.coffee create mode 100644 src/core/Announcerment.js delete mode 100644 src/core/BaseApplication.coffee create mode 100644 src/core/BaseApplication.js delete mode 100644 src/core/BaseDialog.coffee create mode 100644 src/core/BaseDialog.js delete mode 100644 src/core/BaseEvent.coffee create mode 100644 src/core/BaseEvent.js delete mode 100644 src/core/BaseModel.coffee create mode 100644 src/core/BaseModel.js delete mode 100644 src/core/BaseService.coffee create mode 100644 src/core/BaseService.js delete mode 100644 src/core/core.coffee create mode 100644 src/core/core.js delete mode 100644 src/core/db.coffee create mode 100644 src/core/db.js delete mode 100644 src/core/gui.coffee create mode 100644 src/core/gui.js delete mode 100644 src/core/handles/RemoteHandle.coffee create mode 100644 src/core/handles/RemoteHandle.js delete mode 100644 src/core/handles/TestHandle.coffee create mode 100644 src/core/handles/TestHandle.js delete mode 100644 src/core/settings.coffee create mode 100644 src/core/settings.js delete mode 100644 src/core/tags/AppDockTag.coffee create mode 100644 src/core/tags/AppDockTag.js delete mode 100644 src/core/tags/ButtonTag.coffee create mode 100644 src/core/tags/ButtonTag.js delete mode 100644 src/core/tags/CalendarTag.coffee create mode 100644 src/core/tags/CalendarTag.js delete mode 100644 src/core/tags/ColorPickerTag.coffee create mode 100644 src/core/tags/ColorPickerTag.js delete mode 100644 src/core/tags/FileViewTag.coffee create mode 100644 src/core/tags/FileViewTag.js delete mode 100644 src/core/tags/FloatListTag.coffee create mode 100644 src/core/tags/FloatListTag.js delete mode 100644 src/core/tags/GridViewTag.coffee create mode 100644 src/core/tags/GridViewTag.js delete mode 100644 src/core/tags/LabelTag.coffee create mode 100644 src/core/tags/LabelTag.js delete mode 100644 src/core/tags/ListViewTag.coffee create mode 100644 src/core/tags/ListViewTag.js delete mode 100644 src/core/tags/MenuTag.coffee create mode 100644 src/core/tags/MenuTag.js delete mode 100644 src/core/tags/NSpinnerTag.coffee create mode 100644 src/core/tags/NSpinnerTag.js delete mode 100644 src/core/tags/OverlayTag.coffee create mode 100644 src/core/tags/OverlayTag.js delete mode 100644 src/core/tags/ResizerTag.coffee create mode 100644 src/core/tags/ResizerTag.js delete mode 100644 src/core/tags/SliderTag.coffee create mode 100644 src/core/tags/SliderTag.js delete mode 100644 src/core/tags/SwitchTag.coffee create mode 100644 src/core/tags/SwitchTag.js delete mode 100644 src/core/tags/SystemPanelTag.coffee create mode 100644 src/core/tags/SystemPanelTag.js delete mode 100644 src/core/tags/TabBarTag.coffee create mode 100644 src/core/tags/TabBarTag.js delete mode 100644 src/core/tags/TabContainerTag.coffee create mode 100644 src/core/tags/TabContainerTag.js delete mode 100644 src/core/tags/TileLayoutTags.coffee create mode 100644 src/core/tags/TileLayoutTags.js delete mode 100644 src/core/tags/TreeViewTag.coffee create mode 100644 src/core/tags/TreeViewTag.js delete mode 100644 src/core/tags/WindowTag.coffee create mode 100644 src/core/tags/WindowTag.js delete mode 100644 src/core/tags/tag.coffee create mode 100644 src/core/tags/tag.js delete mode 100644 src/core/vfs.coffee create mode 100644 src/core/vfs.js delete mode 100644 src/core/vfs/GoogleDriveHandle.coffee create mode 100644 src/core/vfs/GoogleDriveHandle.js create mode 100644 src/packages/CodePad/BaseExtension.js create mode 100644 src/packages/CodePad/CommandPalette.js delete mode 100644 src/packages/CodePad/coffees/BaseExtension.coffee delete mode 100644 src/packages/CodePad/coffees/CommandPalette.coffee delete mode 100644 src/packages/CodePad/coffees/extensions/AntOSDK.coffee delete mode 100644 src/packages/CodePad/coffees/extensions/ExtensionMaker.coffee delete mode 100644 src/packages/CodePad/coffees/main.coffee create mode 100644 src/packages/CodePad/extensions/AntOSDK.js create mode 100644 src/packages/CodePad/extensions/ExtensionMaker.js rename src/packages/CodePad/{coffees => }/extensions/coffeescript.js (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/ext-extension.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/ext-main.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/sdk-README.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/sdk-main.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/sdk-package.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/sdk-project.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/templates/sdk-scheme.tpl (100%) rename src/packages/CodePad/{coffees => }/extensions/terser.min.js (100%) create mode 100644 src/packages/CodePad/main.js delete mode 100644 src/packages/Files/main.coffee create mode 100644 src/packages/Files/main.js delete mode 100644 src/packages/MarketPlace/dialog.coffee create mode 100644 src/packages/MarketPlace/dialog.js delete mode 100644 src/packages/MarketPlace/main.coffee create mode 100644 src/packages/MarketPlace/main.js delete mode 100644 src/packages/Setting/AppearanceHandle.coffee create mode 100644 src/packages/Setting/AppearanceHandle.js delete mode 100644 src/packages/Setting/LocaleHandle.coffee create mode 100644 src/packages/Setting/LocaleHandle.js delete mode 100644 src/packages/Setting/StartupHandle.coffee create mode 100644 src/packages/Setting/StartupHandle.js delete mode 100644 src/packages/Setting/VFSHandle.coffee create mode 100644 src/packages/Setting/VFSHandle.js delete mode 100644 src/packages/Setting/main.coffee create mode 100644 src/packages/Setting/main.js delete mode 100644 src/packages/Syslog/Calendar.coffee create mode 100644 src/packages/Syslog/Calendar.js delete mode 100644 src/packages/Syslog/PushNotification.coffee create mode 100644 src/packages/Syslog/PushNotification.js delete mode 100644 src/packages/Syslog/Syslog.coffee create mode 100644 src/packages/Syslog/Syslog.js diff --git a/Makefile b/Makefile index 61c8354..f37ff26 100644 --- a/Makefile +++ b/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: diff --git a/src/antos.coffee b/src/antos.coffee deleted file mode 100644 index 185dfb8..0000000 --- a/src/antos.coffee +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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() \ No newline at end of file diff --git a/src/antos.js b/src/antos.js new file mode 100644 index 0000000..c071135 --- /dev/null +++ b/src/antos.js @@ -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 + +// 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(); +}; \ No newline at end of file diff --git a/src/core/Announcerment.coffee b/src/core/Announcerment.coffee deleted file mode 100644 index 6c708c1..0000000 --- a/src/core/Announcerment.coffee +++ /dev/null @@ -1,88 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 \ No newline at end of file diff --git a/src/core/Announcerment.js b/src/core/Announcerment.js new file mode 100644 index 0000000..d597f88 --- /dev/null +++ b/src/core/Announcerment.js @@ -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 + +// 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; + } + }; \ No newline at end of file diff --git a/src/core/BaseApplication.coffee b/src/core/BaseApplication.coffee deleted file mode 100644 index 6219cfa..0000000 --- a/src/core/BaseApplication.coffee +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 \ No newline at end of file diff --git a/src/core/BaseApplication.js b/src/core/BaseApplication.js new file mode 100644 index 0000000..663cea6 --- /dev/null +++ b/src/core/BaseApplication.js @@ -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 + +// 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; \ No newline at end of file diff --git a/src/core/BaseDialog.coffee b/src/core/BaseDialog.coffee deleted file mode 100644 index 358e875..0000000 --- a/src/core/BaseDialog.coffee +++ /dev/null @@ -1,483 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 = """ - - - -
- -
- - -
- -
- - - - -
- - - -""" -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 = """ - - - -
- -
- -""" - -Ant.OS.GUI.schemes.login = """ -
-

Welcome to AntOS, please login

- - - -
-
-""" \ No newline at end of file diff --git a/src/core/gui.js b/src/core/gui.js new file mode 100644 index 0000000..d873632 --- /dev/null +++ b/src/core/gui.js @@ -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 + +// 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 = $('', { 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 = `\ + +
+ + +
+ + +\ +`; + +Ant.OS.GUI.schemes.login = `\ +
+

Welcome to AntOS, please login

+ + + +
+
\ +`; \ No newline at end of file diff --git a/src/core/handles/RemoteHandle.coffee b/src/core/handles/RemoteHandle.coffee deleted file mode 100644 index d68c140..0000000 --- a/src/core/handles/RemoteHandle.coffee +++ /dev/null @@ -1,105 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 \ No newline at end of file diff --git a/src/core/handles/RemoteHandle.js b/src/core/handles/RemoteHandle.js new file mode 100644 index 0000000..06b59f8 --- /dev/null +++ b/src/core/handles/RemoteHandle.js @@ -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 + +// 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); + } +}; \ No newline at end of file diff --git a/src/core/handles/TestHandle.coffee b/src/core/handles/TestHandle.coffee deleted file mode 100644 index debec49..0000000 --- a/src/core/handles/TestHandle.coffee +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 - \ No newline at end of file diff --git a/src/core/handles/TestHandle.js b/src/core/handles/TestHandle.js new file mode 100644 index 0000000..523c15e --- /dev/null +++ b/src/core/handles/TestHandle.js @@ -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 + +// 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)); + } +}; + \ No newline at end of file diff --git a/src/core/settings.coffee b/src/core/settings.coffee deleted file mode 100644 index 4c7d4b1..0000000 --- a/src/core/settings.coffee +++ /dev/null @@ -1,100 +0,0 @@ - # Copyright 2017-2018 Xuan Sang LE - -# 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 \ No newline at end of file diff --git a/src/core/settings.js b/src/core/settings.js new file mode 100644 index 0000000..bdec423 --- /dev/null +++ b/src/core/settings.js @@ -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 + +// 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; + }); +}; \ No newline at end of file diff --git a/src/core/tags/AppDockTag.coffee b/src/core/tags/AppDockTag.coffee deleted file mode 100644 index 492a970..0000000 --- a/src/core/tags/AppDockTag.coffee +++ /dev/null @@ -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 = $("") - 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 \ No newline at end of file diff --git a/src/core/tags/AppDockTag.js b/src/core/tags/AppDockTag.js new file mode 100644 index 0000000..1dc9f4e --- /dev/null +++ b/src/core/tags/AppDockTag.js @@ -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 = $(""); + 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); \ No newline at end of file diff --git a/src/core/tags/ButtonTag.coffee b/src/core/tags/ButtonTag.coffee deleted file mode 100644 index 30649bd..0000000 --- a/src/core/tags/ButtonTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/ButtonTag.js b/src/core/tags/ButtonTag.js new file mode 100644 index 0000000..1c88b82 --- /dev/null +++ b/src/core/tags/ButtonTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/CalendarTag.coffee b/src/core/tags/CalendarTag.coffee deleted file mode 100644 index 5b7837a..0000000 --- a/src/core/tags/CalendarTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/CalendarTag.js b/src/core/tags/CalendarTag.js new file mode 100644 index 0000000..450767e --- /dev/null +++ b/src/core/tags/CalendarTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/ColorPickerTag.coffee b/src/core/tags/ColorPickerTag.coffee deleted file mode 100644 index c67eebc..0000000 --- a/src/core/tags/ColorPickerTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/ColorPickerTag.js b/src/core/tags/ColorPickerTag.js new file mode 100644 index 0000000..3bef7e9 --- /dev/null +++ b/src/core/tags/ColorPickerTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/FileViewTag.coffee b/src/core/tags/FileViewTag.coffee deleted file mode 100644 index 1b34a92..0000000 --- a/src/core/tags/FileViewTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/FileViewTag.js b/src/core/tags/FileViewTag.js new file mode 100644 index 0000000..d6a871a --- /dev/null +++ b/src/core/tags/FileViewTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/FloatListTag.coffee b/src/core/tags/FloatListTag.coffee deleted file mode 100644 index e42b88d..0000000 --- a/src/core/tags/FloatListTag.coffee +++ /dev/null @@ -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 diff --git a/src/core/tags/FloatListTag.js b/src/core/tags/FloatListTag.js new file mode 100644 index 0000000..66bf197 --- /dev/null +++ b/src/core/tags/FloatListTag.js @@ -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); diff --git a/src/core/tags/GridViewTag.coffee b/src/core/tags/GridViewTag.coffee deleted file mode 100644 index d1db043..0000000 --- a/src/core/tags/GridViewTag.coffee +++ /dev/null @@ -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 = $("") - .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 \ No newline at end of file diff --git a/src/core/tags/GridViewTag.js b/src/core/tags/GridViewTag.js new file mode 100644 index 0000000..e371124 --- /dev/null +++ b/src/core/tags/GridViewTag.js @@ -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 = $("") + .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); \ No newline at end of file diff --git a/src/core/tags/LabelTag.coffee b/src/core/tags/LabelTag.coffee deleted file mode 100644 index e0c189e..0000000 --- a/src/core/tags/LabelTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/LabelTag.js b/src/core/tags/LabelTag.js new file mode 100644 index 0000000..341382c --- /dev/null +++ b/src/core/tags/LabelTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/ListViewTag.coffee b/src/core/tags/ListViewTag.coffee deleted file mode 100644 index c7fbf91..0000000 --- a/src/core/tags/ListViewTag.coffee +++ /dev/null @@ -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 = $("").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 \ No newline at end of file diff --git a/src/core/tags/ListViewTag.js b/src/core/tags/ListViewTag.js new file mode 100644 index 0000000..50a56ba --- /dev/null +++ b/src/core/tags/ListViewTag.js @@ -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 = $("").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); \ No newline at end of file diff --git a/src/core/tags/MenuTag.coffee b/src/core/tags/MenuTag.coffee deleted file mode 100644 index 24e4cf1..0000000 --- a/src/core/tags/MenuTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/MenuTag.js b/src/core/tags/MenuTag.js new file mode 100644 index 0000000..d997c0b --- /dev/null +++ b/src/core/tags/MenuTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/NSpinnerTag.coffee b/src/core/tags/NSpinnerTag.coffee deleted file mode 100644 index 3f4969c..0000000 --- a/src/core/tags/NSpinnerTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/NSpinnerTag.js b/src/core/tags/NSpinnerTag.js new file mode 100644 index 0000000..76c9ac5 --- /dev/null +++ b/src/core/tags/NSpinnerTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/OverlayTag.coffee b/src/core/tags/OverlayTag.coffee deleted file mode 100644 index 721e4ee..0000000 --- a/src/core/tags/OverlayTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/OverlayTag.js b/src/core/tags/OverlayTag.js new file mode 100644 index 0000000..804aa8c --- /dev/null +++ b/src/core/tags/OverlayTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/ResizerTag.coffee b/src/core/tags/ResizerTag.coffee deleted file mode 100644 index f700b80..0000000 --- a/src/core/tags/ResizerTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/ResizerTag.js b/src/core/tags/ResizerTag.js new file mode 100644 index 0000000..bf2f854 --- /dev/null +++ b/src/core/tags/ResizerTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/SliderTag.coffee b/src/core/tags/SliderTag.coffee deleted file mode 100644 index 58c71e2..0000000 --- a/src/core/tags/SliderTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/SliderTag.js b/src/core/tags/SliderTag.js new file mode 100644 index 0000000..169fea6 --- /dev/null +++ b/src/core/tags/SliderTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/SwitchTag.coffee b/src/core/tags/SwitchTag.coffee deleted file mode 100644 index 0be71c6..0000000 --- a/src/core/tags/SwitchTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/SwitchTag.js b/src/core/tags/SwitchTag.js new file mode 100644 index 0000000..0d91c6d --- /dev/null +++ b/src/core/tags/SwitchTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/SystemPanelTag.coffee b/src/core/tags/SystemPanelTag.coffee deleted file mode 100644 index 29b99ed..0000000 --- a/src/core/tags/SystemPanelTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/SystemPanelTag.js b/src/core/tags/SystemPanelTag.js new file mode 100644 index 0000000..d8aaa6f --- /dev/null +++ b/src/core/tags/SystemPanelTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/TabBarTag.coffee b/src/core/tags/TabBarTag.coffee deleted file mode 100644 index 663890f..0000000 --- a/src/core/tags/TabBarTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/TabBarTag.js b/src/core/tags/TabBarTag.js new file mode 100644 index 0000000..6336d56 --- /dev/null +++ b/src/core/tags/TabBarTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/TabContainerTag.coffee b/src/core/tags/TabContainerTag.coffee deleted file mode 100644 index 9edbafa..0000000 --- a/src/core/tags/TabContainerTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/TabContainerTag.js b/src/core/tags/TabContainerTag.js new file mode 100644 index 0000000..38b9f8a --- /dev/null +++ b/src/core/tags/TabContainerTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/TileLayoutTags.coffee b/src/core/tags/TileLayoutTags.coffee deleted file mode 100644 index e80b87d..0000000 --- a/src/core/tags/TileLayoutTags.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/TileLayoutTags.js b/src/core/tags/TileLayoutTags.js new file mode 100644 index 0000000..b0c0e1b --- /dev/null +++ b/src/core/tags/TileLayoutTags.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/TreeViewTag.coffee b/src/core/tags/TreeViewTag.coffee deleted file mode 100644 index 4548b5d..0000000 --- a/src/core/tags/TreeViewTag.coffee +++ /dev/null @@ -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 = $("").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 \ No newline at end of file diff --git a/src/core/tags/TreeViewTag.js b/src/core/tags/TreeViewTag.js new file mode 100644 index 0000000..79cbf6e --- /dev/null +++ b/src/core/tags/TreeViewTag.js @@ -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 = $("").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); \ No newline at end of file diff --git a/src/core/tags/WindowTag.coffee b/src/core/tags/WindowTag.coffee deleted file mode 100644 index 5d68c3e..0000000 --- a/src/core/tags/WindowTag.coffee +++ /dev/null @@ -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 \ No newline at end of file diff --git a/src/core/tags/WindowTag.js b/src/core/tags/WindowTag.js new file mode 100644 index 0000000..b09bd31 --- /dev/null +++ b/src/core/tags/WindowTag.js @@ -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); \ No newline at end of file diff --git a/src/core/tags/tag.coffee b/src/core/tags/tag.coffee deleted file mode 100644 index a290d44..0000000 --- a/src/core/tags/tag.coffee +++ /dev/null @@ -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 - diff --git a/src/core/tags/tag.js b/src/core/tags/tag.js new file mode 100644 index 0000000..792c8c8 --- /dev/null +++ b/src/core/tags/tag.js @@ -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; + diff --git a/src/core/vfs.coffee b/src/core/vfs.coffee deleted file mode 100644 index 65a4466..0000000 --- a/src/core/vfs.coffee +++ /dev/null @@ -1,446 +0,0 @@ -# Copyright 2017-2018 Xuan Sang LE - -# 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 \ No newline at end of file diff --git a/src/core/vfs.js b/src/core/vfs.js new file mode 100644 index 0000000..45beaed --- /dev/null +++ b/src/core/vfs.js @@ -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 + +// 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); \ No newline at end of file diff --git a/src/core/vfs/GoogleDriveHandle.coffee b/src/core/vfs/GoogleDriveHandle.coffee deleted file mode 100644 index f1ca006..0000000 --- a/src/core/vfs/GoogleDriveHandle.coffee +++ /dev/null @@ -1,355 +0,0 @@ - -# Copyright 2017-2018 Xuan Sang LE - -# 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 = ($ '').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 = $ '