mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-04-11 10:36:45 +02:00
Compare commits
204 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
59fb302e18 | ||
|
4bbc6c770a | ||
|
bfeef223a8 | ||
|
a554a678d8 | ||
29702459b4 | |||
73f925ac8c | |||
147353327b | |||
|
9fa3ab92bf | ||
|
45b019725d | ||
|
b99668f4f2 | ||
|
04da2a9d39 | ||
|
8c20cfec5e | ||
|
24788a421c | ||
|
e479fe43c9 | ||
|
4a46104710 | ||
|
5605d6c35a | ||
|
6ac2429dba | ||
|
297d471c1d | ||
|
d95f9382f3 | ||
|
d00fd3bd82 | ||
|
cbeab0d0f2 | ||
|
a86da11532 | ||
|
984cdae438 | ||
|
3e201bfbba | ||
|
3d09a20512 | ||
|
87dad4eded | ||
|
e6bf4d5352 | ||
|
a5257bf108 | ||
|
eac84a3ab8 | ||
|
6523fafe91 | ||
|
acd36a7a29 | ||
|
add5ef77c8 | ||
|
4d59b104b9 | ||
|
0ac886f644 | ||
|
f52e9a38d2 | ||
|
a74cf39152 | ||
|
082a85644b | ||
|
a00acf4bbf | ||
|
bff2c94fa9 | ||
|
0f2ab549e8 | ||
|
cd6a6c373a | ||
|
56ca945b0c | ||
|
82f35f791e | ||
|
b30a2bb44c | ||
|
2c64dfe00d | ||
|
1d1218acbd | ||
|
0d8daa36e8 | ||
|
d72a4c954b | ||
|
5f92e41021 | ||
|
d3540d7575 | ||
|
60137fb4fb | ||
|
7196f9ff57 | ||
|
1063ae9c4f | ||
|
649c7d942a | ||
|
b490ae9d42 | ||
|
ae91401965 | ||
|
d482d2cad4 | ||
|
b2ec7cc8db | ||
|
db006345a9 | ||
|
bf793ec204 | ||
|
cb9ccb576f | ||
|
ea160c2ccb | ||
|
edb427d6c3 | ||
|
be72a62156 | ||
|
77b89c44f7 | ||
|
242df06a28 | ||
|
c52e4b649e | ||
|
30c63748b0 | ||
|
84cfc87ce0 | ||
|
f21a958ea0 | ||
|
81d13c1601 | ||
|
9f07a86901 | ||
|
7b3072576c | ||
|
be73a3c7ae | ||
|
038823a7cb | ||
|
70301d8817 | ||
|
9d1c66fe50 | ||
|
6948b0e339 | ||
|
b35812bb43 | ||
|
11fb8c97af | ||
|
d9314fc829 | ||
|
722e672947 | ||
|
93b58c7aa2 | ||
|
9e7c7e6d78 | ||
|
25e9efff46 | ||
|
9baee31c01 | ||
|
de7f027940 | ||
|
870b1ec105 | ||
|
03cee726ed | ||
|
303fc9ba20 | ||
|
243dfa7a89 | ||
|
bc77329294 | ||
|
e1c1895070 | ||
|
0b80a29d00 | ||
|
b026b96bf2 | ||
|
3c25d8b52e | ||
|
b5863702cb | ||
|
2ae390ad4b | ||
|
2e887851c5 | ||
|
1a6ece4e7c | ||
|
d5d6a16a85 | ||
|
cd294f58a6 | ||
|
13969511a5 | ||
|
dd56643e01 | ||
|
576051aca0 | ||
|
fec05d115f | ||
|
5313f0b224 | ||
|
25d1c5fd47 | ||
|
2620d2ccb6 | ||
|
d1fdb47ca2 | ||
|
31d928c977 | ||
|
50e74cef5f | ||
|
cbb8948dbf | ||
|
c377a80d0d | ||
|
f466cf1200 | ||
|
53867c9d03 | ||
|
4cd271cc51 | ||
|
9b5da170e7 | ||
|
b381294064 | ||
|
a0ee2d1090 | ||
|
b6c90e56cd | ||
|
5f29d5b382 | ||
|
b806f20bdb | ||
|
19a8610f1c | ||
|
14b72ef425 | ||
|
c96919ea12 | ||
|
1cf718117f | ||
|
e76a3f4ef1 | ||
|
bb8991c1a1 | ||
|
558ef1b3ef | ||
|
ef95e55d50 | ||
|
4a2f4483d6 | ||
|
a03feba430 | ||
|
5014cc730f | ||
|
b3a4dd4bd1 | ||
|
1e230c9f9d | ||
|
2a4a68b902 | ||
|
969222b687 | ||
|
76b16f54a3 | ||
|
255f9dc285 | ||
|
b26a62464d | ||
|
d08b33a0b9 | ||
|
09e12437a8 | ||
|
e4ccfedbd8 | ||
|
da5bbdab60 | ||
|
699c697344 | ||
|
2fd4bb5c96 | ||
|
6cbb463f6f | ||
|
f7081ae48a | ||
|
5d17c429f7 | ||
|
583a0c0349 | ||
|
c0603cd47d | ||
|
66af4d63f6 | ||
|
8b029c2e0a | ||
|
86bcaf9dea | ||
|
084c377bcf | ||
|
61de95788c | ||
|
52af4b6940 | ||
|
e63cae1550 | ||
|
b2ce9ec978 | ||
|
f97a45be15 | ||
|
fdcc5ce784 | ||
|
81d78aa8e5 | ||
|
5734ce8ca9 | ||
|
7f0ae264fe | ||
|
d109d6af39 | ||
|
c26e27d7ec | ||
|
c2e72a067e | ||
|
8b23ebeeff | ||
|
deae7bbc57 | ||
|
9c5272757a | ||
|
2cdd8f9a43 | ||
|
079af3b0ce | ||
|
6af1260918 | ||
|
a1d26a6069 | ||
|
dc2a7c36d8 | ||
|
15f8de8910 | ||
|
f912e57e3b | ||
|
a6d725ea71 | ||
|
0624f421f7 | ||
|
64094c29d5 | ||
|
d208aea1b9 | ||
|
149566ec5d | ||
|
3c67b1c134 | ||
|
3a24df169c | ||
|
e345a61269 | ||
|
b3d38ccb05 | ||
|
66e96cc0db | ||
|
86a94a817a | ||
|
27ac7c06fc | ||
|
99e0d02581 | ||
|
52709d5da4 | ||
|
b399958f69 | ||
|
aab242c1f7 | ||
|
043214c001 | ||
|
9c06d88713 | ||
|
5b125c288d | ||
|
8af672748f | ||
|
facf51fe8c | ||
|
90b6783568 | ||
|
c23cb1bfa8 | ||
|
e6e727246d | ||
|
89add530bc | ||
|
27ef66eca8 |
20
.drone.yml
20
.drone.yml
@ -1,20 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: exec
|
||||
name: default
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
clone:
|
||||
disable: true
|
||||
steps:
|
||||
- name: download
|
||||
commands:
|
||||
- cd /opt/www/htdocs/os && wget https://github.com/lxsang/antos/raw/next-1.2.0/release/antos-1.2.0.tar.gz
|
||||
- name: build
|
||||
commands:
|
||||
- cd /opt/www/htdocs/os && tar xvzf antos-1.2.0.tar.gz
|
||||
- rm /opt/www/htdocs/os/antos-1.2.0.tar.gz
|
||||
trigger:
|
||||
branch:
|
||||
- next-1.2.0
|
22
.gitea/workflows/ci.yml
Normal file
22
.gitea/workflows/ci.yml
Normal file
@ -0,0 +1,22 @@
|
||||
name: AntOS front-end
|
||||
run-name: build Ant-OS front end
|
||||
on: [push]
|
||||
|
||||
jobs:
|
||||
Build-AntOS-Frontend:
|
||||
runs-on: ci-tools
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v4
|
||||
- name: Building AntOS
|
||||
run: |
|
||||
ls ${{ gitea.workspace }}
|
||||
mkdir build
|
||||
BUILDDIR=$(realpath build) make install_dev main
|
||||
- name: Save artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: build-AntOS
|
||||
path: build/
|
||||
retention-days: 5
|
||||
- run: echo "Build status ${{ job.status }}."
|
14
.travis.yml
14
.travis.yml
@ -1,14 +0,0 @@
|
||||
language: javascript
|
||||
node_js:
|
||||
- 10.21.0
|
||||
install:
|
||||
- npm install terser
|
||||
- npm install uglifycss
|
||||
- npm install typescript@3.9.3
|
||||
- npm install jest @types/jest ts-jest
|
||||
- npm install @types/jquery
|
||||
script:
|
||||
- tsc -v
|
||||
- make
|
||||
- make test
|
||||
- make release
|
74
Makefile
74
Makefile
@ -4,11 +4,15 @@ BUILDDIR?=/opt/www/htdocs/os
|
||||
DOCDIR?=/opt/www/htdocs/doc/antos
|
||||
BLUE=\033[1;34m
|
||||
NC=\033[0m
|
||||
TSC=./node_modules/typescript/bin/tsc
|
||||
UGLIFYJS=./node_modules/terser/bin/terser
|
||||
UGLIFYCSS=./node_modules/uglifycss/uglifycss
|
||||
|
||||
VERSION=1.2.0
|
||||
VERSION?=2.0.3-b
|
||||
BUILDID?=master
|
||||
|
||||
GSED=sed
|
||||
UNAME_S := $(shell uname -s)
|
||||
UNAME_S := $(shell uname -s)cd
|
||||
ifeq ($(UNAME_S),Darwin)
|
||||
GSED=gsed
|
||||
endif
|
||||
@ -22,7 +26,6 @@ tags = dist/core/tags/tag.js \
|
||||
dist/core/tags/ListViewTag.js \
|
||||
dist/core/tags/SwitchTag.js \
|
||||
dist/core/tags/NSpinnerTag.js \
|
||||
dist/core/tags/MenuTag.js \
|
||||
dist/core/tags/GridViewTag.js \
|
||||
dist/core/tags/TabBarTag.js \
|
||||
dist/core/tags/TabContainerTag.js \
|
||||
@ -34,14 +37,18 @@ tags = dist/core/tags/tag.js \
|
||||
dist/core/tags/FileViewTag.js \
|
||||
dist/core/tags/OverlayTag.js \
|
||||
dist/core/tags/AppDockTag.js \
|
||||
dist/core/tags/SystemPanelTag.js
|
||||
dist/core/tags/SystemPanelTag.js \
|
||||
dist/core/tags/DesktopTag.js \
|
||||
dist/core/tags/StackMenuTag.js \
|
||||
dist/core/tags/StackPanelTag.js \
|
||||
dist/core/tags/InputTag.js \
|
||||
dist/core/tags/NotificationTag.js
|
||||
|
||||
javascripts= dist/core/core.js \
|
||||
dist/core/settings.js \
|
||||
dist/core/handles/RemoteHandle.js \
|
||||
dist/core/Announcerment.js \
|
||||
dist/core/vfs.js \
|
||||
dist/core/db.js \
|
||||
dist/core/BaseModel.js \
|
||||
dist/core/BaseApplication.js \
|
||||
dist/core/BaseService.js \
|
||||
@ -54,10 +61,11 @@ javascripts= dist/core/core.js \
|
||||
antfx = $(tags) \
|
||||
dist/core/Announcerment.js
|
||||
|
||||
packages = Syslog Files MarketPlace Setting NotePad
|
||||
packages = SystemServices SystemReport Files MarketPlace Setting NotePad
|
||||
|
||||
main: initd build_javascripts build_themes libs build_packages languages
|
||||
- cp src/index.html $(BUILDDIR)/
|
||||
- cp README.md $(BUILDDIR)/
|
||||
|
||||
initd:
|
||||
- mkdir -p $(BUILDDIR)
|
||||
@ -68,7 +76,7 @@ lite: build_javascripts build_themes build_packages
|
||||
|
||||
ts:
|
||||
-rm -rf dist
|
||||
tsc -p tsconfig.json
|
||||
$(TSC) -p tsconfig.json
|
||||
cat `find dist/core/ -name "*.d.ts"` > d.ts/antos.d.ts
|
||||
rm `find dist/ -name "*.d.ts"`
|
||||
cat d.ts/core.d.ts d.ts/jquery.d.ts d.ts/antos.d.ts > /tmp/corelib.d.ts
|
||||
@ -85,7 +93,7 @@ standalone_tags: ts
|
||||
rm "$${f}";\
|
||||
done
|
||||
echo "var Ant=this;" >> dist/afx.js
|
||||
terser dist/afx.js --compress --mangle --output $(BUILDDIR)/afx.js
|
||||
$(UGLIFYJS) dist/afx.js --compress --mangle --output $(BUILDDIR)/afx.js
|
||||
# standalone theme
|
||||
|
||||
@for f in src/themes/system/afx-*.css; do \
|
||||
@ -101,7 +109,7 @@ standalone_tags: ts
|
||||
(cat "$${f}"; echo) >> $(BUILDDIR)/afx.css; \
|
||||
fi;\
|
||||
done
|
||||
# uglifycss --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css
|
||||
# $(UGLIFYCSS) --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css
|
||||
rm -r dist/core
|
||||
|
||||
build_javascripts: ts
|
||||
@ -113,6 +121,7 @@ build_javascripts: ts
|
||||
(cat "$${f}"; echo) >> dist/antos.js;\
|
||||
rm "$${f}";\
|
||||
done
|
||||
echo 'OS.VERSION.version_string = "$(VERSION)-$(BUILDID)";' >> dist/antos.js
|
||||
cp dist/antos.js $(BUILDDIR)/scripts/
|
||||
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
|
||||
rm -r dist/core
|
||||
@ -137,8 +146,10 @@ build_themes: antos_light antos_dark
|
||||
-rm -rf $(BUILDDIR)/resources/themes/system/*
|
||||
-mkdir -p $(BUILDDIR)/resources/themes/system
|
||||
cp -r src/themes/system/fonts $(BUILDDIR)/resources/themes/system
|
||||
cp -r src/themes/system/icons $(BUILDDIR)/resources/themes/system
|
||||
cp -r src/themes/system/wp $(BUILDDIR)/resources/themes/system
|
||||
for f in src/themes/system/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/system.css;done
|
||||
for f in src/themes/default/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/antos.css;done
|
||||
|
||||
antos_light:
|
||||
@echo "$(BLUE)Building themes name: antos-light$(NC)"
|
||||
@ -173,27 +184,34 @@ package:
|
||||
pkgar:
|
||||
read -r -p "Enter package name: " PKG;\
|
||||
echo $$PKG | make package &&\
|
||||
test -f $(BUILDDIR)/packages/$$PKG/main.js && terser $(BUILDDIR)/packages/$$PKG/main.js --compress --mangle --output $(BUILDDIR)/packages/$$PKG/main.js;\
|
||||
test -f $(BUILDDIR)/packages/$$PKG/main.css && uglifycss --output $(BUILDDIR)/packages/$$PKG/main.css $(BUILDDIR)/packages/$$PKG/main.css;\
|
||||
test -f $(BUILDDIR)/packages/$$PKG/main.js && $(UGLIFYJS) $(BUILDDIR)/packages/$$PKG/main.js --compress --mangle --output $(BUILDDIR)/packages/$$PKG/main.js;\
|
||||
test -f $(BUILDDIR)/packages/$$PKG/main.css && $(UGLIFYCSS) --output $(BUILDDIR)/packages/$$PKG/main.css $(BUILDDIR)/packages/$$PKG/main.css;\
|
||||
cd $(BUILDDIR)/packages/$$PKG && zip -r "$$PKG.zip" ./ ; \
|
||||
cd ../../ && (test -d repo/$$PKG || mkdir repo/$$PKG) && mv packages/$$PKG/"$$PKG.zip" repo/$$PKG && touch repo/$$PKG/$$PKG.md && rm -r packages/$$PKG
|
||||
|
||||
uglify:
|
||||
# sudo npm install terser -g
|
||||
#
|
||||
terser $(BUILDDIR)/scripts/antos.js --compress --mangle --output $(BUILDDIR)/scripts/antos.js
|
||||
# sudo npm install $(UGLIFYJS) -g
|
||||
#
|
||||
mv $(BUILDDIR)/scripts/antos.js $(BUILDDIR)/scripts/antos_src.js
|
||||
cp $(BUILDDIR)/scripts/antos_src.js ./
|
||||
$(UGLIFYJS) antos_src.js --compress --mangle --output antos.js --source-map "url='antos.js.map'"
|
||||
mv antos.js* $(BUILDDIR)/scripts/
|
||||
rm antos_src.js
|
||||
|
||||
# uglify tags
|
||||
# npm install uglifycss -g
|
||||
# npm install $(UGLIFYCSS) -g
|
||||
# uglify the css
|
||||
uglifycss --output $(BUILDDIR)/resources/themes/antos_light/antos_light.css $(BUILDDIR)/resources/themes/antos_light/antos_light.css
|
||||
uglifycss --output $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css
|
||||
uglifycss --output $(BUILDDIR)/resources/themes/system/system.css $(BUILDDIR)/resources/themes/system/system.css
|
||||
$(UGLIFYCSS) --output $(BUILDDIR)/resources/themes/antos_light/antos_light.css $(BUILDDIR)/resources/themes/antos_light/antos_light.css
|
||||
$(UGLIFYCSS) --output $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css
|
||||
$(UGLIFYCSS) --output $(BUILDDIR)/resources/themes/system/system.css $(BUILDDIR)/resources/themes/system/system.css
|
||||
#uglify each packages
|
||||
|
||||
for d in $(packages); do\
|
||||
echo "Uglifying $$d";\
|
||||
test -f $(BUILDDIR)/packages/$$d/main.js && terser $(BUILDDIR)/packages/$$d/main.js --compress --mangle --output $(BUILDDIR)/packages/$$d/main.js;\
|
||||
test -f $(BUILDDIR)/packages/$$d/main.css && uglifycss --output $(BUILDDIR)/packages/$$d/main.css $(BUILDDIR)/packages/$$d/main.css;\
|
||||
test -f $(BUILDDIR)/packages/$$d/main.js && \
|
||||
$(UGLIFYJS) $(BUILDDIR)/packages/$$d/main.js \
|
||||
--compress --mangle --output $(BUILDDIR)/packages/$$d/main.js;\
|
||||
test -f $(BUILDDIR)/packages/$$d/main.css && $(UGLIFYCSS) --output $(BUILDDIR)/packages/$$d/main.css $(BUILDDIR)/packages/$$d/main.css;\
|
||||
done
|
||||
|
||||
ar:
|
||||
@ -206,13 +224,25 @@ ar:
|
||||
echo -n $(VERSION) > release/latest
|
||||
|
||||
release: main uglify
|
||||
|
||||
.PHONY: doc release clean
|
||||
doc:
|
||||
./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS API" --out $(DOCDIR)
|
||||
# npm install typedoc --save-dev
|
||||
# npm install typedoc-plugin-merge-modules --save-dev
|
||||
# ./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS $(VERSION)-$(BUILDID) API" --out $(DOCDIR)
|
||||
./node_modules/.bin/typedoc --hideGenerator --plugin typedoc-plugin-merge-modules --entryPointStrategy expand --name "AntOS $(VERSION)-$(BUILDID) API" --out $(DOCDIR)
|
||||
|
||||
test: build_javascripts
|
||||
jest
|
||||
|
||||
install_dev:
|
||||
npm init -y
|
||||
npm install terser
|
||||
npm install uglifycss
|
||||
npm install typescript@5.0
|
||||
npm install @types/jquery
|
||||
npm i typedoc
|
||||
npm i typedoc-plugin-merge-modules
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/resources
|
||||
rm -rf $(BUILDDIR)/scripts
|
||||
|
98
README.md
98
README.md
@ -1,18 +1,81 @@
|
||||
# antOS v1.2.0
|
||||
# AntOS frontend
|
||||
|
||||
[](https://travis-ci.org/lxsang/antos)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
|
||||
Frontend implementation of AntOS remote desktop environment: [https://github.com/antos-rde](https://github.com/antos-rde).
|
||||
|
||||
AntOS is a front-end system and API that implement the traditional desktop UI environment on the web browser. The front-end can connect to a remote server and acts as a virtual desktop environment (VDE). The original purpose of AntOS is to offer: (1) visual tools to access and control resource on remote server
|
||||
and embedded linux environment; (2) front-end API for SaaS web-based applications. With its application API and the provided SDK, AntOS facilitates the
|
||||
development and deployment of user specific applications inside de VDE environment.
|
||||
The frontend is developed in typescript/javascript + CSS, it provides the
|
||||
Core API, web-based window manager, application APIs, a GUI toolkit, and file system abstractions. It also includes an application store and an SDK for in-browser application development, deployment, and packaging. The frontend is designed to work across devices, including desktop computers and mobile devices.
|
||||
|
||||

|
||||
## Build
|
||||
|
||||
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
|
||||
`Nodejs` and `npm` is necessary to build the project:
|
||||
|
||||
```sh
|
||||
# install dependencies packages
|
||||
make install_dev
|
||||
# build release
|
||||
BUILDDIR=/path/to/output make release
|
||||
# see more in Makefile for more build target
|
||||
```
|
||||
|
||||
## demo
|
||||
A demo of the VDE is available at [https://app.iohub.dev/antos/](https://app.iohub.dev/antos/) using username: demo and password: demo.
|
||||
|
||||
If one want to run AntOS VDE locally in their system, a docker image is available at:
|
||||
[https://github.com/lxsang/antosaio](https://github.com/lxsang/antosaio)
|
||||
|
||||
## AntOS applications (Available on the MarketPlace)
|
||||
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
|
||||
|
||||
## Frontend Documentation
|
||||
|
||||
- API: [https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/](https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/)
|
||||
|
||||
## Change logs
|
||||
* Improvement GUI API
|
||||
### v.2.0.0
|
||||
- Work In Progress: The UI is redesigned to support mobile device
|
||||
### V1.2.1
|
||||
- 9b5da17 - App name now can differ from pkgname
|
||||
- b381294 - fix: fix icon display problem when application is installed, remove all related settings when an application is uinstalled
|
||||
- b6c90e5 - update image path in readme
|
||||
- 14b72ef - Fix dragndrop bug on Fileview (grid mod)
|
||||
- c96919e - fix: correct jenkins build demo stage
|
||||
- 1cf7181 - Fix fileview status incorrect, add more build stage to Jenkinsfile
|
||||
- 255f9dc - update readme file, and include it to delivery
|
||||
- d08b33a - fix ar generation problem: with new version format
|
||||
- da5bbda - Allow to set version number and build ID to the current Antos build
|
||||
- 699c697 - update login form style
|
||||
- 2fd4bb5 - Bug fix + improvement
|
||||
- 6cbb463 - Fileview: list view display modified date instead of mime
|
||||
- f7081ae - Include current Antos version to login screen
|
||||
- 5d17c42 - Makefile read current version from gcode
|
||||
- 583a0c0 - update version number in code
|
||||
- c0603cd - Minor style fixes on menus and dropdown list
|
||||
- 8b029c2 - fix minor visual bug on grid view, list view and tree view
|
||||
- 86bcaf9 - visual bug fix on label: inline block by default
|
||||
- 61de957 - Visual improvements
|
||||
- 52af4b6 - fix visualize bug after style changes
|
||||
- e63cae1 - style improvement on Label, FileView, GridView, system menu and app Panel
|
||||
- f97a45b - Add more control to mem file + bug fix on File
|
||||
- fdcc5ce - allow to create memory-based temporal VFS file system
|
||||
- 81d78aa - robusify VFS mem file handling
|
||||
- d109d6a - fix: file name display inconsitent between views
|
||||
- c26e27d - Fix multiple dialogs focus bug
|
||||
- 8b23ebe - Loading animation is now based on the current context (global or application context)
|
||||
- 2cdd8f9 - support dnd and grid sort
|
||||
- 079af3b - fix type conversion error in gridview tag
|
||||
- a6d725e - User a custom tag to manage the desktop instead of GUI
|
||||
- 0624f42 - API improvement & bug fix: - subscribed global event doesnt unsubcribed when process is killed - process killall API doesnt work as expected - improve core API
|
||||
- 3a24df1 - update announcement system
|
||||
- e345a61 - update bootstrap icons to v.1.7.1
|
||||
- b3d38cc - allow multiple files upload in single request
|
||||
- 66e96cc - update VFS API
|
||||
- 86a94a8 - update GUI API
|
||||
- 27ac7c0 - Minor bug fix on desktop handling
|
||||
- 99e0d02 - enable setting blur overlay window
|
||||
- 52709d5 - improve Window GUI API
|
||||
- 9c06d88 - AntOS load automatically custom VFS handles if available
|
||||
- c23cb1b - Improve core API: - improve OS exit API - improve VFS API
|
||||
### V.1.2.0 Improvement GUI API
|
||||
- [x] File dialog should remember last opened folder
|
||||
- [x] Add dynamic key-value dialog that work on any object
|
||||
- [x] Window list panel should show window title in tooltip when mouse hovering on application icon
|
||||
@ -41,23 +104,9 @@ Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
|
||||
- Introduce new JSON based syntax for SDK task/target definition
|
||||
* From this version, docker image of All-in-one AntOS system is available at: [https://hub.docker.com/r/xsangle/antosaio](https://hub.docker.com/r/xsangle/antosaio)
|
||||
|
||||
## Demo
|
||||
A demo of the VDE is available at [https://app.iohub.dev/antos/](https://app.iohub.dev/antos/) using username: demo and password: demo.
|
||||
|
||||
If one want to run AntOS VDE locally in their system, a docker image is available at:
|
||||
[https://github.com/lxsang/antosaio](https://github.com/lxsang/antosaio)
|
||||
|
||||
## AntOS applications (Available on the MarketPlace)
|
||||
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
|
||||
|
||||
## Documentation
|
||||
|
||||
- Documentation: [https://doc.iohub.dev/antos](https://doc.iohub.dev/antos)
|
||||
- API: (https://doc.iohub.dev/antos/api/)[https://doc.iohub.dev/antos/api/]
|
||||
|
||||
## Licence
|
||||
|
||||
Copyright 2017-2021 Xuan Sang LE <mrsang AT iohub DOT dev>
|
||||
Copyright 2017-2022 Xuan Sang LE <mrsang AT iohub DOT dev>
|
||||
|
||||
AnTOS is is licensed under the GNU General Public License v3.0, see the LICENCE file for more information
|
||||
|
||||
@ -74,3 +123,4 @@ AnTOS is is licensed under the GNU General Public License v3.0, see the LICENCE
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
**For comercial use, please contact author**
|
||||
|
@ -1 +0,0 @@
|
||||
theme: jekyll-theme-slate
|
17223
d.ts/antos.d.ts
vendored
17223
d.ts/antos.d.ts
vendored
File diff suppressed because it is too large
Load Diff
3
d.ts/jquery.d.ts
vendored
3
d.ts/jquery.d.ts
vendored
@ -1,7 +1,4 @@
|
||||
export as namespace Sizzle;
|
||||
|
||||
declare const Sizzle: SizzleStatic;
|
||||
export = Sizzle;
|
||||
|
||||
interface SizzleStatic {
|
||||
selectors: Sizzle.Selectors;
|
||||
|
BIN
release/antos-1.2.1.tar.gz
Normal file
BIN
release/antos-1.2.1.tar.gz
Normal file
Binary file not shown.
BIN
release/antos-2.0.0.tar.gz
Normal file
BIN
release/antos-2.0.0.tar.gz
Normal file
Binary file not shown.
@ -1 +1 @@
|
||||
1.2.0
|
||||
2.0.0
|
@ -21,5 +21,15 @@ Ant.onload = function () {
|
||||
"webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",
|
||||
() => (Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen)
|
||||
);
|
||||
const agent = navigator.userAgent||navigator.vendor||(window as any).opera;
|
||||
Ant.OS.mobile = false;
|
||||
if(
|
||||
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(agent)
|
||||
||
|
||||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(agent.substr(0,4))
|
||||
)
|
||||
{
|
||||
Ant.OS.mobile = true;
|
||||
}
|
||||
return Ant.OS.boot();
|
||||
};
|
@ -18,6 +18,63 @@
|
||||
|
||||
namespace OS {
|
||||
export namespace API {
|
||||
/**
|
||||
* Data type exchanged via
|
||||
* the global Announcement interface
|
||||
*
|
||||
* @export
|
||||
* @interface AnnouncementDataType
|
||||
*/
|
||||
export interface AnnouncementDataType<T> {
|
||||
|
||||
/**
|
||||
* message string
|
||||
*
|
||||
* @type {string| FormattedString}
|
||||
* @memberof AppAnnouncementDataType
|
||||
*/
|
||||
message: string | FormattedString;
|
||||
|
||||
/**
|
||||
* Process ID
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof AppAnnouncementDataType
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* App name
|
||||
*
|
||||
* @type {string | FormattedString}
|
||||
* @memberof AppAnnouncementDataType
|
||||
*/
|
||||
name: string | FormattedString;
|
||||
|
||||
/**
|
||||
* Icon file
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AppAnnouncementDataType
|
||||
*/
|
||||
icon?: string;
|
||||
|
||||
|
||||
/**
|
||||
* App icon class
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AppAnnouncementDataType
|
||||
*/
|
||||
iconclass?: string;
|
||||
/**
|
||||
* User specific data
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof AppAnnouncementDataType
|
||||
*/
|
||||
u_data?: T;
|
||||
}
|
||||
/**
|
||||
* Observable entry type definition
|
||||
*
|
||||
@ -50,21 +107,19 @@ namespace OS {
|
||||
* @interface AnnouncerListenerType
|
||||
*/
|
||||
export interface AnnouncerListenerType {
|
||||
[index: number]: {
|
||||
/**
|
||||
* The event name
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
e: string;
|
||||
/**
|
||||
* The event name
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
e: string;
|
||||
|
||||
/**
|
||||
* The event callback
|
||||
*
|
||||
*/
|
||||
f: (d: any) => void;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* The event callback
|
||||
*
|
||||
*/
|
||||
f: (d: any) => void;
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is the based class used in AntOS event
|
||||
@ -77,7 +132,7 @@ namespace OS {
|
||||
export class Announcer {
|
||||
/**
|
||||
* The observable object that stores event name
|
||||
* and its corresponding callback in [[ObservableEntryType]]
|
||||
* and its corresponding callback in {@link ObservableEntryType}
|
||||
*
|
||||
* @type {GenericObject<ObservableEntryType>}
|
||||
* @memberof Announcer
|
||||
@ -235,33 +290,42 @@ namespace OS {
|
||||
* and callbacks
|
||||
*/
|
||||
export var observable: API.Announcer = new API.Announcer();
|
||||
/**
|
||||
* This variable is used to allocate the `id` of all messages
|
||||
* passing between publishers and subscribers in the
|
||||
* system announcement
|
||||
*/
|
||||
export var quota: 0;
|
||||
/**
|
||||
* Placeholder of all global events listeners
|
||||
*/
|
||||
export var listeners: API.AnnouncerListenerType = {};
|
||||
export var listeners: Map<BaseModel | 0, API.AnnouncerListenerType[]> = new Map();
|
||||
|
||||
/**
|
||||
* Subscribe to a global event
|
||||
*
|
||||
* @export
|
||||
* @param {string} e event name
|
||||
* @param {(d: any) => void} f event callback
|
||||
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
|
||||
* @param {GUI.BaseModel} a the process (Application/service) related to the callback
|
||||
*/
|
||||
export function on(e: string, f: (d: any) => void, a: BaseModel): void {
|
||||
if (!announcer.listeners[a.pid]) {
|
||||
announcer.listeners[a.pid] = [];
|
||||
export function on(e: string, f: (d: API.AnnouncementDataType<any>) => void, a?: BaseModel): void {
|
||||
let key: BaseModel | 0 = 0;
|
||||
if(a)
|
||||
key = a;
|
||||
if (!announcer.listeners.has(key)) {
|
||||
announcer.listeners.set(key, []);
|
||||
}
|
||||
announcer.listeners[a.pid].push({ e, f });
|
||||
const collection = announcer.listeners.get(key);
|
||||
collection.push({ e, f });
|
||||
announcer.observable.on(e, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a global event once
|
||||
*
|
||||
* @export
|
||||
* @param {string} e event name
|
||||
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
|
||||
*/
|
||||
export function one(e: string, f: (d: API.AnnouncementDataType<any>) => void): void {
|
||||
announcer.observable.one(e, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a global event
|
||||
*
|
||||
@ -282,7 +346,7 @@ namespace OS {
|
||||
* @param {Error} e error to be reported
|
||||
*/
|
||||
export function osfail(m: string | FormattedString, e: Error): void {
|
||||
announcer.ostrigger("fail", { m, e });
|
||||
announcer.ostrigger("fail", m, e );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -294,7 +358,7 @@ namespace OS {
|
||||
* @param {Error} e error to be reported
|
||||
*/
|
||||
export function oserror(m: string | FormattedString, e: Error): void {
|
||||
announcer.ostrigger("error", { m, e });
|
||||
announcer.ostrigger("error", m, e );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -304,18 +368,24 @@ namespace OS {
|
||||
* @param {(string | FormattedString)} m notification message
|
||||
*/
|
||||
export function osinfo(m: string | FormattedString): void {
|
||||
announcer.ostrigger("info", { m, e: null });
|
||||
announcer.ostrigger("info", m);
|
||||
}
|
||||
|
||||
/**
|
||||
* trigger a specific global event
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @param {string} e event name
|
||||
* @param {*} d event data
|
||||
* @param {(string| FormattedString)} m event message
|
||||
* @param {*} [d] user data
|
||||
*/
|
||||
export function ostrigger(e: string, d: any): void {
|
||||
announcer.trigger(e, { id: 0, data: d, name: "OS" });
|
||||
export function ostrigger(e: string, m: string| FormattedString, d?: any): void {
|
||||
const aob: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
|
||||
aob.id = 0;
|
||||
aob.message = m;
|
||||
aob.u_data = d;
|
||||
aob.name = "OS";
|
||||
announcer.trigger(e, aob);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -327,27 +397,14 @@ namespace OS {
|
||||
* @returns {void}
|
||||
*/
|
||||
export function unregister(app: BaseModel): void {
|
||||
if (
|
||||
!announcer.listeners[app.pid] ||
|
||||
!(announcer.listeners[app.pid].length > 0)
|
||||
) {
|
||||
if (!announcer.listeners.has(app)) {
|
||||
return;
|
||||
}
|
||||
for (let i of announcer.listeners[app.pid]) {
|
||||
const collection = announcer.listeners.get(app);
|
||||
for (let i of collection) {
|
||||
announcer.observable.off(i.e, i.f);
|
||||
}
|
||||
delete announcer.listeners[app.pid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate message id
|
||||
*
|
||||
* @export
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getMID(): number {
|
||||
quota += 1;
|
||||
return quota;
|
||||
announcer.listeners.delete(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,9 +35,21 @@ namespace OS {
|
||||
*/
|
||||
export abstract class BaseApplication extends BaseModel {
|
||||
/**
|
||||
* Placeholder of all settings specific to the application.
|
||||
* The settings stored in this object will be saved to system
|
||||
* setting when logout and can be reused in the next login session
|
||||
* Watcher of all settings specific to the application.
|
||||
* The settings stored in this object will be saved to application folder
|
||||
* in JSON format as .settings.json and will be loaded automatically
|
||||
* when application is initialized.
|
||||
*
|
||||
* This object is globally accessible to all processes of the same application
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
static setting_wdg: GenericObject<any>;
|
||||
|
||||
|
||||
/**
|
||||
* Reference to per application setting i.e. setting_wdg
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof BaseApplication
|
||||
@ -61,15 +73,6 @@ namespace OS {
|
||||
*/
|
||||
sysdock: GUI.tag.AppDockTag;
|
||||
|
||||
/**
|
||||
* Reference to the system application menu located
|
||||
* on the system panel
|
||||
*
|
||||
* @type {GUI.tag.MenuTag}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
appmenu: GUI.tag.MenuTag;
|
||||
|
||||
/**
|
||||
*Creates an instance of BaseApplication.
|
||||
* @param {string} name application name
|
||||
@ -78,16 +81,8 @@ namespace OS {
|
||||
*/
|
||||
constructor(name: string, args: AppArgumentsType[]) {
|
||||
super(name, args);
|
||||
if (!setting.applications[this.name]) {
|
||||
setting.applications[this.name] = {};
|
||||
}
|
||||
this.setting = setting.applications[this.name];
|
||||
this.setting = (this.constructor as any).setting_wdg;
|
||||
this.keycomb = {};
|
||||
this.subscribe("appregistry", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.data.m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,53 +96,90 @@ namespace OS {
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
init(): void {
|
||||
this.off("*");
|
||||
this.on("exit", () => this.quit(false));
|
||||
// first register some base event to the app
|
||||
this.on("focus", () => {
|
||||
this.sysdock.selectedApp = this;
|
||||
this.appmenu.pid = this.pid;
|
||||
this.appmenu.items = this.baseMenu() || [];
|
||||
this.appmenu.onmenuselect = (
|
||||
d: GUI.tag.MenuEventData
|
||||
): void => {
|
||||
return this.trigger("menuselect", d);
|
||||
};
|
||||
if (this.dialog) {
|
||||
return this.dialog.show();
|
||||
init(): Promise<any> {
|
||||
return new Promise(async (ok, nok) =>{
|
||||
try {
|
||||
this.off("*");
|
||||
this.on("exit", () => this.quit(false));
|
||||
// first register some base event to the app
|
||||
this.on("focus", () => {
|
||||
//if(this.sysdock.selectedApp != this)
|
||||
this.sysdock.selectedApp = this;
|
||||
(this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
|
||||
OS.PM.pidactive = this.pid;
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
return this.dialog.show();
|
||||
}
|
||||
});
|
||||
this.on("hide", () => {
|
||||
this.sysdock.selectedApp = null;
|
||||
if (this.dialog) {
|
||||
return this.dialog.hide();
|
||||
}
|
||||
});
|
||||
this.on("menuselect", (d) => {
|
||||
switch (d.data.item.data.dataid) {
|
||||
case `${this.name}-about`:
|
||||
return this.openDialog("AboutDialog");
|
||||
case `${this.name}-exit`:
|
||||
return this.trigger("exit", undefined);
|
||||
}
|
||||
});
|
||||
this.on("apptitlechange", () => this.sysdock.update(this));
|
||||
this.subscribe("APP-REGISTRY", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.message as string);
|
||||
}
|
||||
});
|
||||
|
||||
this.updateLocale(this.systemsetting.system.locale);
|
||||
await this.loadScheme();
|
||||
this.applyAllSetting();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
nok(__e(e));
|
||||
}
|
||||
});
|
||||
this.on("hide", () => {
|
||||
this.sysdock.selectedApp = null;
|
||||
this.appmenu.items = [];
|
||||
this.appmenu.pid = -1;
|
||||
if (this.dialog) {
|
||||
return this.dialog.hide();
|
||||
}
|
||||
});
|
||||
this.on("menuselect", (d) => {
|
||||
switch (d.data.item.data.dataid) {
|
||||
case `${this.name}-about`:
|
||||
return this.openDialog("AboutDialog");
|
||||
case `${this.name}-exit`:
|
||||
return this.trigger("exit", undefined);
|
||||
}
|
||||
});
|
||||
this.on("apptitlechange", () => this.sysdock.update(this));
|
||||
this.updateLocale(this.systemsetting.system.locale);
|
||||
return this.loadScheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* API function to register responsive UI event to the current window tag
|
||||
*
|
||||
* @protected
|
||||
* @param {GUI.TagResponsiveValidator} responsive validator
|
||||
* @param {GUI.TagResponsiveCallback} responsive callback
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected morphon(validator: GUI.TagResponsiveValidator, callback: GUI.TagResponsiveCallback)
|
||||
{
|
||||
const win = this.scheme as GUI.tag.WindowTag;
|
||||
win.morphon(validator, callback);
|
||||
}
|
||||
/**
|
||||
* API function to unregister responsive UI event from current window tag
|
||||
*
|
||||
* @protected
|
||||
* @param {GUI.TagResponsiveValidator} responsive validator
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected morphoff(validator: GUI.TagResponsiveValidator)
|
||||
{
|
||||
const win = this.scheme as GUI.tag.WindowTag;
|
||||
win.morphoff(validator);
|
||||
}
|
||||
/**
|
||||
* Render the application UI by first loading its scheme
|
||||
* and then mount this scheme to the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @returns {Promise<any>}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected loadScheme(): void {
|
||||
protected loadScheme(): Promise<any> {
|
||||
//now load the scheme
|
||||
const path = `${this.meta().path}/scheme.html`;
|
||||
return this.render(path);
|
||||
@ -155,9 +187,8 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* API function to perform an heavy task.
|
||||
* This function will trigger the global `loading`
|
||||
* event at the beginning of the task, and the `loaded`
|
||||
* event after finishing the task
|
||||
* This function will create a Task that is tracked by any
|
||||
* task manager implementation
|
||||
*
|
||||
* @protected
|
||||
* @param {Promise<any>} promise the promise on a task to be performed
|
||||
@ -165,15 +196,11 @@ namespace OS {
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected load(promise: Promise<any>): Promise<void> {
|
||||
const q = this._api.mid();
|
||||
return new Promise(async (resolve, reject) => {
|
||||
this._api.loading(q, this.name);
|
||||
return this._api.Task(async (resolve, reject) => {
|
||||
try {
|
||||
await promise;
|
||||
this._api.loaded(q, this.name, "OK");
|
||||
return resolve();
|
||||
return resolve(undefined);
|
||||
} catch (e) {
|
||||
this._api.loaded(q, this.name, "FAIL");
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
@ -195,7 +222,7 @@ namespace OS {
|
||||
f: (e: JQuery.KeyboardEventBase) => void
|
||||
): void {
|
||||
const arr = k.toUpperCase().split("-");
|
||||
const c = arr.pop();
|
||||
let c = arr.pop();
|
||||
let fnk = "";
|
||||
if (arr.includes("META")) {
|
||||
fnk += "META";
|
||||
@ -209,7 +236,10 @@ namespace OS {
|
||||
if (arr.includes("SHIFT")) {
|
||||
fnk += "SHIFT";
|
||||
}
|
||||
|
||||
if (fnk == "" && arr.length == 0 && c == "ESC") {
|
||||
fnk = "ESC";
|
||||
c = String.fromCharCode(27).toUpperCase();
|
||||
}
|
||||
if ( fnk == "") {
|
||||
return;
|
||||
}
|
||||
@ -225,12 +255,11 @@ namespace OS {
|
||||
* Update the application local from the system
|
||||
* locale or application specific locale configuration
|
||||
*
|
||||
* @private
|
||||
* @param {string} name locale name e.g. `en_GB`
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected updateLocale(name: string): void {
|
||||
updateLocale(name: string): void {
|
||||
const meta = this.meta();
|
||||
if (!meta || !meta.locales) {
|
||||
return;
|
||||
@ -289,21 +318,6 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value to the application setting
|
||||
* registry
|
||||
*
|
||||
* @protected
|
||||
* @param {string} k setting name
|
||||
* @param {*} v setting value
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected registry(k: string, v: any): void {
|
||||
this.setting[k] = v;
|
||||
return this.publish("appregistry", k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the appliation
|
||||
*
|
||||
@ -321,10 +335,11 @@ namespace OS {
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
blur(): void {
|
||||
if (this.appmenu && this.pid === this.appmenu.pid) {
|
||||
this.appmenu.items = [];
|
||||
this.trigger("blur", undefined);
|
||||
if(this.dialog)
|
||||
{
|
||||
this.dialog.blur();
|
||||
}
|
||||
return this.trigger("blur", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -357,7 +372,17 @@ namespace OS {
|
||||
title(): string | FormattedString {
|
||||
return (this.scheme as GUI.tag.WindowTag).apptitle;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Getter to access the application window instance
|
||||
*
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
get window(): GUI.tag.WindowTag
|
||||
{
|
||||
return this.scheme as GUI.tag.WindowTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when the application exit.
|
||||
* If the input exit event is prevented, the application
|
||||
@ -371,9 +396,6 @@ namespace OS {
|
||||
protected onexit(evt: BaseEvent): void {
|
||||
this.cleanup(evt);
|
||||
if (!evt.prevent) {
|
||||
if (this.pid === this.appmenu.pid) {
|
||||
this.appmenu.items = [];
|
||||
}
|
||||
$(this.scheme).remove();
|
||||
}
|
||||
}
|
||||
@ -392,7 +414,7 @@ namespace OS {
|
||||
* Base menu definition. This function
|
||||
* returns the based menu definition of all applications.
|
||||
* Other application specific menu entries
|
||||
* should be defined in [[menu]] function
|
||||
* should be defined in {@link menu} function
|
||||
*
|
||||
* @protected
|
||||
* @returns {GUI.BasicItemType[]}
|
||||
@ -436,7 +458,36 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* The cleanup function that is called by [[onexit]] function.
|
||||
* Show local toast notification
|
||||
*
|
||||
* @param {any} data to send
|
||||
* @param {GUI.ToastOptions} notification options see {@link GUI.ToastOptions}
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
toast(data: any, opts?: GUI.ToastOptions): void {
|
||||
let options: GUI.ToastOptions = {
|
||||
location: GUI.ANCHOR.SOUTH_EST,
|
||||
timeout: 3,
|
||||
tag: "afx-label"
|
||||
};
|
||||
if(opts)
|
||||
{
|
||||
for(const k in opts)
|
||||
{
|
||||
options[k] = opts[k];
|
||||
}
|
||||
}
|
||||
let d = data;
|
||||
if(typeof data == "string" || data instanceof FormattedString)
|
||||
{
|
||||
d = {text: data};
|
||||
}
|
||||
this._gui.toast(d,options, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* The cleanup function that is called by {@link onexit} function.
|
||||
* Application need to override this function to perform some
|
||||
* specific task before exiting or to prevent the application
|
||||
* to be exited
|
||||
|
@ -17,6 +17,7 @@
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
declare var showdown: any;
|
||||
/**
|
||||
* the SubWindow class is the abstract prototype of all
|
||||
* modal windows or dialogs definition in AntOS
|
||||
@ -44,6 +45,7 @@ namespace OS {
|
||||
*/
|
||||
parent: BaseModel | typeof GUI;
|
||||
|
||||
|
||||
/**
|
||||
*Creates an instance of SubWindow.
|
||||
* @param {string} name SubWindow (class) name
|
||||
@ -56,22 +58,15 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the sub-window
|
||||
* Purge the model from the system
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof SubWindow
|
||||
* @protected
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
quit(): void {
|
||||
const evt = new BaseEvent("exit", false);
|
||||
this.onexit(evt);
|
||||
if (!evt.prevent) {
|
||||
delete this._observable;
|
||||
if (this.scheme) {
|
||||
$(this.scheme).remove();
|
||||
}
|
||||
if (this.dialog) {
|
||||
return this.dialog.quit();
|
||||
}
|
||||
protected destroy(): void
|
||||
{
|
||||
if (this.scheme) {
|
||||
$(this.scheme).remove();
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,10 +77,26 @@ namespace OS {
|
||||
*
|
||||
* Need to be implemented by subclasses
|
||||
*
|
||||
* @abstract
|
||||
* @memberof SubWindow
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseDialog
|
||||
*/
|
||||
abstract init(): void;
|
||||
init(): void {
|
||||
// show the app if it is not active
|
||||
this.on("focus",() => {
|
||||
if((this.pid == -1) || (PM.pidactive == this.pid))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const app = PM.appByPid(this.pid);
|
||||
if(app)
|
||||
{
|
||||
app.show();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entry point after rendering of the sub-window
|
||||
@ -115,8 +126,8 @@ namespace OS {
|
||||
* @memberof SubWindow
|
||||
*/
|
||||
show(): void {
|
||||
this.trigger("focus");
|
||||
$(this.scheme).css("z-index", GUI.zindex + 2);
|
||||
this.trigger("focus", undefined);
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
this.dialog.show();
|
||||
}
|
||||
@ -129,8 +140,25 @@ namespace OS {
|
||||
* @memberof SubWindow
|
||||
*/
|
||||
hide(): void {
|
||||
return this.trigger("hide");
|
||||
this.trigger("hide", undefined);
|
||||
if (this.dialog) {
|
||||
this.dialog.hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* blur the sub-window
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof SubWindow
|
||||
*/
|
||||
blur(): void {
|
||||
this.trigger("blur", undefined);
|
||||
if (this.dialog) {
|
||||
this.dialog.blur();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SubWindow.type = ModelType.SubWindow;
|
||||
@ -182,6 +210,20 @@ namespace OS {
|
||||
return (this.parent.dialog = undefined);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the dialog
|
||||
*
|
||||
* @memberof BaseDialog
|
||||
*/
|
||||
show(): void {
|
||||
this.trigger("focus", undefined);
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
this.dialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,6 +236,7 @@ namespace OS {
|
||||
* @extends {BaseDialog}
|
||||
*/
|
||||
export class BasicDialog extends BaseDialog {
|
||||
['constructor']: typeof BasicDialog
|
||||
/**
|
||||
* Placeholder for the UI scheme to be rendered. This can
|
||||
* be either the string definition of the scheme or
|
||||
@ -207,7 +250,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* If the `markup` variable is not provided, then
|
||||
* the [[init]] function will find the scheme definition
|
||||
* the {@link init} function will find the scheme definition
|
||||
* in this class variable
|
||||
*
|
||||
* @static
|
||||
@ -238,19 +281,19 @@ namespace OS {
|
||||
* @memberof BasicDialog
|
||||
*/
|
||||
init(): void {
|
||||
super.init();
|
||||
//this._onenter = undefined;
|
||||
if (this.markup) {
|
||||
if (typeof this.markup === "string") {
|
||||
return GUI.htmlToScheme(this.markup, this, this.host);
|
||||
} else {
|
||||
// a file handle
|
||||
return this.render(this.markup.path);
|
||||
this.render(this.markup.path);
|
||||
}
|
||||
} else if (
|
||||
GUI.dialogs[this.name] &&
|
||||
GUI.dialogs[this.name].scheme
|
||||
this.constructor.scheme
|
||||
) {
|
||||
const html: string = GUI.dialogs[this.name].scheme;
|
||||
const html: string = this.constructor.scheme;
|
||||
return GUI.htmlToScheme(html.trim(), this, this.host);
|
||||
} else {
|
||||
this.error(__("Unable to find dialog scheme"));
|
||||
@ -264,7 +307,6 @@ namespace OS {
|
||||
*/
|
||||
main(): void {
|
||||
const win = this.scheme as tag.WindowTag;
|
||||
$(win).attr("tabindex", 0);
|
||||
$(win).on('keydown', (e) => {
|
||||
switch (e.which) {
|
||||
case 27:
|
||||
@ -284,6 +326,7 @@ namespace OS {
|
||||
}
|
||||
win.resizable = false;
|
||||
win.minimizable = false;
|
||||
win.menu = undefined;
|
||||
$(win).trigger("focus");
|
||||
}
|
||||
}
|
||||
@ -329,23 +372,15 @@ namespace OS {
|
||||
*/
|
||||
main(): void {
|
||||
super.main();
|
||||
const $input = $(this.find("txtInput"));
|
||||
if (this.data && this.data.label) {
|
||||
(this.find(
|
||||
"lbl"
|
||||
) as tag.LabelTag).text = this.data.label;
|
||||
}
|
||||
if (this.data && this.data.value) {
|
||||
$input.val(this.data.value);
|
||||
}
|
||||
|
||||
if (this.data && this.data.type) {
|
||||
($input[0] as HTMLInputElement).type = this.data.type
|
||||
const input = this.find("txtInput") as GUI.tag.InputTag;
|
||||
if(this.data)
|
||||
{
|
||||
input.set(this.data);
|
||||
}
|
||||
|
||||
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
|
||||
if (this.handle) {
|
||||
this.handle($input.val());
|
||||
this.handle(input.value);
|
||||
}
|
||||
return this.quit();
|
||||
};
|
||||
@ -356,49 +391,39 @@ namespace OS {
|
||||
return this.quit();
|
||||
};
|
||||
|
||||
$input.on("keyup", (e) => {
|
||||
input.on("keyup", (e) => {
|
||||
if (e.which !== 13) {
|
||||
return;
|
||||
}
|
||||
if (this.handle) {
|
||||
this.handle($input.val());
|
||||
this.handle(input.value);
|
||||
}
|
||||
return this.quit();
|
||||
});
|
||||
|
||||
$input.trigger("focus");
|
||||
input.trigger("focus");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Scheme definition of the Prompt dialog
|
||||
*/
|
||||
PromptDialog.scheme = `\
|
||||
<afx-app-window width='200' height='150' apptitle = "Prompt">
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-label data-id = "lbl" ></afx-label>
|
||||
<input type = "text" data-id= "txtInput" ></input>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='250' height='200' apptitle = "Prompt">
|
||||
<afx-vbox padding = "10">
|
||||
<afx-input data-id= "txtInput"></afx-input>
|
||||
<div data-height="35" style="text-align: right;">
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
|
||||
/**
|
||||
* A text dialog is similar to a [[PromptDialog]] nut allows
|
||||
* A text dialog is similar to a {@link PromptDialog} nut allows
|
||||
* user to input multi-line text.
|
||||
*
|
||||
* Refer to [[PromptDialog]] for the definition of input and callback data
|
||||
* Refer to {@link PromptDialog} for the definition of input and callback data
|
||||
* of the dialog
|
||||
*
|
||||
* @export
|
||||
@ -421,15 +446,11 @@ namespace OS {
|
||||
*/
|
||||
main(): void {
|
||||
super.main();
|
||||
const $input = $(this.find("txtInput"));
|
||||
if (this.data && this.data.value) {
|
||||
$input.val(this.data.value);
|
||||
}
|
||||
if (this.data && this.data.disable) {
|
||||
$input.prop('disabled', true);
|
||||
}
|
||||
const input = this.find("txtInput") as tag.InputTag;
|
||||
if(this.data)
|
||||
input.set(this.data);
|
||||
(this.find("btn-Ok") as tag.ButtonTag).onbtclick = (_e) => {
|
||||
const value = $input.val();
|
||||
const value = input.value;
|
||||
if (!value || value === "") {
|
||||
return;
|
||||
}
|
||||
@ -445,7 +466,7 @@ namespace OS {
|
||||
return this.quit();
|
||||
};
|
||||
|
||||
$input.focus();
|
||||
input.trigger("focus");
|
||||
}
|
||||
}
|
||||
/**
|
||||
@ -453,21 +474,12 @@ namespace OS {
|
||||
*/
|
||||
TextDialog.scheme = `\
|
||||
<afx-app-window data-id = "TextDialog" width='400' height='300'>
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<textarea data-id= "txtInput" ></textarea>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btn-Ok" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-vbox padding="10">
|
||||
<afx-input data-id= "txtInput" verbose="true"></afx-input>
|
||||
<div data-height="40" style="text-align:right;padding-top:5px;">
|
||||
<afx-button data-id = "btn-Ok" text = "__(Ok)" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -532,23 +544,13 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
CalendarDialog.scheme = `\
|
||||
<afx-app-window width='300' height='230' apptitle = "Calendar" >
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-calendar-view data-id = "cal" ></afx-calendar-view>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="10" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='350' height='380' apptitle = "Calendar" >
|
||||
<afx-vbox padding="10">
|
||||
<afx-calendar-view data-id = "cal" ></afx-calendar-view>
|
||||
<div data-height="35" style = 'text-align: right;'>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -563,7 +565,7 @@ namespace OS {
|
||||
* title: string // window title
|
||||
* }
|
||||
* ```
|
||||
* Callback data: [[ColorType]] object
|
||||
* Callback data: {@link ColorType} object
|
||||
*
|
||||
* @export
|
||||
* @class ColorPickerDialog
|
||||
@ -611,23 +613,13 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
ColorPickerDialog.scheme = `\
|
||||
<afx-app-window width='320' height='250' apptitle = "Color picker" >
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-color-picker data-id = "cpicker" ></afx-color-picker>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="10" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='320' height='300' apptitle = "Color picker" >
|
||||
<afx-vbox padding = "10">
|
||||
<afx-color-picker data-id = "cpicker" ></afx-color-picker>
|
||||
<div data-height="35" style = "text-align: right;">
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -672,7 +664,7 @@ namespace OS {
|
||||
}
|
||||
for (let k in this.data) {
|
||||
const v = this.data[k];
|
||||
rows.push([{ text: k }, { text: v }]);
|
||||
rows.push([{ text: k }, { text: v, selectable: true }]);
|
||||
}
|
||||
const grid = this.find("grid") as tag.GridViewTag;
|
||||
grid.header = [
|
||||
@ -691,22 +683,12 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
InfoDialog.scheme = `\
|
||||
<afx-app-window width='250' height='300' apptitle = "Info" >
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-grid-view data-id = "grid" ></afx-grid-view>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="10" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='300' height='350' apptitle = "Info" >
|
||||
<afx-vbox padding = "10">
|
||||
<afx-grid-view data-id = "grid" focusable="true"></afx-grid-view>
|
||||
<div data-height="35" style="text-align: right;">
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -772,22 +754,13 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
YesNoDialog.scheme = `\
|
||||
<afx-app-window width='200' height='150' apptitle = "Prompt">
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-label data-id = "lbl" ></afx-label>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnYes" text = "__(Yes)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnNo" text = "__(No)" data-width = "40" ></afx-button>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='250' height='200' apptitle = "Warning">
|
||||
<afx-vbox padding = "10">
|
||||
<afx-label data-id = "lbl" valign="top" ></afx-label>
|
||||
<div data-height="35" style = "text-align: right;">
|
||||
<afx-button data-id = "btnYes" text = "__(Yes)" ></afx-button>
|
||||
<afx-button data-id = "btnNo" text = "__(No)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -858,22 +831,13 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
SelectionDialog.scheme = `\
|
||||
<afx-app-window width='250' height='300' apptitle = "Selection">
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-list-view data-id = "list" ></afx-list-view>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='350' height='300' apptitle = "Selection">
|
||||
<afx-vbox padding = "10">
|
||||
<afx-list-view data-id = "list" focusable="true"></afx-list-view>
|
||||
<div data-height="35" style = "text-align: right;">
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -917,19 +881,23 @@ namespace OS {
|
||||
iconclass: mt.iconclass,
|
||||
text: `${mt.name}(v${mt.version})`,
|
||||
});
|
||||
$(this.find("mydesc")).html(mt.description);
|
||||
$(this.find("mydesc")).html(`${mt.description} <br/> ${mt.info.author} (${mt.info.email})`);
|
||||
// grid data for author info
|
||||
if (!mt.info) {
|
||||
return;
|
||||
}
|
||||
const rows = [];
|
||||
for (let k in mt.info) {
|
||||
const v = mt.info[k];
|
||||
rows.push([{ text: k }, { text: v }]);
|
||||
}
|
||||
const grid = this.find("mygrid") as tag.GridViewTag;
|
||||
grid.header = [{ text: "", width: 100 }, { text: "" }];
|
||||
grid.rows = rows;
|
||||
`pkg://${mt.pkgname?mt.pkgname:mt.app}/README.md`
|
||||
.asFileHandle()
|
||||
.read()
|
||||
.then(async (data) => {
|
||||
let _ = await API.requires("os://scripts/showdown.min.js");
|
||||
const converter = new showdown.Converter();
|
||||
const html = converter.makeHtml(data);
|
||||
const el = this.find("read-me");
|
||||
$(el).html(html);
|
||||
$("img", el).css("width", "100%");
|
||||
})
|
||||
.catch(e => {});
|
||||
(this.find("btnCancel") as tag.ButtonTag).onbtclick = (
|
||||
_e
|
||||
): void => {
|
||||
@ -941,24 +909,19 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
AboutDialog.scheme = `\
|
||||
<afx-app-window data-id = 'about-window' width='300' height='200'>
|
||||
<afx-vbox>
|
||||
<afx-app-window data-id = 'about-window' width='550' height='450'>
|
||||
<afx-vbox padding = "5">
|
||||
<div style="text-align:center; margin-top:10px;" data-height="50">
|
||||
<h3 style = "margin:0;padding:0;">
|
||||
<afx-label data-id = 'mylabel'></afx-label>
|
||||
<afx-label data-id = 'mylabel' style="display: inline-block;"></afx-label>
|
||||
</h3>
|
||||
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
|
||||
</div>
|
||||
<afx-hbox>
|
||||
<div data-width="10"></div>
|
||||
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
|
||||
</afx-hbox>
|
||||
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" ></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height = "10"></div>
|
||||
<div data-id="read-me" style="overflow-x: hidden; overflow-y: auto;"></div>
|
||||
<div data-height="10"></div>
|
||||
<div data-height="35" style = "text-align: right;">
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
@ -1021,7 +984,7 @@ namespace OS {
|
||||
super.main();
|
||||
const fileview = this.find("fileview") as tag.FileViewTag;
|
||||
const location = this.find("location") as tag.ListViewTag;
|
||||
const filename = this.find("filename") as HTMLInputElement;
|
||||
const filename = this.find("filename") as tag.InputTag;
|
||||
fileview.fetch = (path: string) =>
|
||||
new Promise(function (resolve, reject) {
|
||||
if (!path) {
|
||||
@ -1035,12 +998,6 @@ namespace OS {
|
||||
return reject(d);
|
||||
}
|
||||
FileDialog.last_opened = path;
|
||||
if (!dir.isRoot()) {
|
||||
const p = dir.parent();
|
||||
p.filename = "[..]";
|
||||
p.type = "dir";
|
||||
d.result.unshift(p);
|
||||
}
|
||||
return resolve(d.result);
|
||||
})
|
||||
.catch((e: Error): void => reject(__e(e)));
|
||||
@ -1052,7 +1009,7 @@ namespace OS {
|
||||
__("Resource not found: {0}", path)
|
||||
);
|
||||
}
|
||||
return (fileview.path = path);
|
||||
fileview.path = path;
|
||||
};
|
||||
|
||||
if (!this.data || !this.data.root) {
|
||||
@ -1084,15 +1041,18 @@ namespace OS {
|
||||
}
|
||||
fileview.onfileselect = function (e) {
|
||||
if (e.data.type === "file") {
|
||||
return $(filename).val(e.data.filename);
|
||||
return filename.value = e.data.filename;
|
||||
}
|
||||
};
|
||||
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
|
||||
const f = fileview.selectedFile;
|
||||
let f = fileview.selectedFile;
|
||||
if (!f) {
|
||||
return this.notify(
|
||||
__("Please select a file/fofler")
|
||||
);
|
||||
const sel = location.selectedItem;
|
||||
if(!sel)
|
||||
return this.notify(
|
||||
__("Please select a file/fofler")
|
||||
);
|
||||
f = sel.data as API.FileInfoType;
|
||||
}
|
||||
if (
|
||||
this.data &&
|
||||
@ -1128,7 +1088,7 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
|
||||
const name = $(filename).val();
|
||||
const name = filename.value;
|
||||
if (this.handle) {
|
||||
this.handle({ file: f, name });
|
||||
}
|
||||
@ -1142,9 +1102,13 @@ namespace OS {
|
||||
};
|
||||
|
||||
if (this.data && this.data.file) {
|
||||
$(filename)
|
||||
.css("display", "block")
|
||||
.val(this.data.file.basename || "Untitled");
|
||||
$(filename).show();
|
||||
filename.value = (this.data.file.basename || "Untitled");
|
||||
this.trigger("resize");
|
||||
}
|
||||
else
|
||||
{
|
||||
$(filename).hide();
|
||||
this.trigger("resize");
|
||||
}
|
||||
if (this.data && this.data.hidden) {
|
||||
@ -1158,21 +1122,16 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
FileDialog.scheme = `\
|
||||
<afx-app-window width='400' height='300'>
|
||||
<afx-hbox>
|
||||
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
|
||||
<afx-vbox>
|
||||
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
|
||||
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" ></input>
|
||||
<afx-hbox data-height = '30'>
|
||||
<div style=' text-align:right;'>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
<div data-width="5"></div>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='400' height='450'>
|
||||
<afx-vbox>
|
||||
<afx-list-view data-id = "location" dropdown = "true" data-height = "35"></afx-list-view>
|
||||
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
|
||||
<afx-input data-height = '52' label = "__(Target file/folder)" type = "text" data-id = "filename" ></afx-input>
|
||||
<div style=' text-align:right;' data-height="35">
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
`;
|
||||
|
||||
@ -1254,17 +1213,15 @@ namespace OS {
|
||||
* @memberof MultiInputDialog
|
||||
*/
|
||||
init(): void {
|
||||
let height = 60;
|
||||
let height = 85;
|
||||
let html = "";
|
||||
if (this.data && this.data.model) {
|
||||
const model = this.data.model;
|
||||
for (const key in model) {
|
||||
html += `\
|
||||
<afx-label data-height="25" text="{0}" ></afx-label>
|
||||
<input data-height="25" type="text" name="{1}" ></input>
|
||||
<div data-height="10" ></div>
|
||||
<afx-input data-height="52" text="{0}" type="text" name = {1} ></afx-input>
|
||||
`.format(model[key], key);
|
||||
height += 60;
|
||||
height += 52;
|
||||
}
|
||||
}
|
||||
this.markup = MultiInputDialog.scheme.format(height, html);
|
||||
@ -1307,20 +1264,13 @@ namespace OS {
|
||||
*/
|
||||
MultiInputDialog.scheme = `\
|
||||
<afx-app-window width='350' height='{0}'>
|
||||
<afx-hbox>
|
||||
<div data-width="10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="5" ></div>
|
||||
{1}
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="5" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-vbox padding = "5">
|
||||
{1}
|
||||
<div data-height="35" style = "text-align: right;">
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-app-window>`;
|
||||
|
||||
|
||||
@ -1406,28 +1356,22 @@ namespace OS {
|
||||
*/
|
||||
private addField(key: string, value: string, removable: boolean): void {
|
||||
const div = $("<div>")
|
||||
.css("width", "100%")
|
||||
.css("display", "flex")
|
||||
.css("flex-direction", "row")
|
||||
.appendTo(this.container);
|
||||
$("<input>")
|
||||
.attr("type", "text")
|
||||
.css("width", "120px")
|
||||
.css("height", "23px")
|
||||
.css("flex", "1")
|
||||
.val(key)
|
||||
.appendTo(div);
|
||||
$("<input>")
|
||||
.attr("type", "text")
|
||||
.css("width", "200px")
|
||||
.css("height", "23px")
|
||||
.css("flex", "1")
|
||||
.val(value)
|
||||
.appendTo(div);
|
||||
if (removable) {
|
||||
const btn = $("<afx-button>");
|
||||
btn[0].uify(undefined);
|
||||
$("button", btn)
|
||||
.css("width", "23px")
|
||||
.css("height", "23px");
|
||||
(btn[0] as tag.ButtonTag).iconclass = "fa fa-minus";
|
||||
btn
|
||||
.on("click", () => {
|
||||
@ -1435,12 +1379,13 @@ namespace OS {
|
||||
})
|
||||
.appendTo(div);
|
||||
}
|
||||
else {
|
||||
else
|
||||
{
|
||||
$("<div>")
|
||||
.css("width", "23px")
|
||||
.appendTo(div);
|
||||
.css("width", "40px")
|
||||
.css("height", "35px")
|
||||
.appendTo(div);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1449,23 +1394,18 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
KeyValueDialog.scheme = `\
|
||||
<afx-app-window width='350' height='300'>
|
||||
<afx-hbox>
|
||||
<div data-width="10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="5" ></div>
|
||||
<afx-app-window width='400' height='350'>
|
||||
<afx-vbox padding = "10">
|
||||
<afx-label text="__(Enter key-value data)" data-height="30"></afx-label>
|
||||
<div data-id="container"></div>
|
||||
<afx-hbox data-height="30">
|
||||
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "30" ></afx-button>
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
<afx-hbox data-height="35">
|
||||
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "35" ></afx-button>
|
||||
<div style = "text-align: right;">
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-hbox>
|
||||
<div data-height="5" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10" ></div>
|
||||
</afx-hbox>
|
||||
</afx-app-window>`;
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ namespace OS {
|
||||
*/
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum definition of different model types
|
||||
*
|
||||
@ -154,7 +155,7 @@ namespace OS {
|
||||
* to handle all local events inside that model.
|
||||
*
|
||||
* This observable object is propagate to all the
|
||||
* UI elements ([[AFXTag]]) inside the model
|
||||
* UI elements ({@link OS.GUI.AFXTag}) inside the model
|
||||
*
|
||||
* @protected
|
||||
* @type {API.Announcer}
|
||||
@ -192,10 +193,10 @@ namespace OS {
|
||||
* The HTML element ID of the virtual desktop
|
||||
*
|
||||
* @protected
|
||||
* @type {string}
|
||||
* @type {HTMLElement}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected host: string;
|
||||
protected host: HTMLElement;
|
||||
|
||||
/**
|
||||
* The process number of the current model.
|
||||
@ -293,12 +294,10 @@ namespace OS {
|
||||
this._gui = GUI;
|
||||
this.systemsetting = setting;
|
||||
this.on("exit", () => this.quit(false));
|
||||
this.host = this._gui.workspace;
|
||||
this.host = this._gui.desktop();
|
||||
this.dialog = undefined;
|
||||
this.subscribe("systemlocalechange", (name) => {
|
||||
this.updateLocale(name);
|
||||
return this.update();
|
||||
});
|
||||
// relay global events to local events
|
||||
this.subscribe("DESKTOP-RESIZE", (e) => this.observable.trigger("desktopresize", e));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,20 +313,19 @@ namespace OS {
|
||||
/**
|
||||
* Update the model locale
|
||||
*
|
||||
* @protected
|
||||
* @param {string} name
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected updateLocale(name: string) {}
|
||||
updateLocale(name: string) {}
|
||||
/**
|
||||
* Render the model's UI
|
||||
*
|
||||
* @protected
|
||||
* @param {string} p VFS path to the UI scheme definition
|
||||
* @returns {void}
|
||||
* @returns {Promise<any>}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected render(p: string): void {
|
||||
protected render(p: string): Promise<any> {
|
||||
return GUI.loadScheme(p, this, this.host);
|
||||
}
|
||||
|
||||
@ -338,7 +336,7 @@ namespace OS {
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
quit(force: boolean): void {
|
||||
quit(force: boolean = false): void {
|
||||
const evt = new BaseEvent("exit", force);
|
||||
this.onexit(evt);
|
||||
if (!evt.prevent) {
|
||||
@ -350,10 +348,22 @@ namespace OS {
|
||||
if (this.dialog) {
|
||||
this.dialog.quit();
|
||||
}
|
||||
return PM.kill(this);
|
||||
announcer.unregister(this);
|
||||
this.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Purge the model from the system
|
||||
*
|
||||
* @protected
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected destroy(): void
|
||||
{
|
||||
return PM.kill(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Model meta data, need to be implemented by
|
||||
* subclasses
|
||||
@ -508,7 +518,6 @@ namespace OS {
|
||||
/**
|
||||
* trigger a local event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {*} [d] event data
|
||||
* @returns {void}
|
||||
@ -524,14 +533,14 @@ namespace OS {
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {(d: any) => void} f event callback
|
||||
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
subscribe(e: string, f: (d: any) => void): void {
|
||||
subscribe(e: string, f: (d: API.AnnouncementDataType<any>) => void): void {
|
||||
return announcer.on(e, f, this);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Open a dialog
|
||||
*
|
||||
@ -570,10 +579,10 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a [[YesNoDialog]] to confirm a task
|
||||
* Open a {@link OS.GUI.dialogs.YesNoDialog} to confirm a task
|
||||
*
|
||||
* @protected
|
||||
* @param {GenericObject<any>} data [[YesNoDialog]] input data
|
||||
* @param {GenericObject<any>} data {@link OS.GUI.dialogs.YesNoDialog} input data
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
@ -587,41 +596,39 @@ namespace OS {
|
||||
* @protected
|
||||
* @param {string} t event name
|
||||
* @param {(string | FormattedString)} m event message
|
||||
* @param {Error} [e] error object if any
|
||||
* @param {any} u_data user data object if any
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected publish(
|
||||
t: string,
|
||||
m: string | FormattedString,
|
||||
e?: Error
|
||||
u_data?: any
|
||||
): void {
|
||||
const mt = this.meta();
|
||||
let icon: string = undefined;
|
||||
const data: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
|
||||
data.icon = undefined;
|
||||
if (mt && mt.icon) {
|
||||
icon = `${mt.path}/${mt.icon}`;
|
||||
data.icon = `${mt.path}/${mt.icon}`;
|
||||
}
|
||||
return announcer.trigger(t, {
|
||||
id: this.pid,
|
||||
name: this.name,
|
||||
data: {
|
||||
m: m,
|
||||
icon: icon,
|
||||
iconclass: mt?mt.iconclass:undefined,
|
||||
e: e,
|
||||
},
|
||||
});
|
||||
data.id = this.pid;
|
||||
data.name = this.name;
|
||||
data.message = m;
|
||||
data.iconclass = mt?mt.iconclass:undefined;
|
||||
data.u_data = u_data;
|
||||
return announcer.trigger(t, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a global notification
|
||||
*
|
||||
* @param {(string | FormattedString)} m notification string
|
||||
* @param {any} u_data user data object if any
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
notify(m: string | FormattedString): void {
|
||||
return this.publish("notification", m);
|
||||
notify(m: string | FormattedString, data?: any): void {
|
||||
return this.publish("notification", m, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -677,7 +684,11 @@ namespace OS {
|
||||
*/
|
||||
update(): void {
|
||||
if (this.scheme) {
|
||||
return this.scheme.update();
|
||||
this.scheme.update();
|
||||
}
|
||||
if(this.dialog)
|
||||
{
|
||||
this.dialog.update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -690,12 +701,16 @@ namespace OS {
|
||||
* @returns {HTMLElement}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected find(id: string): HTMLElement {
|
||||
protected find<T extends HTMLElement>(id: string): T {
|
||||
if (this.scheme) {
|
||||
return $(`[data-id='${id}']`, this.scheme)[0];
|
||||
return $(`[data-id='${id}']`, this.scheme)[0] as T;
|
||||
}
|
||||
}
|
||||
|
||||
/*protected $(id: string) : T {
|
||||
return this.find(id) as T;
|
||||
}*/
|
||||
|
||||
/**
|
||||
* Select all DOM Element inside the UI of the model
|
||||
* using JQuery selector
|
||||
|
@ -55,10 +55,10 @@ namespace OS {
|
||||
/**
|
||||
* Text of the service shown in the system tray
|
||||
*
|
||||
* @type {string}
|
||||
* @type {string | FormattedString}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
text: string;
|
||||
text: string | FormattedString;
|
||||
|
||||
/**
|
||||
* Reference to the menu entry DOM element attached
|
||||
@ -71,7 +71,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Reference to the timer that periodically executes the callback
|
||||
* defined in [[watch]].
|
||||
* defined in {@link watch}.
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
@ -79,13 +79,6 @@ namespace OS {
|
||||
*/
|
||||
private timer: number;
|
||||
|
||||
/**
|
||||
* Reference to the system tray menu
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
holder: HTMLElement;
|
||||
|
||||
/**
|
||||
* Placeholder for service select callback
|
||||
@ -93,7 +86,7 @@ namespace OS {
|
||||
* @memberof BaseService
|
||||
*/
|
||||
onmenuselect: (
|
||||
d: OS.GUI.TagEventType<GUI.tag.MenuEventData>
|
||||
d: OS.GUI.TagEventType<GUI.tag.StackMenuEventData>
|
||||
) => void;
|
||||
|
||||
/**
|
||||
@ -108,7 +101,6 @@ namespace OS {
|
||||
this.iconclass = "fa fa-paper-plane-o";
|
||||
this.text = "";
|
||||
this.timer = undefined;
|
||||
this.holder = undefined;
|
||||
this.onmenuselect = (d) => {
|
||||
return this.awake(d);
|
||||
};
|
||||
@ -140,7 +132,9 @@ namespace OS {
|
||||
* @memberof BaseService
|
||||
*/
|
||||
update(): void {
|
||||
(this.domel as GUI.tag.MenuEntryTag).data = this;
|
||||
if(!this.domel)
|
||||
return;
|
||||
(this.domel as GUI.tag.ListViewItemTag).data = this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,17 +147,6 @@ namespace OS {
|
||||
return application[this.name].meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the service to a menu element
|
||||
* such as the system tray menu
|
||||
*
|
||||
* @param {HTMLElement} h
|
||||
* @memberof BaseService
|
||||
*/
|
||||
attach(h: HTMLElement): void {
|
||||
this.holder = h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback that will be called periodically
|
||||
* after a period of time.
|
||||
@ -234,7 +217,7 @@ namespace OS {
|
||||
* @param {GUI.TagEventType} e
|
||||
* @memberof BaseService
|
||||
*/
|
||||
abstract awake(e: GUI.TagEventType<GUI.tag.MenuEventData>): void;
|
||||
abstract awake(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void;
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
|
465
src/core/core.ts
465
src/core/core.ts
@ -39,8 +39,8 @@ interface String {
|
||||
|
||||
/**
|
||||
* Parse the current string and convert it
|
||||
* to an object of type [[Version]] if the string
|
||||
* is in the format recognized by [[Version]],
|
||||
* to an object of type {@link OS.Version} if the string
|
||||
* is in the format recognized by {@link OS.Version},
|
||||
* e.g.: `1.0.1-a`
|
||||
*
|
||||
* @returns {OS.Version}
|
||||
@ -98,7 +98,7 @@ interface String {
|
||||
format(...args: any[]): string;
|
||||
|
||||
/**
|
||||
* Create a [[FormattedString]] object using the current
|
||||
* Create a {@link OS.FormattedString} object using the current
|
||||
* string and the input parameters
|
||||
*
|
||||
* @param {...any[]} args
|
||||
@ -157,9 +157,28 @@ interface String {
|
||||
trimBy(arg: string): string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Extend the Array prototype with some API
|
||||
* functions used by AntOS API
|
||||
*
|
||||
* @interface Array
|
||||
* @template T
|
||||
*/
|
||||
interface Array<T> {
|
||||
/**
|
||||
* Check if the array includes an element
|
||||
*
|
||||
* @param {T} element to check
|
||||
* @returns {boolean}
|
||||
* @memberof Array
|
||||
*/
|
||||
includes(element: T): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the Data prototype with the
|
||||
* [[timestamp]] function
|
||||
* {@link timestamp} function
|
||||
*
|
||||
* @interface Date
|
||||
*/
|
||||
@ -171,6 +190,13 @@ interface Date {
|
||||
* @memberof Date
|
||||
*/
|
||||
timestamp(): number;
|
||||
/**
|
||||
* Covnert to GMTString
|
||||
*
|
||||
* @returns {number}
|
||||
* @memberof Date
|
||||
*/
|
||||
toGMTString(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +210,7 @@ interface GenericObject<T> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Global function to create a [[FormattedString]] from
|
||||
* Global function to create a {@link OS.FormattedString} from
|
||||
* a formatted string and a list of parameters. Example
|
||||
*
|
||||
* ```typescript
|
||||
@ -207,6 +233,71 @@ declare function __(...args: any[]): OS.FormattedString | string;
|
||||
*/
|
||||
declare function __e(e: Error): Error;
|
||||
|
||||
/**
|
||||
* JQuery event-extensions to support doubletap on
|
||||
* mobile device
|
||||
*/
|
||||
jQuery.event.special.dbltap = {
|
||||
bindType: 'touchend',
|
||||
delegateType: 'touchend',
|
||||
|
||||
handle: function (event: any) {
|
||||
var handleObj = event.handleObj,
|
||||
targetData = jQuery.data(event.target),
|
||||
now = new Date().getTime(),
|
||||
delta = targetData.lastTouchEnd ? now - targetData.lastTouchEnd : 0,
|
||||
delay = delay == null ? 300 : delay;
|
||||
|
||||
if (delta < delay && delta > 30) {
|
||||
targetData.lastTouchEnd = null;
|
||||
event.type = handleObj.origType;
|
||||
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function (property) {
|
||||
event[property] = event.originalEvent.changedTouches[0][property];
|
||||
})
|
||||
|
||||
// let jQuery handle the triggering of "dbltap" event handlers
|
||||
handleObj.handler.apply(this, arguments);
|
||||
} else {
|
||||
targetData.lastTouchEnd = now;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* JQuery event-extensions to support long touch event on
|
||||
* mobile device
|
||||
*/
|
||||
jQuery.event.special.longtouch = {
|
||||
bindType: 'touchstart',
|
||||
//delegateType: 'touchstart',
|
||||
|
||||
handle: function (evt: any) {
|
||||
let targetData = jQuery.data(evt.target);
|
||||
let handleObj = evt.handleObj;
|
||||
targetData.lastTouchStart = new Date().getTime();
|
||||
|
||||
$(evt.target).on("touchend", (event) => {
|
||||
let now = new Date().getTime();
|
||||
let end_targetData = jQuery.data(event.target);
|
||||
let delta = end_targetData.lastTouchStart ? now - end_targetData.lastTouchStart : 0;
|
||||
$(event.target).off("touchend");
|
||||
const offset_top = Math.abs(event.originalEvent.changedTouches[0].clientY - evt.originalEvent.changedTouches[0].clientY);
|
||||
const offset_left = Math.abs(event.originalEvent.changedTouches[0].clientX - evt.originalEvent.changedTouches[0].clientX);
|
||||
console.log(offset_left, offset_top);
|
||||
if(delta > 1000 && offset_top < 10 && offset_left < 10)
|
||||
{
|
||||
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function (property) {
|
||||
evt[property] = event.originalEvent.changedTouches[0][property];
|
||||
})
|
||||
event.preventDefault();
|
||||
evt.type = handleObj.origType;
|
||||
handleObj.handler.apply(this, arguments);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This namespace is the main entry point of AntOS
|
||||
* API
|
||||
@ -252,7 +343,7 @@ namespace OS {
|
||||
};
|
||||
|
||||
Ant.__e = function (e: Error): Error {
|
||||
const reason = new Error(e.toString());
|
||||
const reason = new Error(e.toString().replace(/^Error: /g, ""));
|
||||
reason.stack += "\nCaused By:\n" + e.stack;
|
||||
return reason;
|
||||
};
|
||||
@ -279,7 +370,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* The value of the format pattern represented
|
||||
* in [[fs]]
|
||||
* in {@link fs}
|
||||
*
|
||||
* @type {any[]}
|
||||
* @memberof FormattedString
|
||||
@ -417,13 +508,14 @@ namespace OS {
|
||||
* AntOS version number is in the following format:
|
||||
*
|
||||
* ```
|
||||
* [major_number].[minor_number].[patch]-[branch]
|
||||
* [major_number].[minor_number].[patch]-[branch]-[build ID])
|
||||
*
|
||||
* e.g.: 1.2.3-r means that:
|
||||
* e.g.: 1.2.3-r-b means that:
|
||||
* - version major number is 1
|
||||
* - version minor number is 2
|
||||
* - patch version is 3
|
||||
* - the current branch is release `r`
|
||||
* - build ID (optional)
|
||||
* ```
|
||||
*
|
||||
* @export
|
||||
@ -433,10 +525,11 @@ namespace OS {
|
||||
/**
|
||||
* The version string
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof Version
|
||||
*/
|
||||
string: string;
|
||||
private string: string;
|
||||
|
||||
/**
|
||||
* The current branch
|
||||
@ -474,13 +567,38 @@ namespace OS {
|
||||
*/
|
||||
patch: number;
|
||||
|
||||
/**
|
||||
* Version build ID (optional): usually the current git commit hash
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Version
|
||||
*/
|
||||
build_id: string;
|
||||
|
||||
/**
|
||||
*Creates an instance of Version.
|
||||
*
|
||||
* @param {string} string string represents the version
|
||||
* @memberof Version
|
||||
*/
|
||||
constructor(string: string) {
|
||||
this.string = string;
|
||||
this.version_string = string;
|
||||
}
|
||||
/**
|
||||
* Setter/getter to set the version string to the object
|
||||
*
|
||||
* @memberof Version
|
||||
*/
|
||||
set version_string(v: string) {
|
||||
if (!v) {
|
||||
this.string = undefined;
|
||||
this.major = undefined;
|
||||
this.minor = undefined;
|
||||
this.patch = undefined;
|
||||
this.build_id = undefined;
|
||||
return;
|
||||
}
|
||||
this.string = v;
|
||||
const arr = this.string.split("-");
|
||||
const br = {
|
||||
r: 3,
|
||||
@ -488,9 +606,13 @@ namespace OS {
|
||||
a: 1,
|
||||
};
|
||||
this.branch = 3;
|
||||
if (arr.length === 2 && br[arr[1]]) {
|
||||
if (arr.length >= 2 && br[arr[1]]) {
|
||||
this.branch = br[arr[1]];
|
||||
if (arr[2]) {
|
||||
this.build_id = arr[2];
|
||||
}
|
||||
}
|
||||
|
||||
const mt = arr[0].match(/\d+/g);
|
||||
if (!mt) {
|
||||
API.throwe(
|
||||
@ -510,6 +632,9 @@ namespace OS {
|
||||
this.patch = Number(mt[2]);
|
||||
}
|
||||
}
|
||||
get version_string(): string {
|
||||
return this.string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the current version with another version.
|
||||
@ -752,16 +877,27 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Variable represents the current AntOS version, it
|
||||
* is an instance of [[Version]]
|
||||
* is an instance of {@link OS.Version}
|
||||
*/
|
||||
export const VERSION: Version = "1.0.0-a".__v();
|
||||
export const VERSION: Version = new Version(undefined);
|
||||
|
||||
/**
|
||||
* Variable represents the current AntOS source code repository
|
||||
* is an instance of string
|
||||
*/
|
||||
export const REPOSITORY: string = "https://github.com/antos-rde/antos";
|
||||
|
||||
/**
|
||||
* Indicate whether the current de
|
||||
*/
|
||||
export var mobile: boolean = false;
|
||||
/**
|
||||
* Register a model prototype to the system namespace.
|
||||
* There are two types of model to be registered, if the model
|
||||
* is of type [[SubWindow]], its prototype will be registered
|
||||
* in the [[dialogs]] namespace, otherwise, if the model type
|
||||
* is [[Application]] or [[Service]], its prototype will be
|
||||
* registered in the [[application]] namespace.
|
||||
* is of type {@link OS.GUI.SubWindow}, its prototype will be registered
|
||||
* in the {@link OS.GUI.dialogs} namespace, otherwise, if the model type
|
||||
* is {@link OS.application.BaseApplication} or {@link OS.application.BaseService}, its prototype will be
|
||||
* registered in the {@link application} namespace.
|
||||
*
|
||||
* When a model is loaded in the system, its prototype is registered
|
||||
* for later uses
|
||||
@ -807,7 +943,6 @@ namespace OS {
|
||||
$("#wrapper").empty();
|
||||
GUI.clearTheme();
|
||||
announcer.observable = new API.Announcer();
|
||||
announcer.quota = 0;
|
||||
resetSetting();
|
||||
PM.processes = {};
|
||||
PM.pidalloc = 0;
|
||||
@ -815,7 +950,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Booting up AntOS. This function checks whether the user
|
||||
* is successfully logged in, then call [[startAntOS]], otherwise
|
||||
* is successfully logged in, then call {@link OS.GUI.startAntOS}, otherwise
|
||||
* it shows the login screen
|
||||
*
|
||||
* @export
|
||||
@ -823,6 +958,7 @@ namespace OS {
|
||||
export function boot(): void {
|
||||
//first login
|
||||
console.log("Booting system");
|
||||
// check whether we are on mobile device
|
||||
API.handle
|
||||
.auth()
|
||||
.then(function (d: API.RequestResult) {
|
||||
@ -843,39 +979,40 @@ namespace OS {
|
||||
* exits. These callbacks are useful when an application or service wants
|
||||
* to perform a particular task before shuting down the system
|
||||
*/
|
||||
export const cleanupHandles: { [index: string]: () => void } = {};
|
||||
export const cleanupHandles: { [index: string]: () => Promise<any> } = {};
|
||||
|
||||
/**
|
||||
* Perform the system shutdown operation. This function calls all
|
||||
* clean up handles in [[cleanupHandles]], then save the system setting
|
||||
* clean up handles in {@link cleanupHandles}, then save the system setting
|
||||
* before exiting
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export function exit(): void {
|
||||
//do clean up first
|
||||
const promises: Promise<any>[] = [];
|
||||
for (let n in cleanupHandles) {
|
||||
const f = cleanupHandles[n];
|
||||
f();
|
||||
promises.push(cleanupHandles[n]());
|
||||
}
|
||||
API.handle
|
||||
.setting()
|
||||
.then(function (r: any) {
|
||||
promises.push(API.handle.setting());
|
||||
Promise.all(promises)
|
||||
.then(async function (r: any) {
|
||||
cleanup();
|
||||
return API.handle.logout().then((d: any) => boot());
|
||||
const d = await API.handle.logout();
|
||||
return boot();
|
||||
})
|
||||
.catch((e: Error) => console.error(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a callback to the system [[cleanupHandles]]
|
||||
* Register a callback to the system {@link cleanupHandles}
|
||||
*
|
||||
* @export
|
||||
* @param {string} n callback string name
|
||||
* @param {() => void} f the callback handle
|
||||
* @returns
|
||||
*/
|
||||
export function onexit(n: string, f: () => void) {
|
||||
export function onexit(n: string, f: () => Promise<any>) {
|
||||
if (!cleanupHandles[n]) {
|
||||
return (cleanupHandles[n] = f);
|
||||
}
|
||||
@ -906,7 +1043,7 @@ namespace OS {
|
||||
export interface PackageMetaType {
|
||||
/**
|
||||
* The application class name, if the package has only services
|
||||
* this property is ignored and [[pkgname]] should be specified
|
||||
* this property is ignored and {@link pkgname} should be specified
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PackageMetaType
|
||||
@ -914,7 +1051,7 @@ namespace OS {
|
||||
app?: string;
|
||||
|
||||
/**
|
||||
* Package name, in case of [[app]] being undefined, this property
|
||||
* Package name, in case of {@link app} being undefined, this property
|
||||
* need to be specified
|
||||
*
|
||||
* @type {string}
|
||||
@ -1053,7 +1190,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Package version, should be in a format conforming
|
||||
* to the version definition in [[Version]] class
|
||||
* to the version definition in {@link Version} class
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PackageMetaType
|
||||
@ -1110,14 +1247,41 @@ namespace OS {
|
||||
export var lang: GenericObject<string> = {};
|
||||
|
||||
/**
|
||||
* Re-export the system announcement [[getMID]] function to the
|
||||
* core API
|
||||
*
|
||||
* A task is a Promise object that is tracked by AntOS via the
|
||||
* Announcerment system
|
||||
*
|
||||
* Task manager implementation can subscribe to the following global events
|
||||
* - ANTOS-TASK-PENDING : a new task/promise is created and executing
|
||||
* - ANTOS-TASK-FULFILLED: a fullfilled task is a resolved promise
|
||||
* - ANTOS-TASK-REJECTED: a rejected task is a rejected or error promise
|
||||
*
|
||||
* Whenever a task is created by this API, it states will be automatically announced
|
||||
* to any subscribers of these events
|
||||
*
|
||||
* @export
|
||||
* @returns {number}
|
||||
* @param {Promise} a Promise object
|
||||
*/
|
||||
export function mid(): number {
|
||||
return announcer.getMID();
|
||||
export function Task(fn: (resolve: (any) => void, reject: (any) => void) => void): Promise<any> {
|
||||
return new Promise(async (ok, nok) => {
|
||||
const promise = new Promise(fn);
|
||||
const ann: API.AnnouncementDataType<Promise<any>> = {} as API.AnnouncementDataType<Promise<any>>;
|
||||
ann.name = "OS";
|
||||
ann.u_data = promise;
|
||||
ann.id = Math.floor(Math.random() * 1e6);
|
||||
try {
|
||||
ann.message = "ANTOS-TASK-PENDING";
|
||||
announcer.trigger(ann.message, ann);
|
||||
const data = await promise;
|
||||
ok(data);
|
||||
ann.message = "ANTOS-TASK-FULFILLED";
|
||||
announcer.trigger(ann.message, ann);
|
||||
}
|
||||
catch (e) {
|
||||
ann.message = "ANTOS-TASK-REJECTED";
|
||||
announcer.trigger(ann.message, ann);
|
||||
nok(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1132,34 +1296,35 @@ namespace OS {
|
||||
* @returns {Promise<any>} a promise on the result data
|
||||
*/
|
||||
export function post(p: string, d: any): Promise<any> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const q = announcer.getMID();
|
||||
API.loading(q, p);
|
||||
return $.ajax({
|
||||
type: "POST",
|
||||
url: p,
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(
|
||||
d,
|
||||
function (k, v) {
|
||||
if (k === "domel") {
|
||||
return undefined;
|
||||
}
|
||||
return v;
|
||||
},
|
||||
4
|
||||
),
|
||||
dataType: "json",
|
||||
success: null,
|
||||
})
|
||||
.done(function (data) {
|
||||
API.loaded(q, p, "OK");
|
||||
return resolve(data);
|
||||
return API.Task(async (resolve, reject) => {
|
||||
try {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: p,
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(
|
||||
d,
|
||||
function (k, v) {
|
||||
if (k === "domel") {
|
||||
return undefined;
|
||||
}
|
||||
return v;
|
||||
},
|
||||
4
|
||||
),
|
||||
dataType: "json",
|
||||
success: null,
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
API.loaded(q, p, "FAIL");
|
||||
return reject(API.throwe(s));
|
||||
});
|
||||
.done(function (data) {
|
||||
return resolve(data);
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
reject(e);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1175,21 +1340,17 @@ namespace OS {
|
||||
* @returns {Promise<ArrayBuffer>} a promise on the returned binary data
|
||||
*/
|
||||
export function blob(p: string): Promise<ArrayBuffer> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const q = announcer.getMID();
|
||||
return API.Task(function (resolve, reject) {
|
||||
const r = new XMLHttpRequest();
|
||||
r.open("GET", p, true);
|
||||
r.responseType = "arraybuffer";
|
||||
r.onload = function (e) {
|
||||
if (this.status === 200 && this.readyState === 4) {
|
||||
API.loaded(q, p, "OK");
|
||||
resolve(this.response);
|
||||
} else {
|
||||
API.loaded(q, p, "FAIL");
|
||||
reject(API.throwe(__("Unable to get blob: {0}", p)));
|
||||
}
|
||||
};
|
||||
API.loading(q, p);
|
||||
r.send();
|
||||
});
|
||||
}
|
||||
@ -1205,49 +1366,41 @@ namespace OS {
|
||||
* @returns {Promise<any>}
|
||||
*/
|
||||
export function upload(p: string, d: string): Promise<any> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
const q = announcer.getMID();
|
||||
return new Promise((resolve, reject) => {
|
||||
//insert a temporal file selector
|
||||
const o =
|
||||
const o =
|
||||
$("<input>")
|
||||
.attr("type","file")
|
||||
.attr("multiple","true");
|
||||
o.on("change", function () {
|
||||
const files = (o[0] as HTMLInputElement).files;
|
||||
const n_files = files.length;
|
||||
const tasks = [];
|
||||
if (n_files > 0)
|
||||
API.loading(q, p);
|
||||
Array.from(files).forEach(file => {
|
||||
.attr("type", "file")
|
||||
.attr("multiple", "true");
|
||||
o.on("change", async () => {
|
||||
try {
|
||||
const files = (o[0] as HTMLInputElement).files;
|
||||
const formd = new FormData();
|
||||
formd.append("path", d);
|
||||
formd.append("upload", file);
|
||||
return $.ajax({
|
||||
url: p,
|
||||
data: formd,
|
||||
type: "POST",
|
||||
contentType: false,
|
||||
processData: false,
|
||||
})
|
||||
.done(function (data) {
|
||||
tasks.push("OK");
|
||||
if (tasks.length == n_files)
|
||||
{
|
||||
API.loaded(q, p, "OK");
|
||||
resolve(data);
|
||||
o.remove();
|
||||
}
|
||||
jQuery.each(files, (i, file) => {
|
||||
formd.append(`upload-${i}`, file);
|
||||
});
|
||||
const ret = await API.Task((ok, nok) => {
|
||||
$.ajax({
|
||||
url: p,
|
||||
data: formd,
|
||||
type: "POST",
|
||||
contentType: false,
|
||||
processData: false,
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
tasks.push("FAIL");
|
||||
if (tasks.length == n_files)
|
||||
{
|
||||
API.loaded(q, p, "FAIL");
|
||||
o.remove();
|
||||
}
|
||||
reject(API.throwe(s));
|
||||
});
|
||||
});
|
||||
.done(function (data) {
|
||||
ok(data);
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
//o.remove();
|
||||
nok(API.throwe(s));
|
||||
});
|
||||
});
|
||||
resolve(ret);
|
||||
}
|
||||
catch (e) {
|
||||
reject(__e(e));
|
||||
}
|
||||
});
|
||||
return o.trigger("click");
|
||||
});
|
||||
@ -1274,42 +1427,6 @@ namespace OS {
|
||||
o.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to trigger the global `loading`
|
||||
* event. This event should be triggered in the
|
||||
* beginning of a heavy task
|
||||
*
|
||||
* @export
|
||||
* @param {number} q message id, see [[mid]]
|
||||
* @param {string} p message string
|
||||
*/
|
||||
export function loading(q: number, p: string): void {
|
||||
announcer.trigger("loading", {
|
||||
id: q,
|
||||
data: { m: `${p}`, s: true },
|
||||
name: "OS",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to trigger the global `loaded`
|
||||
* event: This event should be triggered in the
|
||||
* end of a heavy task that has previously triggered
|
||||
* the `loading` event
|
||||
*
|
||||
* @export
|
||||
* @param {number} q the message id of the corresponding `loading` event
|
||||
* @param {string} p the message string
|
||||
* @param {string} m message status (`OK` of `FAIL`)
|
||||
*/
|
||||
export function loaded(q: number, p: string, m: string): void {
|
||||
announcer.trigger("loaded", {
|
||||
id: q,
|
||||
data: { m: `${m}: ${p}`, s: false },
|
||||
name: "OS",
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform an REST GET request
|
||||
*
|
||||
@ -1323,7 +1440,7 @@ namespace OS {
|
||||
* @returns {Promise<any>} a Promise on the requested data
|
||||
*/
|
||||
export function get(p: string, t: string = undefined): Promise<any> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
return API.Task(function (resolve, reject) {
|
||||
const conf: any = {
|
||||
type: "GET",
|
||||
url: p,
|
||||
@ -1331,15 +1448,11 @@ namespace OS {
|
||||
if (t) {
|
||||
conf.dataType = t;
|
||||
}
|
||||
const q = announcer.getMID();
|
||||
API.loading(q, p);
|
||||
return $.ajax(conf)
|
||||
.done(function (data) {
|
||||
API.loaded(q, p, "OK");
|
||||
return resolve(data);
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
API.loaded(q, p, "FAIL");
|
||||
return reject(API.throwe(s));
|
||||
});
|
||||
});
|
||||
@ -1395,7 +1508,7 @@ namespace OS {
|
||||
* @returns {Promise<void>} a promise on the result data
|
||||
*/
|
||||
export function requires(l: string, force: boolean = false): Promise<void> {
|
||||
return new Promise(async (resolve, reject) =>{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
if (!API.shared[l] || force) {
|
||||
const libfp = l.asFileHandle();
|
||||
@ -1461,8 +1574,8 @@ namespace OS {
|
||||
* Fetch the package meta-data from the server
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} Promise on a [[RequestResult]].
|
||||
* A success request result should contain a list of [[PackageMetaType]]
|
||||
* @returns {Promise<RequestResult>} Promise on a {@link RequestResult}.
|
||||
* A success request result should contain a list of {@link PackageMetaType}
|
||||
*/
|
||||
export function fetch(): Promise<RequestResult> {
|
||||
return API.handle.packages({
|
||||
@ -1508,7 +1621,7 @@ namespace OS {
|
||||
* Save the current user setting
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} promise on a {@link RequestResult}
|
||||
*/
|
||||
export function setting(): Promise<RequestResult> {
|
||||
return API.handle.setting();
|
||||
@ -1554,7 +1667,7 @@ namespace OS {
|
||||
* text in spotlight.
|
||||
*
|
||||
* This function will call all the search handles stored
|
||||
* in [[searchHandle]] and build the search result based
|
||||
* in {@link searchHandle} and build the search result based
|
||||
* on output of these handle
|
||||
*
|
||||
* @export
|
||||
@ -1567,11 +1680,11 @@ namespace OS {
|
||||
for (let k in searchHandle) {
|
||||
const ret = searchHandle[k](text);
|
||||
if (ret.length > 0) {
|
||||
ret.unshift({
|
||||
/*ret.unshift({
|
||||
text: k,
|
||||
class: "search-header",
|
||||
dataid: "header",
|
||||
});
|
||||
});*/
|
||||
r = r.concat(ret);
|
||||
}
|
||||
}
|
||||
@ -1579,7 +1692,7 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a search handle to the global [[searchHandle]]
|
||||
* Register a search handle to the global {@link searchHandle}
|
||||
*
|
||||
* @export
|
||||
* @param {string} name handle name string
|
||||
@ -1598,7 +1711,7 @@ namespace OS {
|
||||
* Set the current system locale: This function will
|
||||
* find and load the locale dictionary definition file in the
|
||||
* system asset resource, then trigger the global event
|
||||
* `systemlocalechange` to translated all translatable text
|
||||
* `SYSTEM-LOCALE-CHANGED` to translated all translatable text
|
||||
* to the target language
|
||||
*
|
||||
* @export
|
||||
@ -1612,7 +1725,7 @@ namespace OS {
|
||||
const d = await API.get(path, "json");
|
||||
OS.setting.system.locale = name;
|
||||
API.lang = d;
|
||||
announcer.trigger("systemlocalechange", name);
|
||||
announcer.ostrigger("SYSTEM-LOCALE-CHANGED", name);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
@ -1738,5 +1851,37 @@ namespace OS {
|
||||
});
|
||||
return o;
|
||||
}
|
||||
|
||||
/**
|
||||
* A watcher is a Proxy wrapper to an object
|
||||
*
|
||||
* It is used to automatically detect changes in the
|
||||
* target object and notify the change to a callback
|
||||
* handler
|
||||
*
|
||||
* @export
|
||||
* @param {Object} target object
|
||||
* @param {(obj: Object, key: string, value: any, path: any[]) => void} callback function
|
||||
* @returns {Proxy} the wrapper object
|
||||
*/
|
||||
export function watcher(target: GenericObject<any>, callback: (obj: Object, key: any, value: any, path: any[]) => void): Object {
|
||||
const create_handle_for = (path: any[]) => {
|
||||
return {
|
||||
get: (obj: Object, key: any) => {
|
||||
if (typeof obj[key] === "object" && obj[key] !== null) {
|
||||
return new Proxy(obj[key], create_handle_for(path.concat(key)));
|
||||
}
|
||||
return obj[key];
|
||||
},
|
||||
set: (obj: Object, prop: any, value: any) => {
|
||||
obj[prop] = value;
|
||||
callback(obj, prop, value, path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
};
|
||||
return new Proxy(target, create_handle_for([]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
202
src/core/db.ts
202
src/core/db.ts
@ -1,202 +0,0 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
export namespace API {
|
||||
/**
|
||||
* Simple Virtual Database (VDB) application API.
|
||||
*
|
||||
* This API abstracts and provides a standard way to
|
||||
* connect to a server-side relational database (e.g. sqlite).
|
||||
*
|
||||
* Each user when connected has their own database previously
|
||||
* created. All VDB operations related to that user will be
|
||||
* performed on this database.
|
||||
*
|
||||
* The creation of user database need to be managed by the server-side API.
|
||||
* The VDB API assumes that the database already exist. All operations
|
||||
* is performed in tables level
|
||||
*
|
||||
* @export
|
||||
* @class DB
|
||||
*/
|
||||
export class DB {
|
||||
/**
|
||||
* A table name on the user's database
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof DB
|
||||
*/
|
||||
private table: string;
|
||||
|
||||
/**
|
||||
*Creates an instance of DB.
|
||||
* @param {string} table table name
|
||||
* @memberof DB
|
||||
*/
|
||||
constructor(table: string) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to the current table. The input
|
||||
* data must conform to the table record format.
|
||||
*
|
||||
* On the server side, if the table doest not
|
||||
* exist yet, it should be created automatically
|
||||
* by inferring the data structure of the input
|
||||
* object
|
||||
*
|
||||
* @param {GenericObject<any>} d data object represents a current table record
|
||||
* @returns {Promise<API.RequestResult>}
|
||||
* @memberof DB
|
||||
*/
|
||||
save(d: GenericObject<any>): Promise<API.RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.dbquery("save", {
|
||||
table: this.table,
|
||||
data: d,
|
||||
});
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* delete record(s) from the current table by
|
||||
* a conditional object
|
||||
*
|
||||
* @param {*} c conditional object, c can be:
|
||||
*
|
||||
* * a `number`: the operation will delete the record with `id = c`
|
||||
* * a `string`: The SQL string condition that selects record to delete
|
||||
* * a conditional object represents a SQL condition statement as an object,
|
||||
* example: `pid = 10 AND cid = 2` is represented by:
|
||||
*
|
||||
* ```typescript
|
||||
* {
|
||||
* exp: {
|
||||
* "and": {
|
||||
* pid: 10,
|
||||
* cid: 2
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns {Promise<API.RequestResult>}
|
||||
* @memberof DB
|
||||
*/
|
||||
delete(
|
||||
c: GenericObject<any> | number | string
|
||||
): Promise<API.RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const rq: any = { table: this.table };
|
||||
if (!c || c === "") {
|
||||
reject(API.throwe("OS.DB: unknown condition"));
|
||||
}
|
||||
if (isNaN(c as number)) {
|
||||
rq.cond = c;
|
||||
} else {
|
||||
rq.id = c;
|
||||
}
|
||||
try {
|
||||
const r = await API.handle.dbquery("delete", rq);
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a record in the table by its primary key
|
||||
*
|
||||
* @param {number} id the primary key value
|
||||
* @returns {Promise<GenericObject<any>>} Promise on returned record data
|
||||
* @memberof DB
|
||||
*/
|
||||
get(id: number): Promise<GenericObject<any>> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.dbquery("get", {
|
||||
table: this.table,
|
||||
id: id,
|
||||
});
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r.result as GenericObject<any>);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find records by a condition
|
||||
*
|
||||
* @param {GenericObject<any>} cond conditional object
|
||||
*
|
||||
* a conditional object represents a SQL condition statement as an object,
|
||||
* example: `pid = 10 AND cid = 2 ORDER BY date DESC` is represented by:
|
||||
*
|
||||
* ```typescript
|
||||
* {
|
||||
* exp: {
|
||||
* "and": {
|
||||
* pid: 10,
|
||||
* cid: 2
|
||||
* }
|
||||
* },
|
||||
* order: {
|
||||
* date: "DESC"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @returns {Promise<GenericObject<any>[]>}
|
||||
* @memberof DB
|
||||
*/
|
||||
find(cond: GenericObject<any>): Promise<GenericObject<any>[]> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.dbquery("select", {
|
||||
table: this.table,
|
||||
cond,
|
||||
});
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r.result as GenericObject<any>[]);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
777
src/core/gui.ts
777
src/core/gui.ts
File diff suppressed because it is too large
Load Diff
@ -158,7 +158,7 @@ namespace OS {
|
||||
*
|
||||
* @export
|
||||
* @param {string} p a VFS file path e.g. home://test/
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
|
||||
* which contains an error or a list of FileInfoType
|
||||
*/
|
||||
export function scandir(p: string): Promise<RequestResult> {
|
||||
@ -203,7 +203,7 @@ namespace OS {
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS file path
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
|
||||
* which contains an error or an object of FileInfoType
|
||||
*/
|
||||
export function fileinfo(p: string): Promise<RequestResult> {
|
||||
@ -226,12 +226,12 @@ namespace OS {
|
||||
* - xml, html: the response is a XML/HTML object
|
||||
* - text: plain text
|
||||
*
|
||||
* @returns {Promise<any>} A promise on a [[RequestResult]]
|
||||
* which contains an error or an object of [[FileInfoType]]
|
||||
* @returns {Promise<any>} A promise on a {@link RequestResult}
|
||||
* which contains an error or an object of {@link FileInfoType}
|
||||
*/
|
||||
export function readfile(p: string, t: string): Promise<any> {
|
||||
const path = `${API.REST}/VFS/get/`;
|
||||
return API.get(path + p, t);
|
||||
return API.get(path + encodeURIComponent(p), t);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -240,7 +240,7 @@ namespace OS {
|
||||
* @export
|
||||
* @param {string} s VFS source file path
|
||||
* @param {string} d VFS destination file path
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
|
||||
* which contains an error or a success response
|
||||
*/
|
||||
export function move(s: string, d: string): Promise<RequestResult> {
|
||||
@ -253,7 +253,7 @@ namespace OS {
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS file path
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
|
||||
* which contains an error or a success response
|
||||
*/
|
||||
export function remove(p: string): Promise<RequestResult> {
|
||||
@ -278,7 +278,7 @@ namespace OS {
|
||||
*
|
||||
* @export
|
||||
* @param {PackageCommandType} d a package command of type PackageCommandType
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
|
||||
*/
|
||||
export function packages(
|
||||
d: PackageCommandType
|
||||
@ -292,7 +292,7 @@ namespace OS {
|
||||
*
|
||||
* @export
|
||||
* @param {string} d VFS destination directory path
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
|
||||
*/
|
||||
export function upload(d: string): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/upload`;
|
||||
@ -305,7 +305,7 @@ namespace OS {
|
||||
* @export
|
||||
* @param {string} p path to the VFS file
|
||||
* @param {string} d file data encoded in Base 64
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
|
||||
*/
|
||||
export function write(
|
||||
p: string,
|
||||
@ -371,8 +371,8 @@ namespace OS {
|
||||
* Check if a user is logged in
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
|
||||
* contains an error or a [[UserSettingType]] object
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult} that
|
||||
* contains an error or a {@link OS.setting.UserSettingType} object
|
||||
*/
|
||||
export function auth(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/user/auth`;
|
||||
@ -383,9 +383,9 @@ namespace OS {
|
||||
* Perform a login operation
|
||||
*
|
||||
* @export
|
||||
* @param {UserLoginType} d user data [[UserLoginType]]
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
|
||||
* contains an error or a [[UserSettingType]] object
|
||||
* @param {UserLoginType} d user data {@link UserLoginType}
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult} that
|
||||
* contains an error or a {@link OS.setting.UserSettingType} object
|
||||
*/
|
||||
export function login(d: UserLoginType): Promise<RequestResult> {
|
||||
const p = `${API.REST}/user/login`;
|
||||
@ -396,7 +396,7 @@ namespace OS {
|
||||
* Perform a logout operation
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
|
||||
*/
|
||||
export function logout(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/user/logout`;
|
||||
@ -407,7 +407,7 @@ namespace OS {
|
||||
* Save the current user settings
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
|
||||
*/
|
||||
export function setting(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/system/settings`;
|
||||
@ -415,56 +415,14 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the low level function of AntOS VDB API.
|
||||
* It requests the server API to perform some simple
|
||||
* SQL query.
|
||||
* Query the current versions of all system components
|
||||
*
|
||||
* @export
|
||||
* @param {string} cmd action to perform: save, delete, get, select
|
||||
* @param {GenericObject<any>} d data object of the request based on each action:
|
||||
* - save:
|
||||
* ```
|
||||
* { table: "table name", data: [record data object]}
|
||||
* ```
|
||||
* - get:
|
||||
* ```
|
||||
* { table: "table name", id: [record id]}
|
||||
* ```
|
||||
* - delete:
|
||||
* ```
|
||||
* { table: "table name", id: [record id]}
|
||||
* or
|
||||
* { table: "table name", cond: [conditional object]}
|
||||
* ```
|
||||
* - select:
|
||||
* ```
|
||||
* { table: "table name", cond: [conditional object]}
|
||||
* ```
|
||||
* @returns {Promise<RequestResult>} a promise of [[RequestResult]] on the
|
||||
* query data
|
||||
*
|
||||
* A conditional object represents a SQL condition statement as an object,
|
||||
* example: `pid = 10 AND cid = 2 ORDER BY date DESC`
|
||||
* ```
|
||||
* {
|
||||
* exp: {
|
||||
* "and": {
|
||||
* pid: 10,
|
||||
* cid: 2
|
||||
* }
|
||||
* },
|
||||
* order: {
|
||||
* date: "DESC"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
|
||||
*/
|
||||
export function dbquery(
|
||||
cmd: string,
|
||||
d: GenericObject<any>
|
||||
): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VDB/${cmd}`;
|
||||
return API.post(path, d);
|
||||
export function versions(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/system/version`;
|
||||
return API.get(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ namespace OS {
|
||||
| application.BaseApplication
|
||||
| application.BaseService;
|
||||
/**
|
||||
* Alias to all classes that extends [[BaseModel]]
|
||||
* Alias to all classes that extends {@link BaseModel}
|
||||
*/
|
||||
export type ModelTypeClass = {
|
||||
new <T extends BaseModel>(args: AppArgumentsType[]): T;
|
||||
@ -24,6 +24,10 @@ namespace OS {
|
||||
/**
|
||||
* All running processes is stored in this variables
|
||||
*/
|
||||
/**
|
||||
* Current active process ID
|
||||
*/
|
||||
export var pidactive: number = 0;
|
||||
export var processes: GenericObject<BaseModel[]> = {};
|
||||
/**
|
||||
* Create a new process of application or service
|
||||
@ -31,7 +35,7 @@ namespace OS {
|
||||
* @export
|
||||
* @param {string} app class name string
|
||||
* @param {ProcessTypeClass} cls prototype class
|
||||
* @param {GUI.AppArgumentsType[]} [args] process arguments
|
||||
* @param {AppArgumentsType[]} [args] process arguments
|
||||
* @returns {Promise<ProcessType>} a promise on the created process
|
||||
*/
|
||||
export function createProcess(
|
||||
@ -63,6 +67,10 @@ namespace OS {
|
||||
obj.birth = new Date().getTime();
|
||||
PM.pidalloc++;
|
||||
obj.pid = PM.pidalloc;
|
||||
obj.subscribe("SYSTEM-LOCALE-CHANGED", (d) => {
|
||||
obj.updateLocale(d.message as string);
|
||||
return obj.update();
|
||||
});
|
||||
PM.processes[app].push(obj);
|
||||
if (metaclass.type === ModelType.Application) {
|
||||
GUI.dock(
|
||||
@ -128,10 +136,28 @@ namespace OS {
|
||||
if (i >= 0) {
|
||||
if (application[app.name].type === ModelType.Application) {
|
||||
GUI.undock(app as application.BaseApplication);
|
||||
// save setting file if any
|
||||
if(PM.processes[app.name].length == 1)
|
||||
{
|
||||
const app_class = application[app.name] as typeof OS.application.BaseApplication;
|
||||
let file = `${app_class.meta.path}/.settings.json`.asFileHandle();
|
||||
if(file.protocol !== "home")
|
||||
{
|
||||
file = `home:///.antos/settings/${app.name}.json`.asFileHandle();
|
||||
}
|
||||
file.cache = app_class.setting_wdg;
|
||||
//file.cache = JSON.stringify(app_class.setting_wdg, undefined, 4);
|
||||
file
|
||||
.write("object")
|
||||
.catch((e) =>{
|
||||
return announcer.osinfo(
|
||||
__("Unable to save settings for application {0}: {1}", app.name, e.toString()));
|
||||
});
|
||||
}
|
||||
} else {
|
||||
GUI.detachservice(app as application.BaseService);
|
||||
}
|
||||
announcer.unregister(app);
|
||||
// announcer.unregister(app);
|
||||
delete PM.processes[app.name][i];
|
||||
PM.processes[app.name].splice(i, 1);
|
||||
}
|
||||
@ -149,7 +175,25 @@ namespace OS {
|
||||
if (!PM.processes[app]) {
|
||||
return;
|
||||
}
|
||||
PM.processes[app].map((a) => a.quit(force));
|
||||
const arr = PM.processes[app].map( e => e);
|
||||
for(const p of arr)
|
||||
{
|
||||
p.quit(force);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current active application
|
||||
* @export
|
||||
* @returns {BaseModel}
|
||||
*/
|
||||
export function getActiveApp():BaseModel
|
||||
{
|
||||
if(PM.pidactive === 0)
|
||||
{
|
||||
return undefined;
|
||||
}
|
||||
return PM.appByPid(PM.pidactive);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -308,7 +308,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Package repositories setting.
|
||||
* This configuration is used by [[MarketPlace]]
|
||||
* This configuration is used by {@link OS.application.MarketPlace}
|
||||
* for package management
|
||||
*
|
||||
* @type {{
|
||||
@ -398,7 +398,7 @@ namespace OS {
|
||||
*/
|
||||
export function resetSetting(): void {
|
||||
setting.desktop = {
|
||||
path: "home://.desktop",
|
||||
path: "home://.antos/desktop",
|
||||
menu: [],
|
||||
showhidden: false,
|
||||
};
|
||||
@ -476,13 +476,13 @@ namespace OS {
|
||||
menu: [],
|
||||
packages: {},
|
||||
pkgpaths: {
|
||||
user: "home://.packages",
|
||||
user: "home://.antos/packages",
|
||||
system: "os://packages",
|
||||
},
|
||||
repositories: [],
|
||||
startup: {
|
||||
apps: [],
|
||||
services: ["Syslog/PushNotification", "Syslog/Calendar"],
|
||||
services: ["SystemServices/PushNotification", "SystemServices/Calendar"],
|
||||
pinned: [],
|
||||
},
|
||||
};
|
||||
@ -535,7 +535,7 @@ namespace OS {
|
||||
setting.system.repositories = [
|
||||
{
|
||||
text: "Github",
|
||||
url: "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/packages.json"
|
||||
url: "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/packages.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ namespace OS {
|
||||
* @type {application.BaseApplication}
|
||||
* @memberof AppDockItemType
|
||||
*/
|
||||
app: application.BaseApplication;
|
||||
app?: application.BaseApplication;
|
||||
|
||||
/**
|
||||
* Reference to the DOM element of
|
||||
@ -38,15 +38,12 @@ namespace OS {
|
||||
*/
|
||||
export class AppDockTag extends AFXTag {
|
||||
/**
|
||||
* variable holds the application select event
|
||||
* callback handle
|
||||
*
|
||||
* Cache of touch event
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<any>}
|
||||
* @memberof AppDockTag
|
||||
* @meberof AppDockTag
|
||||
*/
|
||||
private _onappselect: TagEventCallback<any>;
|
||||
|
||||
private _previous_touch: {x: number, y: number};
|
||||
/**
|
||||
* Items data of the dock
|
||||
*
|
||||
@ -72,7 +69,6 @@ namespace OS {
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._onappselect = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -98,9 +94,6 @@ namespace OS {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i !== -1) {
|
||||
$(this.items[i].domel).attr("tooltip", `cr:${app.title()}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -111,6 +104,8 @@ namespace OS {
|
||||
*/
|
||||
protected init(): void {
|
||||
this._items = [];
|
||||
|
||||
this._previous_touch = {x: 0, y:0};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,17 +150,23 @@ namespace OS {
|
||||
let el = undefined;
|
||||
for (let it of this.items) {
|
||||
it.app.blur();
|
||||
$(it.domel).removeClass();
|
||||
$(it.domel).removeClass("selected");
|
||||
if (v && v === it.app) {
|
||||
el = it;
|
||||
}
|
||||
}
|
||||
this._selectedItem = el;
|
||||
if (!el) {
|
||||
PM.pidactive = 0;
|
||||
return;
|
||||
}
|
||||
$(el.domel).addClass("selected");
|
||||
($(Ant.OS.GUI.workspace)[0] as FloatListTag).unselect();
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: v
|
||||
};
|
||||
announcer.trigger("APP-SELECT", evt);
|
||||
}
|
||||
|
||||
get selectedApp(): application.BaseApplication {
|
||||
@ -187,6 +188,63 @@ namespace OS {
|
||||
return this._selectedItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a button to the dock
|
||||
*
|
||||
* @private
|
||||
* @param {string} [name] associated application name
|
||||
* @param {AppDockItemType} [item] dock item
|
||||
* @param {boolean} [pinned] the button is pinned to the dock ?
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
private add_button(name:string, item: AppDockItemType, pinned: boolean = false): void
|
||||
{
|
||||
const collection = $(this).children().filter((i, e) => {
|
||||
return (e as ButtonTag).data.name == name;
|
||||
});
|
||||
if(collection.length > 0)
|
||||
{
|
||||
(collection[0] as ButtonTag).data.pinned = true;
|
||||
item.domel = collection[0] as ButtonTag;
|
||||
return;
|
||||
}
|
||||
const el = $("<afx-button>");
|
||||
const bt = el[0] as ButtonTag;
|
||||
el.appendTo(this);
|
||||
el[0].uify(this.observable);
|
||||
bt.set(item);
|
||||
bt.data = {
|
||||
name: name,
|
||||
pinned: pinned
|
||||
};
|
||||
item.domel = bt;
|
||||
bt.onbtclick = (e) => {
|
||||
e.data.stopPropagation();
|
||||
this
|
||||
.handleAppSelect(bt);
|
||||
};
|
||||
}
|
||||
|
||||
private update_button(el: ButtonTag): void
|
||||
{
|
||||
const collection = this.items.filter(it => it.app.name == el.data.name);
|
||||
if(collection.length == 1)
|
||||
{
|
||||
$(el).removeClass("plural");
|
||||
}
|
||||
if(collection.length == 0)
|
||||
{
|
||||
if(el.data.pinned)
|
||||
{
|
||||
$(el).removeClass();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(el).remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a new application process is created, this function
|
||||
* will be called to add new application entry to the dock.
|
||||
@ -196,25 +254,84 @@ namespace OS {
|
||||
* @param {AppDockItemType} item an application dock item entry
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
newapp(item: AppDockItemType): void {
|
||||
addapp(item: AppDockItemType): void {
|
||||
const collection = this.items.filter(it => it.app.name == item.app.name);
|
||||
let bt = undefined;
|
||||
if(collection.length == 0)
|
||||
{
|
||||
this.add_button(item.app.name, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
bt = collection[0].domel;
|
||||
item.domel = bt;
|
||||
$(bt).addClass("plural");
|
||||
}
|
||||
this.items.push(item);
|
||||
const el = $("<afx-button>");
|
||||
const bt = el[0] as ButtonTag;
|
||||
el.appendTo(this);
|
||||
el[0].uify(this.observable);
|
||||
bt.set(item);
|
||||
bt.data = item.app;
|
||||
item.domel = bt;
|
||||
$(bt).attr("tooltip", `cr:${item.app.title()}`);
|
||||
bt.onbtclick = (e) => {
|
||||
e.id = this.aid;
|
||||
//e.data.item = item;
|
||||
this._onappselect(e);
|
||||
item.app.show();
|
||||
};
|
||||
this.selectedApp = item.app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the application selection action
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise<application.BaseApplication>}
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
private handleAppSelect(bt: ButtonTag): Promise<application.BaseApplication>
|
||||
{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const name = bt.data.name as string;
|
||||
const collection = this.items.filter(it => it.app.name == name);
|
||||
const ctxmenu = $("#contextmenu")[0] as tag.StackMenuTag;
|
||||
ctxmenu.hide();
|
||||
if(collection.length == 0)
|
||||
{
|
||||
resolve(await GUI.launch(name, []) as application.BaseApplication);
|
||||
return;
|
||||
}
|
||||
if(collection.length == 1)
|
||||
{
|
||||
if(PM.getActiveApp() == collection[0].app)
|
||||
{
|
||||
collection[0].app.hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
collection[0].app.show();
|
||||
}
|
||||
resolve(collection[0].app);
|
||||
return;
|
||||
}
|
||||
// show the context menu containning a list of application to select
|
||||
const menu_data = collection.map(e => {
|
||||
return {
|
||||
text: (e.app.scheme as WindowTag).apptitle,
|
||||
icon: e.icon,
|
||||
iconclass: e.iconclass,
|
||||
app: e.app
|
||||
};
|
||||
});
|
||||
const offset = $(bt).offset();
|
||||
ctxmenu.nodes = menu_data;
|
||||
$(ctxmenu)
|
||||
.css("left", offset.left)
|
||||
.css("bottom", $(this).height());
|
||||
ctxmenu.onmenuselect = (e) =>
|
||||
{
|
||||
e.data.item.data.app.show();
|
||||
resolve(e.data.item.data.app);
|
||||
}
|
||||
ctxmenu.show();
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete and application entry from the dock.
|
||||
* This function will be called when an application
|
||||
@ -235,9 +352,11 @@ namespace OS {
|
||||
}
|
||||
|
||||
if (i !== -1) {
|
||||
const appName = this.items[i].app.name;
|
||||
const el = this.items[i].domel as ButtonTag;
|
||||
delete this.items[i].app;
|
||||
this.items.splice(i, 1);
|
||||
$($(this).children()[i]).remove();
|
||||
this.update_button(el);
|
||||
}
|
||||
}
|
||||
|
||||
@ -252,36 +371,39 @@ namespace OS {
|
||||
if (e.target === this) {
|
||||
return;
|
||||
}
|
||||
m.hide();
|
||||
const bt = ($(e.target).closest(
|
||||
"afx-button"
|
||||
)[0] as any) as ButtonTag;
|
||||
const app = bt.data as application.BaseApplication;
|
||||
m.items = [
|
||||
const name = bt.data.name as string;
|
||||
const collection = this.items.filter(it => it.app.name == name);
|
||||
m.nodes = [
|
||||
{ text: "__(New window)", dataid: "new" },
|
||||
{ text: "__(Show)", dataid: "show" },
|
||||
{ text: "__(Hide)", dataid: "hide" },
|
||||
{ text: "__(Close)", dataid: "quit" },
|
||||
{ text: "__(Hide all)", dataid: "hide" },
|
||||
{ text: "__(Close all)", dataid: "quit" },
|
||||
];
|
||||
m.onmenuselect = function (evt) {
|
||||
const item = evt.data.item.data;
|
||||
if (app[item.dataid]) {
|
||||
return app[item.dataid]();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (item.dataid) {
|
||||
case "new":
|
||||
GUI.launch(app.name, []);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (evt.data.item.data.dataid) {
|
||||
case "new":
|
||||
GUI.launch(bt.data.name as string, []);
|
||||
break;
|
||||
case "hide":
|
||||
collection.forEach((el,_) => el.app.hide());
|
||||
break;
|
||||
case "quit":
|
||||
collection.forEach((el,_) => el.app.quit());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
return m.show(e);
|
||||
const offset = $(bt).offset();
|
||||
$(m)
|
||||
.css("left", offset.left)
|
||||
.css("bottom", $(this).height());
|
||||
return m.show();
|
||||
};
|
||||
announcer.trigger("sysdockloaded", undefined);
|
||||
announcer.trigger("SYS-DOCK-LOADED", undefined);
|
||||
GUI.bindKey("CTRL-ALT-2", (e) =>{
|
||||
if(!this.items || this.items.length === 0)
|
||||
{
|
||||
@ -317,6 +439,60 @@ namespace OS {
|
||||
}
|
||||
this.items[index].app.trigger("focus");
|
||||
});
|
||||
|
||||
this.addEventListener("touchstart", e => {
|
||||
this._previous_touch.x = e.touches[0].pageX;
|
||||
this._previous_touch.y = e.touches[0].pageY;
|
||||
}, { passive: true});
|
||||
this.addEventListener("touchmove", e => {
|
||||
const offset = {x:0, y:0};
|
||||
offset.x = this._previous_touch.x - e.touches[0].pageX ;
|
||||
offset.y = this._previous_touch.y - e.touches[0].pageY;
|
||||
(this as any).scrollLeft += offset.x;
|
||||
this._previous_touch.x = e.touches[0].pageX;
|
||||
this._previous_touch.y = e.touches[0].pageY;
|
||||
}, { passive: true});
|
||||
this.addEventListener("wheel", (evt)=>{
|
||||
(this as any).scrollLeft += (evt as WheelEvent).deltaY;
|
||||
},{ passive: true});
|
||||
announcer.on("APP-PINNED", (_) => {
|
||||
this.refresh_pinned_app();
|
||||
});
|
||||
this.refresh_pinned_app();
|
||||
}
|
||||
/**
|
||||
* refresh the pinned application list
|
||||
*
|
||||
* @private
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
private refresh_pinned_app(): void
|
||||
{
|
||||
if(!setting.system.startup.pinned)
|
||||
return;
|
||||
// unpin all application on the dock
|
||||
$(this).children().each((i,e) => {
|
||||
(e as ButtonTag).data.pinned = false;
|
||||
});
|
||||
// pin all setting application on the dock
|
||||
setting.system.startup.pinned
|
||||
.filter((el) =>{
|
||||
const app = setting.system.packages[el];
|
||||
return app && app.app
|
||||
})
|
||||
.forEach((name) => {
|
||||
const app = setting.system.packages[name];
|
||||
const item = {
|
||||
icon: app.icon,
|
||||
iconclass: app.iconclass,
|
||||
app: undefined
|
||||
};
|
||||
this.add_button(name, item, true);
|
||||
});
|
||||
// update to remove the button
|
||||
$(this).children().each((i,e) => {
|
||||
this.update_button(e as ButtonTag);
|
||||
});
|
||||
}
|
||||
}
|
||||
define("afx-apps-dock", AppDockTag);
|
||||
|
@ -21,10 +21,25 @@ namespace OS {
|
||||
/**
|
||||
* Custom user data
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @type {any}
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
data: GenericObject<any>;
|
||||
private _data: any;
|
||||
|
||||
/**
|
||||
* Custom user data setter/gettter
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set data(v: any)
|
||||
{
|
||||
this._data = v;
|
||||
this.set(v);
|
||||
}
|
||||
get data(): any
|
||||
{
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
*Creates an instance of ButtonTag.
|
||||
@ -64,6 +79,27 @@ namespace OS {
|
||||
$(this).attr("iconclass", v);
|
||||
(this.refs.label as LabelTag).iconclass = v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the icon class on the right side of the button, this property
|
||||
* allows to style the button icon using CSS
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set iconclass$(v: string) {
|
||||
$(this).attr("iconclass_end", v);
|
||||
(this.refs.label as LabelTag).iconclass$ = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSS class of the label icon on the right side
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set iconclass_end(v: string) {
|
||||
this.iconclass$ = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the text of the button
|
||||
|
@ -113,8 +113,20 @@ namespace OS {
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.prev).on("click",(e) => this.prevmonth());
|
||||
$(this.refs.next).on("click",(e) => this.nextmonth());
|
||||
(this.refs.prev as ButtonTag).iconclass = "fa fa-angle-left";
|
||||
(this.refs.next as ButtonTag).iconclass = "fa fa-angle-right";
|
||||
(this.refs.prev_year as ButtonTag).iconclass = "fa fa-angle-left";
|
||||
(this.refs.next_year as ButtonTag).iconclass = "fa fa-angle-right";
|
||||
(this.refs.prev as ButtonTag).onbtclick = (e) => this.prevmonth();
|
||||
(this.refs.next as ButtonTag).onbtclick = (e) => this.nextmonth();
|
||||
(this.refs.next_year as ButtonTag).onbtclick = (e) => {
|
||||
this._year++;
|
||||
this.calendar(new Date(this._year, this._month, 1));
|
||||
}
|
||||
(this.refs.prev_year as ButtonTag).onbtclick = (e) => {
|
||||
this._year--;
|
||||
this.calendar(new Date(this._year, this._month, 1));
|
||||
}
|
||||
const grid = this.refs.grid as GridViewTag;
|
||||
grid.header = [
|
||||
{ text: "__(Sun)" },
|
||||
@ -138,7 +150,7 @@ namespace OS {
|
||||
* This function triggers the date select event
|
||||
*
|
||||
* @private
|
||||
* @param {TagEventType} e AFX tag event data [[TagEventType]]
|
||||
* @param {TagEventType} e AFX tag event data {@link TagEventType}
|
||||
* @returns {void}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
@ -249,6 +261,8 @@ namespace OS {
|
||||
];
|
||||
const this_month = new Date(this._year, this._month, 1);
|
||||
const next_month = new Date(this._year, this._month + 1, 1);
|
||||
const grid = this.refs.grid as GridViewTag;
|
||||
grid.rows = [];
|
||||
// Find out when this month starts and ends.
|
||||
const first_week_day = this_month.getDay();
|
||||
const days_in_this_month = Math.round(
|
||||
@ -292,18 +306,16 @@ namespace OS {
|
||||
week_day++;
|
||||
}
|
||||
for (
|
||||
let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2;
|
||||
asc2 ? i <= end2 : i >= end2;
|
||||
asc2 ? i++ : i--
|
||||
let i = 0; i < 7 - row.length; i++
|
||||
) {
|
||||
row.push({ text: "" });
|
||||
}
|
||||
rows.push(row);
|
||||
const grid = this.refs.grid as GridViewTag;
|
||||
grid.rows = rows;
|
||||
(this.refs.mlbl as LabelTag).text = `${
|
||||
months[this._month]
|
||||
} ${this._year}`;
|
||||
}`;
|
||||
(this.refs.ylbl as LabelTag).text = `${this._year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -319,9 +331,13 @@ namespace OS {
|
||||
el: "div",
|
||||
ref: "ctrl",
|
||||
children: [
|
||||
{ el: "i", class: "prevmonth", ref: "prev" },
|
||||
{ el: "afx-button", ref: "prev" },
|
||||
{ el: "afx-label", ref: "mlbl" },
|
||||
{ el: "i", class: "nextmonth", ref: "next" },
|
||||
{ el: "afx-button", ref: "next" },
|
||||
{ el: "div"},
|
||||
{ el: "afx-button", ref: "prev_year" },
|
||||
{ el: "afx-label", ref: "ylbl" },
|
||||
{ el: "afx-button", ref: "next_year" },
|
||||
],
|
||||
},
|
||||
{ el: "afx-grid-view", ref: "grid" },
|
||||
|
292
src/core/tags/DesktopTag.ts
Normal file
292
src/core/tags/DesktopTag.ts
Normal file
@ -0,0 +1,292 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
|
||||
/**
|
||||
* Meta tag that represents the virtual desktop environment.
|
||||
* In near future, we may have multiple virtual desktop environments.
|
||||
* Each desktop environment has a simple file manager and a window
|
||||
* manager that render the window in a specific order.
|
||||
*
|
||||
* @export
|
||||
* @class DesktopTag
|
||||
* @extends {FloatListTag}
|
||||
*/
|
||||
export class DesktopTag extends FloatListTag {
|
||||
|
||||
|
||||
/**
|
||||
* internal handle to the desktop file location
|
||||
*
|
||||
* @private
|
||||
* @type {API.VFS.BaseFileHandle}
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
private file: API.VFS.BaseFileHandle;
|
||||
|
||||
/**
|
||||
* local observer that detect if a new child element is
|
||||
* added or removed
|
||||
*
|
||||
* @private
|
||||
* @type {MutationObserver}
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
private observer: MutationObserver;
|
||||
|
||||
/**
|
||||
* Internal list of the current opened window
|
||||
*
|
||||
* @private
|
||||
* @type {Set<WindowTag>}
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
private window_list: Set<WindowTag>;
|
||||
|
||||
/**
|
||||
* Creates an instance of DesktopTag.
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this.observer = undefined;
|
||||
this.window_list = new Set<WindowTag>();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mount the virtual desktop to the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
/**
|
||||
* TRICKY HACK
|
||||
* When focusing on a window which overflows the desktop,
|
||||
* the desktop scrolls automatically to bottom,
|
||||
* even when `overflow: hidden` is set on CSS.
|
||||
*
|
||||
* The following event listener prevents
|
||||
* the desktop to scroll down in this case
|
||||
*/
|
||||
$(this).on("scroll", (e) =>{
|
||||
if(this.scrollTop != 0)
|
||||
this.scrollTop = 0;
|
||||
});
|
||||
if(this.observer)
|
||||
{
|
||||
this.observer.disconnect();
|
||||
this.observer = undefined;
|
||||
}
|
||||
this.observer = new MutationObserver((mutations_list) =>{
|
||||
mutations_list.forEach((mutation) => {
|
||||
mutation.removedNodes.forEach((removed_node) =>{
|
||||
if((removed_node as HTMLElement).tagName === "AFX-APP-WINDOW")
|
||||
{
|
||||
this.window_list.delete(removed_node as WindowTag);
|
||||
}
|
||||
});
|
||||
mutation.addedNodes.forEach((added_node) =>{
|
||||
if((added_node as HTMLElement).tagName === "AFX-APP-WINDOW")
|
||||
{
|
||||
this.selectWindow(added_node as WindowTag);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
this.observer.observe(this, { subtree: false, childList: true });
|
||||
|
||||
this.onready = (_) => {
|
||||
this.observable = OS.announcer.observable;
|
||||
window.onresize = () => {
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: {
|
||||
width: $(this).width(),
|
||||
height: $(this).height()
|
||||
}
|
||||
}
|
||||
announcer.trigger("DESKTOP-RESIZE", evt);
|
||||
this.calibrate();
|
||||
};
|
||||
|
||||
this.onlistselect = (d: TagEventType<tag.ListItemEventData>) => {
|
||||
GUI.systemDock().selectedApp = null;
|
||||
};
|
||||
|
||||
this.onlistdbclick = (d: TagEventType<tag.ListItemEventData>) => {
|
||||
GUI.systemDock().selectedApp = null;
|
||||
const it = this.selectedItem;
|
||||
return GUI.openWith(it.data as AppArgumentsType);
|
||||
};
|
||||
|
||||
//($ "#workingenv").on "click", (e) ->
|
||||
// desktop[0].set "selected", -1
|
||||
|
||||
$(this).on("click", (e) => {
|
||||
let el: any = $(e.target).closest("afx-app-window")[0];
|
||||
if (el) {
|
||||
return;
|
||||
}
|
||||
el = $(e.target).parent();
|
||||
if (!(el.length > 0)) {
|
||||
return;
|
||||
}
|
||||
el = el.parent();
|
||||
if (!(el.length > 0)) {
|
||||
return;
|
||||
}
|
||||
if (el[0] !== this) {
|
||||
return;
|
||||
}
|
||||
this.unselect();
|
||||
GUI.systemDock().selectedApp = null;
|
||||
});
|
||||
|
||||
this.contextmenuHandle = (e, m) => {
|
||||
if (e.target.tagName.toUpperCase() === "UL") {
|
||||
this.unselect();
|
||||
}
|
||||
GUI.systemDock().selectedApp = null;
|
||||
let menu = [
|
||||
{ text: __("Open"), dataid: "desktop-open" } as GUI.BasicItemType,
|
||||
{ text: __("Refresh"), dataid: "desktop-refresh" } as GUI.BasicItemType,
|
||||
];
|
||||
menu = menu.concat(setting.desktop.menu.map(e => e));
|
||||
m.nodes = menu;
|
||||
m.onmenuselect = (evt) => {
|
||||
if (!evt.data || !evt.data.item) return;
|
||||
const item = evt.data.item.data;
|
||||
switch (item.dataid) {
|
||||
case "desktop-open":
|
||||
var it = this.selectedItem;
|
||||
if (it) {
|
||||
return GUI.openWith(
|
||||
it.data as AppArgumentsType
|
||||
);
|
||||
}
|
||||
let arg = setting.desktop.path.asFileHandle() as AppArgumentsType;
|
||||
arg.mime = "dir";
|
||||
arg.type = "dir";
|
||||
return GUI.openWith(arg);
|
||||
case "desktop-refresh":
|
||||
return this.refresh();
|
||||
default:
|
||||
if (item.app) {
|
||||
return GUI.launch(item.app, item.args);
|
||||
}
|
||||
}
|
||||
};
|
||||
return m.show(e);
|
||||
};
|
||||
|
||||
this.refresh();
|
||||
announcer.on("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
|
||||
if (["read", "publish", "download"].includes(d.message as string)) {
|
||||
return;
|
||||
}
|
||||
if (d.u_data.hash() === this.file.hash() || d.u_data.parent().hash() === this.file.hash()) {
|
||||
return this.refresh();
|
||||
}
|
||||
});
|
||||
return announcer.ostrigger("DESKTOP-LOADED", undefined);
|
||||
};
|
||||
super.mount();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display all files and folders in the specific desktop location
|
||||
*
|
||||
* @return {*} {Promise<any>}
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
|
||||
refresh(): Promise<any> {
|
||||
return new Promise<any>(async (resolve, reject) => {
|
||||
try {
|
||||
this.file = setting.desktop.path.asFileHandle();
|
||||
await this.file.onready();
|
||||
const d = await this.file.read();
|
||||
if (d.error) {
|
||||
throw new Error(d.error);
|
||||
}
|
||||
const items = [];
|
||||
$.each(d.result, function (i, v) {
|
||||
if (v.filename[0] === "." && !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);
|
||||
});
|
||||
this.data = items;
|
||||
this.calibrate();
|
||||
}
|
||||
catch (err) {
|
||||
announcer.osfail(err.toString(), err);
|
||||
reject(__e(err));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove this element from its parent
|
||||
*
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
remove(): void {
|
||||
if(this.observer)
|
||||
{
|
||||
this.observer.disconnect();
|
||||
}
|
||||
super.remove();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Active a window above all other windows
|
||||
*
|
||||
* @private
|
||||
* @param {WindowTag} win
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
private selectWindow(win: WindowTag)
|
||||
{
|
||||
if(this.window_list.has(win))
|
||||
{
|
||||
this.window_list.delete(win);
|
||||
}
|
||||
else
|
||||
{
|
||||
win.observable.on("focused",(_)=>{
|
||||
this.selectWindow(win);
|
||||
});
|
||||
}
|
||||
this.window_list.add(win);
|
||||
this.render();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Render all windows in order from bottom to top
|
||||
*
|
||||
* @private
|
||||
* @memberof DesktopTag
|
||||
*/
|
||||
private render(){
|
||||
let zindex = 10;
|
||||
for(let win of this.window_list)
|
||||
{
|
||||
$(win).css("z-index", zindex++);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
define("afx-desktop", DesktopTag);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,13 +28,22 @@ namespace OS {
|
||||
private _onfileopen: TagEventCallback<API.FileInfoType>;
|
||||
|
||||
/**
|
||||
* Reference to the currently selected file meta-data
|
||||
* placeholder for directory changed event callback
|
||||
*
|
||||
* @private
|
||||
* @type {API.FileInfoType}
|
||||
* @type {TagEventCallback<API.VFS.BaseFileHandle>}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _selectedFile: API.FileInfoType;
|
||||
private _ondirchanged: TagEventCallback<API.VFS.BaseFileHandle>;
|
||||
|
||||
/**
|
||||
* Reference to the all selected files meta-datas
|
||||
*
|
||||
* @private
|
||||
* @type {API.FileInfoType[]}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _selectedFiles: API.FileInfoType[];
|
||||
|
||||
/**
|
||||
* Data placeholder of the current working directory
|
||||
@ -58,10 +67,10 @@ namespace OS {
|
||||
* Header definition of the widget grid view
|
||||
*
|
||||
* @private
|
||||
* @type {(GenericObject<string | number>[])}
|
||||
* @type {(GenericObject<any>[])}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _header: GenericObject<string | number>[];
|
||||
private _header: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
* placeholder for the user-specified meta-data fetch function
|
||||
@ -92,10 +101,57 @@ namespace OS {
|
||||
this.chdir = true;
|
||||
this.view = "list";
|
||||
this._onfileopen = this._onfileselect = (e) => { };
|
||||
this._ondirchanged = (e) => { };
|
||||
this._selectedFiles = [];
|
||||
const fn = function(r1, r2, i) {
|
||||
let t1 = r1[i].text;
|
||||
let t2 = r2[i].text;
|
||||
if(!t1 || !t2) return 0;
|
||||
if(i == 1)
|
||||
{
|
||||
// sort by date
|
||||
t1 = new Date(t1);
|
||||
t2 = new Date(t2);
|
||||
}
|
||||
else if(i==2)
|
||||
{
|
||||
// sort by size
|
||||
t1 = parseInt(t1);
|
||||
t2 = parseInt(t2);
|
||||
}
|
||||
else
|
||||
{
|
||||
// sort by name
|
||||
t1 = t1.toString().toLowerCase();
|
||||
t2 = t2.toString().toLowerCase();
|
||||
}
|
||||
if(this.desc)
|
||||
{
|
||||
if(t1 < t2) { return -1; }
|
||||
if(t1 > t2) { return 1; }
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if(t1 > t2) { return -1; }
|
||||
if(t1 < t2) { return 1; };
|
||||
|
||||
}
|
||||
return 0;
|
||||
};
|
||||
this._header = [
|
||||
{ text: "__(File name)" },
|
||||
{ text: "__(Type)" },
|
||||
{ text: "__(Size)" },
|
||||
{
|
||||
text: "__(File name)",
|
||||
sort: fn
|
||||
},
|
||||
{
|
||||
text: "__(Modified)",
|
||||
sort: fn
|
||||
},
|
||||
{
|
||||
text: "__(Size)",
|
||||
sort: fn
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -111,7 +167,7 @@ namespace OS {
|
||||
/**
|
||||
* set the function that allows to fetch file entries.
|
||||
* This handle function should return a promise on
|
||||
* an arry of [[API.FileInfoType]]
|
||||
* an arry of {@link API.FileInfoType}
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
@ -122,7 +178,7 @@ namespace OS {
|
||||
/**
|
||||
* set the callback handle for the file select event.
|
||||
* The parameter of the callback should be an object
|
||||
* of type [[TagEventType]]<T> with the data type `T` is [[API.FileInfoType]]
|
||||
* of type {@link TagEventType}<T> with the data type `T` is {@link API.FileInfoType}
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
@ -130,10 +186,21 @@ namespace OS {
|
||||
this._onfileselect = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the callback handle for the directory changed event.
|
||||
* The parameter of the callback should be an object
|
||||
* of type {@link TagEventType}<T> with the data type `T` is {@link API.VFS.BaseFileHandle}
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set onchdir(e: TagEventCallback<API.VFS.BaseFileHandle>) {
|
||||
this._ondirchanged = e;
|
||||
}
|
||||
|
||||
/**
|
||||
set the callback handle for the file open event.
|
||||
* The parameter of the callback should be an object
|
||||
* of type [[TagEventType]]<T> with the data type `T` is [[API.FileInfoType]]
|
||||
* of type {@link TagEventType}<T> with the data type `T` is {@link API.FileInfoType}
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
@ -168,7 +235,7 @@ namespace OS {
|
||||
*
|
||||
* Turn on/off the changing current working directory feature
|
||||
* of the widget when a directory is double clicked. If enabled,
|
||||
* the widget will use the configured [[fetch]] function to query
|
||||
* the widget will use the configured {@link fetch} function to query
|
||||
* the content of the selected directory
|
||||
*
|
||||
* Getter:
|
||||
@ -227,6 +294,28 @@ namespace OS {
|
||||
return this.hasattr("showhidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Allow multiple selection on file view
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the multiselection is actived
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set multiselect(v: boolean) {
|
||||
this.attsw(v, "multiselect");
|
||||
(this.refs.listview as ListViewTag).multiselect = v;
|
||||
(this.refs.gridview as GridViewTag).multiselect = v;
|
||||
}
|
||||
get multiselect(): boolean {
|
||||
return this.hasattr("multiselect");
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Get the current selected file
|
||||
*
|
||||
@ -235,7 +324,21 @@ namespace OS {
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
get selectedFile(): API.FileInfoType {
|
||||
return this._selectedFile;
|
||||
if(this._selectedFiles.length == 0)
|
||||
return undefined;
|
||||
return this._selectedFiles[this._selectedFiles.length - 1];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get all selected files
|
||||
*
|
||||
* @readonly
|
||||
* @type {API.FileInfoType[]}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
get selectedFiles(): API.FileInfoType[] {
|
||||
return this._selectedFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -243,7 +346,7 @@ namespace OS {
|
||||
*
|
||||
* Set the path of the current working directory.
|
||||
* When called the widget will refresh the current
|
||||
* working directory using the configured [[fetch]]
|
||||
* working directory using the configured {@link fetch}
|
||||
* function
|
||||
*
|
||||
* Getter:
|
||||
@ -270,6 +373,9 @@ namespace OS {
|
||||
if (this.status) {
|
||||
(this.refs.status as LabelTag).text = " ";
|
||||
}
|
||||
const evt = { id: this.aid, data: v.asFileHandle() };
|
||||
this._ondirchanged(evt);
|
||||
this.observable.trigger("chdir", evt);
|
||||
})
|
||||
.catch((e: Error) =>
|
||||
announcer.oserror(e.toString(), e)
|
||||
@ -286,7 +392,7 @@ namespace OS {
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set data(v: API.FileInfoType[]) {
|
||||
set data(v: API.FileInfoType[]) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
@ -305,11 +411,21 @@ namespace OS {
|
||||
*/
|
||||
set ondragndrop(
|
||||
v: TagEventCallback<
|
||||
DnDEventDataType<TreeViewTag | ListViewItemTag>
|
||||
DnDEventDataType<TreeViewTag | ListViewItemTag | GridCellPrototype>
|
||||
>
|
||||
) {
|
||||
(this.refs.treeview as TreeViewTag).ondragndrop = v;
|
||||
(this.refs.listview as ListViewTag).ondragndrop = v;
|
||||
(this.refs.gridview as GridViewTag).ondragndrop = (e) => {
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: {
|
||||
from: e.data.from.map(x => x.data[0].domel),
|
||||
to: e.data.to.data[0].domel
|
||||
}
|
||||
};
|
||||
v(evt);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -365,7 +481,7 @@ namespace OS {
|
||||
let h = $(this).outerHeight();
|
||||
const w = $(this).width();
|
||||
if (this.status) {
|
||||
h -= $(this.refs.status).height() + 10;
|
||||
h -= $(this.refs.status).height();
|
||||
}
|
||||
$(this.refs.listview).css("height", h + "px");
|
||||
$(this.refs.gridview).css("height", h + "px");
|
||||
@ -388,12 +504,15 @@ namespace OS {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return;
|
||||
}
|
||||
v.text = v.filename;
|
||||
if(!v.text)
|
||||
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;
|
||||
if(v.icon)
|
||||
v.iconclass = undefined;
|
||||
items.push(v);
|
||||
});
|
||||
(this.refs.listview as ListViewTag).data = items;
|
||||
@ -412,12 +531,19 @@ namespace OS {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return;
|
||||
}
|
||||
v.text = v.filename;
|
||||
v.iconclass = v.iconclass ? v.iconclass : v.type;
|
||||
if(v.icon)
|
||||
v.iconclass = undefined;
|
||||
const row = [
|
||||
v,
|
||||
{
|
||||
text: v.mime,
|
||||
text: v.filename,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
path: v.path,
|
||||
data: v
|
||||
},
|
||||
{
|
||||
text: v.mtime,
|
||||
data: v,
|
||||
},
|
||||
{
|
||||
@ -467,13 +593,15 @@ namespace OS {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return undefined;
|
||||
}
|
||||
v.text = v.filename;
|
||||
if(!v.text)
|
||||
v.text = v.filename;
|
||||
if (v.type === "dir") {
|
||||
v.nodes = [];
|
||||
v.open = false;
|
||||
}
|
||||
v.iconclass = v.iconclass ? v.iconclass : v.type;
|
||||
v.icon = v.icon;
|
||||
if(v.icon)
|
||||
v.iconclass = undefined;
|
||||
return nodes.push(v);
|
||||
});
|
||||
return nodes;
|
||||
@ -511,7 +639,7 @@ namespace OS {
|
||||
$(this.refs.listview).hide();
|
||||
$(this.refs.gridview).hide();
|
||||
$(this.refs.treecontainer).hide();
|
||||
this._selectedFile = undefined;
|
||||
this._selectedFiles = [];
|
||||
switch (this.view) {
|
||||
case "icon":
|
||||
$(this.refs.listview).show();
|
||||
@ -543,13 +671,12 @@ namespace OS {
|
||||
}
|
||||
if (this.status) {
|
||||
(this.refs.status as LabelTag).text = __(
|
||||
"Selected: {0} ({1} bytes)",
|
||||
"{0} ({1} bytes)",
|
||||
e.filename,
|
||||
e.size ? e.size : "0"
|
||||
);
|
||||
}
|
||||
const evt = { id: this.aid, data: e };
|
||||
this._selectedFile = e;
|
||||
this._onfileselect(evt);
|
||||
this.observable.trigger("fileselect", evt);
|
||||
}
|
||||
@ -566,10 +693,11 @@ namespace OS {
|
||||
e.type = "dir";
|
||||
e.mime = "dir";
|
||||
}
|
||||
const evt = { id: this.aid, data: e };
|
||||
if (e.type === "dir" && this.chdir) {
|
||||
this.path = e.path;
|
||||
} else {
|
||||
const evt = { id: this.aid, data: e };
|
||||
|
||||
this._onfileopen(evt);
|
||||
this.observable.trigger("fileopen", evt);
|
||||
}
|
||||
@ -605,29 +733,38 @@ namespace OS {
|
||||
};
|
||||
const grid = this.refs.gridview as GridViewTag;
|
||||
const list = this.refs.listview as ListViewTag;
|
||||
|
||||
list.focusable = true;
|
||||
grid.focusable = true;
|
||||
tree.focusable = true;
|
||||
|
||||
grid.resizable = true;
|
||||
grid.header = this._header;
|
||||
tree.dragndrop = true;
|
||||
list.dragndrop = true;
|
||||
grid.dragndrop = true;
|
||||
// even handles
|
||||
list.onlistselect = (e) => {
|
||||
this.fileselect(e.data.item.data as API.FileInfoType);
|
||||
this._selectedFiles = e.data.items.map( x => x.data as API.FileInfoType);
|
||||
};
|
||||
grid.onrowselect = (e) => {
|
||||
this.fileselect(
|
||||
$(e.data.item).children()[0]
|
||||
.data as API.FileInfoType
|
||||
($(e.data.item).children()[0] as GridCellPrototype)
|
||||
.data.data as API.FileInfoType
|
||||
);
|
||||
this._selectedFiles = e.data.items.map( x => ($(x).children()[0] as GridCellPrototype).data.data as API.FileInfoType);
|
||||
};
|
||||
tree.ontreeselect = (e) => {
|
||||
this.fileselect(e.data.item.data as API.FileInfoType);
|
||||
this._selectedFiles = [e.data.item.data as API.FileInfoType];
|
||||
};
|
||||
// dblclick
|
||||
list.onlistdbclick = (e) => {
|
||||
this.filedbclick(e.data.item.data as API.FileInfoType);
|
||||
};
|
||||
grid.oncelldbclick = (e) => {
|
||||
this.filedbclick(e.data.item.data as API.FileInfoType);
|
||||
this.filedbclick(e.data.item.data.data as API.FileInfoType);
|
||||
};
|
||||
tree.ontreedbclick = (e) => {
|
||||
this.filedbclick(e.data.item.data as API.FileInfoType);
|
||||
|
@ -48,30 +48,6 @@ namespace OS {
|
||||
this._onready = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the direction of the list item layout.
|
||||
* Two directions are available:
|
||||
* - `vertical`
|
||||
* - `horizontal`
|
||||
*
|
||||
* This setter acts as a DOM attribute
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the currently set direction of list
|
||||
* item layout
|
||||
*
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
set dir(v: string) {
|
||||
$(this).attr("dir", v);
|
||||
this.calibrate();
|
||||
}
|
||||
get dir(): string {
|
||||
return $(this).attr("dir");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the dropdown option in this list
|
||||
@ -125,6 +101,7 @@ namespace OS {
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.current).hide();
|
||||
$(this.refs.container)
|
||||
.css("width", "100%")
|
||||
.css("height", "100%");
|
||||
@ -147,49 +124,26 @@ namespace OS {
|
||||
*/
|
||||
push(v: GenericObject<any>) {
|
||||
const el = super.push(v);
|
||||
this.enable_drag(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable drag and drop on the list
|
||||
*
|
||||
* @private
|
||||
* @param {ListViewItemTag} el the list item DOM element
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
private enable_drag(el: ListViewItemTag): void {
|
||||
$(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: JQuery.MouseEventBase
|
||||
) {
|
||||
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: JQuery.MouseEventBase) {
|
||||
$(window).off("mousemove", mouse_move);
|
||||
return $(window).off("mouseup", mouse_up);
|
||||
};
|
||||
$(window).on("mousemove", mouse_move);
|
||||
return $(window).on("mouseup", mouse_up);
|
||||
});
|
||||
.css("position", "absolute");
|
||||
el.enable_drag();
|
||||
$(el).on("dragging", (evt) => {
|
||||
const e = evt.originalEvent as CustomEvent;
|
||||
const globalof = $(this.refs.mlist).offset();
|
||||
const offset = e.detail.offset;
|
||||
let top = e.detail.current.clientY - offset.top - globalof.top;
|
||||
let left =
|
||||
e.detail.current.clientX - globalof.left - offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
top = top < 0 ? 0 : top;
|
||||
$(el)
|
||||
.css("top", `${top}px`)
|
||||
.css("left", `${left}px`);
|
||||
})
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,7 +169,7 @@ namespace OS {
|
||||
.css("left", `${cleft}px`);
|
||||
const w = $(e).width();
|
||||
const h = $(e).height();
|
||||
if (this.dir === "vertical") {
|
||||
if (this.dir === "column") {
|
||||
ctop += h + 20;
|
||||
if (ctop + h > gh) {
|
||||
ctop = 20;
|
||||
|
@ -19,6 +19,10 @@ interface Array<T> {
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Row item event data type
|
||||
*/
|
||||
export type GridRowEventData = TagEventDataType<GridRowTag>;
|
||||
/**
|
||||
* A grid Row is a simple element that
|
||||
* contains a group of grid cell
|
||||
@ -31,10 +35,20 @@ namespace OS {
|
||||
/**
|
||||
* Data placeholder for a collection of cell data
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>[]}
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
data: GenericObject<any>[];
|
||||
private _data: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
* placeholder for the row select event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<GridRowEventData>}
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
private _onselect: TagEventCallback<GridRowEventData>;
|
||||
|
||||
/**
|
||||
*Creates an instance of GridRowTag.
|
||||
@ -44,15 +58,64 @@ namespace OS {
|
||||
super();
|
||||
|
||||
this.refs.yield = this;
|
||||
this._onselect = (e) => { };
|
||||
}
|
||||
|
||||
/**
|
||||
* Set item select event handle
|
||||
*
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
set onrowselect(v: TagEventCallback<GridRowEventData>) {
|
||||
this._onselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: select/unselect the current item
|
||||
*
|
||||
* Getter: Check whether the current item is selected
|
||||
*
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
set selected(v: boolean) {
|
||||
this.attsw(v, "selected");
|
||||
$(this).removeClass();
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this).addClass("afx-grid-row-selected");
|
||||
this._onselect({ id: this.aid, data: this });
|
||||
}
|
||||
get selected(): boolean {
|
||||
return this.hasattr("selected");
|
||||
}
|
||||
|
||||
/**
|
||||
* setter: set row data
|
||||
*
|
||||
* getter: get row data
|
||||
*/
|
||||
set data(v: GenericObject<any>[]) {
|
||||
this._data = v;
|
||||
if(v)
|
||||
{
|
||||
this.attach(v);
|
||||
}
|
||||
for (let celi = 0; celi < this.children.length; celi++) {
|
||||
const cel = (this.children[celi] as GridCellPrototype);
|
||||
cel.data = v[celi];
|
||||
}
|
||||
}
|
||||
get data(): GenericObject<any>[] {
|
||||
return this._data;
|
||||
}
|
||||
/**
|
||||
* Mount the tag, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected mount(): void {}
|
||||
protected mount(): void { }
|
||||
|
||||
/**
|
||||
* Init the tag before mounting: reset the data placeholder
|
||||
@ -81,7 +144,7 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
protected calibrate(): void { }
|
||||
|
||||
/**
|
||||
* This function does nothing in this tag
|
||||
@ -90,7 +153,7 @@ namespace OS {
|
||||
* @param {*} [d]
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
protected reload(d?: any): void { }
|
||||
}
|
||||
|
||||
/**
|
||||
@ -166,7 +229,7 @@ namespace OS {
|
||||
* Setter:
|
||||
*
|
||||
* Set the data of the cell, this will trigger
|
||||
* the [[ondatachange]] function
|
||||
* the {@link ondatachange} function
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
@ -177,6 +240,7 @@ namespace OS {
|
||||
set data(v: GenericObject<any>) {
|
||||
if (!v) return;
|
||||
this._data = v;
|
||||
this.attach(v);
|
||||
this.ondatachange();
|
||||
if (!v.selected) {
|
||||
return;
|
||||
@ -191,7 +255,7 @@ namespace OS {
|
||||
* Setter:
|
||||
*
|
||||
* Set/unset the current cell as selected.
|
||||
* This will trigger the [[cellselect]]
|
||||
* This will trigger the {@link cellselect}
|
||||
* event
|
||||
*
|
||||
* Getter:
|
||||
@ -234,14 +298,14 @@ namespace OS {
|
||||
$(this).attr("class", "afx-grid-cell");
|
||||
this.oncelldbclick = this.oncellselect = (
|
||||
e: TagEventType<GridCellPrototype>
|
||||
): void => {};
|
||||
): void => { };
|
||||
this.selected = false;
|
||||
$(this).css("display", "block");
|
||||
$(this).on("click",(e) => {
|
||||
//$(this).css("display", "block");
|
||||
$(this).on("click", (e) => {
|
||||
let evt = { id: this.aid, data: this };
|
||||
return this.cellselect(evt, false);
|
||||
});
|
||||
$(this).on("dblclick", (e) => {
|
||||
$(this).on(OS.mobile?"dbltap":"dblclick", (e) => {
|
||||
let evt = { id: this.aid, data: this };
|
||||
return this.cellselect(evt, true);
|
||||
});
|
||||
@ -281,7 +345,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Simple grid cell defines a grid cell with
|
||||
* an [[LabelTag]] as it cell layout
|
||||
* an {@link LabelTag} as it cell layout
|
||||
*
|
||||
* @export
|
||||
* @class SimpleGridCellTag
|
||||
@ -303,6 +367,9 @@ namespace OS {
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
const label = (this.refs.cell as LabelTag);
|
||||
label.icon = undefined;
|
||||
label.iconclass = undefined;
|
||||
(this.refs.cell as LabelTag).set(this.data);
|
||||
}
|
||||
|
||||
@ -312,7 +379,7 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
protected init(): void {}
|
||||
protected init(): void { }
|
||||
|
||||
/**
|
||||
* This function do nothing in this tag
|
||||
@ -320,10 +387,10 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
protected calibrate(): void { }
|
||||
|
||||
/**
|
||||
* The layout of the cell with a simple [[LabelTag]]
|
||||
* The layout of the cell with a simple {@link LabelTag}
|
||||
*
|
||||
* @returns
|
||||
* @memberof SimpleGridCellTag
|
||||
@ -420,14 +487,104 @@ namespace OS {
|
||||
*/
|
||||
private _oncelldbclick: TagEventCallback<CellEventData>;
|
||||
|
||||
/**
|
||||
* Event data passing between mouse event when performing
|
||||
* drag and drop on the list
|
||||
*
|
||||
* @private
|
||||
* @type {{ from: GridRowTag[]; to: GridRowTag }}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _dnd: { from: GridRowTag[]; to: GridRowTag };
|
||||
|
||||
/**
|
||||
* Grid navigation index
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
*/
|
||||
private _nav_index: number;
|
||||
|
||||
/**
|
||||
* placeholder of list drag and drop event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<DnDEventDataType<GridRowTag>>}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _ondragndrop: TagEventCallback<
|
||||
DnDEventDataType<GridRowTag>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Creates an instance of GridViewTag.
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._nav_index = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set drag and drop event handle
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set ondragndrop(
|
||||
v: TagEventCallback<DnDEventDataType<GridRowTag>>
|
||||
) {
|
||||
this._ondragndrop = v;
|
||||
this.dragndrop = this.dragndrop;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Enable/disable drag and drop event in the list
|
||||
*
|
||||
* Getter: Check whether the drag and drop event is enabled
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set dragndrop(v: boolean) {
|
||||
this.attsw(v, "dragndrop");
|
||||
if (!v) {
|
||||
$(this.refs.container).off("mousedown", this._onmousedown);
|
||||
}
|
||||
else {
|
||||
$(this.refs.container).on(
|
||||
"mousedown",
|
||||
this._onmousedown
|
||||
);
|
||||
}
|
||||
}
|
||||
get dragndrop(): boolean {
|
||||
return this.hasattr("dragndrop");
|
||||
}
|
||||
|
||||
/**
|
||||
* placeholder of drag and drop mouse down event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _onmousedown: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
/**
|
||||
* placeholder of drag and drop mouse up event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _onmouseup: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
/**
|
||||
* placeholder of drag and drop mouse move event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _onmousemove: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
|
||||
/**
|
||||
* Init the grid view before mounting.
|
||||
* Reset all the placeholders to default values
|
||||
@ -444,9 +601,13 @@ namespace OS {
|
||||
this._selectedRow = undefined;
|
||||
this._rows = [];
|
||||
this.resizable = false;
|
||||
this.dragndrop = false;
|
||||
this._oncellselect = this._onrowselect = this._oncelldbclick = (
|
||||
e: TagEventType<CellEventData>
|
||||
): void => {};
|
||||
): void => { };
|
||||
this._ondragndrop = (
|
||||
e: TagEventType<DnDEventDataType<GridRowTag>>
|
||||
) => { };
|
||||
}
|
||||
|
||||
/**
|
||||
@ -456,7 +617,7 @@ namespace OS {
|
||||
* @param {*} [d]
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
protected reload(d?: any): void { }
|
||||
|
||||
/**
|
||||
* set the cell select event callback
|
||||
@ -507,7 +668,13 @@ namespace OS {
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set cellitem(v: string) {
|
||||
const currci = this.cellitem;
|
||||
$(this).attr("cellitem", v);
|
||||
if (v != currci) {
|
||||
// force render data
|
||||
$(this.refs.grid).empty();
|
||||
this.rows = this.rows;
|
||||
}
|
||||
}
|
||||
get cellitem(): string {
|
||||
return $(this).attr("cellitem");
|
||||
@ -538,14 +705,24 @@ namespace OS {
|
||||
)[0] as GridCellPrototype;
|
||||
element.uify(this.observable);
|
||||
element.data = item;
|
||||
item.domel = element;
|
||||
element.oncellselect = (e) => {
|
||||
if (element.data.sort) {
|
||||
this.sort(element.data, element.data.sort);
|
||||
if (element.data.desc) {
|
||||
$(element).attr("sort", "desc");
|
||||
}
|
||||
else {
|
||||
$(element).attr("sort", "asc");
|
||||
}
|
||||
}
|
||||
};
|
||||
i++;
|
||||
if (this.resizable) {
|
||||
if (i != v.length) {
|
||||
const rz = $(`<afx-resizer>`).appendTo(
|
||||
this.refs.header
|
||||
)[0] as ResizerTag;
|
||||
$(rz).css("width", "3px");
|
||||
$(rz).css("width", "1px");
|
||||
let next_item = undefined;
|
||||
if (i < v.length) {
|
||||
next_item = v[i];
|
||||
@ -604,9 +781,38 @@ namespace OS {
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set rows(rows: GenericObject<any>[][]) {
|
||||
$(this.refs.grid).empty();
|
||||
this._rows = rows;
|
||||
rows.map((row) => this.push(row, false));
|
||||
if (!rows) return;
|
||||
for (const el of this._header) {
|
||||
$(el.domel).attr("sort", "none");
|
||||
}
|
||||
// update existing row with new data
|
||||
const ndrows = rows.length;
|
||||
const ncrows = this.refs.grid.children.length;
|
||||
const nmin = ndrows < ncrows ? ndrows : ncrows;
|
||||
if (this.selectedRow) {
|
||||
this.selectedRow.selected = false;
|
||||
this._selectedRow = undefined;
|
||||
this._selectedRows = [];
|
||||
}
|
||||
for (let i = 0; i < nmin; i++) {
|
||||
const rowel = (this.refs.grid.children[i] as GridRowTag);
|
||||
rowel.data = rows[i];
|
||||
}
|
||||
// remove existing remaining rows
|
||||
if (ndrows < ncrows) {
|
||||
const arr = Array.prototype.slice.call(this.refs.grid.children);
|
||||
const blacklist = arr.slice(nmin, ncrows);
|
||||
for (const r of blacklist) {
|
||||
this.delete(r);
|
||||
}
|
||||
}
|
||||
// or add more rows
|
||||
else if (ndrows > ncrows) {
|
||||
for (let i = nmin; i < ndrows; i++) {
|
||||
this.push(rows[i], false);
|
||||
}
|
||||
}
|
||||
}
|
||||
get rows(): GenericObject<any>[][] {
|
||||
return this._rows;
|
||||
@ -639,7 +845,24 @@ namespace OS {
|
||||
get resizable(): boolean {
|
||||
return this.hasattr("resizable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the grid using a sort function
|
||||
*
|
||||
* @param {context: any} context of the executed function
|
||||
* @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data
|
||||
* * @param {index: number} current header index
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
sort(context: any, fn: (a: GenericObject<any>[], b: GenericObject<any>[], index?: number) => number): void {
|
||||
const index = this._header.indexOf(context);
|
||||
const __fn = (a, b) => {
|
||||
return fn.call(context, a, b, index);
|
||||
}
|
||||
this._rows.sort(__fn);
|
||||
context.desc = !context.desc;
|
||||
this.rows = this._rows;
|
||||
}
|
||||
/**
|
||||
* Delete a grid rows
|
||||
*
|
||||
@ -670,13 +893,61 @@ namespace OS {
|
||||
$(row).remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the grid view to item
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
scroll_to_item(item: GridRowTag)
|
||||
{
|
||||
const cell = $(item).children()[0];
|
||||
const offset = $(this.refs.container).offset();
|
||||
const cell_offset = $(cell).offset();
|
||||
|
||||
const top = $(this.refs.container).scrollTop();
|
||||
const cell_height = $(cell).outerHeight();
|
||||
const container_height = $(this.refs.container).outerHeight();
|
||||
if (cell_offset.top + cell_height > container_height + offset.top)
|
||||
{
|
||||
$(this.refs.container).scrollTop(
|
||||
top +
|
||||
(cell_offset.top + cell_height - offset.top - container_height)
|
||||
);
|
||||
} else if (cell_offset.top < offset.top) {
|
||||
$(this.refs.container).scrollTop(
|
||||
top -
|
||||
(offset.top - cell_offset.top)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the grid view to bottom
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
scroll_to_bottom()
|
||||
{
|
||||
this.refs.container.scrollTo({ top: this.refs.container.scrollHeight, behavior: 'smooth' })
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the grid view to top
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
scroll_to_top()
|
||||
{
|
||||
this.refs.container.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a row to the grid
|
||||
*
|
||||
* @param {GenericObject<any>[]} row list of cell data
|
||||
* @param {boolean} flag indicates where the row is add to beginning or end
|
||||
* of the row
|
||||
* @memberof GridViewTags
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
push(row: GenericObject<any>[], flag: boolean): void {
|
||||
const rowel = $("<afx-grid-row>").css(
|
||||
@ -697,22 +968,23 @@ namespace OS {
|
||||
|
||||
const el = rowel[0] as GridRowTag;
|
||||
rowel[0].uify(this.observable);
|
||||
el.data = row;
|
||||
row.domel = rowel[0];
|
||||
|
||||
|
||||
for (let cell of row) {
|
||||
let tag = this.cellitem;
|
||||
if (cell.tag) {
|
||||
({ tag } = cell);
|
||||
tag = cell.tag;
|
||||
}
|
||||
const el = $(`<${tag}>`).appendTo(rowel);
|
||||
cell.domel = el[0];
|
||||
const element = el[0] as GridCellPrototype;
|
||||
element.uify(this.observable);
|
||||
element.oncellselect = (e) => this.cellselect(e, false);
|
||||
element.oncelldbclick = (e) => this.cellselect(e, true);
|
||||
element.data = cell;
|
||||
}
|
||||
el.data = row;
|
||||
el.onrowselect = (e) => this.rowselect({
|
||||
id: el.aid,
|
||||
data: { item: el }
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -751,7 +1023,10 @@ namespace OS {
|
||||
} else {
|
||||
this.observable.trigger("cellselect", e);
|
||||
this._oncellselect(e);
|
||||
return this.rowselect(e);
|
||||
const row = ($(
|
||||
e.data.item
|
||||
).parent()[0] as any) as GridRowTag;
|
||||
row.selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -759,11 +1034,11 @@ namespace OS {
|
||||
* This function triggers the row select event, a cell select
|
||||
* event will also trigger this event
|
||||
*
|
||||
* @param {TagEventType<CellEventData>} e
|
||||
* @param {TagEventType<GridRowEventData>} e
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private rowselect(e: TagEventType<CellEventData>): void {
|
||||
private rowselect(e: TagEventType<GridRowEventData>): void {
|
||||
if (!e.data.item) {
|
||||
return;
|
||||
}
|
||||
@ -774,39 +1049,56 @@ namespace OS {
|
||||
items: [],
|
||||
},
|
||||
};
|
||||
const row = ($(
|
||||
e.data.item
|
||||
).parent()[0] as any) as GridRowTag;
|
||||
const row = e.data.item as GridRowTag;
|
||||
if (this.multiselect) {
|
||||
if (this.selectedRows.includes(row)) {
|
||||
this.selectedRows.splice(
|
||||
this.selectedRows.indexOf(row),
|
||||
1
|
||||
);
|
||||
$(row).removeClass();
|
||||
row.selected = false;
|
||||
return;
|
||||
} else {
|
||||
this.selectedRows.push(row);
|
||||
$(row)
|
||||
.removeClass()
|
||||
.addClass("afx-grid-row-selected");
|
||||
}
|
||||
evt.data.items = this.selectedRows;
|
||||
} else {
|
||||
if (this.selectedRows.length > 0) {
|
||||
for (const item of this.selectedRows) {
|
||||
if (item != row) {
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.selectedRow === row) {
|
||||
return;
|
||||
}
|
||||
$(this.selectedRow).removeClass();
|
||||
this._selectedRows = [row];
|
||||
evt.data.item = row;
|
||||
if (this.selectedRow)
|
||||
this.selectedRow.selected = false;
|
||||
evt.data.items = [row];
|
||||
$(row).removeClass().addClass("afx-grid-row-selected");
|
||||
this._selectedRows = [row];
|
||||
}
|
||||
evt.data.item = row;
|
||||
this._selectedRow = row;
|
||||
this._nav_index = $(row).index();
|
||||
this._onrowselect(evt);
|
||||
return this.observable.trigger("rowselect", evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect all the selected rows in the grid
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
unselect(): void {
|
||||
for (let v of this.selectedRows) {
|
||||
v.selected = false;
|
||||
}
|
||||
this._selectedRows = [];
|
||||
this._selectedRow = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the grid has header
|
||||
*
|
||||
@ -831,8 +1123,8 @@ namespace OS {
|
||||
$(this.refs.container).css(
|
||||
"height",
|
||||
$(this).height() -
|
||||
$(this.refs.header).height() +
|
||||
"px"
|
||||
$(this.refs.header).height() +
|
||||
"px"
|
||||
);
|
||||
} else {
|
||||
$(this.refs.container).css(
|
||||
@ -882,8 +1174,8 @@ namespace OS {
|
||||
let i = 0;
|
||||
for (let v of colssize) {
|
||||
template += `${v}px `;
|
||||
i++;
|
||||
template_header += `${v}px `;
|
||||
i++;
|
||||
if (i < colssize.length && this.resizable) {
|
||||
template_header += "3px ";
|
||||
}
|
||||
@ -893,6 +1185,105 @@ namespace OS {
|
||||
"grid-template-columns",
|
||||
template_header
|
||||
);
|
||||
if(this.resizable)
|
||||
{
|
||||
$(this.refs.grid).css("column-gap","3px");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the navigation indicator
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private nav_reset() {
|
||||
const el = $(this.refs.grid).children().eq(this._nav_index);
|
||||
if(el)
|
||||
{
|
||||
$(el).removeClass("gridview-nav-focus");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate the list up
|
||||
*
|
||||
* @public
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public nav_prev() {
|
||||
console.log("nav_prev");
|
||||
if(this._nav_index <= 0) {
|
||||
return;
|
||||
}
|
||||
this.nav_reset();
|
||||
this._nav_index--;
|
||||
const el = $(this.refs.grid).children().eq(this._nav_index);
|
||||
if(el) {
|
||||
$(el).addClass("gridview-nav-focus");
|
||||
this.scroll_to_item(el[0] as GridRowTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate the list down
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public nav_next() {
|
||||
console.log("nav_next");
|
||||
if(this._nav_index >= this.rows.length - 1) {
|
||||
return;
|
||||
}
|
||||
this.nav_reset();
|
||||
this._nav_index++;
|
||||
const el = $(this.refs.grid).children().eq(this._nav_index);
|
||||
if(el) {
|
||||
$(el).addClass("gridview-nav-focus");
|
||||
this.scroll_to_item(el[0] as GridRowTag);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the navigated item
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public nav_commit() {
|
||||
if(this._nav_index > this.rows.length - 1 || this._nav_index < 0) {
|
||||
return;
|
||||
}
|
||||
const el = $(this.refs.grid).children().eq(this._nav_index);
|
||||
if(el) {
|
||||
(el[0] as GridRowTag).selected = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle special key event such as key up and down
|
||||
*
|
||||
* @private
|
||||
* @param {JQuery.KeyboardEventBase} event
|
||||
* @returns {void}
|
||||
*/
|
||||
private handle_special_key(event: JQuery.KeyboardEventBase) {
|
||||
switch (event.which) {
|
||||
case 37:
|
||||
case 38:
|
||||
this.nav_prev();
|
||||
return event.preventDefault();
|
||||
case 39:
|
||||
case 40:
|
||||
this.nav_next();
|
||||
return event.preventDefault();
|
||||
case 13:
|
||||
event.preventDefault();
|
||||
return this.nav_commit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -903,8 +1294,10 @@ namespace OS {
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this).on("keyup", (e) => {
|
||||
this.handle_special_key(e);
|
||||
});
|
||||
$(this).css("overflow", "hidden");
|
||||
|
||||
$(this.refs.grid).css("display", "grid");
|
||||
$(this.refs.header).css("display", "grid");
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
@ -912,6 +1305,70 @@ namespace OS {
|
||||
.css("width", "100%")
|
||||
.css("overflow-x", "hidden")
|
||||
.css("overflow-y", "auto");
|
||||
// drag and drop
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
this._onmousedown = (e) => {
|
||||
if (this.multiselect || this.selectedRows == undefined || this.selectedRows.length == 0) {
|
||||
return;
|
||||
}
|
||||
let el: any = $(e.target).closest("afx-grid-row");
|
||||
if (el.length === 0) {
|
||||
return;
|
||||
}
|
||||
el = el[0];
|
||||
if (!this.selectedRows.includes(el)) {
|
||||
return;
|
||||
}
|
||||
this._dnd.from = this.selectedRows;
|
||||
this._dnd.to = undefined;
|
||||
$(window).on("mouseup", this._onmouseup);
|
||||
$(window).on("mousemove", this._onmousemove);
|
||||
};
|
||||
|
||||
this._onmouseup = (e) => {
|
||||
$(window).off("mouseup", this._onmouseup);
|
||||
$(window).off("mousemove", this._onmousemove);
|
||||
$("#systooltip").hide();
|
||||
let el: any = $(e.target).closest("afx-grid-row");
|
||||
if (el.length === 0) {
|
||||
return;
|
||||
}
|
||||
el = el[0];
|
||||
if (this._dnd.from.includes(el)) {
|
||||
return;
|
||||
}
|
||||
this._dnd.to = el;
|
||||
this._ondragndrop({ id: this.aid, data: this._dnd });
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
this._onmousemove = (e) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
if (!this._dnd.from) {
|
||||
return;
|
||||
}
|
||||
const data = {
|
||||
text: __("{0} selected elements", this._dnd.from.length).__(),
|
||||
items: this._dnd.from
|
||||
};
|
||||
const $label = $("#systooltip");
|
||||
const top = e.clientY + 5;
|
||||
const left = e.clientX + 5;
|
||||
$label.show();
|
||||
const label = $label[0] as LabelTag;
|
||||
label.set(data);
|
||||
return $label
|
||||
.css("top", top + "px")
|
||||
.css("left", left + "px");
|
||||
};
|
||||
return this.calibrate();
|
||||
}
|
||||
|
||||
@ -928,6 +1385,7 @@ namespace OS {
|
||||
{
|
||||
el: "div",
|
||||
ref: "container",
|
||||
class: "grid_content_container",
|
||||
children: [{ el: "div", ref: "grid" }],
|
||||
},
|
||||
];
|
||||
|
265
src/core/tags/InputTag.ts
Normal file
265
src/core/tags/InputTag.ts
Normal file
@ -0,0 +1,265 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* This tag define a basic text input and its behavior
|
||||
*
|
||||
* @export
|
||||
* @class InputTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class InputTag extends AFXTag {
|
||||
|
||||
/**
|
||||
*Creates an instance of InputTag.
|
||||
* @memberof InputTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the header icon, the path should be
|
||||
* a VFS file path
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
set icon(v: string) {
|
||||
$(this).attr("icon", v);
|
||||
(this.refs.label as LabelTag).icon = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the icon class to the header
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
set iconclass(v: string) {
|
||||
$(this).attr("iconclass", v);
|
||||
(this.refs.label as LabelTag).iconclass = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Alias to header setter/getter
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
set text(v: string | FormattedString) {
|
||||
this.label = v;
|
||||
}
|
||||
|
||||
get text(): string | FormattedString {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the text of the label
|
||||
*
|
||||
* Getter: Get the current label test
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
set label(v: string | FormattedString) {
|
||||
(this.refs.label as LabelTag).text = v;
|
||||
}
|
||||
|
||||
get label(): string | FormattedString {
|
||||
return (this.refs.label as LabelTag).text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Enable or disable the input
|
||||
*
|
||||
* Getter: Get the `enable` property of the input
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
set disable(v: boolean) {
|
||||
$(this.refs.area).prop("disabled", v);
|
||||
$(this.refs.input).prop("disabled", v);
|
||||
}
|
||||
get disable(): boolean {
|
||||
return !$(this.input).prop("disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set verbosity of the input
|
||||
*
|
||||
* Getter: Get the current input verbosity
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
set verbose(v: boolean) {
|
||||
this.attsw(v, "verbose");
|
||||
this.calibrate();
|
||||
}
|
||||
get verbose(): boolean {
|
||||
return this.hasattr("verbose");
|
||||
}
|
||||
|
||||
/**
|
||||
* JQuery style generic event handling on the input element
|
||||
*
|
||||
* @param {string} enname: JQuery event name
|
||||
* @param {JQuery.TypeEventHandler<HTMLInputElement | HTMLTextAreaElement, unknown, any, any, string>} handle: JQuery handle
|
||||
* @memberof InputTag
|
||||
*/
|
||||
on(ename: string, handle:JQuery.TypeEventHandler<HTMLInputElement | HTMLTextAreaElement, unknown, any, any, string> ): void
|
||||
{
|
||||
$(this.input).on(ename, handle);
|
||||
}
|
||||
/**
|
||||
* Manually trigger an event
|
||||
*
|
||||
* @param {string} evt: JQuery event name
|
||||
* @memberof InputTag
|
||||
*/
|
||||
trigger(evt: string)
|
||||
{
|
||||
$(this.input).trigger(evt);
|
||||
}
|
||||
/**
|
||||
* Mount the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof InputTag
|
||||
*/
|
||||
protected mount() {
|
||||
// Do nothing
|
||||
}
|
||||
/**
|
||||
* Get the current active input element
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
get input(): HTMLInputElement | HTMLTextAreaElement
|
||||
{
|
||||
if(this.verbose)
|
||||
{
|
||||
return this.refs.area as HTMLTextAreaElement;
|
||||
}
|
||||
return this.refs.input as HTMLInputElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set the current active input value
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
get value(): string{
|
||||
return this.input.value;
|
||||
}
|
||||
set value(v: string)
|
||||
{
|
||||
this.input.value = v;
|
||||
}
|
||||
/**
|
||||
* Get/set input type
|
||||
* This only affects the inline input element
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
get type(): string{
|
||||
if(this.verbose) return undefined;
|
||||
|
||||
return (this.input as HTMLInputElement).type;
|
||||
}
|
||||
set type(v: string)
|
||||
{
|
||||
if(!this.verbose)
|
||||
{
|
||||
(this.input as HTMLInputElement).type = v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get/set input name
|
||||
*
|
||||
* @memberof InputTag
|
||||
*/
|
||||
get name(): string{
|
||||
return (this.input as HTMLInputElement).name;
|
||||
}
|
||||
set name(v: string)
|
||||
{
|
||||
(this.input as HTMLInputElement).name = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof InputTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.disable = false;
|
||||
this.verbose = false;
|
||||
this.type = "text";
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-calibrate, do nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof InputTag
|
||||
*/
|
||||
protected calibrate(): void
|
||||
{
|
||||
/*$(this.refs.area)
|
||||
.css("width", "100%");
|
||||
$(this.refs.input)
|
||||
.css("width", "100%");*/
|
||||
if(this.verbose)
|
||||
{
|
||||
$(this.refs.area).show();
|
||||
$(this.refs.input).hide();
|
||||
(this.refs.input as HTMLInputElement).value = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this.refs.area).hide();
|
||||
$(this.refs.input).show();
|
||||
(this.refs.area as HTMLTextAreaElement).value = "";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current tag, do nothing in this tag
|
||||
*
|
||||
* @param {*} [d]
|
||||
* @memberof InputTag
|
||||
*/
|
||||
reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Input layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof InputTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "afx-label",
|
||||
ref: "label"
|
||||
},
|
||||
{
|
||||
el: "input",
|
||||
ref:"input"
|
||||
},
|
||||
{
|
||||
el: "textarea",
|
||||
ref: "area"
|
||||
},
|
||||
{
|
||||
el: "div"
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-input", InputTag);
|
||||
}
|
||||
}
|
||||
}
|
@ -33,7 +33,18 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
protected mount() {}
|
||||
protected mount() {
|
||||
$(this.refs.container)
|
||||
.css("display", "flex");
|
||||
$(this.refs.iclass)
|
||||
.css("flex-shrink",0);
|
||||
$(this.refs.iclass_end)
|
||||
.css("flex-shrink",0);
|
||||
$(this.refs.i)
|
||||
.css("flex-shrink",0);
|
||||
$(this.refs.text)
|
||||
.css("flex",1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the text in the label
|
||||
@ -56,6 +67,8 @@ namespace OS {
|
||||
this.icon = undefined;
|
||||
this.iconclass = undefined;
|
||||
this.text = undefined;
|
||||
this.selectable = false;
|
||||
this.iconclass$ = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,6 +97,49 @@ namespace OS {
|
||||
$(this.refs.i).hide();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* set horizontal aligment of the label content
|
||||
*
|
||||
* @param {string} v shall be "left, right, or center"
|
||||
*/
|
||||
set halign(v: string)
|
||||
{
|
||||
let align = "center";
|
||||
switch(v)
|
||||
{
|
||||
case "left":
|
||||
align = "flex-start";
|
||||
break;
|
||||
case "right":
|
||||
align = "flex-end";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$(this.refs.container).css("justify-content", align);
|
||||
}
|
||||
|
||||
/**
|
||||
* set horizontal aligment of the label content
|
||||
*
|
||||
* @param {string} v shall be "top, bottom, or center"
|
||||
*/
|
||||
set valign(v: string)
|
||||
{
|
||||
let align = "center";
|
||||
switch(v)
|
||||
{
|
||||
case "top":
|
||||
align = "flex-start";
|
||||
break;
|
||||
case "bottom":
|
||||
align = "flex-end";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
$(this.refs.container).css("align-items", align);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSS class of the label icon
|
||||
@ -101,6 +157,31 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSS class of the label icon on the right side
|
||||
*
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
set iconclass_end(v: string) {
|
||||
this.iconclass$ = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSS class of the label icon on the right side
|
||||
*
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
set iconclass$(v: string) {
|
||||
$(this).attr("iconclass_end", v);
|
||||
$(this.refs.iclass_end).removeClass();
|
||||
if (v) {
|
||||
$(this.refs.iclass_end).addClass(v);
|
||||
$(this.refs.iclass_end).show();
|
||||
} else {
|
||||
$(this.refs.iclass_end).hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the text of the label
|
||||
*
|
||||
@ -110,9 +191,9 @@ namespace OS {
|
||||
*/
|
||||
set text(v: string | FormattedString) {
|
||||
this._text = v;
|
||||
if (v && v !== "") {
|
||||
if (v) {
|
||||
$(this.refs.text).show();
|
||||
$(this.refs.text).html(v.__());
|
||||
$(this.refs.text).text(v.__());
|
||||
} else {
|
||||
$(this.refs.text).hide();
|
||||
}
|
||||
@ -121,6 +202,33 @@ namespace OS {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off text selection
|
||||
*
|
||||
* Getter: Check whether the label is selectable
|
||||
*
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
set selectable(v: boolean) {
|
||||
this.attsw(v, "selectable");
|
||||
if(v)
|
||||
{
|
||||
$(this.refs.text)
|
||||
.css("user-select", "text")
|
||||
.css("cursor", "text");
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this.refs.text)
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default");
|
||||
}
|
||||
}
|
||||
get swon(): boolean {
|
||||
return this.hasattr("selectable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Lqbel layout definition
|
||||
*
|
||||
@ -137,6 +245,7 @@ namespace OS {
|
||||
{ el: "i", ref: "iclass" },
|
||||
{ el: "i", ref: "i", class: "icon-style" },
|
||||
{ el: "i", ref: "text", class: "label-text" },
|
||||
{ el: "i", ref: "iclass_end" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -6,7 +6,7 @@ namespace OS {
|
||||
*/
|
||||
export type ListItemEventData = TagEventDataType<ListViewItemTag>;
|
||||
/**
|
||||
* A list item represent the individual view of an item in the [[ListView]].
|
||||
* A list item represent the individual view of an item in the {@link OS.GUI.tag.ListViewTag}.
|
||||
* This class is an abstract prototype class, implementation of any
|
||||
* list view item should extend it
|
||||
*
|
||||
@ -42,7 +42,7 @@ namespace OS {
|
||||
* @type {TagEventCallback<ListItemEventData>}
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
private _onctxmenu: TagEventCallback<ListItemEventData>;
|
||||
//private _onctxmenu: TagEventCallback<ListItemEventData>;
|
||||
|
||||
/**
|
||||
* Click event callback placeholder
|
||||
@ -77,7 +77,7 @@ namespace OS {
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._onselect = this._onctxmenu = this._onclick = this._ondbclick = this._onclose = (
|
||||
this._onselect /*= this._onctxmenu*/ = this._onclick = this._ondbclick = this._onclose = (
|
||||
e
|
||||
) => {};
|
||||
}
|
||||
@ -134,9 +134,11 @@ namespace OS {
|
||||
*
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
/*
|
||||
set onctxmenu(v: TagEventCallback<ListViewItemTag>) {
|
||||
this._onctxmenu = v;
|
||||
}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Set the item click event handle
|
||||
@ -172,20 +174,23 @@ namespace OS {
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.item).attr("dataref", "afx-list-item");
|
||||
$(this.refs.item).on("contextmenu", (e) => {
|
||||
$(this).addClass("afx-list-item");
|
||||
/*
|
||||
$(this.refs.item).on(OS.mobile?"longtouch":"contextmenu", (e) => {
|
||||
this._onctxmenu({ id: this.aid, data: this });
|
||||
});
|
||||
|
||||
*/
|
||||
$(this.refs.item).on("click",(e) => {
|
||||
this._onclick({ id: this.aid, data: this });
|
||||
this._onclick({ id: this.aid, data: this, originalEvent: e });
|
||||
//e.stopPropagation();
|
||||
});
|
||||
|
||||
$(this.refs.item).on("dblclick",(e) => {
|
||||
this._ondbclick({ id: this.aid, data: this });
|
||||
$(this.refs.item).on(OS.mobile?"dbltap":"dblclick",(e) => {
|
||||
this._ondbclick({ id: this.aid, data: this, originalEvent: e });
|
||||
e.stopPropagation();
|
||||
});
|
||||
$(this.refs.btcl).on("click",(e) => {
|
||||
this._onclose({ id: this.aid, data: this });
|
||||
this._onclose({ id: this.aid, data: this, originalEvent: e });
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
});
|
||||
@ -195,21 +200,29 @@ namespace OS {
|
||||
* Layout definition of the item tag.
|
||||
* This function define the outer layout of the item.
|
||||
* Custom inner layout of each item implementation should
|
||||
* be defined in [[itemlayout]]
|
||||
* be defined in {@link itemlayout}
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
let children = [{el: "i", class: "closable", ref: "btcl"}] as TagLayoutType[];
|
||||
const itemlayout = this.itemlayout();
|
||||
if(Array.isArray(itemlayout))
|
||||
{
|
||||
children = children.concat(itemlayout);
|
||||
}
|
||||
else
|
||||
{
|
||||
children.unshift(itemlayout);
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
el: "li",
|
||||
ref: "item",
|
||||
children: [
|
||||
this.itemlayout(),
|
||||
{ el: "i", class: "closable", ref: "btcl" },
|
||||
],
|
||||
children:children,
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -218,7 +231,7 @@ namespace OS {
|
||||
* Setter:
|
||||
*
|
||||
* Set the data of the list item. This will
|
||||
* trigger the [[ondatachange]] function
|
||||
* trigger the {@link ondatachange} function
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
@ -228,6 +241,10 @@ namespace OS {
|
||||
*/
|
||||
set data(v: GenericObject<any>) {
|
||||
this._data = v;
|
||||
if(v)
|
||||
{
|
||||
this.attach(v);
|
||||
}
|
||||
this.ondatachange();
|
||||
}
|
||||
get data(): GenericObject<any> {
|
||||
@ -240,10 +257,10 @@ namespace OS {
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @returns {TagLayoutType}
|
||||
* @returns {TagLayoutType | TagLayoutType[]}
|
||||
* @memberof ListViewItemTag
|
||||
*/
|
||||
protected abstract itemlayout(): TagLayoutType;
|
||||
protected abstract itemlayout(): TagLayoutType | TagLayoutType[];
|
||||
|
||||
/**
|
||||
* This function is called when the item data is changed.
|
||||
@ -329,14 +346,101 @@ namespace OS {
|
||||
* List item custom layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType}
|
||||
* @returns {TagLayoutType | TagLayoutType[]}
|
||||
* @memberof SimpleListItemTag
|
||||
*/
|
||||
protected itemlayout(): TagLayoutType {
|
||||
protected itemlayout(): TagLayoutType | TagLayoutType[] {
|
||||
return { el: "afx-label", ref: "label" };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* The layout of a double line list item contains two
|
||||
* AFX labels
|
||||
*
|
||||
* @export
|
||||
* @class DoubleLineListItemTag
|
||||
* @extends {ListViewItemTag}
|
||||
*/
|
||||
export class DoubleLineListItemTag extends ListViewItemTag {
|
||||
/**
|
||||
*Creates an instance of DoubleLineListItemTag.
|
||||
* @memberof DoubleLineListItemTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some property to default
|
||||
*
|
||||
* @protected
|
||||
* @memberof DoubleLineListItemTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.closable = false;
|
||||
this.data = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof DoubleLineListItemTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Refresh the inner label when the item data
|
||||
* is changed
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof DoubleLineListItemTag
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
const v = this.data;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
const line1 = this.refs.line1 as LabelTag;
|
||||
const line2 = this.refs.line2 as LabelTag;
|
||||
line1.set(v);
|
||||
if(v.description)
|
||||
{
|
||||
line2.set(v.description);
|
||||
}
|
||||
if (v.selected) {
|
||||
this.selected = v.selected;
|
||||
}
|
||||
if (v.closable) {
|
||||
this.closable = v.closable;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-render the list item
|
||||
*
|
||||
* @protected
|
||||
* @memberof DoubleLineListItemTag
|
||||
*/
|
||||
protected reload(): void {
|
||||
this.data = this.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* List item custom layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType | TagLayoutType[]}
|
||||
* @memberof DoubleLineListItemTag
|
||||
*/
|
||||
protected itemlayout(): TagLayoutType | TagLayoutType[] {
|
||||
return [{ el: "afx-label", ref: "line1", class:"title" }, { el: "afx-label", ref: "line2", class:"description" }];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This tag defines a traditional or a dropdown list widget.
|
||||
* It contains a collection of list items in which layout
|
||||
@ -422,14 +526,22 @@ namespace OS {
|
||||
/**
|
||||
* A collection of selected items in the list.
|
||||
* The maximum size of this collection is 1 if
|
||||
* the [[multiselect]] feature is disabled
|
||||
* the {@link multiselect} feature is disabled
|
||||
*
|
||||
* @private
|
||||
* @type {ListViewItemTag[]}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private _selectedItems: ListViewItemTag[];
|
||||
|
||||
|
||||
/**
|
||||
* The anchor element that the list view positioned on
|
||||
* This is helpful when rendering dropdown list
|
||||
* @private
|
||||
* @type{HTMLElement}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private _anchor: HTMLElement;
|
||||
/**
|
||||
* Data placeholder of the list
|
||||
*
|
||||
@ -438,16 +550,28 @@ namespace OS {
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private _data: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
* Navigation index, used for keyboard navigation only
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private _nav_index: number;
|
||||
|
||||
private _drop: (any) => void;
|
||||
private _show: (any) => void;
|
||||
|
||||
/**
|
||||
* Event data passing between mouse event when performing
|
||||
* drag and drop on the list
|
||||
*
|
||||
* @private
|
||||
* @type {{ from: ListViewItemTag; to: ListViewItemTag }}
|
||||
* @type {{ from: ListViewItemTag[]; to: ListViewItemTag }}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private _dnd: { from: ListViewItemTag; to: ListViewItemTag };
|
||||
private _dnd: { from: ListViewItemTag[]; to: ListViewItemTag };
|
||||
|
||||
/**
|
||||
*Creates an instance of ListViewTag.
|
||||
@ -468,6 +592,8 @@ namespace OS {
|
||||
) => {};
|
||||
this._selectedItems = [];
|
||||
this._selectedItem = undefined;
|
||||
this._drop = (e) => {this.dropoff(e)};
|
||||
this._show = (e) => {this.showlist(e)};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -482,10 +608,10 @@ namespace OS {
|
||||
this.dropdown = false;
|
||||
this.selected = -1;
|
||||
this.dragndrop = false;
|
||||
$(this)
|
||||
.css("display", "flex")
|
||||
.css("flex-direction", "column");
|
||||
this._anchor = undefined;
|
||||
this.itemtag = "afx-list-item";
|
||||
this._nav_index = -1;
|
||||
$(this).addClass("afx-list-view");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -508,32 +634,20 @@ namespace OS {
|
||||
this.attsw(v, "dropdown");
|
||||
$(this.refs.container).removeAttr("style");
|
||||
$(this.refs.mlist).removeAttr("style");
|
||||
$(this.refs.container).css("flex", 1);
|
||||
this.dir = "column";
|
||||
$(this).removeClass("dropdown");
|
||||
const drop = (e: any) => {
|
||||
return this.dropoff(e);
|
||||
};
|
||||
const show = (e: any) => {
|
||||
return this.showlist(e);
|
||||
};
|
||||
if (v) {
|
||||
$(this).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");
|
||||
$(document).on("click", this._drop);
|
||||
$(this.refs.current).on("click", this._show);
|
||||
$(this.refs.mlist).hide();
|
||||
this.calibrate();
|
||||
} else {
|
||||
$(document).off("click", this._drop);
|
||||
$(this.refs.current).off("click", this._show);
|
||||
$(this.refs.current).hide();
|
||||
$(document).off("click", drop);
|
||||
$(this.refs.current).off("click", show);
|
||||
$(this.refs.mlist).show();
|
||||
}
|
||||
}
|
||||
|
||||
@ -642,7 +756,7 @@ namespace OS {
|
||||
* Button layout allows to add some custom
|
||||
* behaviors to the list.
|
||||
*
|
||||
* Each button data should define the [[onbtclick]]
|
||||
* Each button data should define the {@link OS.GUI.tag.ButtonTag.onbtclick}
|
||||
* event handle to specify the custom behavior
|
||||
*
|
||||
* When the list is configured as dropdown. The buttons
|
||||
@ -676,7 +790,32 @@ namespace OS {
|
||||
(bt[0] as ButtonTag).set(item);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter: Get list direction: row or column (default)
|
||||
*
|
||||
* Setter: Get list direction: row or column
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
set dir(v: string) {
|
||||
if(this.dropdown)
|
||||
{
|
||||
$(this).attr("dir", "column");
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).attr("dir", v);
|
||||
}
|
||||
this.calibrate();
|
||||
}
|
||||
get dir(): string {
|
||||
const v = $(this).attr("dir");
|
||||
if(!v) {
|
||||
return "column";
|
||||
}
|
||||
return v;
|
||||
}
|
||||
/**
|
||||
* Getter: Get data of the list
|
||||
*
|
||||
@ -737,14 +876,17 @@ namespace OS {
|
||||
const el = data[i].domel as ListViewItemTag;
|
||||
el.selected = true;
|
||||
};
|
||||
this.nav_reset();
|
||||
if (Array.isArray(idx)) {
|
||||
if (this.multiselect) {
|
||||
for (const i of idx as number[]) {
|
||||
select(i);
|
||||
this._nav_index = i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
select(idx as number);
|
||||
this._nav_index = idx;
|
||||
}
|
||||
}
|
||||
|
||||
@ -769,7 +911,13 @@ namespace OS {
|
||||
get selectedItems(): ListViewItemTag[] {
|
||||
return this._selectedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the selected item index
|
||||
*
|
||||
* @readonly
|
||||
* @type {number}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
get selected(): number | number[] {
|
||||
if (this.multiselect) {
|
||||
return this.selectedItems.map(function (
|
||||
@ -808,7 +956,7 @@ namespace OS {
|
||||
* Add an item to the beginning or end of the list
|
||||
*
|
||||
* @param {GenericObject<any>} item list item data
|
||||
* @param {boolean} [flag] indicates whether to add the item in the beginning of the list
|
||||
* @param {boolean} flag indicates whether to add the item in the beginning of the list
|
||||
* @returns {ListViewItemTag} the added list item element
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
@ -832,9 +980,11 @@ namespace OS {
|
||||
}
|
||||
el[0].uify(this.observable);
|
||||
const element = el[0] as ListViewItemTag;
|
||||
$(element).attr("list-id",this.aid);
|
||||
/*
|
||||
element.onctxmenu = (e) => {
|
||||
return this.iclick(e, true);
|
||||
};
|
||||
};*/
|
||||
element.onitemdbclick = (e) => {
|
||||
this.idbclick(e);
|
||||
this.iclick(e, false);
|
||||
@ -849,7 +999,6 @@ namespace OS {
|
||||
return this.iclose(e);
|
||||
};
|
||||
element.data = item;
|
||||
item.domel = el[0];
|
||||
return element;
|
||||
}
|
||||
|
||||
@ -943,24 +1092,116 @@ namespace OS {
|
||||
if (!e.data) {
|
||||
return;
|
||||
}
|
||||
this.nav_reset();
|
||||
this._nav_index = -1;
|
||||
const list = this.selectedItems;
|
||||
if (this.multiselect && list.includes(e.data) && !flag) {
|
||||
list.splice(list.indexOf(e.data), 1);
|
||||
e.data.selected = false;
|
||||
return;
|
||||
}
|
||||
this._nav_index = $(e.data).index();
|
||||
e.data.selected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the navigation indicator
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
private nav_reset() {
|
||||
if(this._nav_index >= 0 && this._nav_index < this.data.length)
|
||||
{
|
||||
$(this.data[this._nav_index].domel as ListViewItemTag).removeClass("listview_nav_focus");
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Navigate the list up
|
||||
*
|
||||
* @public
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public nav_prev() {
|
||||
if(this._nav_index <= 0) {
|
||||
return;
|
||||
}
|
||||
this.nav_reset();
|
||||
this._nav_index--;
|
||||
const new_el = this.data[this._nav_index].domel as ListViewItemTag;
|
||||
if(new_el) {
|
||||
$(new_el).addClass("listview_nav_focus");
|
||||
this.scroll_to_item(new_el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Navigate the list down
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public nav_next() {
|
||||
if(this._nav_index >= this.data.length - 1) {
|
||||
return;
|
||||
}
|
||||
this.nav_reset();
|
||||
this._nav_index++;
|
||||
const new_el = this.data[this._nav_index].domel as ListViewItemTag;
|
||||
if(new_el) {
|
||||
$(new_el).addClass("listview_nav_focus");
|
||||
this.scroll_to_item(new_el);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Commit the navigated item
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public nav_commit() {
|
||||
if(this._nav_index > this.data.length - 1 || this._nav_index < 0) {
|
||||
return;
|
||||
}
|
||||
this.selected = this._nav_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle special key event such as key up and down
|
||||
*
|
||||
* @private
|
||||
* @param {JQuery.KeyboardEventBase} event
|
||||
* @returns {void}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private handle_special_key(event: JQuery.KeyboardEventBase) {
|
||||
switch (event.which) {
|
||||
case 37:
|
||||
case 38:
|
||||
this.nav_prev();
|
||||
return event.preventDefault();
|
||||
case 39:
|
||||
case 40:
|
||||
this.nav_next();
|
||||
return event.preventDefault();
|
||||
case 13:
|
||||
event.preventDefault();
|
||||
return this.nav_commit();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the double click event on an item
|
||||
*
|
||||
* @private
|
||||
* @protected
|
||||
* @param {TagEventType} e tag event object
|
||||
* @returns
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private idbclick(e: TagEventType<ListViewItemTag>) {
|
||||
protected idbclick(e: TagEventType<ListViewItemTag>) {
|
||||
const evt: TagEventType<ListItemEventData> = {
|
||||
id: this.aid,
|
||||
data: { item: e.data },
|
||||
@ -968,16 +1209,66 @@ namespace OS {
|
||||
this._onlistdbclick(evt);
|
||||
return this.observable.trigger("listdbclick", evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function scroll to item if it is not visible
|
||||
*
|
||||
* @public
|
||||
* @param {ListViewItemTag} item tag event object
|
||||
* @returns
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
public scroll_to_item(item: ListViewItemTag) {
|
||||
const li = $(item).children()[0];
|
||||
const offset = $(this.refs.container).offset();
|
||||
const li_offset = $(li).offset();
|
||||
|
||||
if(this.dir == "column") {
|
||||
const top = $(this.refs.container).scrollTop();
|
||||
const li_height = $(li).outerHeight();
|
||||
const container_height = $(this.refs.container).outerHeight();
|
||||
if (li_offset.top + li_height > container_height + offset.top)
|
||||
{
|
||||
$(this.refs.container).scrollTop(
|
||||
top +
|
||||
(li_offset.top + li_height - offset.top - container_height)
|
||||
);
|
||||
} else if ($(li).offset().top < offset.top) {
|
||||
$(this.refs.container).scrollTop(
|
||||
top -
|
||||
(offset.top - li_offset.top)
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
const left = $(this.refs.container).scrollLeft();
|
||||
const li_w = $(li).outerWidth();
|
||||
const container_w = $(this.refs.container).outerWidth();
|
||||
if (li_offset.left + li_w > offset.left + container_w)
|
||||
{
|
||||
$(this.refs.container).scrollLeft(
|
||||
left +
|
||||
(li_offset.left + li_w - offset.left - container_w)
|
||||
);
|
||||
} else if ($(li).offset().left < offset.left) {
|
||||
$(this.refs.container).scrollLeft(
|
||||
left -
|
||||
(offset.left - li_offset.left)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the list item select event
|
||||
*
|
||||
* @private
|
||||
* @protected
|
||||
* @param {TagEventType} e tag event object
|
||||
* @returns
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private iselect(e: TagEventType<ListViewItemTag>) {
|
||||
protected iselect(e: TagEventType<ListViewItemTag>) {
|
||||
if (!e.data) {
|
||||
return;
|
||||
}
|
||||
@ -990,6 +1281,16 @@ namespace OS {
|
||||
this.selectedItems.push(e.data);
|
||||
edata.items = this.selectedItems;
|
||||
} else {
|
||||
if(this.selectedItems.length > 0)
|
||||
{
|
||||
for(const item of this.selectedItems)
|
||||
{
|
||||
if(item != e.data)
|
||||
{
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.selectedItem === e.data) {
|
||||
return;
|
||||
}
|
||||
@ -1000,30 +1301,13 @@ namespace OS {
|
||||
this._selectedItems = [e.data];
|
||||
edata.items = [e.data];
|
||||
//scroll element
|
||||
const li = $(e.data).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()
|
||||
);
|
||||
}
|
||||
this.scroll_to_item(e.data);
|
||||
}
|
||||
|
||||
|
||||
// set the label content event it is hidden
|
||||
const label = this.refs.drlabel as LabelTag;
|
||||
label.set(e.data.data);
|
||||
if (this.dropdown) {
|
||||
const label = this.refs.drlabel as LabelTag;
|
||||
label.set(e.data.data);
|
||||
$(this.refs.mlist).hide();
|
||||
}
|
||||
const evt = { id: this.aid, data: edata };
|
||||
@ -1043,15 +1327,29 @@ namespace OS {
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
$(this).on("keyup", (e) => {
|
||||
this.handle_special_key(e);
|
||||
});
|
||||
//$(this.refs.search).on("click",(e) => {
|
||||
// console.log("focus")
|
||||
//})
|
||||
this._onmousedown = (e) => {
|
||||
if(this.multiselect || this.selectedItems == undefined || this.selectedItems.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let el: any = $(e.target).closest(
|
||||
"li[dataref='afx-list-item']"
|
||||
`[list-id='${this.aid}']`
|
||||
);
|
||||
if (el.length === 0) {
|
||||
return;
|
||||
}
|
||||
el = el.parent()[0] as ListViewItemTag;
|
||||
this._dnd.from = el;
|
||||
el = el[0];
|
||||
if(!this.selectedItems.includes(el))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this._dnd.from = this.selectedItems;
|
||||
this._dnd.to = undefined;
|
||||
$(window).on("mouseup", this._onmouseup);
|
||||
$(window).on("mousemove", this._onmousemove);
|
||||
@ -1062,13 +1360,13 @@ namespace OS {
|
||||
$(window).off("mousemove", this._onmousemove);
|
||||
$("#systooltip").hide();
|
||||
let el: any = $(e.target).closest(
|
||||
"li[dataref='afx-list-item']"
|
||||
`[list-id='${this.aid}']`
|
||||
);
|
||||
if (el.length === 0) {
|
||||
return;
|
||||
}
|
||||
el = el.parent()[0];
|
||||
if (el === this._dnd.from) {
|
||||
el = el[0];
|
||||
if (this._dnd.from.includes(el)) {
|
||||
return;
|
||||
}
|
||||
this._dnd.to = el;
|
||||
@ -1086,7 +1384,18 @@ namespace OS {
|
||||
if (!this._dnd.from) {
|
||||
return;
|
||||
}
|
||||
const data = this._dnd.from.data;
|
||||
const data = {
|
||||
text: '',
|
||||
items: this._dnd.from
|
||||
};
|
||||
if(this._dnd.from.length == 1)
|
||||
{
|
||||
data.text = this._dnd.from[0].data.text;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.text = __("{0} selected elements", this._dnd.from.length).__();
|
||||
}
|
||||
const $label = $("#systooltip");
|
||||
const top = e.clientY + 5;
|
||||
const left = e.clientX + 5;
|
||||
@ -1097,9 +1406,20 @@ namespace OS {
|
||||
.css("top", top + "px")
|
||||
.css("left", left + "px");
|
||||
};
|
||||
|
||||
const label = (this.refs.drlabel as LabelTag);
|
||||
label.iconclass$ = "bi bi-chevron-down";
|
||||
label.text = "";
|
||||
$(this.refs.drlabel).css("display", "inline-block");
|
||||
$(this.refs.btlist).hide();
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
let anchor = $(this).parent();
|
||||
while (anchor && anchor.css('position') === 'static') {
|
||||
anchor = anchor.parent();
|
||||
}
|
||||
if(anchor && anchor[0])
|
||||
{
|
||||
this._anchor = anchor[0];
|
||||
}
|
||||
return this.calibrate();
|
||||
}
|
||||
|
||||
@ -1138,16 +1458,31 @@ namespace OS {
|
||||
if (!this.dropdown) {
|
||||
return;
|
||||
}
|
||||
const desktoph = $(Ant.OS.GUI.workspace).height();
|
||||
const offset =
|
||||
$(this).offset().top + $(this.refs.mlist).height();
|
||||
if (offset > desktoph) {
|
||||
$(this.refs.mlist).css(
|
||||
"top",
|
||||
`-${$(this.refs.mlist).outerHeight()}px`
|
||||
);
|
||||
if(! $(this.refs.mlist).is(":hidden"))
|
||||
{
|
||||
$(this.refs.mlist).hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const desktoph = $(Ant.OS.GUI.workspace).outerHeight();
|
||||
const wheight = $(this).offset().top + $(this.refs.mlist).outerHeight()*1.5;
|
||||
const position = $(this).position();
|
||||
let offset = 0;
|
||||
if(this._anchor)
|
||||
{
|
||||
offset = $(this._anchor).scrollTop();
|
||||
}
|
||||
if (wheight > desktoph) {
|
||||
|
||||
const ypos = offset + position.top - $(this.refs.mlist).outerHeight();
|
||||
$(this.refs.mlist)
|
||||
.css("top",`${ypos}px`)
|
||||
.css("left", `${position.left}px`);
|
||||
} else {
|
||||
$(this.refs.mlist).css("top", "100%");
|
||||
const ypos = offset + $(this).position().top + $(this.refs.container).outerHeight();
|
||||
$(this.refs.mlist)
|
||||
.css("top", `${ypos}px`)
|
||||
.css("left", `${position.left}px`);
|
||||
}
|
||||
$(this.refs.mlist).show();
|
||||
}
|
||||
@ -1167,6 +1502,40 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the list view to end
|
||||
*
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
scroll_to_end()
|
||||
{
|
||||
if(this.dir == "column")
|
||||
{
|
||||
this.refs.mlist.scrollTo({ top: this.refs.mlist.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
else
|
||||
{
|
||||
this.refs.mlist.scrollTo({ left: this.refs.mlist.scrollWidth, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the list view to beginning
|
||||
*
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
scroll_to_start()
|
||||
{
|
||||
if(this.dir == "column")
|
||||
{
|
||||
this.refs.mlist.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
else
|
||||
{
|
||||
this.refs.mlist.scrollTo({ left: 0, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calibrate the list layout
|
||||
*
|
||||
@ -1178,9 +1547,12 @@ namespace OS {
|
||||
if (!this.dropdown) {
|
||||
return;
|
||||
}
|
||||
const w = `${$(this).width()}px`;
|
||||
$(this.refs.container).css("width", w);
|
||||
$(this.refs.current).css("width", w);
|
||||
const w = `${$(this).innerWidth()}px`;
|
||||
const h = `${$(this).outerHeight()}px`;
|
||||
$(this.refs.container).css("width", "100%");
|
||||
$(this.refs.container).css("height", h);
|
||||
|
||||
$(this.refs.current).css("width", "100%");
|
||||
$(this.refs.mlist).css("width", w);
|
||||
}
|
||||
|
||||
@ -1215,6 +1587,7 @@ namespace OS {
|
||||
|
||||
define("afx-list-view", ListViewTag);
|
||||
define("afx-list-item", SimpleListItemTag);
|
||||
define("afx-dbline-list-item", DoubleLineListItemTag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,826 +0,0 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Menu event data interface definition
|
||||
*/
|
||||
export type MenuEventData = TagEventDataType<MenuEntryTag>;
|
||||
|
||||
/**
|
||||
* This class defines the abstract prototype of an menu entry.
|
||||
* Any implementation of menu entry tag should extend this class
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class MenuEntryTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export abstract class MenuEntryTag extends AFXTag {
|
||||
/**
|
||||
* Data placeholder of the menu entry
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private _data: GenericObject<any>;
|
||||
|
||||
/**
|
||||
* placeholder of `menu entry select` event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<MenuEventData>}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private _onmenuselect: TagEventCallback<MenuEventData>;
|
||||
|
||||
/**
|
||||
* placeholder of `sub-menu entry select event` handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<MenuEventData>}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private _onchildselect: TagEventCallback<MenuEventData>;
|
||||
|
||||
/**
|
||||
* Reference to the parent menu entry of current one
|
||||
*
|
||||
* @type {MenuEntryTag}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
parent: MenuEntryTag;
|
||||
|
||||
/**
|
||||
* Reference to the root menu entry
|
||||
*
|
||||
* @type {MenuTag}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
root: MenuTag;
|
||||
|
||||
/**
|
||||
*Creates an instance of MenuEntryTag.
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._onmenuselect = this._onchildselect = (
|
||||
e: TagEventType<MenuEventData>
|
||||
): void => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.nodes = undefined;
|
||||
}
|
||||
/**
|
||||
* Set the `menu entry select` event handle
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set onmenuselect(v: TagEventCallback<MenuEventData>) {
|
||||
this._onmenuselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the `sub menu entry select` event handle
|
||||
*
|
||||
* Getter: get the current `sub menu entry select` event handle
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set onchildselect(v: TagEventCallback<MenuEventData>) {
|
||||
this._onchildselect = v;
|
||||
}
|
||||
get onchildselect(): TagEventCallback<MenuEventData> {
|
||||
return this._onchildselect;
|
||||
}
|
||||
/**
|
||||
* Setter: Set data to the entry
|
||||
*
|
||||
* Getter: Get data of the current menu entry
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set data(data: GenericObject<any>) {
|
||||
this._data = data;
|
||||
this.set(data);
|
||||
}
|
||||
get data(): GenericObject<any> {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current menu entry has sub-menu
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected has_nodes(): boolean {
|
||||
const ch = this.nodes;
|
||||
return ch && ch.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current menu entry is the root entry
|
||||
*
|
||||
* @protected
|
||||
* @returns
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected is_root() {
|
||||
if (this.parent) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the menu entry
|
||||
* This function define the outer layout of the menu entry.
|
||||
* Custom inner layout of each item implementation should
|
||||
* be defined in [[itemlayout]]
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "li",
|
||||
ref: "container",
|
||||
children: [
|
||||
{
|
||||
el: "a",
|
||||
ref: "entry",
|
||||
children: this.itemlayout(),
|
||||
},
|
||||
{ el: "afx-menu", ref: "submenu" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the sub-menu data
|
||||
*
|
||||
* Getter: Get the sub-menu data
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set nodes(v: GenericObject<any>[]) {
|
||||
$(this.refs.container).removeClass("afx_submenu");
|
||||
if (!v || !(v.length > 0)) {
|
||||
$(this.refs.submenu).hide();
|
||||
return;
|
||||
}
|
||||
$(this.refs.container).addClass("afx_submenu");
|
||||
$(this.refs.submenu).show().attr("style", "");
|
||||
const element = this.refs.submenu as MenuTag;
|
||||
element.parent = this;
|
||||
element.root = this.root;
|
||||
element.items = v;
|
||||
// ensure that the data is in sync
|
||||
this._data.nodes = v;
|
||||
if (this.is_root()) {
|
||||
$(this.refs.container).on("mouseleave",(e) => {
|
||||
return $(this.refs.submenu).attr("style", "");
|
||||
});
|
||||
}
|
||||
}
|
||||
get nodes(): GenericObject<any>[] {
|
||||
if (this.data && this.data.nodes) {
|
||||
return this.data.nodes;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Bind some base event to the menu entry
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.entry).on("click",(e) => this.select(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the sub-menu of the current menu entry
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private submenuoff(): void {
|
||||
const p = this.parent;
|
||||
if (!p) {
|
||||
$(this.refs.submenu).attr("style", "");
|
||||
return;
|
||||
}
|
||||
return p.submenuoff();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function trigger two event:
|
||||
* - the `onmenuselect` event on the current entry
|
||||
* - the `onchildselect` event on the parent of the current entry
|
||||
*
|
||||
* @protected
|
||||
* @param {JQuery.ClickEvent} e
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected select(e: JQuery.ClickEvent): void {
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: { item: this, event: e },
|
||||
};
|
||||
e.preventDefault();
|
||||
if (this.is_root() && this.has_nodes()) {
|
||||
$(this.refs.submenu).show();
|
||||
} else {
|
||||
this.submenuoff();
|
||||
}
|
||||
this._onmenuselect(evt);
|
||||
if (this.parent) {
|
||||
this.parent.onchildselect(evt);
|
||||
}
|
||||
if (this.root) {
|
||||
this.root.onmenuitemselect(evt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* custom inner layout of a menu entry
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected abstract itemlayout(): TagLayoutType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This class extends the [[MenuEntryTag]] prototype. It inner layout is
|
||||
* defined with the following elements:
|
||||
* - a [[SwitchTag]] acts as checker or radio
|
||||
* - a [[LabelTag]] to display the content of the menu entry
|
||||
* - a `span` element that display the keyboard shortcut of the entry
|
||||
*
|
||||
* @class SimpleMenuEntryTag
|
||||
* @extends {MenuEntryTag}
|
||||
*/
|
||||
export class SimpleMenuEntryTag extends MenuEntryTag {
|
||||
/**
|
||||
*Creates an instance of SimpleMenuEntryTag.
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some properties to default value
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.switch = false;
|
||||
this.radio = false;
|
||||
this.checked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off the checker feature of the menu entry
|
||||
*
|
||||
* Getter: Check whether the checker feature is enabled on this menu entry
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set switch(v: boolean) {
|
||||
this.attsw(v, "switch");
|
||||
if (this.radio || v) {
|
||||
$(this.refs.switch).show();
|
||||
} else {
|
||||
$(this.refs.switch).hide();
|
||||
}
|
||||
}
|
||||
get switch(): boolean {
|
||||
return this.hasattr("switch");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off the radio feature of the menu entry
|
||||
*
|
||||
* Getter: Check whether the radio feature is enabled
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set radio(v: boolean) {
|
||||
this.attsw(v, "radio");
|
||||
if (this.switch || v) {
|
||||
$(this.refs.switch).show();
|
||||
} else {
|
||||
$(this.refs.switch).hide();
|
||||
}
|
||||
}
|
||||
get radio(): boolean {
|
||||
return this.hasattr("radio");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Toggle the switch on the menu entry, this setter
|
||||
* only works when the `checker` or `radio` feature is
|
||||
* enabled
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the switch is turned on
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set checked(v: boolean) {
|
||||
this.attsw(v, "checked");
|
||||
if (this.data) this.data.checked = v;
|
||||
if (!this.radio && !this.switch) {
|
||||
return;
|
||||
}
|
||||
(this.refs.switch as SwitchTag).swon = v;
|
||||
}
|
||||
get checked(): boolean {
|
||||
return this.hasattr("checked");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label icon using a VFS path
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set icon(v: string) {
|
||||
$(this.refs.container).removeClass("fix_padding");
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
//$(this).attr("icon", v);
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.icon = v;
|
||||
$(this.refs.container).addClass("fix_padding");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label CSS icon class
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set iconclass(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.iconclass = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label text
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set text(v: string) {
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.text = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keyboard shortcut text
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set shortcut(v: string) {
|
||||
$(this.refs.shortcut).hide();
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.shortcut).show();
|
||||
$(this.refs.shortcut).text(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncheck all sub-menu items of the current menu entry
|
||||
* that have the radio feature enabled
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected reset_radio(): void {
|
||||
if (!this.has_nodes()) {
|
||||
return;
|
||||
}
|
||||
for (let v of this.nodes) {
|
||||
if (!v.domel.radio) {
|
||||
continue;
|
||||
}
|
||||
v.domel.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the current tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
super.mount();
|
||||
(this.refs.switch as SwitchTag).enable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the onmenuselect and onchildselect events
|
||||
*
|
||||
* @protected
|
||||
* @param {JQuery.ClickEvent} e Mouse click event
|
||||
* @returns {void}
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected select(e: JQuery.ClickEvent): void {
|
||||
if (this.switch) {
|
||||
this.checked = !this.checked;
|
||||
} else if (this.radio) {
|
||||
const p = this.parent as SimpleMenuEntryTag;
|
||||
if (p) {
|
||||
p.reset_radio();
|
||||
}
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
return super.select(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner item layout of the menu entry
|
||||
*
|
||||
* @returns
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
itemlayout() {
|
||||
return [
|
||||
{ el: "afx-switch", ref: "switch" },
|
||||
{ el: "afx-label", ref: "label" },
|
||||
{ el: "span", class: "shortcut", ref: "shortcut" },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu tag contains a collection of menu entries in which each
|
||||
* entry maybe a leaf entry or may contain a submenu
|
||||
*
|
||||
* @export
|
||||
* @class MenuTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class MenuTag extends AFXTag {
|
||||
/**
|
||||
* Reference to the parent menu entry of the current value.
|
||||
* This value is `undefined` in case of the current menu is
|
||||
* the root menu
|
||||
*
|
||||
* @type {MenuEntryTag}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
parent: MenuEntryTag;
|
||||
|
||||
/**
|
||||
* Reference to the root menu
|
||||
*
|
||||
* @type {MenuTag}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
root: MenuTag;
|
||||
|
||||
/**
|
||||
* The `pid` of the application that attached to this menu.
|
||||
* This value is optional
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
pid?: number;
|
||||
|
||||
/**
|
||||
* placeholder for menu select event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<MenuEventData>}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private _onmenuselect: TagEventCallback<MenuEventData>;
|
||||
|
||||
/**
|
||||
* Menu data placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>[]}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private _items: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
*Creates an instance of MenuTag.
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some properties to default value
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.contentag = "afx-menu-entry";
|
||||
this.context = false;
|
||||
this._items = [];
|
||||
this._onmenuselect = (
|
||||
e: TagEventType<MenuEventData>
|
||||
): void => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Set the menu items data
|
||||
*
|
||||
* Getter: Get menu items data
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set items(data: GenericObject<any>[]) {
|
||||
this._items = data;
|
||||
$(this.refs.container).empty();
|
||||
data.map((item) => this.push(item, false));
|
||||
}
|
||||
get items(): GenericObject<any>[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set whether the current menu is a context menu
|
||||
*
|
||||
* Getter: Check whether the current menu is a context menu
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set context(v: boolean) {
|
||||
this.attsw(v, "context");
|
||||
$(this.refs.wrapper).removeClass("context");
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.wrapper).addClass("context");
|
||||
$(this).hide();
|
||||
}
|
||||
get context(): boolean {
|
||||
return this.hasattr("context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set menu select event handle
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set onmenuselect(v: TagEventCallback<MenuEventData>) {
|
||||
this._onmenuselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the default tag name of the menu item.
|
||||
* If the tag is not specified in an item data,
|
||||
* this value will be used
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the default menu entry tag name
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set contentag(v: string) {
|
||||
$(this).attr("contentag", v);
|
||||
}
|
||||
get contentag(): string {
|
||||
return $(this).attr("contentag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference to the function that triggers
|
||||
* the menu select event
|
||||
*
|
||||
* @readonly
|
||||
* @type {TagEventCallback}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
get onmenuitemselect(): TagEventCallback<MenuEventData> {
|
||||
return this.handleselect;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the menu select event
|
||||
*
|
||||
* @private
|
||||
* @param {TagEventType} e
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private handleselect(e: TagEventType<MenuEventData>): void {
|
||||
if (this.context) {
|
||||
$(this).hide();
|
||||
}
|
||||
e.id = this.aid;
|
||||
this._onmenuselect(e);
|
||||
this.observable.trigger("menuselect", e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the current menu. This function is called
|
||||
* only if the current menu is a context menu
|
||||
*
|
||||
* @param {JQuery.MouseEventBase} e JQuery mouse event
|
||||
* @returns {void}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
show(e: JQuery.MouseEventBase): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
$(this)
|
||||
.css("top", e.clientY - 15 + "px")
|
||||
.css("left", e.clientX - 5 + "px")
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the current menu is the root menu
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private is_root(): boolean {
|
||||
return this.root === undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the menu tag and bind some basic events
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.container).css("display", "contents");
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.wrapper).on("mouseleave",(e) => {
|
||||
if (!this.is_root()) {
|
||||
return;
|
||||
}
|
||||
return $(this).hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a menu entry to the beginning of the current
|
||||
* menu
|
||||
*
|
||||
* @param {GenericObject<any>} item menu entry data
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
unshift(item: GenericObject<any>): void {
|
||||
this.push(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a menu entry
|
||||
*
|
||||
* @param {MenuEntryTag} item reference to the DOM element of an menu entry
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
delete(item: MenuEntryTag): void {
|
||||
const el = item.data;
|
||||
const data = this.items;
|
||||
if (data.includes(el)) {
|
||||
data.splice(data.indexOf(el), 1);
|
||||
}
|
||||
$(item).remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an menu entry to the beginning or end of the menu
|
||||
*
|
||||
* @param {GenericObject<any>} item menu entry data
|
||||
* @param {boolean} flag indicates whether the entry should be added to the beginning of the menu
|
||||
* @returns {MenuEntryTag}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
push(item: GenericObject<any>, flag: boolean): MenuEntryTag {
|
||||
let tag = this.contentag;
|
||||
if (item.tag) {
|
||||
tag = item.tag;
|
||||
}
|
||||
const el = $(`<${tag}>`);
|
||||
if (flag) {
|
||||
$(this.refs.container).prepend(el[0]);
|
||||
if (!this.items.includes(item)) {
|
||||
this.items.unshift(item);
|
||||
}
|
||||
} else {
|
||||
el.appendTo(this.refs.container);
|
||||
if (!this.items.includes(item)) {
|
||||
this.items.push(item);
|
||||
}
|
||||
}
|
||||
const entry = el[0] as MenuEntryTag;
|
||||
entry.uify(this.observable);
|
||||
entry.parent = this.parent;
|
||||
entry.root = this.parent ? this.parent.root : this;
|
||||
entry.data = item;
|
||||
item.domel = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu tag layout definition
|
||||
*
|
||||
* @returns
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
layout() {
|
||||
return [
|
||||
{
|
||||
el: "ul",
|
||||
ref: "wrapper",
|
||||
children: [
|
||||
{ el: "li", class: "afx-corner-fix" },
|
||||
{ el: "div", ref: "container" },
|
||||
{ el: "li", class: "afx-corner-fix" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-menu", MenuTag);
|
||||
define("afx-menu-entry", SimpleMenuEntryTag);
|
||||
}
|
||||
}
|
||||
}
|
@ -114,38 +114,6 @@ namespace OS {
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
$(this.refs.holder).css(
|
||||
"width",
|
||||
$(this).width() - 20 + "px"
|
||||
);
|
||||
$(this.refs.holder).css("height", $(this).height() + "px");
|
||||
$(this.refs.spinner)
|
||||
.css("width", "20px")
|
||||
.css("height", $(this).height() + "px");
|
||||
$(this.refs.incr)
|
||||
.css("height", $(this).height() / 2 - 2 + "px")
|
||||
.css("position", "relative");
|
||||
$(this.refs.decr)
|
||||
.css("height", $(this).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: HTMLElement, pos: string) {
|
||||
const el = $(ie).find("i");
|
||||
el.css(
|
||||
pos,
|
||||
($(ie).height() - el.height()) / 2 + "px"
|
||||
).css("left", ($(ie).width() - el.width()) / 2 + "px");
|
||||
};
|
||||
fn(this.refs.decr, "bottom");
|
||||
fn(this.refs.incr, "top");
|
||||
}
|
||||
|
||||
/**
|
||||
|
189
src/core/tags/NotificationTag.ts
Normal file
189
src/core/tags/NotificationTag.ts
Normal file
@ -0,0 +1,189 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
|
||||
/**
|
||||
* Toast notification tag
|
||||
*
|
||||
* @export
|
||||
* @class ToastNotificationTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class ToastNotificationTag extends AFXTag {
|
||||
/**
|
||||
*Creates an instance of ToastNotificationTag.
|
||||
* @memberof ToastNotificationTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mount the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof ToastNotificationTag
|
||||
*/
|
||||
protected mount() {
|
||||
$(this.refs.header).on('click',(e) => {
|
||||
$(this).remove();
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof ToastNotificationTag
|
||||
*/
|
||||
protected init(): void {
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-calibrate tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof ToastNotificationTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Update the current tag, do nothing in this tag
|
||||
*
|
||||
* @param {*} [d]
|
||||
* @memberof ToastNotificationTag
|
||||
*/
|
||||
reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Tag layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof ToastNotificationTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div", id: "toast_container", ref: "container",
|
||||
children:[
|
||||
{
|
||||
el: "div",
|
||||
ref: "header",
|
||||
id: "toast_header",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "yield",
|
||||
id:"toast_content",
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This tag manage all notification UI on the desktop
|
||||
*
|
||||
* @export
|
||||
* @class NotificationTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class NotificationTag extends AFXTag {
|
||||
/**
|
||||
*Creates an instance of NotificationTag.
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mount the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
protected mount() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
protected init(): void {
|
||||
};
|
||||
|
||||
/**
|
||||
* Push anotification to a specific location
|
||||
*
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
push(tag: AFXTag, loc: ANCHOR = ANCHOR.NORTH): void
|
||||
{
|
||||
if(!this.refs[loc])
|
||||
{
|
||||
return;
|
||||
}
|
||||
switch(loc)
|
||||
{
|
||||
case ANCHOR.NORTH:
|
||||
case ANCHOR.NORTH_EST:
|
||||
case ANCHOR.NORTH_WEST:
|
||||
$(this.refs[loc]).prepend(tag);
|
||||
break;
|
||||
case ANCHOR.SOUTH:
|
||||
case ANCHOR.SOUTH_EST:
|
||||
case ANCHOR.SOUTH_WEST:
|
||||
$(this.refs[loc]).append(tag);
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
this.calibrate();
|
||||
}
|
||||
/**
|
||||
* Re-calibrate tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Update the current tag, do nothing in this tag
|
||||
*
|
||||
* @param {*} [d]
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Tag layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof NotificationTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{ el: "div", id: "north", ref: "NORTH" },
|
||||
{ el: "div", id: "south", ref: "SOUTH" },
|
||||
{ el: "div", id: "north_west", ref: "NORTH_WEST" },
|
||||
{ el: "div", id: "south_west", ref: "SOUTH_WEST" },
|
||||
{ el: "div", id: "north_est", ref: "NORTH_EST" },
|
||||
{ el: "div", id: "south_est", ref: "SOUTH_EST" }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-notification", NotificationTag);
|
||||
define("afx-toast-notification", ToastNotificationTag);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,7 +3,7 @@ namespace OS {
|
||||
export namespace tag {
|
||||
/**
|
||||
* An overlay tag is a layout tag that alway stay on top of
|
||||
* the virtual desktop environment. Tile layout elements ([[VBoxTag]], [[HboxTag]])
|
||||
* the virtual desktop environment. Tile layout elements ({@link OS.GUI.tag.VBoxTag}, {@link OS.GUI.tag.HBoxTag})
|
||||
* can be used inside this tag to compose elements
|
||||
*
|
||||
* @export
|
||||
@ -127,13 +127,16 @@ namespace OS {
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
$(this).css("width", this.width).css("height", this.height);
|
||||
$(this)
|
||||
.css("width", this.width)
|
||||
.css("height", this.height);
|
||||
return this.observable.trigger("resize", {
|
||||
id: this.aid,
|
||||
data: {
|
||||
w: this.width,
|
||||
h: this.height,
|
||||
},
|
||||
root: true
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ namespace OS {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A `resizer` tag is basically used to dynamically resize an element using mouse.
|
||||
* It is usually put inside a [[TileLayoutTag]] an can be attached to any element. Example:
|
||||
* It is usually put inside a {@link TileLayoutTag} an can be attached to any element. Example:
|
||||
*
|
||||
* The resizer tag in the following example will be attached to the first `afx-vbox`,
|
||||
* and allows to resize this element using mouse
|
||||
@ -41,8 +41,8 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Reference to the parent tag of the current tag.
|
||||
* The parent tag should be an instance of a [[TileLayoutTag]]
|
||||
* such as [[VBoxTag]] or [[HBoxTag]]
|
||||
* The parent tag should be an instance of a {@link TileLayoutTag}
|
||||
* such as {@link VBoxTag} or {@link HBoxTag}
|
||||
*
|
||||
* @private
|
||||
* @type {*}
|
||||
@ -92,8 +92,8 @@ namespace OS {
|
||||
* Setter:
|
||||
*
|
||||
* Set resize direction, two possible values:
|
||||
* - `hz` - horizontal direction, resize by width
|
||||
* - `ve` - vertical direction, resize by height
|
||||
* - `row` - horizontal direction, resize by width
|
||||
* - `column` - vertical direction, resize by height
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
@ -104,19 +104,15 @@ namespace OS {
|
||||
set dir(v: string) {
|
||||
let att: string;
|
||||
$(this).attr("dir", v);
|
||||
$(this).off("mousedown", null);
|
||||
if (v === "hz") {
|
||||
$(this).css("cursor", "col-resize");
|
||||
$(this).addClass("horizontal");
|
||||
$(this).off("pointerdown", null);
|
||||
if (v === "row") {
|
||||
if (this._resizable_el) {
|
||||
att = $(this._resizable_el).attr("min-width");
|
||||
if (att) {
|
||||
this._minsize = parseInt(att);
|
||||
}
|
||||
}
|
||||
} else if (v === "ve") {
|
||||
$(this).css("cursor", "row-resize");
|
||||
$(this).addClass("vertical");
|
||||
} else if (v === "column") {
|
||||
if (this._resizable_el) {
|
||||
att = $(this._resizable_el).attr("min-height");
|
||||
if (att) {
|
||||
@ -187,14 +183,31 @@ namespace OS {
|
||||
? $(this).prev()[0]
|
||||
: undefined;
|
||||
}
|
||||
|
||||
if (tagname === "AFX-HBOX") {
|
||||
this.dir = "hz";
|
||||
} else if (tagname === "AFX-VBOX") {
|
||||
this.dir = "ve";
|
||||
} else {
|
||||
this.dir = "hz";
|
||||
if(this.dir)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (tagname === "AFX-HBOX") {
|
||||
this.dir = "row";
|
||||
} else if (tagname === "AFX-VBOX") {
|
||||
this.dir = "column";
|
||||
} else {
|
||||
this.dir = "row";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter Disable or enable the resize event
|
||||
*
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
set disable(v: boolean)
|
||||
{
|
||||
this.attsw(v, "disable");
|
||||
}
|
||||
get disable(): boolean
|
||||
{
|
||||
return this.hasattr("disable");
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,24 +221,28 @@ namespace OS {
|
||||
if (!this.dir || this.dir == "none") {
|
||||
return;
|
||||
}
|
||||
$(this).on("mousedown", (e) => {
|
||||
$(this).on("pointerdown", (e) => {
|
||||
e.preventDefault();
|
||||
$(window).on("mousemove", (evt) => {
|
||||
if(this.disable)
|
||||
{
|
||||
return;
|
||||
}
|
||||
$(window).on("pointermove", (evt) => {
|
||||
if (!this._resizable_el) {
|
||||
return;
|
||||
}
|
||||
if (this.dir === "hz") {
|
||||
return this.horizontalResize(evt);
|
||||
} else if (this.dir === "ve") {
|
||||
return this.verticalResize(evt);
|
||||
if (this.dir === "row") {
|
||||
return this.horizontalResize(evt as JQuery.MouseEventBase);
|
||||
} else if (this.dir === "column") {
|
||||
return this.verticalResize(evt as JQuery.MouseEventBase);
|
||||
}
|
||||
});
|
||||
|
||||
return $(window).on("mouseup", function (evt) {
|
||||
$(window).off("mousemove", null);
|
||||
$(window).off("mouseup", null);
|
||||
return $(window).on("pointerup", function (evt) {
|
||||
$(window).off("pointermove", null);
|
||||
$(window).off("pointerup", null);
|
||||
|
||||
return $(window).off("mouseup", null);
|
||||
return $(window).off("pointerup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -65,6 +65,7 @@ namespace OS {
|
||||
this.enable = true;
|
||||
this._max = 100;
|
||||
this._value = 0;
|
||||
this.precision = false;
|
||||
this._onchange = this._onchanging = () => {};
|
||||
}
|
||||
|
||||
@ -98,7 +99,17 @@ namespace OS {
|
||||
set onvaluechanging(f: TagEventCallback<number>) {
|
||||
this._onchanging = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter/Getter: set and get precision reading
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
set precision(v: boolean) {
|
||||
this.attsw(v, "precision");
|
||||
}
|
||||
get precision(): boolean {
|
||||
return this.hasattr("precision");
|
||||
}
|
||||
/**
|
||||
* Setter: Enable/disable the slider
|
||||
*
|
||||
@ -110,15 +121,15 @@ namespace OS {
|
||||
this.attsw(v, "enable");
|
||||
if (v) {
|
||||
$(this)
|
||||
.on("mouseover",() => {
|
||||
.on("pointerover",() => {
|
||||
return $(this.refs.point).show();
|
||||
})
|
||||
.on("mouseout",() => {
|
||||
.on("pointerout",() => {
|
||||
return $(this.refs.point).hide();
|
||||
});
|
||||
} else {
|
||||
$(this.refs.point).hide();
|
||||
$(this).off("mouseover").off("mouseout");
|
||||
$(this).off("pointerover").off("pointerout");
|
||||
}
|
||||
}
|
||||
get enable(): boolean {
|
||||
@ -188,7 +199,16 @@ namespace OS {
|
||||
*/
|
||||
calibrate(): void {
|
||||
if (this.value > this.max) {
|
||||
this.value = this.max;
|
||||
this._value = this.max;
|
||||
}
|
||||
if(! this.precision)
|
||||
{
|
||||
this._value = Math.round(this.value);
|
||||
$(this.refs.point).text(this.value);
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this.refs.point).text((Math.round(this.value * 100) / 100).toFixed(2));
|
||||
}
|
||||
$(this.refs.container).css("width", $(this).width() + "px");
|
||||
const w =
|
||||
@ -197,17 +217,17 @@ namespace OS {
|
||||
$(this.refs.prg)
|
||||
.css("width", w + "px")
|
||||
.css("height", $(this.refs.container).height() + "px");
|
||||
if (this.enable) {
|
||||
const ow = w - $(this.refs.point).width() / 2;
|
||||
const top = Math.floor(
|
||||
($(this.refs.prg).height() -
|
||||
$(this.refs.point).height()) /
|
||||
2
|
||||
);
|
||||
$(this.refs.point)
|
||||
.css("left", ow + "px")
|
||||
.css("top", top + "px");
|
||||
}
|
||||
//if (this.enable) {
|
||||
const ow = w - ($(this.refs.point).outerWidth() / 2.0);
|
||||
const top = Math.floor(
|
||||
($(this.refs.prg).height() +
|
||||
$(this.refs.point).height()) /
|
||||
2 + 3
|
||||
);
|
||||
$(this.refs.point)
|
||||
.css("left", ow + "px")
|
||||
.css("bottom", top + "px");
|
||||
//}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -220,10 +240,10 @@ namespace OS {
|
||||
$(this.refs.point)
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default");
|
||||
$(this.refs.point).on("mousedown", (e) => {
|
||||
$(this).on("pointerdown", (e) => {
|
||||
e.preventDefault();
|
||||
const offset = $(this.refs.container).offset();
|
||||
$(window).on("mousemove", (e) => {
|
||||
$(window).on("pointermove", (e) => {
|
||||
let left = e.clientX - offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
const maxw = $(this.refs.container).width();
|
||||
@ -236,13 +256,13 @@ namespace OS {
|
||||
});
|
||||
});
|
||||
|
||||
$(window).on("mouseup", (e) => {
|
||||
$(window).on("pointerup", (e) => {
|
||||
this._onchange({
|
||||
id: this.aid,
|
||||
data: this.value,
|
||||
});
|
||||
$(window).off("mousemove", null);
|
||||
return $(window).off("mouseup", null);
|
||||
$(window).off("pointermove", null);
|
||||
return $(window).off("pointerup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
573
src/core/tags/StackMenuTag.ts
Normal file
573
src/core/tags/StackMenuTag.ts
Normal file
@ -0,0 +1,573 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* menu event data type definition
|
||||
*/
|
||||
export type StackMenuEventData = TagEventDataType<ListViewItemTag>;
|
||||
/**
|
||||
* The layout of a simple stack menu item
|
||||
*
|
||||
* @export
|
||||
* @class SimpleStackMenuItemTag
|
||||
* @extends {ListViewItemTag}
|
||||
*/
|
||||
export class SimpleStackMenuItemTag extends ListViewItemTag {
|
||||
/**
|
||||
*Creates an instance of SimpleStackMenuItemTag.
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some property to default
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.closable = false;
|
||||
this.data = {};
|
||||
this.switch = false;
|
||||
this.radio = false;
|
||||
this.checked = false;
|
||||
}
|
||||
/**
|
||||
* Mount the current tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
super.mount();
|
||||
(this.refs.switch as SwitchTag).enable = false;
|
||||
}
|
||||
/**
|
||||
* Setter: Turn on/off the checker feature of the menu entry
|
||||
*
|
||||
* Getter: Check whether the checker feature is enabled on this menu entry
|
||||
*
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
set switch(v: boolean) {
|
||||
this.attsw(v, "switch");
|
||||
if (this.radio || v) {
|
||||
$(this.refs.switch).show();
|
||||
} else {
|
||||
$(this.refs.switch).hide();
|
||||
}
|
||||
}
|
||||
get switch(): boolean {
|
||||
return this.hasattr("switch");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: select/unselect the current item
|
||||
*
|
||||
* Getter: Check whether the current item is selected
|
||||
*
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
set selected(v: boolean) {
|
||||
|
||||
if(v)
|
||||
{
|
||||
if (this.switch) {
|
||||
this.checked = !this.checked;
|
||||
} else if (this.radio) {
|
||||
// reset radio
|
||||
const p = this.parentElement;
|
||||
if (p) {
|
||||
for(let item of Array.from(p.children))
|
||||
{
|
||||
const el = item as SimpleStackMenuItemTag;
|
||||
if(el.radio)
|
||||
{
|
||||
el.checked = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
}
|
||||
super.selected = v;
|
||||
}
|
||||
get selected(): boolean {
|
||||
return this.hasattr("selected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off the radio feature of the menu entry
|
||||
*
|
||||
* Getter: Check whether the radio feature is enabled
|
||||
*
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
set radio(v: boolean) {
|
||||
this.attsw(v, "radio");
|
||||
if (this.switch || v) {
|
||||
$(this.refs.switch).show();
|
||||
} else {
|
||||
$(this.refs.switch).hide();
|
||||
}
|
||||
}
|
||||
get radio(): boolean {
|
||||
return this.hasattr("radio");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Toggle the switch on the menu entry, this setter
|
||||
* only works when the `checker` or `radio` feature is
|
||||
* enabled
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the switch is turned on
|
||||
*
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
set checked(v: boolean) {
|
||||
this.attsw(v, "checked");
|
||||
if (this.data) this.data.checked = v;
|
||||
if (!this.radio && !this.switch) {
|
||||
return;
|
||||
}
|
||||
(this.refs.switch as SwitchTag).swon = v;
|
||||
}
|
||||
get checked(): boolean {
|
||||
return this.hasattr("checked");
|
||||
}
|
||||
/**
|
||||
* Set the keyboard shortcut text
|
||||
*
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
set shortcut(v: string) {
|
||||
$(this.refs.shortcut).hide();
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.shortcut).show();
|
||||
$(this.refs.shortcut).text(v);
|
||||
}
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
protected calibrate(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the inner label when the item data
|
||||
* is changed
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
const v = this.data;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
if(v.nodes && v.nodes.length > 0)
|
||||
{
|
||||
$(this.refs.submenu).show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this.refs.submenu).hide();
|
||||
}
|
||||
const label = this.refs.label as LabelTag;
|
||||
this.set(v);
|
||||
label.set(v);
|
||||
if (v.selected) {
|
||||
this.selected = v.selected;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-render the list item
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
protected reload(): void {
|
||||
this.data = this.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* List item custom layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType}
|
||||
* @memberof SimpleStackMenuItemTag
|
||||
*/
|
||||
protected itemlayout(): TagLayoutType {
|
||||
return {
|
||||
el:"div",
|
||||
children: [
|
||||
{ el: "afx-switch", ref: "switch" },
|
||||
{ el: "afx-label", ref: "label" },
|
||||
{ el: "span", class: "shortcut", ref: "shortcut" },
|
||||
{ el: "span", class: "afx-submenu", ref: "submenu" },
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
/**
|
||||
* A stack menu is a multilevel menu that
|
||||
* uses a single list view to navigate all menu levels
|
||||
* instead of using a traditional cascade style menu
|
||||
*
|
||||
* @export
|
||||
* @class StackMenuTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class StackMenuTag extends AFXTag {
|
||||
/**
|
||||
* Data stack, the list always displays the
|
||||
* element on the top of the stack
|
||||
*
|
||||
* @type {GenericObject<any>[][]}
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
private stack: GenericObject<any>[][];
|
||||
/**
|
||||
* Update the current tag, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
/**
|
||||
* Placeholder of tab select event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<TabEventData>}
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
private _onmenuselect: TagEventCallback<StackMenuEventData>;
|
||||
/**
|
||||
* Stack menu constructor
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
/**
|
||||
* Reset to default some property value
|
||||
*
|
||||
* @protected
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.stack = [];
|
||||
this._onmenuselect = (_) => {};
|
||||
this.context = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalcutate the menu coordinate in case of
|
||||
* context menu
|
||||
*
|
||||
* @protected
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
protected calibrate(): void {
|
||||
if(this.context)
|
||||
{
|
||||
const offset = $(this).position();
|
||||
let left = offset.left;
|
||||
let top = offset.top;
|
||||
const ph = $(this).parent().height();
|
||||
const pw = $(this).parent().width();
|
||||
|
||||
const dy = top + $(this).height() - ph;
|
||||
const dx = left + $(this).width() - pw;
|
||||
if(dx < 0 && dy < 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
top -= dy > 0?dy:0;
|
||||
left -= dx > 0?dx:0;
|
||||
$(this)
|
||||
.css("top", top + "px")
|
||||
.css("left", left + "px");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the menu to its initial state
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
reset(): void {
|
||||
const btn = this.refs.title as ButtonTag;
|
||||
const list = this.refs.list as ListViewTag;
|
||||
list.selected = -1;
|
||||
btn.data = undefined;
|
||||
if(this.stack.length > 0)
|
||||
{
|
||||
let arr = this.stack[0];
|
||||
this.stack = [];
|
||||
list.data = arr[1] as any;
|
||||
$(btn).hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the menu and bind some basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
const btn = this.refs.title as ButtonTag;
|
||||
const list = this.refs.list as ListViewTag;
|
||||
list.itemtag = "afx-stack-menu-item";
|
||||
btn.onbtclick = (_) => {
|
||||
let arr = this.stack.pop();
|
||||
if(this.stack.length == 0)
|
||||
{
|
||||
$(btn).hide();
|
||||
btn.data = undefined;
|
||||
}
|
||||
else
|
||||
{
|
||||
btn.data = arr[0];
|
||||
btn.iconclass = "bi bi-backspace";
|
||||
}
|
||||
list.data = arr[1] as any;
|
||||
};
|
||||
list.onlistselect = (e) => {
|
||||
let data = e.data.item.data;
|
||||
e.id = this.aid;
|
||||
if(btn.data && btn.data.onchildselect)
|
||||
{
|
||||
btn.data.onchildselect(e);
|
||||
}
|
||||
if(data.onmenuselect)
|
||||
{
|
||||
data.onmenuselect(e);
|
||||
}
|
||||
this._onmenuselect(e);
|
||||
this.observable.trigger("menuselect", e);
|
||||
if(data.nodes && data.nodes.length > 0)
|
||||
{
|
||||
this.stack.push([btn.data, list.data]);
|
||||
btn.data = data;
|
||||
btn.iconclass = "bi bi-backspace";
|
||||
$(btn).show();
|
||||
list.selected = -1;
|
||||
list.data = data.nodes;
|
||||
if(this.context)
|
||||
{
|
||||
this.calibrate();
|
||||
}
|
||||
} else if (this.context) {
|
||||
$(this).hide();
|
||||
}
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Setter: set current selected item index
|
||||
*
|
||||
* Getter: Get current selected item index
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
set selected(i: number | number[])
|
||||
{
|
||||
const list = this.refs.list as ListViewTag;
|
||||
list.selected = i;
|
||||
}
|
||||
get selected(): number | number[]
|
||||
{
|
||||
const list = this.refs.list as ListViewTag;
|
||||
return list.selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set whether the current menu is a context menu
|
||||
*
|
||||
* Getter: Check whether the current menu is a context menu
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
set context(v: boolean) {
|
||||
this.attsw(v, "context");
|
||||
$(this).removeClass("context");
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this).addClass("context");
|
||||
$(this).hide();
|
||||
}
|
||||
get context(): boolean {
|
||||
return this.hasattr("context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest selected item
|
||||
*
|
||||
* @readonly
|
||||
* @type {ListViewItemTag}
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
get selectedItem(): ListViewItemTag {
|
||||
const list = this.refs.list as ListViewTag;
|
||||
return list.selectedItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the selected items
|
||||
*
|
||||
* @readonly
|
||||
* @type {ListViewItemTag[]}
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
get selectedItems(): ListViewItemTag[] {
|
||||
const list = this.refs.list as ListViewTag;
|
||||
return list.selectedItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* The following setter/getter are keep for backward compatible
|
||||
* with the MenuTag interface
|
||||
*
|
||||
* Setter: Set the menu data
|
||||
*
|
||||
* Getter: Get the menu data
|
||||
*
|
||||
* @deprecated
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
set items(v: GenericObject<any>[]) {
|
||||
this.nodes = v;
|
||||
}
|
||||
get items(): GenericObject<any>[] {
|
||||
return this.nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the menu data
|
||||
*
|
||||
* Getter: Get the menu data
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
set nodes(v: GenericObject<any>[]) {
|
||||
this.stack = [];
|
||||
this.reset();
|
||||
(this.refs.list as ListViewTag).data = v;
|
||||
$(this.refs.title).hide();
|
||||
}
|
||||
get nodes(): GenericObject<any>[] {
|
||||
return (this.refs.list as ListViewTag).data;
|
||||
}
|
||||
/**
|
||||
* Set the `menu entry select` event handle
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
set onmenuselect(v: TagEventCallback<StackMenuEventData>) {
|
||||
this._onmenuselect = v;
|
||||
}
|
||||
/**
|
||||
* Hide the current menu. This function is called
|
||||
* only if the current menu is context menu
|
||||
*
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
hide(): void
|
||||
{
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
$(this)
|
||||
.css("bottom", "unset")
|
||||
.css("top", "unset")
|
||||
.css("left", "unset")
|
||||
.css("right", "unset")
|
||||
.hide();
|
||||
}
|
||||
/**
|
||||
* Show the current menu. This function is called
|
||||
* only if the current menu is a context menu
|
||||
*
|
||||
* @param {JQuery.MouseEventBase} e JQuery mouse event
|
||||
* @returns {void}
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
show(e?: JQuery.MouseEventBase): void {
|
||||
const list = this.refs.list as ListViewTag;
|
||||
const btn = this.refs.title as ButtonTag;
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
if(e)
|
||||
{
|
||||
const offset = $(this).parent().offset();
|
||||
let top = e.clientY - offset.top - 15;
|
||||
let left = e.clientX - offset.left - 5;
|
||||
|
||||
$(this)
|
||||
.css("top", top + "px")
|
||||
.css("left", left + "px")
|
||||
.css("bottom", "unset")
|
||||
.css("right", "unset");
|
||||
|
||||
}
|
||||
const dropoff = (e) => {
|
||||
if($(e.target).closest(`[list-id="${list.aid}"]`).length > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if($(e.target).closest(btn).length > 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.hide();
|
||||
|
||||
$(document).off("click", dropoff);
|
||||
};
|
||||
$(document).on("click", dropoff);
|
||||
$(this).show();
|
||||
this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof StackMenuTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "title"
|
||||
},
|
||||
{
|
||||
el: "afx-list-view",
|
||||
ref: "list",
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
define("afx-stack-menu", StackMenuTag);
|
||||
define("afx-stack-menu-item", SimpleStackMenuItemTag);
|
||||
}
|
||||
}
|
||||
}
|
76
src/core/tags/StackPanelTag.ts
Normal file
76
src/core/tags/StackPanelTag.ts
Normal file
@ -0,0 +1,76 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A stack pannel allows to navigate back and forth between pannels
|
||||
* (container widget). Each container widget in the stack should be
|
||||
* composed inside a {@link HBoxTag}
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @class StackPanelTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class StackPanelTag extends TabContainerTag {
|
||||
private _current_pannel_index: number;
|
||||
/**
|
||||
* Mount the tag and bind basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof StackPanelTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this._current_pannel_index = -1;
|
||||
super.mount();
|
||||
this.observable.one("mounted", (id) => {
|
||||
this.tabbarheight = 0;
|
||||
$(this.refs.bar).hide();
|
||||
this.navigateNext();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab select event handle
|
||||
*
|
||||
* @memberof StackPanelTag
|
||||
*/
|
||||
set ontabselect(f: TagEventCallback<TabContainerTabType>) {
|
||||
}
|
||||
/**
|
||||
* Navigate to the next panel
|
||||
*
|
||||
* @memberof StackPanelTag
|
||||
*/
|
||||
navigateNext(): void
|
||||
{
|
||||
if(this._current_pannel_index >= this.tabs.length)
|
||||
return;
|
||||
this._current_pannel_index++;
|
||||
this.navigate();
|
||||
}
|
||||
/**
|
||||
* Navigate back to the previous panel
|
||||
*
|
||||
* @memberof StackPanelTag
|
||||
*/
|
||||
navigateBack(): void
|
||||
{
|
||||
if(this._current_pannel_index <= 0)
|
||||
return;
|
||||
this._current_pannel_index--;
|
||||
this.navigate()
|
||||
}
|
||||
/**
|
||||
* Navigate to a custom panel
|
||||
*
|
||||
* @memberof StackPanelTag
|
||||
*/
|
||||
private navigate()
|
||||
{
|
||||
this.selectedIndex = this._current_pannel_index;
|
||||
}
|
||||
}
|
||||
define("afx-stack-panel", StackPanelTag);
|
||||
}
|
||||
}
|
||||
}
|
@ -34,10 +34,19 @@ namespace OS {
|
||||
* Store pending loading task
|
||||
*
|
||||
* @private
|
||||
* @type {Promise<any>[]}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private _pending_task: Promise<any>[];
|
||||
|
||||
/**
|
||||
* Store the current attached service
|
||||
*
|
||||
* @private
|
||||
* @type {number[]}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private _pending_task: number[];
|
||||
private _services: application.BaseService[];
|
||||
|
||||
/**
|
||||
* Loading animation check timeout
|
||||
@ -70,13 +79,15 @@ namespace OS {
|
||||
constructor() {
|
||||
super();
|
||||
this._osmenu = {
|
||||
text: __("Start"),
|
||||
iconclass: "fa fa-circle",
|
||||
text: "",
|
||||
//iconclass: "fa fa-circle",
|
||||
icon: "os://resources/themes/system/icons/antos-32x32.png"
|
||||
};
|
||||
this._view = false;
|
||||
this._pending_task = [];
|
||||
this._loading_toh = undefined;
|
||||
this.app_list= [];
|
||||
this._services = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,7 +108,7 @@ namespace OS {
|
||||
protected reload(d?: any): void { }
|
||||
|
||||
/**
|
||||
* Attach a service to the system tray on the pannel,
|
||||
* Attach a service to the system tray on the panel,
|
||||
* this operation is performed when a service is started
|
||||
*
|
||||
* @param {BaseService} s
|
||||
@ -105,8 +116,7 @@ namespace OS {
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
attachservice(s: application.BaseService) {
|
||||
(this.refs.systray as MenuTag).unshift(s);
|
||||
return s.attach(this.refs.systray);
|
||||
this._services.unshift(s);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,9 +133,6 @@ namespace OS {
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (!el.data || el.data.dataid === "header") {
|
||||
return;
|
||||
}
|
||||
this.toggle(false);
|
||||
// launch the app or open the file
|
||||
Ant.OS.GUI.openWith(el.data as AppArgumentsType);
|
||||
@ -142,25 +149,25 @@ namespace OS {
|
||||
*/
|
||||
private search(e: JQuery.KeyboardEventBase): void {
|
||||
const applist = this.refs.applist as ListViewTag;
|
||||
const catlist = this.refs.catlist as ListViewTag;
|
||||
const catlist = this.refs.catlist as TabBarTag;
|
||||
switch (e.which) {
|
||||
case 27:
|
||||
// escape key
|
||||
return this.toggle(false);
|
||||
|
||||
case 37:
|
||||
applist.nav_prev();
|
||||
return e.preventDefault();
|
||||
case 38:
|
||||
applist.selectPrev();
|
||||
return e.preventDefault();
|
||||
case 39:
|
||||
applist.nav_next();
|
||||
return e.preventDefault();
|
||||
case 40:
|
||||
applist.selectNext();
|
||||
return e.preventDefault();
|
||||
case 13:
|
||||
e.preventDefault();
|
||||
return this.open();
|
||||
return applist.nav_commit();
|
||||
default:
|
||||
catlist.selected = 0;
|
||||
var text = (this.refs.search as HTMLInputElement)
|
||||
@ -187,9 +194,8 @@ namespace OS {
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
detachservice(s: application.BaseService): void {
|
||||
(this.refs.systray as MenuTag).delete(
|
||||
s.domel as MenuEntryTag
|
||||
);
|
||||
const index = this._services.indexOf(s);
|
||||
this._services.splice(index, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -206,23 +212,17 @@ namespace OS {
|
||||
ref: "panel",
|
||||
children: [
|
||||
{
|
||||
el: "afx-menu",
|
||||
el: "afx-button",
|
||||
ref: "osmenu",
|
||||
class: "afx-panel-os-menu",
|
||||
},
|
||||
{
|
||||
el: "afx-menu",
|
||||
ref: "pinned",
|
||||
class: "afx-panel-os-pinned-app",
|
||||
el: "afx-apps-dock",
|
||||
ref: "sysdock",
|
||||
id: "sysdock"
|
||||
},
|
||||
{
|
||||
el: "afx-menu",
|
||||
id: "appmenu",
|
||||
ref: "appmenu",
|
||||
class: "afx-panel-os-app",
|
||||
},
|
||||
{
|
||||
el: "afx-menu",
|
||||
el: "afx-button",
|
||||
id: "systray",
|
||||
ref: "systray",
|
||||
class: "afx-panel-os-stray",
|
||||
@ -247,17 +247,13 @@ namespace OS {
|
||||
],
|
||||
},
|
||||
{
|
||||
el: "afx-hbox",
|
||||
el: "afx-vbox",
|
||||
children: [
|
||||
{
|
||||
el: "afx-list-view",
|
||||
el: "afx-tab-bar",
|
||||
id: "catlist",
|
||||
ref: "catlist",
|
||||
width:"40%"
|
||||
},
|
||||
{
|
||||
el: "afx-resizer",
|
||||
width: 3,
|
||||
height:45
|
||||
},
|
||||
{
|
||||
el: "afx-list-view",
|
||||
@ -269,7 +265,7 @@ namespace OS {
|
||||
{
|
||||
el: "afx-hbox",
|
||||
id: "btlist",
|
||||
height: 30,
|
||||
height: 40,
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
@ -293,6 +289,7 @@ namespace OS {
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
@ -304,7 +301,7 @@ namespace OS {
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private refreshAppList(): void {
|
||||
let catlist_el = (this.refs.catlist as tag.ListViewTag);
|
||||
let catlist_el = (this.refs.catlist as tag.TabBarTag);
|
||||
let k: string, v: API.PackageMetaType;
|
||||
const catlist = new Set();
|
||||
this.app_list = [];
|
||||
@ -349,7 +346,7 @@ namespace OS {
|
||||
iconclass: "bi bi-gear-wide"
|
||||
});
|
||||
});
|
||||
catlist_el.data = cat_list_data;
|
||||
catlist_el.items = cat_list_data;
|
||||
catlist_el.selected = 0;
|
||||
}
|
||||
|
||||
@ -369,7 +366,11 @@ namespace OS {
|
||||
this.calibrate();
|
||||
$(document).on("click", this._cb);
|
||||
(this.refs.search as HTMLInputElement).value = "";
|
||||
$(this.refs.search).trigger("focus");
|
||||
if(!OS.mobile)
|
||||
{
|
||||
$(this.refs.search).trigger("focus");
|
||||
}
|
||||
|
||||
} else {
|
||||
$(this.refs.overlay).hide();
|
||||
$(document).off("click", this._cb);
|
||||
@ -386,34 +387,6 @@ namespace OS {
|
||||
}px`;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Refresh the pinned applications menu
|
||||
*
|
||||
* @private
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private RefreshPinnedApp(): void
|
||||
{
|
||||
if(!setting.system.startup.pinned)
|
||||
return;
|
||||
(this.refs.pinned as GUI.tag.MenuTag).items =
|
||||
setting.system.startup.pinned
|
||||
.filter((el) =>{
|
||||
const app = setting.system.packages[el];
|
||||
return app && app.app
|
||||
})
|
||||
.map((name) => {
|
||||
const app = setting.system.packages[name];
|
||||
return {
|
||||
icon: app.icon,
|
||||
iconclass: app.iconclass,
|
||||
app: app.app,
|
||||
tooltip: `cb:${app.name}`
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the loading tasks ended,
|
||||
* if it the case, stop the animation
|
||||
@ -431,6 +404,18 @@ namespace OS {
|
||||
clearTimeout(this._loading_toh);
|
||||
this._loading_toh = undefined;
|
||||
}
|
||||
|
||||
private show_systray(): void
|
||||
{
|
||||
const ctxmenu = $("#contextmenu")[0] as tag.StackMenuTag;
|
||||
ctxmenu.hide();
|
||||
ctxmenu.nodes = this._services;
|
||||
$(ctxmenu)
|
||||
.css("right", 0)
|
||||
.css("bottom", $(this).height());
|
||||
ctxmenu.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag bind some basic event
|
||||
*
|
||||
@ -438,19 +423,16 @@ namespace OS {
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
(this.refs.osmenu as MenuTag).items = [this._osmenu];
|
||||
const systray = this.refs.systray as GUI.tag.ButtonTag;
|
||||
(this.refs.osmenu as ButtonTag).set(this._osmenu);
|
||||
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).trigger("focus");
|
||||
}
|
||||
};
|
||||
$(this.refs.appmenu).css("z-index", 1000000);
|
||||
$(this.refs.systray).css("z-index", 1000000);
|
||||
(this.refs.btscreen as ButtonTag).set({
|
||||
iconclass: "fa fa-tv",
|
||||
onbtclick: (e) => {
|
||||
@ -475,17 +457,24 @@ namespace OS {
|
||||
return Ant.OS.exit();
|
||||
},
|
||||
});
|
||||
(this.refs.osmenu as MenuTag).onmenuselect = (e) => {
|
||||
return this.toggle(true);
|
||||
(this.refs.osmenu as ButtonTag).onbtclick = (e) => {
|
||||
if($(this.refs.overlay).is(":hidden"))
|
||||
{
|
||||
this.toggle(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.toggle(false);
|
||||
}
|
||||
};
|
||||
|
||||
$(this.refs.search).on("keyup", (e) => {
|
||||
return this.search(e);
|
||||
});
|
||||
|
||||
$(this.refs.applist).on("click", (e) => {
|
||||
(this.refs.applist as ListViewTag).onlistselect = (_) => {
|
||||
return this.open();
|
||||
});
|
||||
};
|
||||
Ant.OS.GUI.bindKey("CTRL- ", (e) => {
|
||||
if (this._view === false) {
|
||||
return this.toggle(true);
|
||||
@ -493,8 +482,13 @@ namespace OS {
|
||||
return this.toggle(false);
|
||||
}
|
||||
});
|
||||
const catlist = (this.refs.catlist as tag.ListViewTag);
|
||||
catlist.onlistselect = (e) => {
|
||||
Ant.OS.GUI.bindKey("ESC", (e) => {
|
||||
if (this._view === true) {
|
||||
return this.toggle(false);
|
||||
}
|
||||
});
|
||||
const catlist = (this.refs.catlist as tag.TabBarTag);
|
||||
catlist.ontabselect = (e) => {
|
||||
const applist = (this.refs.applist as ListViewTag);
|
||||
if(catlist.selected === 0)
|
||||
{
|
||||
@ -510,55 +504,57 @@ namespace OS {
|
||||
applist.selected = -1;
|
||||
};
|
||||
$(this.refs.overlay)
|
||||
.css("left", 0)
|
||||
.css("top", `${$(this.refs.panel).height()}px`)
|
||||
.css("bottom", "0")
|
||||
.hide();
|
||||
(this.refs.pinned as GUI.tag.MenuTag).onmenuselect = (e) => {
|
||||
const app = e.data.item.data.app;
|
||||
if(!app)
|
||||
return;
|
||||
GUI.launch(app, []);
|
||||
};
|
||||
this.refs.appmenu.contextmenuHandle = (e, m) => { }
|
||||
this.refs.osmenu.contextmenuHandle = (e, m) => { }
|
||||
this.refs.systray.contextmenuHandle = (e, m) => { }
|
||||
this.refs.pinned.contextmenuHandle = (e, m) => { }
|
||||
this.refs.panel.contextmenuHandle = (e, m) => {
|
||||
let menu = [
|
||||
{ text: __("Applications and services setting"), dataid: "app&srv" }
|
||||
];
|
||||
m.items = menu;
|
||||
m.onmenuselect = function (
|
||||
evt: TagEventType<tag.MenuEventData>
|
||||
) {
|
||||
GUI.launch("Setting",[]);
|
||||
}
|
||||
m.show(e);
|
||||
};
|
||||
announcer.observable.on("app-pinned", (d) => {
|
||||
this.RefreshPinnedApp();
|
||||
});
|
||||
announcer.observable.on("loading", (o) => {
|
||||
this._pending_task.push(o.id);
|
||||
if(!$(this.refs.panel).hasClass("loading"))
|
||||
this.refs.osmenu.contextmenuHandle = (e, m) => { };
|
||||
systray.contextmenuHandle = (e, m) => { };
|
||||
this.refs.panel.contextmenuHandle = (e, m) => { };
|
||||
announcer.on("ANTOS-TASK-PENDING", (o: API.AnnouncementDataType<Promise<any>>) => {
|
||||
if(this._pending_task.length == 0)
|
||||
{
|
||||
$(this.refs.panel).addClass("loading");
|
||||
systray.iconclass = "fa-spin fa fa-cog";
|
||||
}
|
||||
this._pending_task.push(o.u_data);
|
||||
|
||||
$(GUI.workspace).css("cursor", "wait");
|
||||
});
|
||||
|
||||
announcer.observable.on("loaded", (o) => {
|
||||
const i = this._pending_task.indexOf(o.id);
|
||||
systray.iconclass = "bi bi-sliders";
|
||||
systray.onbtclick = (e) => {
|
||||
e.data.stopPropagation();
|
||||
this.show_systray();
|
||||
};
|
||||
const remove_task = (o: Promise<any>) => {
|
||||
const i = this._pending_task.indexOf(o);
|
||||
if (i >= 0) {
|
||||
this._pending_task.splice(i, 1);
|
||||
}
|
||||
if (this._pending_task.length === 0) {
|
||||
// set time out
|
||||
systray.iconclass = "bi bi-sliders";
|
||||
if(!this._loading_toh)
|
||||
this._loading_toh = setTimeout(() => this.animation_check(),1000);
|
||||
}
|
||||
};
|
||||
announcer.on("ANTOS-TASK-FULFILLED", (o: API.AnnouncementDataType<Promise<any>>) => {
|
||||
remove_task(o.u_data);
|
||||
});
|
||||
this.RefreshPinnedApp();
|
||||
Ant.OS.announcer.trigger("syspanelloaded", undefined);
|
||||
|
||||
announcer.on("ANTOS-TASK-REJECTED", (o: API.AnnouncementDataType<Promise<any>>) => {
|
||||
remove_task(o.u_data);
|
||||
});
|
||||
|
||||
announcer.on("DESKTOP-RESIZE", (e) => {
|
||||
this.calibrate();
|
||||
});
|
||||
announcer.on("APP-SELECT", (e) => {
|
||||
if(this._view)
|
||||
this.toggle(false);
|
||||
});
|
||||
announcer.on("APP-LOADING", (e) => {
|
||||
if(this._view)
|
||||
this.toggle(false);
|
||||
});
|
||||
Ant.OS.announcer.trigger("SYS-PANEL-LOADED", undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,6 +39,13 @@ namespace OS {
|
||||
*/
|
||||
private _ontabselect: TagEventCallback<TabEventData>;
|
||||
|
||||
/**
|
||||
* Cache of touch event
|
||||
*
|
||||
* @private
|
||||
* @meberof TabBarTag
|
||||
*/
|
||||
private _previous_touch: {x: number, y: number};
|
||||
/**
|
||||
*Creates an instance of TabBarTag.
|
||||
* @memberof TabBarTag
|
||||
@ -57,6 +64,8 @@ namespace OS {
|
||||
*/
|
||||
protected init(): void {
|
||||
this.selected = -1;
|
||||
this.dir = "row";
|
||||
this._previous_touch = {x: 0, y:0};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,6 +91,30 @@ namespace OS {
|
||||
return this.hasattr("closable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the tab bar direction:
|
||||
* - `row`: horizontal direction
|
||||
* - `column`: vertical direction
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the tab bar direction
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
set dir(v: string) {
|
||||
$(this).attr("dir", v);
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
(this.refs.list as ListViewTag).dir = v;
|
||||
}
|
||||
get dir(): string {
|
||||
return $(this).attr("dir") as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tab in the end of the tab bar
|
||||
*
|
||||
@ -144,6 +177,16 @@ namespace OS {
|
||||
get selected(): number | number[] {
|
||||
return (this.refs.list as ListViewTag).selected;
|
||||
}
|
||||
/**
|
||||
* Get the latest selected item
|
||||
*
|
||||
* @readonly
|
||||
* @type {ListViewItemTag}
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
get selectedItem(): ListViewItemTag {
|
||||
return (this.refs.list as ListViewTag).selectedItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab close event handle
|
||||
@ -179,6 +222,75 @@ namespace OS {
|
||||
this._ontabselect(e);
|
||||
return this.observable.trigger("tabselect", e);
|
||||
};
|
||||
|
||||
const list_container = $(".list-container", this.refs.list);
|
||||
list_container.each((i,el) => {
|
||||
el.addEventListener("touchstart", e => {
|
||||
this._previous_touch.x = e.touches[0].pageX;
|
||||
this._previous_touch.y = e.touches[0].pageY;
|
||||
}, {passive: true});
|
||||
el.addEventListener("touchmove", e => {
|
||||
const offset = {x:0, y:0};
|
||||
offset.x = this._previous_touch.x - e.touches[0].pageX ;
|
||||
offset.y = this._previous_touch.y - e.touches[0].pageY;
|
||||
if(this.dir == "row")
|
||||
{
|
||||
el.scrollLeft += offset.x;
|
||||
}
|
||||
else
|
||||
{
|
||||
el.scrollTop += offset.y;
|
||||
}
|
||||
this._previous_touch.x = e.touches[0].pageX;
|
||||
this._previous_touch.y = e.touches[0].pageY;
|
||||
}, {passive: true});
|
||||
el.addEventListener("wheel", (evt)=>{
|
||||
if(this.dir == "row")
|
||||
{
|
||||
el.scrollLeft += (evt as WheelEvent).deltaY;
|
||||
}
|
||||
else
|
||||
{
|
||||
el.scrollTop += (evt as WheelEvent).deltaY;
|
||||
}
|
||||
}, {passive: true});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the tabbar to end
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
scroll_to_end()
|
||||
{
|
||||
const list_container = $(".list-container", this.refs.list)[0];
|
||||
if(this.dir == "column")
|
||||
{
|
||||
list_container.scrollTo({ top: list_container.scrollHeight, behavior: 'smooth' });
|
||||
}
|
||||
else
|
||||
{
|
||||
list_container.scrollTo({ left: list_container.scrollWidth, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll the tabbar to begin
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
scroll_to_start()
|
||||
{
|
||||
const list_container = $(".list-container", this.refs.list)[0];
|
||||
if(this.dir == "column")
|
||||
{
|
||||
list_container.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
else
|
||||
{
|
||||
list_container.scrollTo({ left: 0, behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,9 +19,9 @@ namespace OS {
|
||||
}
|
||||
export namespace tag {
|
||||
/**
|
||||
* A tab container allows to attach each tab on a [[TabBarTag]]
|
||||
* A tab container allows to attach each tab on a {@link TabBarTag}
|
||||
* with a container widget. The attached container widget should be
|
||||
* composed inside a [[HBoxTag]]
|
||||
* composed inside a {@link HBoxTag}
|
||||
*
|
||||
* The tab bar in a tab container can be configured to display tabs
|
||||
* in horizontal (row) or vertical (column) order. Default to vertical order
|
||||
@ -45,11 +45,11 @@ namespace OS {
|
||||
/**
|
||||
* Placeholder of the tab select event handle
|
||||
*
|
||||
* @private
|
||||
* @protected
|
||||
* @type {TagEventCallback<TabContainerTabType>}
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
private _ontabselect: TagEventCallback<TabContainerTabType>;
|
||||
protected _ontabselect: TagEventCallback<TabContainerTabType>;
|
||||
|
||||
/**
|
||||
*Creates an instance of TabContainerTag.
|
||||
@ -102,13 +102,17 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a tab by its index
|
||||
*
|
||||
* Setter: Select a tab by its index
|
||||
* Getter: Get the current selected index
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set selectedIndex(i: number) {
|
||||
(this.refs.bar as TabBarTag).selected = i;
|
||||
}
|
||||
get selectedIndex(): number {
|
||||
return (this.refs.bar as TabBarTag).selected as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
@ -129,6 +133,14 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
(this.refs.wrapper as TileLayoutTag).dir = v;
|
||||
if(v == "row")
|
||||
{
|
||||
(this.refs.bar as TabBarTag).dir = "column";
|
||||
}
|
||||
else
|
||||
{
|
||||
(this.refs.bar as TabBarTag).dir = "row";
|
||||
}
|
||||
}
|
||||
get dir(): "row" | "column" {
|
||||
return $(this).attr("dir") as any;
|
||||
@ -157,7 +169,7 @@ namespace OS {
|
||||
}
|
||||
$(v.container).show();
|
||||
this.observable.trigger("resize", undefined);
|
||||
$(v.container).attr("tabindex",-1).css("outline", "none").trigger("focus");
|
||||
$(v.container).trigger("focus");
|
||||
}
|
||||
get selectedTab(): TabContainerTabType {
|
||||
return this._selectedTab;
|
||||
@ -175,7 +187,7 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
$(this.refs.bar).attr("data-width", `${v}`);
|
||||
(this.refs.wrapper as TileLayoutTag).calibrate();
|
||||
this.observable.trigger("resize", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,8 +197,11 @@ namespace OS {
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set tabbarheight(v: number) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.bar).attr("data-height", `${v}`);
|
||||
(this.refs.wrapper as TileLayoutTag).calibrate();
|
||||
this.observable.trigger("resize", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -215,6 +230,7 @@ namespace OS {
|
||||
$(item.container)
|
||||
.css("width", "100%")
|
||||
.css("height", "100%")
|
||||
.attr("tabindex",-1).css("outline", "none") // allow focus this tab
|
||||
.hide();
|
||||
const el = (this.refs.bar as TabBarTag).push(
|
||||
item
|
||||
|
@ -1,6 +1,9 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
|
||||
type TileItemDirection = "row" | "column" | "row-reverse" | "column-reverse";
|
||||
|
||||
/**
|
||||
* A tile layout organize it child elements
|
||||
* in a fixed horizontal or vertical direction.
|
||||
@ -22,6 +25,8 @@ namespace OS {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private _padding: number;
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
@ -29,7 +34,9 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
protected init(): void {}
|
||||
protected init(): void {
|
||||
this.padding = 0;
|
||||
}
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
@ -73,17 +80,66 @@ namespace OS {
|
||||
*
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
set dir(v: "row" | "column") {
|
||||
set dir(v: TileItemDirection) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this).attr("dir", v);
|
||||
$(this.refs.yield).css("flex-direction", v);
|
||||
this.reversed = this.reversed;
|
||||
this.calibrate();
|
||||
}
|
||||
get dir(): "row" | "column" {
|
||||
get dir(): TileItemDirection {
|
||||
return $(this).attr("dir") as any;
|
||||
}
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* SET content padding
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get content padding
|
||||
*
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
set padding(v: number)
|
||||
{
|
||||
$(this).attr("padding", v);
|
||||
this._padding = v;
|
||||
}
|
||||
get padding(): number
|
||||
{
|
||||
return this._padding;
|
||||
}
|
||||
/**
|
||||
* setter: Reverse order of the content in the tile
|
||||
*
|
||||
* getter: return if the tile's content is in reversed order
|
||||
*
|
||||
* @meberof TileLayoutTags
|
||||
*/
|
||||
set reversed(v: boolean)
|
||||
{
|
||||
this.attsw(v, "reversed");
|
||||
if(!this.dir)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let newdir = "row";
|
||||
if(this.dir.startsWith("column"))
|
||||
{
|
||||
newdir = "column"
|
||||
}
|
||||
if(v)
|
||||
{
|
||||
newdir += "-reverse";
|
||||
}
|
||||
$(this.refs.yield).css("flex-direction", newdir);
|
||||
}
|
||||
get reversed(): boolean
|
||||
{
|
||||
return this.hasattr("reversed");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the element
|
||||
@ -95,9 +151,7 @@ namespace OS {
|
||||
protected mount(): void {
|
||||
$(this).css("display", "block");
|
||||
$(this.refs.yield)
|
||||
.css("display", "flex")
|
||||
.css("width", "100%")
|
||||
.css("height", "100%");
|
||||
.css("display", "flex");
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
return this.calibrate();
|
||||
}
|
||||
@ -109,6 +163,10 @@ namespace OS {
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
$(this.refs.yield)
|
||||
.css("padding", this.padding)
|
||||
.css("width", `${$(this).width() - this.padding*2}px`)
|
||||
.css("height", `${$(this).height() - this.padding*2}px`);
|
||||
if (this.dir === "row") {
|
||||
return this.hcalibrate();
|
||||
}
|
||||
@ -128,30 +186,34 @@ namespace OS {
|
||||
private hcalibrate(): void {
|
||||
const auto_width = [];
|
||||
let ocwidth = 0;
|
||||
const avaiheight = $(this).height();
|
||||
const avaiWidth = $(this).width();
|
||||
//$(this.refs.yield).css("height", `${avaiheight}px`);
|
||||
const avaiWidth = $(this).width() - this.padding * 2;
|
||||
const avaiheight = $(this).innerHeight() - this.padding * 2;
|
||||
$(this.refs.yield)
|
||||
.children()
|
||||
.each(function (e) {
|
||||
$(this).css("height", "100%");
|
||||
let attv = $(this).attr("data-width");
|
||||
let dw = 0;
|
||||
if (attv && attv !== "grow") {
|
||||
if (attv[attv.length - 1] === "%") {
|
||||
dw =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiWidth) /
|
||||
100;
|
||||
} else {
|
||||
dw = parseInt(attv);
|
||||
}
|
||||
$(this).css("width", `${dw}px`);
|
||||
ocwidth += dw;
|
||||
} else {
|
||||
if (!attv || attv == "grow") {
|
||||
$(this).css("flex-grow", "1");
|
||||
auto_width.push(this);
|
||||
return;
|
||||
}
|
||||
if(attv == "content")
|
||||
{
|
||||
ocwidth += $(this).width();
|
||||
return;
|
||||
}
|
||||
if (attv[attv.length - 1] === "%") {
|
||||
dw =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiWidth) /
|
||||
100;
|
||||
} else {
|
||||
dw = parseInt(attv);
|
||||
}
|
||||
$(this).css("width", `${dw}px`);
|
||||
ocwidth += dw;
|
||||
});
|
||||
|
||||
const csize = (avaiWidth - ocwidth) / auto_width.length;
|
||||
@ -160,7 +222,7 @@ namespace OS {
|
||||
$(v).css("width", `${csize}px`)
|
||||
);
|
||||
}
|
||||
return this.observable.trigger("hboxchange", {
|
||||
this.observable.trigger("hboxchange", {
|
||||
id: this.aid,
|
||||
data: { w: avaiWidth, h: avaiheight },
|
||||
});
|
||||
@ -177,30 +239,34 @@ namespace OS {
|
||||
private vcalibrate(): void {
|
||||
const auto_height = [];
|
||||
let ocheight = 0;
|
||||
const avaiheight = $(this).height();
|
||||
const avaiwidth = $(this).width();
|
||||
//$(this.refs.yield).css("height", `${avaiheight}px`);
|
||||
const avaiheight = $(this).innerHeight() - this.padding * 2;
|
||||
const avaiwidth = $(this).width() - this.padding * 2;
|
||||
$(this.refs.yield)
|
||||
.children()
|
||||
.each(function (e) {
|
||||
let dh = 0;
|
||||
$(this).css("width", "100%");
|
||||
let attv = $(this).attr("data-height");
|
||||
if (attv && attv !== "grow") {
|
||||
if (attv[attv.length - 1] === "%") {
|
||||
dh =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiheight) /
|
||||
100;
|
||||
} else {
|
||||
dh = parseInt(attv);
|
||||
}
|
||||
$(this).css("height", `${dh}px`);
|
||||
ocheight += dh;
|
||||
} else {
|
||||
if (!attv || attv == "grow") {
|
||||
$(this).css("flex-grow", "1");
|
||||
auto_height.push(this);
|
||||
return;
|
||||
}
|
||||
if(attv == "content")
|
||||
{
|
||||
ocheight += $(this).height();
|
||||
return;
|
||||
}
|
||||
if (attv[attv.length - 1] === "%") {
|
||||
dh =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiheight) /
|
||||
100;
|
||||
} else {
|
||||
dh = parseInt(attv);
|
||||
}
|
||||
$(this).css("height", `${dh}px`);
|
||||
ocheight += dh;
|
||||
});
|
||||
|
||||
const csize = (avaiheight - ocheight) / auto_height.length;
|
||||
@ -210,7 +276,7 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
|
||||
return this.observable.trigger("vboxchange", {
|
||||
this.observable.trigger("vboxchange", {
|
||||
id: this.aid,
|
||||
data: { w: avaiwidth, h: avaiheight },
|
||||
});
|
||||
|
@ -111,7 +111,7 @@ namespace OS {
|
||||
* Placeholder for the `fetch` function of the node.
|
||||
* This function is used to fetch the child nodes of the
|
||||
* current nodes. This function should return a promise on
|
||||
* a list of [[TreeViewDataType]]
|
||||
* a list of {@link TreeViewDataType}
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
@ -164,7 +164,7 @@ namespace OS {
|
||||
* Setter:
|
||||
*
|
||||
* Set the data of the current node. This will trigger the
|
||||
* [[ondatachange]] function
|
||||
* {@link ondatachange} function
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
@ -182,7 +182,7 @@ namespace OS {
|
||||
this.treepath = v.path;
|
||||
}
|
||||
this.selected = v.selected;
|
||||
v.domel = this;
|
||||
this.attach(v);
|
||||
this.ondatachange();
|
||||
}
|
||||
get data(): TreeViewDataType {
|
||||
@ -294,7 +294,8 @@ namespace OS {
|
||||
.css("padding", 0)
|
||||
.css("margin", 0)
|
||||
.css("background-color", "transparent")
|
||||
.css("width", v * 15 + "px");
|
||||
.css("width", v * 15 + "px")
|
||||
.css("flex-shrink", 0);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -375,19 +376,23 @@ namespace OS {
|
||||
$(this.refs.container)
|
||||
.css("padding", 0)
|
||||
.css("margin", 0)
|
||||
.css("display","flex")
|
||||
.css("flex-direction", "row")
|
||||
.css("align-items", "center")
|
||||
.css("white-space", "nowrap");
|
||||
$(this.refs.itemholder).css("display", "inline-block");
|
||||
//$(this.refs.itemholder).css("display", "inline-block");
|
||||
$(this.refs.wrapper).on("click",(e) => {
|
||||
this.selected = true;
|
||||
});
|
||||
$(this.refs.wrapper).on("dblclick", (e) => {
|
||||
$(this.refs.wrapper).on(OS.mobile?"dbltap":"dblclick", (e) => {
|
||||
this._evt.data.dblclick = true;
|
||||
this.selected = true;
|
||||
});
|
||||
|
||||
$(this.refs.toggle)
|
||||
.css("display", "inline-block")
|
||||
//.css("display", "inline-block")
|
||||
.css("width", "15px")
|
||||
.css("flex-shrink", 0)
|
||||
.addClass("afx-tree-view-item")
|
||||
.on("click",(e) => {
|
||||
this.open = !this.open;
|
||||
@ -400,7 +405,7 @@ namespace OS {
|
||||
* Layout definition of a node. This function
|
||||
* returns the definition of the base outer layout
|
||||
* of a node. Custom inner layout of the node should
|
||||
* be defined in the [[itemlayout]] function
|
||||
* be defined in the {@link itemlayout} function
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
@ -459,8 +464,8 @@ namespace OS {
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleTreeViewItem extends [[TreeViewItemPrototype]] and
|
||||
* define it inner layout using a [[LabelTag]]
|
||||
* SimpleTreeViewItem extends {@link TreeViewItemPrototype} and
|
||||
* define it inner layout using a {@link LabelTag}
|
||||
*
|
||||
* @export
|
||||
* @class SimpleTreeViewItem
|
||||
@ -586,10 +591,10 @@ namespace OS {
|
||||
* Private data object passing between dragndrop mouse event
|
||||
*
|
||||
* @private
|
||||
* @type {{ from: TreeViewTag; to: TreeViewTag }}
|
||||
* @type {{ from: TreeViewTag[]; to: TreeViewTag }}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _dnd: { from: TreeViewTag; to: TreeViewTag };
|
||||
private _dnd: { from: TreeViewTag[]; to: TreeViewTag };
|
||||
|
||||
/**
|
||||
* Reference to parent tree of the current tree.
|
||||
@ -636,9 +641,9 @@ namespace OS {
|
||||
* Placeholder for the `fetch` function of the tree.
|
||||
* This function is used to fetch the child nodes of the
|
||||
* current tree. This function should return a promise on
|
||||
* a list of [[TreeViewDataType]]
|
||||
* a list of {@link TreeViewDataType}
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
fetch: (
|
||||
d: TreeViewItemPrototype
|
||||
@ -920,7 +925,7 @@ namespace OS {
|
||||
*/
|
||||
protected mount(): void {
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
from: [],
|
||||
to: undefined,
|
||||
};
|
||||
this._treemousedown = (e) => {
|
||||
@ -932,7 +937,7 @@ namespace OS {
|
||||
if (el === this) {
|
||||
return;
|
||||
}
|
||||
this._dnd.from = el;
|
||||
this._dnd.from = [el];
|
||||
this._dnd.to = undefined;
|
||||
$(window).on("mouseup", this._treemouseup);
|
||||
return $(window).on("mousemove", this._treemousemove);
|
||||
@ -951,8 +956,8 @@ namespace OS {
|
||||
el = el.parent;
|
||||
}
|
||||
if (
|
||||
el === this._dnd.from ||
|
||||
el === this._dnd.from.parent
|
||||
el === this._dnd.from[0] ||
|
||||
el === this._dnd.from[0].parent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -962,7 +967,7 @@ namespace OS {
|
||||
data: this._dnd,
|
||||
});
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
from: [],
|
||||
to: undefined,
|
||||
};
|
||||
};
|
||||
@ -974,7 +979,7 @@ namespace OS {
|
||||
if (!this._dnd.from) {
|
||||
return;
|
||||
}
|
||||
const data = this._dnd.from.data;
|
||||
const data = this._dnd.from[0].data;
|
||||
const $label = $("#systooltip");
|
||||
const top = e.clientY + 5;
|
||||
const left = e.clientX + 5;
|
||||
|
@ -63,6 +63,14 @@ namespace OS {
|
||||
*/
|
||||
private _history: GenericObject<any>;
|
||||
|
||||
/**
|
||||
* This placeholder store the callback for the menu open event
|
||||
*
|
||||
* @private
|
||||
* @type {(el: StackMenuTag) => void}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _onmenuopen: (el: StackMenuTag) => void;
|
||||
/**
|
||||
* This placeholder stores the offset of the virtual desktop element
|
||||
*
|
||||
@ -71,7 +79,7 @@ namespace OS {
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _desktop_pos: GenericObject<any>;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of WindowTag.
|
||||
* @memberof WindowTag
|
||||
@ -79,7 +87,33 @@ namespace OS {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* blur overlay: If active the window overlay will be shown
|
||||
* on inactive (blur event)
|
||||
*
|
||||
* Setter: Enable the switch
|
||||
*
|
||||
* Getter: Check whether the switch is enabled
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set blur_overlay(v: boolean) {
|
||||
this.attsw(v, "blur-overlay");
|
||||
}
|
||||
get blur_overlay(): boolean {
|
||||
return this.hasattr("blur-overlay");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set menu open event handler
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set onmenuopen(f: (el: StackMenuTag) => void)
|
||||
{
|
||||
this._onmenuopen = f;
|
||||
}
|
||||
/**
|
||||
* Init window tag
|
||||
* - `shown`: false
|
||||
@ -100,6 +134,7 @@ namespace OS {
|
||||
this.minimizable = true;
|
||||
this.resizable = true;
|
||||
this.apptitle = "Untitled";
|
||||
this._onmenuopen = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,7 +143,7 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
protected calibrate(): void { }
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
@ -117,7 +152,7 @@ namespace OS {
|
||||
* @param {*} [d]
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
protected reload(d?: any): void { }
|
||||
|
||||
/**
|
||||
* Setter: Set the window width
|
||||
@ -157,6 +192,24 @@ namespace OS {
|
||||
get height(): number {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the application menu content
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set menu(v: GenericObject<any>[])
|
||||
{
|
||||
if(!v || v.length == 0)
|
||||
{
|
||||
$(this.refs.btnMenu).hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
(this.refs.stackmenu as StackMenuTag).nodes = v;
|
||||
$(this.refs.btnMenu).show();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: enable/disable window minimizable
|
||||
@ -188,14 +241,26 @@ namespace OS {
|
||||
this.attsw(v, "resizable");
|
||||
if (v) {
|
||||
$(this.refs["maxbt"]).show();
|
||||
$(this.refs["grip"]).show();
|
||||
$(this.refs["grip_br"]).show();
|
||||
$(this.refs["grip_tl"]).show();
|
||||
$(this.refs["grip_tr"]).show();
|
||||
$(this.refs["grip_bl"]).show();
|
||||
|
||||
$(this.refs["grip_bottom"]).show();
|
||||
$(this.refs["grip_right"]).show();
|
||||
$(this.refs["grip_top"]).show();
|
||||
$(this.refs["grip_left"]).show();
|
||||
} else {
|
||||
$(this.refs["maxbt"]).hide();
|
||||
$(this.refs["grip"]).hide();
|
||||
$(this.refs["grip_br"]).hide();
|
||||
$(this.refs["grip_tl"]).hide();
|
||||
$(this.refs["grip_tr"]).hide();
|
||||
$(this.refs["grip_bl"]).hide();
|
||||
|
||||
$(this.refs["grip_bottom"]).hide();
|
||||
$(this.refs["grip_right"]).hide();
|
||||
$(this.refs["grip_top"]).hide();
|
||||
$(this.refs["grip_left"]).hide();
|
||||
}
|
||||
}
|
||||
get resizable(): boolean {
|
||||
@ -220,6 +285,15 @@ namespace OS {
|
||||
return $(this).attr("apptitle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the notification tag
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
get notification(): NotificationTag
|
||||
{
|
||||
return this.refs.notification as NotificationTag;
|
||||
}
|
||||
/**
|
||||
* Resize all the children of the window based on its width and height
|
||||
*
|
||||
@ -245,30 +319,64 @@ namespace OS {
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this.contextmenuHandle = function (e) {};
|
||||
$(this.refs["minbt"]).on("click",(e) => {
|
||||
const btn_menu = (this.refs.btnMenu as ButtonTag);
|
||||
const min_btn = (this.refs["minbt"] as ButtonTag);
|
||||
const max_btn = (this.refs["maxbt"] as ButtonTag);
|
||||
const close_btn = (this.refs["closebt"] as ButtonTag);
|
||||
const stackmenu = (this.refs.stackmenu as StackMenuTag);
|
||||
stackmenu.context = true;
|
||||
btn_menu.iconclass = "bi bi-list";
|
||||
min_btn.iconclass = "bi bi-dash";
|
||||
max_btn.iconclass = "bi bi-stop";
|
||||
close_btn.iconclass = "bi bi-x";
|
||||
this.contextmenuHandle = function (e) { };
|
||||
min_btn.onbtclick =(_) => {
|
||||
return this.observable.trigger("hide", {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
|
||||
$(this.refs["maxbt"]).on("click",(e) => {
|
||||
};
|
||||
btn_menu.onbtclick = (e) => {
|
||||
e.data.stopPropagation();
|
||||
if($(stackmenu).is(":hidden"))
|
||||
{
|
||||
if(this._onmenuopen)
|
||||
{
|
||||
this._onmenuopen(stackmenu);
|
||||
}
|
||||
else
|
||||
{
|
||||
stackmenu.reset();
|
||||
}
|
||||
stackmenu.show();
|
||||
}
|
||||
else
|
||||
{
|
||||
$(stackmenu).hide();
|
||||
}
|
||||
};
|
||||
max_btn.onbtclick = (_) => {
|
||||
return this.toggle_window();
|
||||
});
|
||||
};
|
||||
|
||||
$(this.refs["closebt"]).on("click",(e) => {
|
||||
close_btn.onbtclick = (_) => {
|
||||
return this.observable.trigger("exit", {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
};
|
||||
stackmenu.onmenuselect = (e) => {
|
||||
if(!e.data.item.data.nodes)
|
||||
{
|
||||
stackmenu.selected = -1;
|
||||
}
|
||||
}
|
||||
const left = ($(this.desktop).width() - this.width) / 2;
|
||||
const top = ($(this.desktop).height() - this.height) / 2;
|
||||
$(this)
|
||||
.css("position", "absolute")
|
||||
.css("left", `${left}px`)
|
||||
.css("top", `${top}px`)
|
||||
.css("z-index", Ant.OS.GUI.zindex++);
|
||||
$(this).on("mousedown", (e) => {
|
||||
.css("z-index", 10);
|
||||
$(this).on("pointerdown", (e) => {
|
||||
if (this._shown) {
|
||||
return;
|
||||
}
|
||||
@ -276,25 +384,26 @@ namespace OS {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
|
||||
$(this.refs["dragger"]).on("dblclick",(e) => {
|
||||
$(this.refs["dragger"]).on("dblclick", (e) => {
|
||||
return this.toggle_window();
|
||||
});
|
||||
|
||||
this.observable.on("resize", (e) => this.resize());
|
||||
|
||||
this.observable.on("focus", () => {
|
||||
Ant.OS.GUI.zindex++;
|
||||
$(this)
|
||||
.show()
|
||||
.css("z-index", Ant.OS.GUI.zindex)
|
||||
.removeClass("unactive");
|
||||
this._shown = true;
|
||||
$(this.refs.win_overlay).hide();
|
||||
$(this).trigger("focus");
|
||||
});
|
||||
|
||||
this.observable.on("blur", () => {
|
||||
this._shown = false;
|
||||
return $(this).addClass("unactive");
|
||||
$(this).addClass("unactive");
|
||||
if(this.blur_overlay)
|
||||
$(this.refs.win_overlay).show();
|
||||
});
|
||||
this.observable.on("hide", () => {
|
||||
$(this).hide();
|
||||
@ -312,13 +421,42 @@ namespace OS {
|
||||
});
|
||||
}
|
||||
});
|
||||
this.observable.on("loaded", ()=>{
|
||||
$(this.refs.panel).removeClass("loading");
|
||||
$(this).css("cursor", "auto");
|
||||
});
|
||||
this.observable.on("loading", ()=>{
|
||||
if(!$(this.refs.panel).hasClass("loading"))
|
||||
$(this.refs.panel).addClass("loading");
|
||||
$(this).css("cursor", "wait");
|
||||
});
|
||||
this.enable_dragging();
|
||||
this.enable_resize();
|
||||
this.setsize({
|
||||
w: this.width,
|
||||
h: this.height,
|
||||
});
|
||||
return this.observable.trigger("rendered", {
|
||||
this.focusable = true
|
||||
if(OS.mobile)
|
||||
{
|
||||
this.toggle_window();
|
||||
//this.minimizable = false;
|
||||
this.resizable = false;
|
||||
}
|
||||
this.observable.on("desktopresize", (e) => {
|
||||
if(this._isMaxi)
|
||||
{
|
||||
this._isMaxi = false;
|
||||
this.toggle_window(true);
|
||||
}
|
||||
/*else
|
||||
{
|
||||
const w = this.width > e.data.width ? e.data.width: this.width;
|
||||
const h = this.height > e.data.height ? e.data.height: this.height;
|
||||
this.setsize({ w: w, h: h });
|
||||
}*/
|
||||
});
|
||||
this.observable.trigger("rendered", {
|
||||
id: this.aid,
|
||||
});
|
||||
}
|
||||
@ -342,6 +480,7 @@ namespace OS {
|
||||
this.observable.trigger("resize", {
|
||||
id: this.aid,
|
||||
data: o,
|
||||
root: true
|
||||
});
|
||||
}
|
||||
|
||||
@ -355,12 +494,12 @@ namespace OS {
|
||||
$(this.refs["dragger"])
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default");
|
||||
$(this.refs["dragger"]).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
$(this.refs.dragger).on("pointerdown", (e) => {
|
||||
e.originalEvent.preventDefault();
|
||||
const offset = $(this).offset();
|
||||
offset.top = e.clientY - offset.top;
|
||||
offset.left = e.clientX - offset.left;
|
||||
$(window).on("mousemove", (e) => {
|
||||
$(window).on("pointermove", (e) => {
|
||||
$(this.refs.win_overlay).show();
|
||||
let left: number, top: number;
|
||||
if (this._isMaxi) {
|
||||
@ -386,10 +525,10 @@ namespace OS {
|
||||
.css("top", `${top}px`)
|
||||
.css("left", `${left}px`);
|
||||
});
|
||||
return $(window).on("mouseup", (e) => {
|
||||
return $(window).on("pointerup", (e) => {
|
||||
$(this.refs.win_overlay).hide();
|
||||
$(window).off("mousemove", null);
|
||||
return $(window).off("mouseup", null);
|
||||
$(window).off("pointermove", null);
|
||||
return $(window).off("pointerup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -407,50 +546,63 @@ namespace OS {
|
||||
const mouse_move_hdl = (e) => {
|
||||
let w = $(this).width();
|
||||
let h = $(this).height();
|
||||
let coord = $(this).offset();
|
||||
|
||||
$(this.refs.win_overlay).show();
|
||||
if(target != this.refs.grip_bottom)
|
||||
{
|
||||
w += e.clientX - offset.left;
|
||||
}
|
||||
if(target != this.refs.grip_right)
|
||||
{
|
||||
if ((target == this.refs.grip_bottom) || (target == this.refs.grip_bl) || (target == this.refs.grip_br)) {
|
||||
h += e.clientY - offset.top;
|
||||
}
|
||||
if ((target == this.refs.grip_right) || (target == this.refs.grip_br) || (target == this.refs.grip_tr)) {
|
||||
w += e.clientX - offset.left;
|
||||
}
|
||||
|
||||
if ((target == this.refs.grip_left) || (target == this.refs.grip_bl) || (target == this.refs.grip_tl)) {
|
||||
w -= e.clientX - offset.left;
|
||||
coord.left += e.clientX - offset.left;
|
||||
}
|
||||
|
||||
if ((target == this.refs.grip_top) || (target == this.refs.grip_tr) || (target == this.refs.grip_tl)) {
|
||||
h -= e.clientY - offset.top;
|
||||
coord.top += e.clientY - offset.top;
|
||||
}
|
||||
|
||||
w = w < 100 ? 100 : w;
|
||||
h = h < 100 ? 100 : h;
|
||||
coord.top = coord.top < 0 ? 0: coord.top;
|
||||
coord.left = coord.left < 0 ? 0: coord.left;
|
||||
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
this._isMaxi = false;
|
||||
$(this)
|
||||
.css("top", `${coord.top}px`)
|
||||
.css("left", `${coord.left}px`);
|
||||
this.setsize({ w, h });
|
||||
}
|
||||
const mouse_up_hdl = (e) => {
|
||||
$(this.refs.win_overlay).hide();
|
||||
$(window).off("mousemove", mouse_move_hdl);
|
||||
return $(window).off("mouseup", mouse_up_hdl);
|
||||
$(window).off("pointermove", mouse_move_hdl);
|
||||
return $(window).off("pointerup", mouse_up_hdl);
|
||||
}
|
||||
$(this.refs["grip"]).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
target = this.refs.grip;
|
||||
$(window).on("mousemove", mouse_move_hdl);
|
||||
$(window).on("mouseup", mouse_up_hdl);
|
||||
});
|
||||
$(this.refs.grip_bottom).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
target = this.refs.grip_bottom;
|
||||
$(window).on("mousemove", mouse_move_hdl);
|
||||
$(window).on("mouseup", mouse_up_hdl);
|
||||
});
|
||||
$(this.refs.grip_right).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
target = this.refs.grip_right;
|
||||
$(window).on("mousemove", mouse_move_hdl);
|
||||
$(window).on("mouseup", mouse_up_hdl);
|
||||
$.each(
|
||||
[
|
||||
this.refs.grip_bl,
|
||||
this.refs.grip_br,
|
||||
this.refs.grip_tl,
|
||||
this.refs.grip_tr,
|
||||
this.refs.grip_left,
|
||||
this.refs.grip_bottom,
|
||||
this.refs.grip_right,
|
||||
this.refs.grip_top
|
||||
], function(){
|
||||
$(this).on("pointerdown", (e) => {
|
||||
e.preventDefault();
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
target = this;
|
||||
$(window).on("pointermove", mouse_move_hdl);
|
||||
$(window).on("pointerup", mouse_up_hdl);
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
@ -461,9 +613,9 @@ namespace OS {
|
||||
* @returns {void}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private toggle_window(): void {
|
||||
private toggle_window(force?: boolean): void {
|
||||
let h: number, w: number;
|
||||
if (!this.resizable) {
|
||||
if (!this.resizable && !force) {
|
||||
return;
|
||||
}
|
||||
if (this._isMaxi === false) {
|
||||
@ -473,8 +625,8 @@ namespace OS {
|
||||
width: $(this).css("width"),
|
||||
height: $(this).css("height"),
|
||||
};
|
||||
w = $(this.desktop).width();
|
||||
h = $(this.desktop).height();
|
||||
w = $(this.desktop).width() - 2;
|
||||
h = $(this.desktop).height() - 2;
|
||||
$(this).css("top", "0").css("left", "0");
|
||||
this.setsize({ w, h });
|
||||
this._isMaxi = true;
|
||||
@ -506,22 +658,17 @@ namespace OS {
|
||||
children: [
|
||||
{
|
||||
el: "ul",
|
||||
ref: 'panel',
|
||||
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",
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "btnMenu",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
@ -534,9 +681,38 @@ namespace OS {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-minimize",
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "minbt",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-maximize",
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "maxbt",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-close",
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "closebt",
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
{ el: "div", class: "afx-clear" },
|
||||
{
|
||||
el: "div",
|
||||
ref: "yield",
|
||||
@ -544,8 +720,23 @@ namespace OS {
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip",
|
||||
class: "afx-window-grip",
|
||||
ref: "grip_br",
|
||||
class: "afx-window-grip-bottom-right",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip_tl",
|
||||
class: "afx-window-grip-top-left",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip_bl",
|
||||
class: "afx-window-grip-bottom-left",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip_tr",
|
||||
class: "afx-window-grip-top-right",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
@ -557,11 +748,29 @@ namespace OS {
|
||||
ref: "grip_right",
|
||||
class: "afx-window-grip-right",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip_top",
|
||||
class: "afx-window-grip-top",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip_left",
|
||||
class: "afx-window-grip-left",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "win_overlay",
|
||||
class: "afx-window-overlay",
|
||||
},
|
||||
{
|
||||
el: "afx-stack-menu",
|
||||
ref: "stackmenu"
|
||||
},
|
||||
{
|
||||
el: "afx-notification",
|
||||
ref: "notification"
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -24,10 +24,10 @@ interface HTMLElement {
|
||||
* defined on any child of this element will be ignored.
|
||||
*
|
||||
* @param {JQuery.MouseEventBase} e a mouse event
|
||||
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]]
|
||||
* @param {OS.GUI.tag.StackMenuTag} m The context menu element {@link OS.GUI.tag.StackMenuTag}
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void;
|
||||
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.StackMenuTag): void;
|
||||
|
||||
/**
|
||||
* Mount the element and all the children on its DOM subtree. This action
|
||||
@ -51,7 +51,25 @@ interface HTMLElement {
|
||||
afxml(o: OS.API.Announcer): void;
|
||||
|
||||
/**
|
||||
* Perform DOM generation ([[afxml]]) then mount ([[sync]]) all the
|
||||
* Enable the drag event dispatching on this
|
||||
* element
|
||||
*
|
||||
* This will trigger the `dragging` and `drop` event on the enabled
|
||||
* element when the mouse is down, move, then up, then move
|
||||
*
|
||||
* The event can be listened using the traditional way,
|
||||
* Example:
|
||||
* ```
|
||||
* elem.addEventListener('dragging', (e) => { }, false);
|
||||
* elem.addEventListener('drop', (e) => { }, false);
|
||||
* ```
|
||||
*
|
||||
* @meberof HTMLElement
|
||||
*/
|
||||
enable_drag(): void;
|
||||
|
||||
/**
|
||||
* Perform DOM generation ({@link afxml}) then mount ({@link sync}) all the
|
||||
* elements.
|
||||
*
|
||||
* @param {OS.API.Announcer} o an AntOS observable object
|
||||
@ -98,7 +116,7 @@ interface Document {
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
/**
|
||||
* [[TagLayoutType]] interface using by AFX tags to defined
|
||||
* TagLayoutType interface using by AFX tags to defined
|
||||
* its internal DOM hierarchy
|
||||
*
|
||||
* @export
|
||||
@ -139,7 +157,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* this is the `data-id` attribute of the element,
|
||||
* can be query by the [[aid]] Tag API function.
|
||||
* can be query by the {@link OS.GUI.AFXTag.aid} Tag API function.
|
||||
* Not to be confused with the DOM `id` attribute
|
||||
*
|
||||
* @type {(string | number)}
|
||||
@ -162,7 +180,7 @@ namespace OS {
|
||||
* @type {number|string}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
width?: number|string;
|
||||
width?: number | string;
|
||||
|
||||
/**
|
||||
** `data-height` of the element, not to be confused with
|
||||
@ -171,7 +189,7 @@ namespace OS {
|
||||
* @type {number|string}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
height?: number|string;
|
||||
height?: number | string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -217,6 +235,20 @@ namespace OS {
|
||||
* @memberof TagEventType
|
||||
*/
|
||||
data: T;
|
||||
/**
|
||||
* Original event if any
|
||||
*
|
||||
* @type {any}
|
||||
* @memberof TagEventType
|
||||
*/
|
||||
originalEvent?: any;
|
||||
/**
|
||||
* is root tag?
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof TagEventType
|
||||
*/
|
||||
root?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -233,7 +265,7 @@ namespace OS {
|
||||
* @type {T}
|
||||
* @memberof DnDEventDataType
|
||||
*/
|
||||
from: T;
|
||||
from: T[];
|
||||
|
||||
/**
|
||||
* Reference to the target DOM element
|
||||
@ -245,12 +277,162 @@ namespace OS {
|
||||
}
|
||||
/**
|
||||
* Tag event callback type
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export type TagEventCallback<T> = (e: TagEventType<T>) => void;
|
||||
/**
|
||||
* Top most element z index value, start by 10
|
||||
* Tag responsive envent callback type
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export var zindex: number = 10;
|
||||
export type TagResponsiveCallback = (fullfilled: boolean) => void;
|
||||
|
||||
/**
|
||||
* A callback record with history of last fulfilled
|
||||
*
|
||||
* @interface TagResponsiveCallbackRecord
|
||||
*/
|
||||
interface TagResponsiveCallbackRecord {
|
||||
/**
|
||||
* Callback function
|
||||
*
|
||||
* @type {TagResponsiveCallback}
|
||||
* @memberof TagResponsiveCallbackRecord
|
||||
*/
|
||||
callback: TagResponsiveCallback,
|
||||
/**
|
||||
* has the event been previously fulfilled?
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof TagResponsiveCallbackRecord
|
||||
*/
|
||||
fulfilled: boolean,
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag responsive validator type
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export type TagResponsiveValidator = (w: number, h: number) => boolean;
|
||||
|
||||
/**
|
||||
* Definitions of some default tag responsive validators
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
|
||||
export const RESPONSIVE = {
|
||||
/**
|
||||
* Extra small devices (phones, 600px and down)
|
||||
*/
|
||||
TINY: function(w: number,_h: number){
|
||||
return w <= 600;
|
||||
},
|
||||
/*
|
||||
Small devices (portrait tablets and large phones, 600px and up)
|
||||
*/
|
||||
SMALL: function(w: number, _h: number){
|
||||
return w <= 768;
|
||||
},
|
||||
/*
|
||||
Medium devices (landscape tablets, 768px and up)
|
||||
*/
|
||||
MEDIUM: function(w: number, _h: number){
|
||||
return w > 768;
|
||||
},
|
||||
/**
|
||||
* Large devices (laptops/desktops, 992px and up)
|
||||
*/
|
||||
LARGE: function(w: number, _h: number){
|
||||
return w > 992;
|
||||
},
|
||||
/**
|
||||
* Extra large devices (large laptops and desktops, 1200px and up)
|
||||
*/
|
||||
HUGE: function(w: number, _h: number){
|
||||
return w > 1200;
|
||||
},
|
||||
/**
|
||||
* Portrait mode
|
||||
*/
|
||||
PORTRAIT: function(w: number, h: number){
|
||||
return h > w;
|
||||
},
|
||||
/**
|
||||
* Landscape mode
|
||||
*/
|
||||
LANDSCAPE: function(w: number, h: number){
|
||||
return h < w;
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom map to handle responsive events, a responsive event
|
||||
* consists of a validator and a callback
|
||||
*
|
||||
* When avalidator return true, its corresponding callback will
|
||||
* be called
|
||||
*/
|
||||
class ResponsiveHandle extends Map<TagResponsiveValidator,TagResponsiveCallbackRecord> {
|
||||
/**
|
||||
* Register a responsive envent to this map
|
||||
*
|
||||
* @param {TagResponsiveValidator} a validator
|
||||
* @param {TagResponsiveCallback} event callback
|
||||
* @memberof ResponsiveHandle
|
||||
*/
|
||||
on(validator: TagResponsiveValidator, callback: TagResponsiveCallback)
|
||||
{
|
||||
let record: TagResponsiveCallbackRecord = {
|
||||
callback: callback,
|
||||
fulfilled: false,
|
||||
}
|
||||
this.set(validator, record);
|
||||
}
|
||||
|
||||
/**
|
||||
* unregister a responsive event
|
||||
*
|
||||
* @param {TagResponsiveValidator} a validator
|
||||
* @memberof ResponsiveHandle
|
||||
*/
|
||||
off(validator: TagResponsiveValidator)
|
||||
{
|
||||
this.delete(validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify validators and execute callbacks
|
||||
*
|
||||
* @param {number} root tag width
|
||||
* @param {number} root tag height
|
||||
* @memberof ResponsiveHandle
|
||||
*/
|
||||
morph(rootw: number, rooth: number)
|
||||
{
|
||||
for (const [validator, record] of this.entries()) {
|
||||
const val = validator(rootw, rooth);
|
||||
if(record.fulfilled && val)
|
||||
{
|
||||
// the record has been previously fulfilled
|
||||
// and the validator returns true
|
||||
// nothing changed
|
||||
continue;
|
||||
}
|
||||
if(!record.fulfilled && !val)
|
||||
{
|
||||
// the record has been previously unfulfilled
|
||||
// and the validator returns false
|
||||
// nothing changed
|
||||
continue;
|
||||
}
|
||||
record.fulfilled =val;
|
||||
record.callback(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base abstract class for tag implementation, any AFX tag should be
|
||||
@ -274,7 +456,7 @@ namespace OS {
|
||||
* Reference to some of the tag's children
|
||||
* element. This reference object is built
|
||||
* based on the `ref` property found in the
|
||||
* tag layout [[TagLayoutType]]
|
||||
* tag layout {@link TagLayoutType}
|
||||
*
|
||||
* @protected
|
||||
* @type {GenericObject<HTMLElement>}
|
||||
@ -293,7 +475,17 @@ namespace OS {
|
||||
protected _mounted: boolean;
|
||||
|
||||
/**
|
||||
*Creates an instance of AFXTag.
|
||||
* a {@link ResponsiveHandle} to handle all responsive event
|
||||
* related to this tag
|
||||
*
|
||||
* @private
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
private _responsive_handle: ResponsiveHandle;
|
||||
|
||||
private _responsive_check: (evt: TagEventType<{w: number, h: number}>) => void;
|
||||
/**
|
||||
* Creates an instance of AFXTag.
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
constructor() {
|
||||
@ -304,6 +496,8 @@ namespace OS {
|
||||
}
|
||||
this._mounted = false;
|
||||
this.refs = {};
|
||||
this._responsive_handle = new ResponsiveHandle();
|
||||
this._responsive_check = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -338,6 +532,19 @@ namespace OS {
|
||||
}
|
||||
$(this).attr("tooltip", v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter to activate or deactivate focus on a Tag
|
||||
*
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
set focusable(v: boolean) {
|
||||
if(v) {
|
||||
$(this).attr("tabindex", 0).css("outline", "none");
|
||||
} else {
|
||||
$(this).removeAttr("tabindex");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
@ -374,6 +581,79 @@ namespace OS {
|
||||
return $(this).attr("data-id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Enable/disable responsive tag
|
||||
*
|
||||
* Getter: get responsive status
|
||||
*/
|
||||
set responsive(v:boolean) {
|
||||
if(!v)
|
||||
{
|
||||
this.attsw(false,"responsive");
|
||||
this.observable.off("resize",this._responsive_check);
|
||||
this._responsive_check = undefined;
|
||||
return;
|
||||
}
|
||||
if(this._responsive_check)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this._responsive_check = (evt: TagEventType<{w: number, h: number}>) => {
|
||||
if(!evt || !evt.root || !evt.data.w || !evt.data.h )
|
||||
{
|
||||
return;
|
||||
}
|
||||
this._responsive_handle.morph(evt.data.w, evt.data.h);
|
||||
}
|
||||
this.observable.on("resize", this._responsive_check);
|
||||
this.attsw(true, "responsive");
|
||||
}
|
||||
get responsive(): boolean {
|
||||
return this.hasattr("responsive");
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a responsive event to this tag
|
||||
*
|
||||
* @param {TagResponsiveValidator} responsive validator
|
||||
* @param {TagResponsiveCallback} responsive callback
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
morphon(validator: TagResponsiveValidator, callback:TagResponsiveCallback)
|
||||
{
|
||||
this._responsive_handle.on(validator, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a responsive event from this tag
|
||||
*
|
||||
* @param {TagResponsiveValidator} responsive validator
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
morphoff(validator: TagResponsiveValidator)
|
||||
{
|
||||
this._responsive_handle.off(validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach a data to this tag
|
||||
*
|
||||
* This function will define a getter `domel`
|
||||
* in the attached data, this getter refers to the
|
||||
* current tag
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
attach(data: GenericObject<any>): void {
|
||||
const self = this;
|
||||
Object.defineProperty(data, 'domel', {
|
||||
get: function () { return self },
|
||||
enumerable: false,
|
||||
configurable: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation from HTMLElement interface,
|
||||
* this function mount the current tag hierarchy
|
||||
@ -448,7 +728,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Init the current tag, this function
|
||||
* is called before the [[mount]] function
|
||||
* is called before the {@link mount} function
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
@ -477,7 +757,7 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Update only the current tag, this function is
|
||||
* called by [[update]] before chaining the
|
||||
* called by {@link update} before chaining the
|
||||
* update process to its children
|
||||
*
|
||||
* @protected
|
||||
@ -494,7 +774,7 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
protected calibrate(): void { }
|
||||
|
||||
/**
|
||||
* This function parses the input layout object
|
||||
@ -594,11 +874,48 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
|
||||
HTMLElement.prototype.enable_drag = function () {
|
||||
$(this)
|
||||
.on("pointerdown", (evt: JQuery.MouseEventBase) => {
|
||||
const offset = $(this).offset();
|
||||
offset.top = evt.clientY - offset.top;
|
||||
offset.left = evt.clientX - offset.left;
|
||||
const mouse_move = (
|
||||
e: JQuery.MouseEventBase
|
||||
) => {
|
||||
const custom_event = new CustomEvent('dragging', {
|
||||
detail: {
|
||||
origin: evt,
|
||||
current: e,
|
||||
offset: offset
|
||||
}
|
||||
});
|
||||
this.dispatchEvent(custom_event);
|
||||
};
|
||||
|
||||
var mouse_up = (e: JQuery.MouseEventBase) => {
|
||||
$(window).off("pointermove", mouse_move);
|
||||
$(window).off("pointerup", mouse_up);
|
||||
// trigger the drop event
|
||||
const custom_event = new CustomEvent('drop', {
|
||||
detail: {
|
||||
origin: evt,
|
||||
current: e,
|
||||
offset: offset
|
||||
}
|
||||
});
|
||||
this.dispatchEvent(custom_event);
|
||||
};
|
||||
$(window).on("pointermove", mouse_move);
|
||||
$(window).on("pointerup", mouse_up);
|
||||
});
|
||||
}
|
||||
|
||||
HTMLElement.prototype.update = function (d): void {
|
||||
$(this)
|
||||
.children()
|
||||
.each(function () {
|
||||
if(this.update)
|
||||
if (this.update)
|
||||
return this.update(d);
|
||||
});
|
||||
};
|
||||
@ -630,6 +947,12 @@ namespace OS {
|
||||
* stored in the `customElements` registry of the browser
|
||||
*/
|
||||
export namespace tag {
|
||||
/**
|
||||
* Alias to all classes that extends {@link AFXTag}
|
||||
*/
|
||||
export type AFXTagTypeClass = {
|
||||
new <T extends AFXTag>(): T;
|
||||
};
|
||||
/**
|
||||
* Define an AFX tag as a custom element and add it to the
|
||||
* global `customElements` registry. If the tag is redefined, i.e.
|
||||
@ -637,14 +960,14 @@ namespace OS {
|
||||
* new definition
|
||||
*
|
||||
* @export
|
||||
* @template T all classes that extends [[AFXTag]]
|
||||
* @template T all classes that extends {@link AFXTag}
|
||||
* @param {string} name name of the tag
|
||||
* @param {{ new (): T }} cls the class that defines the tag
|
||||
* @returns {void}
|
||||
*/
|
||||
export function define<T extends AFXTag>(
|
||||
name: string,
|
||||
cls: { new (): T }
|
||||
cls: { new(): T }
|
||||
): void {
|
||||
try {
|
||||
customElements.define(name, cls);
|
||||
|
564
src/core/vfs.ts
564
src/core/vfs.ts
@ -21,7 +21,7 @@ interface String {
|
||||
* Convert a string to VFS file handle.
|
||||
*
|
||||
* This function will create a file handle object from the string
|
||||
* with the help of [[VFS.findHandles]]
|
||||
* with the help of {@link OS.API.VFS.findHandles}
|
||||
*
|
||||
* @returns {OS.API.VFS.BaseFileHandle}
|
||||
* @memberof String
|
||||
@ -165,7 +165,9 @@ namespace OS {
|
||||
*/
|
||||
export namespace VFS {
|
||||
declare var JSZip: any;
|
||||
|
||||
/** path for custom VFS protocol handles */
|
||||
const VFSX_PATH = "pkg://vfsx/vfsx.js";
|
||||
|
||||
String.prototype.asFileHandle = function (): BaseFileHandle {
|
||||
const list = this.split("://");
|
||||
const handles = API.VFS.findHandles(list[0]);
|
||||
@ -198,13 +200,33 @@ namespace OS {
|
||||
): void {
|
||||
handles[protos] = cls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load custom VFS handles if the package vfsx available
|
||||
*
|
||||
* @export
|
||||
* @param {boolean} [force] force load the file
|
||||
* @return {*} {Promise<any>}
|
||||
*/
|
||||
export function loadVFSX(force?: boolean): Promise<any>
|
||||
{
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
await API.requires(VFSX_PATH, force);
|
||||
resolve(true);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
console.warn("No custom VFS protocol loaded");
|
||||
resolve(true);
|
||||
}
|
||||
})
|
||||
}
|
||||
/**
|
||||
* Looking for a attached file handle class of a string protocol
|
||||
*
|
||||
* When converting a string to file handle, the system will look
|
||||
* for a protocol pattern in the string, if the protocol found,
|
||||
* its attached handle class (found in [[VFS.handles]]) will be
|
||||
* its attached handle class (found in {@link VFS.handles}) will be
|
||||
* used to initialize a file handle object from the string
|
||||
*
|
||||
* ```typescript
|
||||
@ -250,11 +272,12 @@ namespace OS {
|
||||
|
||||
/**
|
||||
* Once read, file content will be cached in this placeholder
|
||||
*
|
||||
*
|
||||
* @private
|
||||
* @type {*}
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
cache: any;
|
||||
private _cache: any;
|
||||
|
||||
/**
|
||||
* Flag indicated whether the file meta-data is loaded
|
||||
@ -299,7 +322,7 @@ namespace OS {
|
||||
basename: string;
|
||||
|
||||
/**
|
||||
* Once loaded, [[ready]] will be set to true and
|
||||
* Once loaded, {@link ready} will be set to true and
|
||||
* file meta-data will be stored in this place holder
|
||||
*
|
||||
* @type {FileInfoType}
|
||||
@ -333,6 +356,8 @@ namespace OS {
|
||||
this.setPath(path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set a file path to the current file handle
|
||||
*
|
||||
@ -355,7 +380,7 @@ namespace OS {
|
||||
if (re === "") {
|
||||
return;
|
||||
}
|
||||
this.genealogy = re.split("/").filter(s=> s!="");
|
||||
this.genealogy = re.split("/").filter(s => s != "");
|
||||
this.path = `${this.protocol}://${this.genealogy.join("/")}`;
|
||||
if (!this.isRoot()) {
|
||||
this.basename = this.genealogy[
|
||||
@ -386,6 +411,22 @@ namespace OS {
|
||||
set filename(v: string) {
|
||||
this.basename = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter: Get the file cache
|
||||
* Setter: set the file cache
|
||||
*
|
||||
* @returns {any}
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
get cache(): any {
|
||||
return this._cache;
|
||||
}
|
||||
set cache(v: any) {
|
||||
this._cache = v;
|
||||
this._cache_changed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data to the file cache
|
||||
*
|
||||
@ -528,32 +569,29 @@ namespace OS {
|
||||
/**
|
||||
* Public read operation
|
||||
*
|
||||
* This function calls the [[_rd]] function to perform the operation.
|
||||
* This function calls the {@link _rd} function to perform the operation.
|
||||
*
|
||||
* If the current file is a directory, then the operation
|
||||
* will return the meta-data of all files inside of the directory.
|
||||
* Otherwise, file content will be returned
|
||||
*
|
||||
* @param {string} t data type
|
||||
* @param {any} formal t data type
|
||||
* - jsonp: the response is an json object
|
||||
* - script: the response is a javascript code
|
||||
* - xml, html: the response is a XML/HTML object
|
||||
* - text: plain text
|
||||
* - binary
|
||||
* - other user case may be user specific data
|
||||
*
|
||||
* @returns {Promise<any>} a promise on the file content
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
read(t?: string): Promise<any> {
|
||||
read(t?: any): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._rd(t);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d = await this._rd(t);
|
||||
resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -563,7 +601,7 @@ namespace OS {
|
||||
/**
|
||||
* Write the file cache to the actual file
|
||||
*
|
||||
* This function calls the [[_wr]] function to perform the operation
|
||||
* This function calls the {@link _wr} function to perform the operation
|
||||
*
|
||||
* @param {string} t data type
|
||||
* - `base64`
|
||||
@ -577,10 +615,7 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r: RequestResult = await this._wr(t);
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "write",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "write",this);
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
@ -591,7 +626,7 @@ namespace OS {
|
||||
/**
|
||||
* Sub-directory creation
|
||||
*
|
||||
* This function calls the [[_mk]] function to perform the operation
|
||||
* This function calls the {@link _mk} function to perform the operation
|
||||
*
|
||||
* @param {string} d sub directory name
|
||||
* @returns {Promise<RequestResult>} promise on the operation result
|
||||
@ -601,16 +636,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d_1 = await this._mk(d);
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "mk",
|
||||
file: this,
|
||||
});
|
||||
return resolve(d_1);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d_1 = await this._mk(d);
|
||||
announcer.ostrigger("VFS","mk",this);
|
||||
return resolve(d_1);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -620,25 +648,18 @@ namespace OS {
|
||||
/**
|
||||
* Delete the file
|
||||
*
|
||||
* This function calls the [[_rm]] function to perform the operation
|
||||
*
|
||||
* This function calls the {@link _rm} function to perform the operation
|
||||
* @param {any} d user data
|
||||
* @returns {Promise<RequestResult>} promise on the operation result
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
remove(): Promise<RequestResult> {
|
||||
remove(data?: any): Promise<RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._rm();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "remove",
|
||||
file: this,
|
||||
});
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d = await this._rm(data);
|
||||
announcer.ostrigger("VFS", "remove",this);
|
||||
return resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -650,7 +671,7 @@ namespace OS {
|
||||
*
|
||||
* Only work when the current file is a directory
|
||||
*
|
||||
* This function calls the [[_up]] function to perform the operation
|
||||
* This function calls the {@link _up} function to perform the operation
|
||||
*
|
||||
* @returns {Promise<RequestResult>} promise on the operation result
|
||||
* @memberof BaseFileHandle
|
||||
@ -659,16 +680,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._up();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "upload",
|
||||
file: this,
|
||||
});
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d = await this._up();
|
||||
announcer.ostrigger("VFS", "upload", this);
|
||||
return resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -680,7 +694,7 @@ namespace OS {
|
||||
*
|
||||
* Only work with file
|
||||
*
|
||||
* This function calls the [[_pub]] function to perform the operation
|
||||
* This function calls the {@link _pub} function to perform the operation
|
||||
*
|
||||
* @returns {Promise<RequestResult>} promise on operation result
|
||||
* @memberof BaseFileHandle
|
||||
@ -689,16 +703,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._pub();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "publish",
|
||||
file: this,
|
||||
});
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d = await this._pub();
|
||||
announcer.ostrigger("VFS", "publish",this);
|
||||
return resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -710,7 +717,7 @@ namespace OS {
|
||||
*
|
||||
* Only work with file
|
||||
*
|
||||
* This function calls the [[_down]] function to perform the operation
|
||||
* This function calls the {@link _down} function to perform the operation
|
||||
*
|
||||
* @returns {Promise<any>} Promise on the operation result
|
||||
* @memberof BaseFileHandle
|
||||
@ -719,16 +726,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._down();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "download",
|
||||
file: this,
|
||||
});
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d = await this._down();
|
||||
announcer.ostrigger("VFS", "download",this);
|
||||
return resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -738,7 +738,7 @@ namespace OS {
|
||||
/**
|
||||
* Move the current file to another location
|
||||
*
|
||||
* This function calls the [[_mv]] function to perform the operation
|
||||
* This function calls the {@link _mv} function to perform the operation
|
||||
*
|
||||
* @param {string} d destination location
|
||||
* @returns {Promise<RequestResult>} promise on the operation result
|
||||
@ -748,16 +748,10 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const data = await this._mv(d);
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "move",
|
||||
file: d.asFileHandle(),
|
||||
});
|
||||
return resolve(data);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const data = await this._mv(d);
|
||||
announcer.ostrigger("VFS", "move",d.asFileHandle());
|
||||
return resolve(data);
|
||||
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -769,7 +763,7 @@ namespace OS {
|
||||
*
|
||||
* This action depends on each file protocol
|
||||
*
|
||||
* This function calls the [[_exec]] function to perform the operation
|
||||
* This function calls the {@link _exec} function to perform the operation
|
||||
*
|
||||
* @returns {Promise<any>}
|
||||
* @memberof BaseFileHandle
|
||||
@ -778,16 +772,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._exec();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "execute",
|
||||
file: this,
|
||||
});
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
const d = await this._exec();
|
||||
announcer.ostrigger("VFS", "execute", this);
|
||||
return resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -826,6 +813,17 @@ namespace OS {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* trigger cache changed event
|
||||
*
|
||||
* This function triggered when the file cached changed
|
||||
*
|
||||
* @protected
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
protected _cache_changed():void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Low level protocol-specific read operation
|
||||
*
|
||||
@ -833,11 +831,11 @@ namespace OS {
|
||||
* that supports the operation
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type, see [[read]]
|
||||
* @param {any} t data type, see {@link read}
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
protected _rd(t: string): Promise<RequestResult> {
|
||||
protected _rd(t: any): Promise<RequestResult> {
|
||||
return this.unsupported("read");
|
||||
}
|
||||
|
||||
@ -848,12 +846,11 @@ namespace OS {
|
||||
* that supports the operation
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type, see [[write]]
|
||||
* @param {*} [d]
|
||||
* @param {string} t data type, see {@link write}
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
protected _wr(t: string, d?: any): Promise<RequestResult> {
|
||||
protected _wr(t: string): Promise<RequestResult> {
|
||||
return this.unsupported("write");
|
||||
}
|
||||
|
||||
@ -877,10 +874,11 @@ namespace OS {
|
||||
* This function should be overridden by the file handle class
|
||||
* that supports the operation
|
||||
*
|
||||
* @param {*} [d] any user data
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
protected _rm(): Promise<RequestResult> {
|
||||
protected _rm(d: any): Promise<RequestResult> {
|
||||
return this.unsupported("remove");
|
||||
}
|
||||
|
||||
@ -1027,7 +1025,7 @@ namespace OS {
|
||||
* Otherwise, file content will be returned
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type see [[read]]
|
||||
* @param {string} t data type see {@link read}
|
||||
* @returns {Promise<any>}
|
||||
* @memberof RemoteFileHandle
|
||||
*/
|
||||
@ -1059,15 +1057,15 @@ namespace OS {
|
||||
* Write file cache to the remote file
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type see [[write]]
|
||||
* @param {string} t data type see {@link write}
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof RemoteFileHandle
|
||||
*/
|
||||
protected _wr(t: string): Promise<RequestResult> {
|
||||
// t is base64 or undefined
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (t === "base64") {
|
||||
try {
|
||||
try {
|
||||
if (t === "base64") {
|
||||
const d = await API.handle.write(
|
||||
this.path,
|
||||
this.cache
|
||||
@ -1080,11 +1078,7 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
} else {
|
||||
const r = await this.b64(t);
|
||||
const result = await API.handle.write(
|
||||
this.path,
|
||||
@ -1102,9 +1096,9 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
return resolve(result);
|
||||
} catch (e_2) {
|
||||
return reject(__e(e_2));
|
||||
}
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1299,7 +1293,7 @@ namespace OS {
|
||||
|
||||
|
||||
/**
|
||||
* Package file is remote file ([[RemoteFileHandle]]) located either in
|
||||
* Package file is remote file ({@link RemoteFileHandle}) located either in
|
||||
* the local user packages location or system packages
|
||||
* location, it should be in the following format:
|
||||
*
|
||||
@ -1369,11 +1363,17 @@ namespace OS {
|
||||
constructor(path: string) {
|
||||
super(path);
|
||||
if (this.basename) {
|
||||
let v: any = OS.setting.system.packages[this.basename];
|
||||
v.type = "app";
|
||||
v.mime = "antos/app";
|
||||
v.size = 0;
|
||||
this.info = v as FileInfoType;
|
||||
let v = OS.setting.system.packages[this.basename];
|
||||
this.info = {} as FileInfoType;
|
||||
for(const p in v)
|
||||
{
|
||||
this.info[p] = v[p];
|
||||
}
|
||||
|
||||
this.info.type = "app";
|
||||
this.info.mime = "antos/app";
|
||||
this.info.size = 0;
|
||||
this.info.text = v.name;
|
||||
}
|
||||
this.ready = true;
|
||||
}
|
||||
@ -1436,6 +1436,8 @@ namespace OS {
|
||||
|
||||
register("^app$", ApplicationHandle);
|
||||
|
||||
|
||||
var MEM_PARTITION: BufferFileHandle = undefined;
|
||||
/**
|
||||
* A buffer file handle represents a virtual file that is stored
|
||||
* on the system memory. Its protocol pattern is defined as:
|
||||
@ -1457,18 +1459,96 @@ namespace OS {
|
||||
*/
|
||||
constructor(path: string, mime: string, data: any) {
|
||||
super(path);
|
||||
if (data) {
|
||||
this.cache = data;
|
||||
}
|
||||
this.info = {
|
||||
mime: mime,
|
||||
mime: mime?mime:'application/octet-stream',
|
||||
path: path,
|
||||
size: data ? data.length : 0,
|
||||
name: this.basename,
|
||||
type: "file",
|
||||
filename:this.basename,
|
||||
ctime: (new Date()).toGMTString(),
|
||||
mtime: (new Date()).toGMTString(),
|
||||
type: 'file',
|
||||
};
|
||||
if (data) {
|
||||
this.cache = data;
|
||||
}
|
||||
return this.init_file_tree();
|
||||
}
|
||||
/**
|
||||
* init the mem file tree if necessary
|
||||
*/
|
||||
private init_file_tree(): BufferFileHandle
|
||||
{
|
||||
if(this.isRoot())
|
||||
{
|
||||
if(!MEM_PARTITION)
|
||||
{
|
||||
this.cache = {};
|
||||
MEM_PARTITION = this;
|
||||
}
|
||||
return MEM_PARTITION;
|
||||
}
|
||||
let curr_level = "mem://".asFileHandle();
|
||||
for(let i = 0;i<this.genealogy.length - 1;i++)
|
||||
{
|
||||
const segment = this.genealogy[i];
|
||||
let handle = undefined;
|
||||
if(segment == "..")
|
||||
{
|
||||
curr_level = curr_level.parent();
|
||||
continue;
|
||||
}
|
||||
|
||||
handle = curr_level.cache[segment];
|
||||
if(!handle)
|
||||
{
|
||||
handle = new BufferFileHandle(`${curr_level.path}/${segment}`, 'dir', {});
|
||||
curr_level.cache[segment] = handle;
|
||||
}
|
||||
curr_level = handle;
|
||||
if(!curr_level.cache)
|
||||
{
|
||||
curr_level.cache = {};
|
||||
}
|
||||
}
|
||||
if(this.basename == "..")
|
||||
{
|
||||
return curr_level.parent() as BufferFileHandle;
|
||||
}
|
||||
if(!curr_level.cache[this.basename])
|
||||
curr_level.cache[this.basename] = this;
|
||||
return curr_level.cache[this.basename];
|
||||
}
|
||||
|
||||
/**
|
||||
* cache changed handle
|
||||
*/
|
||||
protected _cache_changed(): void
|
||||
{
|
||||
if(!this.cache)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.info.mime = (new Date()).toGMTString();
|
||||
if(typeof this.cache === "string" || this.cache instanceof Blob || this.cache instanceof Uint8Array)
|
||||
{
|
||||
this.info.type = "file";
|
||||
if(typeof this.cache === "string")
|
||||
{
|
||||
this.info.mime = "text/plain";
|
||||
}
|
||||
else
|
||||
{
|
||||
this.info.mime = "application/octet-stream";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.info.type = "dir";
|
||||
this.info.mime = "dir";
|
||||
}
|
||||
this.info.size = this.cache.length;
|
||||
}
|
||||
/**
|
||||
* Read the file meta-data
|
||||
*
|
||||
@ -1477,23 +1557,58 @@ namespace OS {
|
||||
*/
|
||||
meta(): Promise<RequestResult> {
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
const data = {};
|
||||
for(const k in this.info)
|
||||
{
|
||||
data[k] = this.info[k];
|
||||
}
|
||||
resolve({
|
||||
result: this.info,
|
||||
result: data,
|
||||
error: false,
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the file meta-data before performing
|
||||
* any task
|
||||
*
|
||||
* @returns {Promise<FileInfoType>} a promise on file meta-data
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
onready(): Promise<FileInfoType> {
|
||||
// read meta data
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try
|
||||
{
|
||||
const d = await this.meta();
|
||||
resolve(d.result as FileInfoType);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Read file content stored in the file cached
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type see [[read]]
|
||||
* @param {string} t data type see {@link read}
|
||||
* @returns {Promise<any>}
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
protected _rd(t: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// read dir
|
||||
if (this.info.type === "dir") {
|
||||
return resolve({
|
||||
result: (Object.values(this.cache) as BufferFileHandle[]).map(o=>o.info),
|
||||
error: false,
|
||||
});
|
||||
}
|
||||
return resolve(this.cache);
|
||||
});
|
||||
}
|
||||
@ -1502,13 +1617,11 @@ namespace OS {
|
||||
* Write data to the file cache
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type, see [[write]]
|
||||
* @param {*} d data
|
||||
* @param {string} t data type, see {@link write}
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
protected _wr(t: string, d: any): Promise<RequestResult> {
|
||||
this.cache = d;
|
||||
protected _wr(t: string): Promise<RequestResult> {
|
||||
return new Promise((resolve, reject) =>
|
||||
resolve({
|
||||
result: true,
|
||||
@ -1516,6 +1629,105 @@ namespace OS {
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create sub directory
|
||||
*
|
||||
* Only work on directory file handle
|
||||
*
|
||||
* @protected
|
||||
* @param {string} d sub directory name
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
protected _mk(d: string): Promise<RequestResult> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.info.type === "file") {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__("{0} is not a directory", this.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
const handle = `${this.path}/${d}`.asFileHandle();
|
||||
if(handle.info.type === "file")
|
||||
{
|
||||
handle.cache = {};
|
||||
}
|
||||
resolve({result: true, error: false});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file/folder
|
||||
*
|
||||
* @protected
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
protected _rm(): Promise<RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const parent = this.parent();
|
||||
parent.cache[this.basename] = undefined;
|
||||
delete parent.cache[this.basename];
|
||||
return resolve({result: true, error: false});
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setPath(p: string): void
|
||||
{
|
||||
super.setPath(p);
|
||||
if(this.info)
|
||||
this.info.path = this.path;
|
||||
}
|
||||
|
||||
private updatePath(path: string)
|
||||
{
|
||||
this.setPath(path);
|
||||
if(this.info.type == "file")
|
||||
{
|
||||
return;
|
||||
}
|
||||
for(const k in this.cache)
|
||||
{
|
||||
const child = this.cache[k];
|
||||
child.updatePath(`${this.path}/${child.basename}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move file/folder
|
||||
*
|
||||
* @protected
|
||||
* @param {string} d
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
protected _mv(d: string): Promise<RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
if(d.includes(this.path))
|
||||
{
|
||||
return reject(API.throwe(__("Unable to move file/folder from {0} to {1}", this.path, d)));
|
||||
}
|
||||
const parent = this.parent()
|
||||
parent.cache[this.basename] = undefined;
|
||||
delete parent.cache[this.basename];
|
||||
|
||||
const dest = d.asFileHandle();
|
||||
this.updatePath(dest.path);
|
||||
dest.parent().cache[dest.basename] = this;
|
||||
return resolve({result: true, error: false});
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Download the buffer file
|
||||
@ -1526,6 +1738,10 @@ namespace OS {
|
||||
*/
|
||||
protected _down(): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if(this.info.type == "dir")
|
||||
{
|
||||
return reject(API.throwe(__("{0} is a directory", this.path)));
|
||||
}
|
||||
const blob = new Blob([this.cache], {
|
||||
type: "octet/stream",
|
||||
});
|
||||
@ -1585,7 +1801,7 @@ namespace OS {
|
||||
* Read URL content
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type see [[read]]
|
||||
* @param {string} t data type see {@link read}
|
||||
* @returns {Promise<any>}
|
||||
* @memberof URLFileHandle
|
||||
*/
|
||||
@ -1639,7 +1855,7 @@ namespace OS {
|
||||
* Read file content
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type, see [[read]]
|
||||
* @param {string} t data type, see {@link read}
|
||||
* @returns {Promise<any>}
|
||||
* @memberof SharedFileHandle
|
||||
*/
|
||||
@ -1658,23 +1874,46 @@ namespace OS {
|
||||
* write data to shared file
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type, see [[write]]
|
||||
* @param {string} d file data
|
||||
* @param {string} t data type, see {@link write}
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof SharedFileHandle
|
||||
*/
|
||||
protected _wr(t: string, d: string): Promise<RequestResult> {
|
||||
protected _wr(t: string): Promise<RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.write(this.path, d);
|
||||
if (r.error) {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__("{0}: {1}", r.error, this.path)
|
||||
)
|
||||
|
||||
if (t === "base64") {
|
||||
const d = await API.handle.write(
|
||||
this.path,
|
||||
this.cache
|
||||
);
|
||||
if (d.error) {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__("{0}: {1}", d.error, this.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
return resolve(d);
|
||||
} else {
|
||||
const r = await this.b64(t);
|
||||
const result = await API.handle.write(
|
||||
this.path,
|
||||
r as string
|
||||
);
|
||||
if (result.error) {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__(
|
||||
"{0}: {1}",
|
||||
result.error,
|
||||
this.path
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return resolve(result);
|
||||
}
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
@ -1855,7 +2094,6 @@ namespace OS {
|
||||
promises.push(new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const file = path.asFileHandle();
|
||||
const tof = `${to}/${file.basename}`.asFileHandle();
|
||||
const meta = await file.onready();
|
||||
if (meta.type === "dir") {
|
||||
const desdir = to.asFileHandle();
|
||||
@ -1871,6 +2109,7 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const tof = `${to}/${file.basename}`.asFileHandle();
|
||||
const content = await file.read("binary");
|
||||
await tof
|
||||
.setCache(
|
||||
@ -1945,14 +2184,12 @@ namespace OS {
|
||||
try {
|
||||
await API.requires("os://scripts/jszip.min.js");
|
||||
const zip = new JSZip();
|
||||
const fhd = src.asFileHandle();
|
||||
const fhd = src.asFileHandle();
|
||||
const meta = await fhd.onready();
|
||||
if(meta.type === "file")
|
||||
{
|
||||
if (meta.type === "file") {
|
||||
await aradd([src], zip, "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
const ret = await fhd.read();
|
||||
await aradd(
|
||||
ret.result.map((v: API.FileInfoType) => v.path), zip, "/");
|
||||
@ -2032,17 +2269,26 @@ namespace OS {
|
||||
const data = await zfile.asFileHandle().read("binary");
|
||||
const zip = await JSZip.loadAsync(data);
|
||||
const to = await dest_callback(zip);
|
||||
const dirs = [];
|
||||
const dirs = new Set<string>();
|
||||
const files = [];
|
||||
for (const name in zip.files) {
|
||||
const file = zip.files[name];
|
||||
for (const _name in zip.files) {
|
||||
const name = _name.trimFromRight("/");
|
||||
const file = zip.files[_name];
|
||||
if (file.dir) {
|
||||
dirs.push(to + "/" + name);
|
||||
dirs.add(to + "/" + name);
|
||||
} else {
|
||||
const segs = name.split("/");
|
||||
segs.pop();
|
||||
let path = to;
|
||||
for(let seg of segs)
|
||||
{
|
||||
path += "/" + seg;
|
||||
dirs.add(path);
|
||||
}
|
||||
files.push(name);
|
||||
}
|
||||
}
|
||||
await mkdirAll(dirs, true);
|
||||
await mkdirAll(Array.from(dirs), true);
|
||||
const promises = [];
|
||||
for (const file of files) {
|
||||
promises.push(new Promise(async (res, rej) => {
|
||||
|
@ -1,401 +0,0 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS101: Remove unnecessary use of Array.from
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* DS202: Simplify dynamic range loops
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
// GoogleDrive File Handle
|
||||
let G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
|
||||
|
||||
class GoogleDriveHandle extends this.OS.API.VFS.BaseFileHandle {
|
||||
constructor(path) {
|
||||
super(path);
|
||||
const me = this;
|
||||
this.setting = Ant.OS.setting.VFS.gdrive;
|
||||
if (!this.setting) { return Ant.OS.announcer.oserror(__("Unknown API setting for {0}", "GAPI"), (Ant.OS.API.throwe("OS.VFS")), null); }
|
||||
if (this.isRoot()) { this.gid = 'root'; }
|
||||
this.cache = "";
|
||||
}
|
||||
|
||||
oninit(f) {
|
||||
const me = this;
|
||||
if (!this.setting) { return; }
|
||||
const fn = function(r) {
|
||||
if (r) { return f(); }
|
||||
// perform the login
|
||||
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
|
||||
return gapi.auth2.getAuthInstance().signIn();
|
||||
};
|
||||
|
||||
if (Ant.OS.API.libready(this.setting.apilink)) {
|
||||
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
|
||||
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
|
||||
} else {
|
||||
return Ant.OS.API.require(this.setting.apilink, function() {
|
||||
// avoid popup block
|
||||
const q = Ant.OS.announcer.getMID();
|
||||
return gapi.load("client:auth2", function() {
|
||||
Ant.OS.API.loading(q, "GAPI");
|
||||
return gapi.client.init({
|
||||
apiKey: me.setting.API_KEY,
|
||||
clientId: me.setting.CLIENT_ID,
|
||||
discoveryDocs: me.setting.DISCOVERY_DOCS,
|
||||
scope: me.setting.SCOPES
|
||||
})
|
||||
.then(function() {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
|
||||
return _GUI.openDialog("YesNoDialog", function(d) {
|
||||
if (!d) { return Ant.OS.announcer.osinfo(__("User abort the authentication")); }
|
||||
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
|
||||
}
|
||||
, __("Authentication")
|
||||
, { text: __("Would you like to login to {0}?", "Google Drive") });})
|
||||
.catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot init {0}: {1}", "GAPI",err.error), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
meta(f) {
|
||||
const me = this;
|
||||
return this.oninit(function() {
|
||||
const q = Ant.OS.announcer.getMID();
|
||||
if (G_CACHE[me.path]) { me.gid = G_CACHE[me.path].id; }
|
||||
if (me.gid) {
|
||||
//console.log "Gid exists ", me.gid
|
||||
Ant.OS.API.loading(q, "GAPI");
|
||||
return gapi.client.drive.files.get({
|
||||
fileId: me.gid,
|
||||
fields: me.fields()
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (!r.result) { return; }
|
||||
r.result.mime = r.result.mimeType;
|
||||
return f(r);}).catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
} else {
|
||||
//console.log "Find file in ", me.parent()
|
||||
const fp = me.parent().asFileHandle();
|
||||
return fp.meta(function(d) {
|
||||
const file = d.result;
|
||||
const q1 = Ant.OS.announcer.getMID();
|
||||
Ant.OS.API.loading(q1, "GAPI");
|
||||
G_CACHE[fp.path] = { id: file.id, mime: file.mimeType };
|
||||
return gapi.client.drive.files.list({
|
||||
q: `name = '${me.basename}' and '${file.id}' in parents and trashed = false`,
|
||||
fields: `files(${me.fields()})`
|
||||
})
|
||||
.then(function(r) {
|
||||
//console.log r
|
||||
Ant.OS.API.loaded(q1, "OK");
|
||||
if (!r.result.files || !(r.result.files.length > 0)) { return; }
|
||||
G_CACHE[me.path] = { id: r.result.files[0].id, mime: r.result.files[0].mimeType };
|
||||
r.result.files[0].mime = r.result.files[0].mimeType;
|
||||
return f({ result: r.result.files[0] });})
|
||||
.catch(function(err) {
|
||||
Ant.OS.API.loaded(q1, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fields() {
|
||||
return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size";
|
||||
}
|
||||
isFolder() {
|
||||
return this.info.mimeType === "application/vnd.google-apps.folder";
|
||||
}
|
||||
|
||||
save(id, m, f) {
|
||||
const me = this;
|
||||
const user = gapi.auth2.getAuthInstance().currentUser.get();
|
||||
const oauthToken = user.getAuthResponse().access_token;
|
||||
const q = Ant.OS.announcer.getMID();
|
||||
const xhr = new XMLHttpRequest();
|
||||
const url = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=media';
|
||||
xhr.open('PATCH', url);
|
||||
xhr.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
|
||||
xhr.setRequestHeader('Content-Type', m);
|
||||
xhr.setRequestHeader('Content-Encoding', 'base64');
|
||||
xhr.setRequestHeader('Content-Transfer-Encoding', 'base64');
|
||||
Ant.OS.API.loading(q, "GAPI");
|
||||
const error = function(e, s) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot save : {0}", me.path), e, s);
|
||||
};
|
||||
xhr.onreadystatechange = function() {
|
||||
if ( xhr.readyState === 4 ) {
|
||||
if ( xhr.status === 200 ) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
return f({ result: JSON.parse(xhr.responseText) });
|
||||
} else {
|
||||
return error(xhr, xhr.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => error(xhr, xhr.status);
|
||||
if (m === "base64") { return xhr.send(me.cache.replace(/^data:[^;]+;base64,/g, "")); }
|
||||
return this.sendB64(m, data => xhr.send(data.replace(/^data:[^;]+;base64,/g, "")));
|
||||
}
|
||||
|
||||
getlink() {
|
||||
if (this.ready) { return this.info.webContentLink; }
|
||||
return undefined;
|
||||
}
|
||||
|
||||
action(n, p, f) {
|
||||
const me = this;
|
||||
const q = Ant.OS.announcer.getMID();
|
||||
Ant.OS.API.loading(q, "GAPI");
|
||||
switch (n) {
|
||||
case "read":
|
||||
if (!this.info.id) { return; }
|
||||
if (this.isFolder()) {
|
||||
return gapi.client.drive.files.list({
|
||||
q: `'${me.info.id}' in parents and trashed = false`,
|
||||
fields: `files(${me.fields()})`
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (!r.result.files) { return; }
|
||||
for (let file of Array.from(r.result.files)) {
|
||||
file.path = me.child(file.name);
|
||||
file.mime = file.mimeType;
|
||||
file.filename = file.name;
|
||||
file.type = "file";
|
||||
file.gid = file.id;
|
||||
if (file.mimeType === "application/vnd.google-apps.folder") {
|
||||
file.mime = "dir";
|
||||
file.type = "dir";
|
||||
file.size = 0;
|
||||
}
|
||||
G_CACHE[file.path] = { id: file.gid, mime: file.mime };
|
||||
}
|
||||
return f({ result: r.result.files });})
|
||||
.catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
} else {
|
||||
return gapi.client.drive.files.get({
|
||||
fileId: me.info.id,
|
||||
alt: 'media'
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (p !== "binary") { return f(r.body); }
|
||||
return f(r.body.asUint8Array());}).catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
}
|
||||
|
||||
case "mk":
|
||||
if (!this.isFolder()) { return f({ error: __("{0} is not a directory", this.path) }); }
|
||||
var meta = {
|
||||
name: p,
|
||||
parents: [this.info.id],
|
||||
mimeType: 'application/vnd.google-apps.folder'
|
||||
};
|
||||
|
||||
gapi.client.drive.files.create({
|
||||
resource: meta,
|
||||
fields: 'id'
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
//console.log r
|
||||
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), r); }
|
||||
G_CACHE[me.child(p)] = { id: r.result.id, mime: "dir" };
|
||||
return f(r);}).catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
|
||||
return;
|
||||
|
||||
case "write":
|
||||
var gid = undefined;
|
||||
if (G_CACHE[me.path]) { gid = G_CACHE[me.path].id; }
|
||||
if (gid) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
return this.save(gid, p, f);
|
||||
} else {
|
||||
const dir = this.parent().asFileHandle();
|
||||
return dir.onready(function() {
|
||||
meta = {
|
||||
name: me.basename,
|
||||
mimeType: p,
|
||||
parents: [dir.info.id]
|
||||
};
|
||||
|
||||
return gapi.client.drive.files.create({
|
||||
resource: meta,
|
||||
fields: 'id'
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
|
||||
G_CACHE[me.path] = { id: r.result.id, mime: p };
|
||||
return me.save(r.result.id, p, f);}).catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
case "upload":
|
||||
if (!this.isFolder()) { return; }
|
||||
//insert a temporal file selector
|
||||
var o = ($('<input>')).attr('type', 'file').css("display", "none");
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
o.change(function() {
|
||||
//Ant.OS.API.loading q, p
|
||||
const fo = o[0].files[0];
|
||||
const file = (me.child(fo.name)).asFileHandle();
|
||||
file.cache = fo;
|
||||
file.write(fo.type, f);
|
||||
return o.remove();
|
||||
});
|
||||
|
||||
//Ant.OS.API.loaded q, p, "OK"
|
||||
//Ant.OS.API.loaded q, p, "FAIL"
|
||||
|
||||
return o.click();
|
||||
|
||||
case "remove":
|
||||
if (!this.info.id) { return; }
|
||||
return gapi.client.drive.files.delete({
|
||||
fileId: me.info.id
|
||||
})
|
||||
.then(function(r) {
|
||||
//console.log r
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
|
||||
G_CACHE[me.path] = null;
|
||||
return f({ result: true });})
|
||||
.catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
|
||||
case "publish":
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
return;
|
||||
|
||||
case "download":
|
||||
return gapi.client.drive.files.get({
|
||||
fileId: me.info.id,
|
||||
alt: 'media'
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (!r.body) { return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
|
||||
let bytes = [];
|
||||
for (let i = 0, end = r.body.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
|
||||
bytes.push(r.body.charCodeAt(i));
|
||||
}
|
||||
bytes = new Uint8Array(bytes);
|
||||
const blob = new Blob([bytes], { type: "octet/stream" });
|
||||
return Ant.OS.API.saveblob(me.basename, blob);}).catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
|
||||
case "move":
|
||||
var dest = p.asFileHandle().parent().asFileHandle();
|
||||
return dest.onready(function() {
|
||||
const previousParents = me.info.parents.join(',');
|
||||
return gapi.client.drive.files.update({
|
||||
fileId: me.info.id,
|
||||
addParents: dest.info.id,
|
||||
removeParents: previousParents,
|
||||
fields: "id"
|
||||
})
|
||||
.then(function(r) {
|
||||
Ant.OS.API.loaded(q, "OK");
|
||||
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
|
||||
return f(r);}).catch(function(err) {
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
|
||||
});
|
||||
}
|
||||
, err => Ant.OS.API.loaded(q, "FAIL"));
|
||||
default:
|
||||
Ant.OS.API.loaded(q, "FAIL");
|
||||
return Ant.OS.announcer.osfail(__("VFS unknown action: {0}", n), (Ant.OS.API.throwe("OS.VFS")), n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
self.OS.API.VFS.register("^gdv$", GoogleDriveHandle);
|
||||
// search the cache for file
|
||||
self.OS.API.onsearch("Google Drive", function(t) {
|
||||
const arr = [];
|
||||
const term = new RegExp(t, "i");
|
||||
for (let k in G_CACHE) {
|
||||
const v = G_CACHE[k];
|
||||
if ((k.match(term)) || (v && v.mime.match(term))) {
|
||||
const file = k.asFileHandle();
|
||||
file.text = file.basename;
|
||||
file.mime = v.mime;
|
||||
file.iconclass = "fa fa-file";
|
||||
if (file.mime === "dir") { file.iconclass = "fa fa-folder"; }
|
||||
file.complex = true;
|
||||
file.detail = [{ text: file.path }];
|
||||
arr.push(file);
|
||||
}
|
||||
}
|
||||
return arr;
|
||||
});
|
||||
|
||||
self.OS.onexit("cleanUpGoogleDrive", function() {
|
||||
G_CACHE = { "gdv://": { id: "root", mime: 'dir' } };
|
||||
if (!Ant.OS.setting.VFS.gdrive || !Ant.OS.API.libready(Ant.OS.setting.VFS.gdrive.apilink)) { return; }
|
||||
const auth2 = gapi.auth2.getAuthInstance();
|
||||
if (!auth2) { return; }
|
||||
if (auth2.isSignedIn.get()) {
|
||||
let el;
|
||||
return el = $('<iframe/>', {
|
||||
src: 'https://www.google.com/accounts/Logout',
|
||||
frameborder: 0,
|
||||
onload() {
|
||||
//console.log("disconnect")
|
||||
return auth2.disconnect();
|
||||
}
|
||||
//$(this).remove()
|
||||
});
|
||||
}
|
||||
});
|
||||
//($ "body").append(el)
|
||||
|
@ -22,8 +22,18 @@
|
||||
<head>
|
||||
<title>AntOS webOS</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
|
||||
<link rel="icon" href="resources/themes/system/icons/antos-16x16.png" type="image/png" />
|
||||
<link rel="preload" href="resources/themes/system/fonts/ubuntu-regular-webfont.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="resources/themes/system/fonts/ubuntu-italic-webfont.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="resources/themes/system/fonts/ubuntu-bolditalic-webfont.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="resources/themes/system/fonts/ubuntu-bold-webfont.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="resources/themes/system/fonts/fontawesome-webfont.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link rel="preload" href="resources/themes/system/fonts/bootstrap-icons.woff2" as="font" type="font/woff2" crossorigin>
|
||||
<link href="resources/themes/system/system.css" rel="stylesheet">
|
||||
<!-- default theme-->
|
||||
<link href="resources/themes/system/antos.css" rel="stylesheet">
|
||||
|
||||
<link id="ostheme" rel="stylesheet" href="">
|
||||
<script src="scripts/jquery-3.4.1.min.js"></script>
|
||||
<script src="scripts/antos.js"></script>
|
||||
|
@ -4,7 +4,7 @@ libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = scheme.html package.json
|
||||
copyfiles = scheme.html package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=Files
|
||||
|
12
src/packages/Files/README.md
Normal file
12
src/packages/Files/README.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Files
|
||||
|
||||
Base AntOS file explorer application.
|
||||
|
||||
This application is included in the AntOS delivery as system application and
|
||||
cannot be removed/uinstalled by regular user
|
||||
|
||||
## Change logs
|
||||
- v0.1.10-b: improve file favorite behaviour
|
||||
- v0.1.9-b: use "column"/"row" for UI direction names
|
||||
- v0.1.8-b: use system css variables in theme
|
||||
- v0.1.7-b: fix - file grid view double click event hanling on diffent cells of a row
|
@ -1,48 +1,56 @@
|
||||
/*afx-app-window[data-id ='files-app-window'] afx-list-view{
|
||||
border-top:1px solid #A6A6A6;
|
||||
}*/
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri']{
|
||||
border-right: 0;
|
||||
/* border-top:1px solid #A6A6A6; */
|
||||
padding:0;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-resizer{
|
||||
background-color: transparent;
|
||||
/* border-left: 1px solid #cbcbcb; */
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-file-view afx-list-view i:before{
|
||||
font-size: 32px;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
text-align: center;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li{
|
||||
background-color: transparent;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected {
|
||||
background-color: #cecece;
|
||||
color:#414339;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] .afx-window-top{
|
||||
border-bottom: 0;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-vbox[data-id = "nav-bar"]{
|
||||
background-color: #dfdfdf;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row.grid_row_header div{
|
||||
border-top:1px solid #A6A6A6;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row afx-label span {
|
||||
white-space: nowrap;
|
||||
/*
|
||||
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected {
|
||||
background-color: var(--background-quaternary) !important;
|
||||
color:var(--text-secondary) !important;
|
||||
}
|
||||
*/
|
||||
/*
|
||||
afx-app-window[data-id ='files-app-window'] afx-file-view {
|
||||
background-color: var(--background-tertiary);
|
||||
}
|
||||
*/
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] button{
|
||||
height: 23px;
|
||||
border-radius: 0;
|
||||
padding-top:2px;
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul li{
|
||||
padding: 0;
|
||||
padding-right: 7px;
|
||||
padding-left: 7px;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] input{
|
||||
border-radius: 3px;
|
||||
afx-app-window[data-id ='files-app-window'] afx-hbox[data-id ='nav-bar'] {
|
||||
border-bottom: 1px solid var(--border-primary);
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul li::before{
|
||||
content: "\F27B";
|
||||
font-family: "bootstrap-icons";
|
||||
font-style: normal;
|
||||
/* font-size: 41px; */
|
||||
display: block;
|
||||
position: absolute;
|
||||
color: var(--text-secondary);
|
||||
right: -7px
|
||||
}
|
||||
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul > afx-list-item > li:hover{
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul > afx-list-item > li.selected {
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
}
|
@ -21,7 +21,7 @@ namespace OS {
|
||||
|
||||
interface FilesClipboardType {
|
||||
cut: boolean;
|
||||
file: API.VFS.BaseFileHandle;
|
||||
files: API.VFS.BaseFileHandle[];
|
||||
}
|
||||
interface FilesViewType {
|
||||
icon: boolean;
|
||||
@ -37,7 +37,6 @@ namespace OS {
|
||||
*/
|
||||
export class Files extends BaseApplication {
|
||||
private view: GUI.tag.FileViewTag;
|
||||
private navinput: HTMLInputElement;
|
||||
private navbar: GUI.tag.HBoxTag;
|
||||
private currdir: API.VFS.BaseFileHandle;
|
||||
private favo: GUI.tag.ListViewTag;
|
||||
@ -50,20 +49,14 @@ namespace OS {
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
* main entry point
|
||||
*
|
||||
* @returns
|
||||
* @memberof Files
|
||||
*/
|
||||
main(): void {
|
||||
this.view = this.find("fileview") as GUI.tag.FileViewTag;
|
||||
this.navinput = this.find("navinput") as HTMLInputElement;
|
||||
this.navbar = this.find("nav-bar") as GUI.tag.HBoxTag;
|
||||
if (this.args && this.args.length > 0) {
|
||||
this.currdir = this.args[0].path.asFileHandle();
|
||||
} else {
|
||||
this.currdir = "home://".asFileHandle();
|
||||
}
|
||||
this.favo = this.find("favouri") as GUI.tag.ListViewTag;
|
||||
this.clipboard = undefined;
|
||||
this.viewType = this._api.switcher("icon", "list", "tree");
|
||||
@ -71,106 +64,107 @@ namespace OS {
|
||||
|
||||
this.view.contextmenuHandle = (e, m) => {
|
||||
const file = this.view.selectedFile;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const apps = [];
|
||||
if (file.type === "dir") {
|
||||
file.mime = "dir";
|
||||
}
|
||||
|
||||
for (let v of this._gui.appsByMime(file.mime)) {
|
||||
apps.push({
|
||||
text: v.text,
|
||||
app: v.app,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
});
|
||||
}
|
||||
let ctx_menu = [
|
||||
{
|
||||
this.mnFile(),
|
||||
];
|
||||
if(file)
|
||||
{
|
||||
ctx_menu.push(this.mnEdit());
|
||||
if (file.type === "dir") {
|
||||
file.mime = "dir";
|
||||
}
|
||||
|
||||
for (let v of this._gui.appsByMime(file.mime)) {
|
||||
apps.push({
|
||||
text: v.text,
|
||||
app: v.app,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
});
|
||||
}
|
||||
ctx_menu.unshift( {
|
||||
text: "__(Open with)",
|
||||
nodes: apps,
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
const it = e.data.item.data;
|
||||
return this._gui.launch(it.app, [file]);
|
||||
},
|
||||
},
|
||||
this.mnFile(),
|
||||
this.mnEdit(),
|
||||
];
|
||||
if(file.mime === "application/zip")
|
||||
{
|
||||
ctx_menu = ctx_menu.concat([
|
||||
{
|
||||
text: "__(Extract Here)",
|
||||
onmenuselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
API.VFS.extractZip(file.path,
|
||||
(z) => new Promise((r,e) => r(file.path.asFileHandle().parent().path)))
|
||||
.catch((err) => this.error(__("Unable to extract file"), err));
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "__(Extract to)",
|
||||
onmenuselect: async (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OS.GUI.dialogs.FileDialog.last_opened = this.currdir.path;
|
||||
const d = await this.openDialog("FileDialog", {
|
||||
title: __("Select extract destination"),
|
||||
type: "dir",
|
||||
file: file.path.replace(".zip","").asFileHandle()
|
||||
});
|
||||
const path = `${d.file.path}/${d.name}`;
|
||||
await API.VFS.mkdirAll([path]);
|
||||
await API.VFS.extractZip(file.path,
|
||||
(z) => new Promise((r,e) => r(path)));
|
||||
} catch (error) {
|
||||
this.error(__("Unable to extract file"), error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx_menu.push(
|
||||
{
|
||||
text: "__(Compress)",
|
||||
onmenuselect: async (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OS.GUI.dialogs.FileDialog.last_opened = this.currdir.path;
|
||||
const d = await this.openDialog("FileDialog", {
|
||||
title: __("Save compressed file to"),
|
||||
type: "dir",
|
||||
file: `${this.currdir.path}/${file.name}.zip`.asFileHandle()
|
||||
});
|
||||
if(d.name.trim() === "")
|
||||
{
|
||||
return this.error(__("Invalid file name"));
|
||||
});
|
||||
if(file.mime === "application/zip")
|
||||
{
|
||||
ctx_menu = ctx_menu.concat([
|
||||
{
|
||||
text: "__(Extract Here)",
|
||||
onmenuselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
API.VFS.extractZip(file.path,
|
||||
(z) => new Promise((r,e) => r(file.path.asFileHandle().parent().path)))
|
||||
.catch((err) => this.error(__("Unable to extract file"), err));
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "__(Extract to)",
|
||||
onmenuselect: async (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OS.GUI.dialogs.FileDialog.last_opened = this.currdir.path;
|
||||
const d = await this.openDialog("FileDialog", {
|
||||
title: __("Select extract destination"),
|
||||
type: "dir",
|
||||
file: file.path.replace(".zip","").asFileHandle()
|
||||
});
|
||||
const path = `${d.file.path}/${d.name}`;
|
||||
await API.VFS.mkdirAll([path]);
|
||||
await API.VFS.extractZip(file.path,
|
||||
(z) => new Promise((r,e) => r(path)));
|
||||
} catch (error) {
|
||||
this.error(__("Unable to extract file"), error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx_menu.push(
|
||||
{
|
||||
text: "__(Compress)",
|
||||
onmenuselect: async (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
OS.GUI.dialogs.FileDialog.last_opened = this.currdir.path;
|
||||
const d = await this.openDialog("FileDialog", {
|
||||
title: __("Save compressed file to"),
|
||||
type: "dir",
|
||||
file: `${this.currdir.path}/${file.name}.zip`.asFileHandle()
|
||||
});
|
||||
if(d.name.trim() === "")
|
||||
{
|
||||
return this.error(__("Invalid file name"));
|
||||
}
|
||||
const path = `${d.file.path}/${d.name}`;
|
||||
await API.VFS.mkar(file.path, path);
|
||||
this.toast(__("Archive file created: {0}",path ));
|
||||
} catch (error) {
|
||||
this.error(__("Unable to compress file, folder"), error);
|
||||
}
|
||||
const path = `${d.file.path}/${d.name}`;
|
||||
await API.VFS.mkar(file.path, path);
|
||||
this.notify(__("Archive file created: {0}",path ));
|
||||
} catch (error) {
|
||||
this.error(__("Unable to compress file, folder"), error);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
m.items = ctx_menu;
|
||||
m.nodes = ctx_menu;
|
||||
m.show(e);
|
||||
};
|
||||
|
||||
@ -185,6 +179,10 @@ namespace OS {
|
||||
};
|
||||
|
||||
this.favo.onlistselect = (e) => {
|
||||
if(this.currdir.path.startsWith(e.data.item.data.path))
|
||||
{
|
||||
return;
|
||||
}
|
||||
return this.view.path = e.data.item.data.path;
|
||||
};
|
||||
|
||||
@ -197,12 +195,6 @@ namespace OS {
|
||||
return this.view.path = p.path;
|
||||
};
|
||||
|
||||
$(this.navinput).keyup((e) => {
|
||||
if (e.keyCode === 13) {
|
||||
return this.view.path = $(this.navinput).val() as string;
|
||||
}
|
||||
}); //enter
|
||||
|
||||
this.view.fetch = (path) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let dir = path.asFileHandle();
|
||||
@ -212,65 +204,121 @@ namespace OS {
|
||||
if (d.error) {
|
||||
return reject(d.error);
|
||||
}
|
||||
if (!dir.isRoot()) {
|
||||
const p = dir.parent();
|
||||
p.filename = "[..]";
|
||||
p.type = "dir";
|
||||
d.result.unshift(p);
|
||||
}
|
||||
this.currdir = dir;
|
||||
$(this.navinput).val(dir.path);
|
||||
(this.scheme as GUI.tag.WindowTag).apptitle = dir.path;
|
||||
return resolve(d.result);
|
||||
})
|
||||
.catch((e) => reject(__e(e)));
|
||||
});
|
||||
};
|
||||
|
||||
const p_list = this.find<GUI.tag.TabBarTag>("path-nav");
|
||||
p_list.ontabselect = (e) => {
|
||||
const handle: API.VFS.BaseFileHandle = e.data.item.data.handle;
|
||||
if(this.currdir.path == handle.path)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this.view.path = handle.path;
|
||||
}
|
||||
this.view.onchdir = (e) => {
|
||||
const dir = (e.data.path as string).asFileHandle();
|
||||
this.currdir = dir;
|
||||
if(dir.genealogy)
|
||||
{
|
||||
(this.scheme as GUI.tag.WindowTag).apptitle = dir.filename;
|
||||
}
|
||||
else
|
||||
{
|
||||
(this.scheme as GUI.tag.WindowTag).apptitle = `${dir.protocol}://`;
|
||||
}
|
||||
// update the path-nav
|
||||
let base_vfs = `${dir.protocol}://`.asFileHandle();
|
||||
let segments = [{
|
||||
text: base_vfs.path,
|
||||
handle: base_vfs
|
||||
}]
|
||||
if(dir.genealogy)
|
||||
{
|
||||
|
||||
segments = segments.concat(dir.genealogy.map((e)=> {
|
||||
base_vfs = `${base_vfs.path}/${e}`.asFileHandle();
|
||||
return {
|
||||
text: e,
|
||||
handle: base_vfs
|
||||
}
|
||||
}));
|
||||
}
|
||||
p_list.items = segments;
|
||||
p_list.scroll_to_end();
|
||||
// update the current favo
|
||||
const matched = this.favo.data.map((e,i) => {
|
||||
return {
|
||||
path: e.path,
|
||||
index:i,
|
||||
}
|
||||
})
|
||||
.filter(e => {
|
||||
return dir.path.startsWith(e.path as string);
|
||||
})
|
||||
.sort((x,y) => {
|
||||
if(x.path.length < y.path.length) return 1;
|
||||
if(x == y) return 0;
|
||||
return -1;
|
||||
});
|
||||
if(matched.length != 0)
|
||||
{
|
||||
// get the longest matched
|
||||
this.favo.selected = matched[0].index;
|
||||
}
|
||||
}
|
||||
this.vfs_event_flag = true;
|
||||
this.view.ondragndrop = (e) => {
|
||||
this.view.ondragndrop = async (e) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
const src = e.data.from.data;
|
||||
const src = e.data.from;
|
||||
const des = e.data.to.data;
|
||||
if (des.type === "file") {
|
||||
return;
|
||||
}
|
||||
const file = src.path.asFileHandle();
|
||||
// ask to confirm
|
||||
const r = await this.ask({
|
||||
title: __("Move files"),
|
||||
text: __("Move selected file to {0}?", des.text)
|
||||
});
|
||||
if(!r)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// disable the vfs event on
|
||||
// we update it manually
|
||||
this.vfs_event_flag = false;
|
||||
return file
|
||||
.move(`${des.path}/${file.basename}`)
|
||||
.then(() => {
|
||||
if (this.view.view === "icon") {
|
||||
this.view.path = this.view.path;
|
||||
} else {
|
||||
this.view.update(file.parent().path);
|
||||
this.view.update(des.path);
|
||||
}
|
||||
//reenable the vfs event
|
||||
return (this.vfs_event_flag = true);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// reenable the vfs event
|
||||
this.vfs_event_flag = true;
|
||||
return this.error(
|
||||
const promises = [];
|
||||
for(const item of src)
|
||||
{
|
||||
let file = item.data.path.asFileHandle();
|
||||
promises.push(
|
||||
file.move(`${des.path}/${file.basename}`));
|
||||
}
|
||||
try{
|
||||
await Promise.all(promises);
|
||||
if (this.view.view === "tree") {
|
||||
this.view.update(src[0].data.path.asFileHandle().parent().path);
|
||||
this.view.update(des.path);
|
||||
} else {
|
||||
this.view.path = this.view.path;
|
||||
}
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
this.error(
|
||||
__(
|
||||
"Unable to move: {0} -> {1}",
|
||||
src.path,
|
||||
"Unable to move files to: {0}",
|
||||
des.path
|
||||
),
|
||||
e
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
this.vfs_event_flag = true;
|
||||
};
|
||||
|
||||
// application setting
|
||||
if (this.setting.sidebar === undefined) {
|
||||
this.setting.sidebar = true;
|
||||
}
|
||||
if (this.setting.nav === undefined) {
|
||||
this.setting.nav = true;
|
||||
}
|
||||
@ -280,37 +328,76 @@ namespace OS {
|
||||
this.applyAllSetting();
|
||||
|
||||
// VFS mount point and event
|
||||
const mntpoints = [];
|
||||
for(let v of this.systemsetting.VFS.mountpoints)
|
||||
{
|
||||
mntpoints.push({
|
||||
text: v.text,
|
||||
path: v.path,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
const mntpoints = this.systemsetting.VFS.mountpoints.map(e => {
|
||||
return {
|
||||
text: e.text,
|
||||
path: e.path,
|
||||
icon: e.icon,
|
||||
iconclass: e.iconclass,
|
||||
selected: false
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
this.favo.data = mntpoints;
|
||||
//@favo.set "selected", -1
|
||||
if (this.setting.view) {
|
||||
this.view.view = this.setting.view;
|
||||
}
|
||||
this.subscribe("VFS", (d) => {
|
||||
this.subscribe("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
|
||||
if (!this.vfs_event_flag) {
|
||||
return;
|
||||
}
|
||||
if (["read", "publish", "download"].includes(d.data.m)) {
|
||||
if (["read", "publish", "download"].includes(d.message as string)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
d.data.file.hash() === this.currdir.hash() ||
|
||||
d.data.file.parent().hash() === this.currdir.hash()
|
||||
d.u_data.hash() === this.currdir.hash() ||
|
||||
d.u_data.parent().hash() === this.currdir.hash()
|
||||
) {
|
||||
return this.view.path = this.currdir.path;
|
||||
}
|
||||
});
|
||||
|
||||
// register responsive event
|
||||
this.morphon(GUI.RESPONSIVE.MEDIUM, (fulfilled:boolean) => {
|
||||
/**
|
||||
* If the Window is bigger than medium size, then
|
||||
* we enable the side bar
|
||||
*
|
||||
* otherwise, use the top bar
|
||||
*/
|
||||
const box = this.find("container") as GUI.tag.TileLayoutTag;
|
||||
const nav = this.find("nav-bar") as GUI.tag.TileLayoutTag;
|
||||
const fav = this.find("favouri") as GUI.tag.ListViewTag;
|
||||
const resizer = this.find("resizer") as GUI.tag.ResizerTag;
|
||||
if(fulfilled)
|
||||
{
|
||||
box.name = "hbox";
|
||||
box.dir = "row";
|
||||
|
||||
nav.reversed = true;
|
||||
nav.name = "vbox";
|
||||
nav.dir = "column";
|
||||
|
||||
fav.dropdown = false;
|
||||
|
||||
resizer.dir = "row";
|
||||
resizer.disable = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
box.name = "vbox";
|
||||
box.dir = "column";
|
||||
|
||||
nav.reversed = false;
|
||||
nav.name = "hbox";
|
||||
nav.dir = "row";
|
||||
|
||||
fav.dropdown = true;
|
||||
|
||||
resizer.dir = "column";
|
||||
resizer.disable = true;
|
||||
}
|
||||
});
|
||||
|
||||
// bind keyboard shortcut
|
||||
this.bindKey("CTRL-F", () =>
|
||||
this.actionFile(`${this.name}-mkf`)
|
||||
@ -346,6 +433,14 @@ namespace OS {
|
||||
this.bindKey("CTRL-ALT-R", ()=>{
|
||||
this.view.path = this.currdir.path;
|
||||
});
|
||||
this.bindKey("CTRL-B", () => {
|
||||
if (this.currdir.isRoot()) {
|
||||
return;
|
||||
}
|
||||
const p = this.currdir.parent();
|
||||
this.favo.selected = -1;
|
||||
return this.view.path = p.path;
|
||||
});
|
||||
(this.find("btgrid") as GUI.tag.ButtonTag).onbtclick = (e) => {
|
||||
this.view.view = "icon";
|
||||
this.viewType.icon = true;
|
||||
@ -355,6 +450,28 @@ namespace OS {
|
||||
this.view.view = "list";
|
||||
this.viewType.list = true;
|
||||
};
|
||||
// enable or disable multi-select by CTRL key
|
||||
$(this.scheme).on("keydown", (evt)=>{
|
||||
if(evt.ctrlKey && evt.which == 17)
|
||||
{
|
||||
this.view.multiselect = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
this.view.multiselect = false;
|
||||
}
|
||||
});
|
||||
$(this.scheme).on("keyup", (evt)=>{
|
||||
if(!evt.ctrlKey)
|
||||
{
|
||||
this.view.multiselect = false;
|
||||
}
|
||||
});
|
||||
if (this.args && this.args.length > 0) {
|
||||
this.currdir = this.args[0].path.asFileHandle();
|
||||
} else {
|
||||
this.currdir = "home://".asFileHandle();
|
||||
}
|
||||
this.view.path = this.currdir.path;
|
||||
}
|
||||
|
||||
@ -365,13 +482,10 @@ namespace OS {
|
||||
return this.view.showhidden = this.setting.showhidden;
|
||||
case "nav":
|
||||
return this.toggleNav(this.setting.nav);
|
||||
case "sidebar":
|
||||
return this.toggleSidebar(this.setting.sidebar);
|
||||
}
|
||||
}
|
||||
|
||||
private mnFile(): GUI.BasicItemType{
|
||||
//console.log file
|
||||
const arr: GUI.BasicItemType = {
|
||||
text: "__(File)",
|
||||
nodes: [
|
||||
@ -405,7 +519,7 @@ namespace OS {
|
||||
shortcut: "C-I",
|
||||
},
|
||||
],
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) =>
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) =>
|
||||
this.actionFile(e.data.item.data.dataid),
|
||||
};
|
||||
return arr;
|
||||
@ -440,7 +554,7 @@ namespace OS {
|
||||
shortcut: "C-P",
|
||||
},
|
||||
],
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) =>
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) =>
|
||||
this.actionEdit(e.data.item.data.dataid),
|
||||
};
|
||||
}
|
||||
@ -451,17 +565,15 @@ namespace OS {
|
||||
{
|
||||
text: "__(View)",
|
||||
nodes: [
|
||||
{
|
||||
text: "__(Toggle responsive)",
|
||||
dataid: `${this.name}-responsive`,
|
||||
},
|
||||
{
|
||||
text: "__(Refresh)",
|
||||
dataid: `${this.name}-refresh`,
|
||||
shortcut: "C-A-R"
|
||||
},
|
||||
{
|
||||
text: "__(Sidebar)",
|
||||
switch: true,
|
||||
checked: this.setting.sidebar,
|
||||
dataid: `${this.name}-side`,
|
||||
},
|
||||
{
|
||||
text: "__(Navigation bar)",
|
||||
switch: true,
|
||||
@ -499,28 +611,19 @@ namespace OS {
|
||||
type: "tree",
|
||||
},
|
||||
],
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
|
||||
const { type } = e.data.item.data;
|
||||
this.view.view = type;
|
||||
return (this.viewType[type] = true);
|
||||
},
|
||||
},
|
||||
],
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => this.actionView(e),
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => this.actionView(e),
|
||||
},
|
||||
];
|
||||
return menu;
|
||||
}
|
||||
|
||||
private toggleSidebar(b: boolean): void {
|
||||
if (b) {
|
||||
$(this.favo).show();
|
||||
} else {
|
||||
$(this.favo).hide();
|
||||
}
|
||||
return this.trigger("resize");
|
||||
}
|
||||
|
||||
private toggleNav(b: boolean): void {
|
||||
if (b) {
|
||||
$(this.navbar).show();
|
||||
@ -530,22 +633,22 @@ namespace OS {
|
||||
return this.trigger("resize");
|
||||
}
|
||||
|
||||
private actionView(e: GUI.TagEventType<GUI.tag.MenuEventData>): void{
|
||||
private actionView(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void{
|
||||
const data = e.data.item.data;
|
||||
switch (data.dataid) {
|
||||
case `${this.name}-hidden`:
|
||||
//@.view.set "showhidden", e.item.data.checked
|
||||
return this.registry("showhidden", data.checked);
|
||||
this.setting.showhidden = data.checked;
|
||||
//@.setting.showhidden = e.item.data.checked
|
||||
case `${this.name}-refresh`:
|
||||
this.view.path = this.currdir.path;
|
||||
return;
|
||||
case `${this.name}-side`:
|
||||
return this.registry("sidebar", data.checked);
|
||||
//@setting.sidebar = e.item.data.checked
|
||||
//@toggleSidebar e.item.data.checked
|
||||
case `${this.name}-responsive`:
|
||||
const win = (this.scheme as GUI.tag.WindowTag);
|
||||
win.responsive = !win.responsive;
|
||||
return;
|
||||
case `${this.name}-nav`:
|
||||
return this.registry("nav", data.checked);
|
||||
this.setting.nav = data.checked;
|
||||
}
|
||||
}
|
||||
//@setting.nav = e.item.data.checked
|
||||
@ -585,20 +688,22 @@ namespace OS {
|
||||
title: "__(Delete)",
|
||||
iconclass: "fa fa-question-circle",
|
||||
text: __(
|
||||
"Do you really want to delete: {0}?",
|
||||
file.filename
|
||||
"Do you really want to delete selected files?"
|
||||
),
|
||||
}).then(async (d) => {
|
||||
if (!d) {
|
||||
return;
|
||||
}
|
||||
const promises = [];
|
||||
for(const f of this.view.selectedFiles)
|
||||
{
|
||||
promises.push(f.path.asFileHandle().remove());
|
||||
}
|
||||
try {
|
||||
return file.path
|
||||
.asFileHandle()
|
||||
.remove();
|
||||
await Promise.all(promises);
|
||||
}
|
||||
catch (e) {
|
||||
return this.error(__("Fail to delete: {0}", file.path), e);
|
||||
return this.error(__("Fail to delete selected files"), e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -609,9 +714,9 @@ namespace OS {
|
||||
}
|
||||
this.clipboard = {
|
||||
cut: true,
|
||||
file: file.path.asFileHandle(),
|
||||
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
|
||||
};
|
||||
return this.notify(__("File {0} cut", file.filename));
|
||||
return this.toast(__("{0} files cut", this.clipboard.files.length));
|
||||
|
||||
case `${this.name}-copy`:
|
||||
if (!file) {
|
||||
@ -619,10 +724,10 @@ namespace OS {
|
||||
}
|
||||
this.clipboard = {
|
||||
cut: false,
|
||||
file: file.path.asFileHandle(),
|
||||
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
|
||||
};
|
||||
return this.notify(
|
||||
__("File {0} copied", file.filename)
|
||||
return this.toast(
|
||||
__("{0} files copied", this.clipboard.files.length)
|
||||
);
|
||||
|
||||
case `${this.name}-paste`:
|
||||
@ -630,29 +735,33 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
if (this.clipboard.cut) {
|
||||
this.clipboard.file
|
||||
.move(
|
||||
`${this.currdir.path}/${this.clipboard.file.basename}`
|
||||
)
|
||||
const promises = [];
|
||||
for(const file of this.clipboard.files)
|
||||
{
|
||||
promises.push(file.move(
|
||||
`${this.currdir.path}/${file.basename}`
|
||||
));
|
||||
}
|
||||
Promise.all(promises)
|
||||
.then((r) => {
|
||||
return (this.clipboard = undefined);
|
||||
})
|
||||
.catch((e) => {
|
||||
return this.error(
|
||||
__(
|
||||
"Fail to paste: {0}",
|
||||
this.clipboard.file.path
|
||||
"Fail to paste to: {0}",
|
||||
this.currdir.path
|
||||
),
|
||||
e
|
||||
);
|
||||
});
|
||||
} else {
|
||||
API.VFS.copy([this.clipboard.file.path],this.currdir.path)
|
||||
API.VFS.copy(this.clipboard.files.map(x => x.path),this.currdir.path)
|
||||
.then(() => {
|
||||
return (this.clipboard = undefined);
|
||||
})
|
||||
.catch((e) => {
|
||||
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
|
||||
return this.error(__("Fail to paste to: {0}", this.currdir.path), e);
|
||||
});
|
||||
}
|
||||
break;
|
||||
@ -716,8 +825,7 @@ namespace OS {
|
||||
.publish()
|
||||
.then((r) => {
|
||||
return this.notify(
|
||||
__("Shared url: {0}", r.result)
|
||||
);
|
||||
__("Shared url: {0}", r.result));
|
||||
})
|
||||
.catch((e) => {
|
||||
return this.error(
|
||||
@ -727,7 +835,7 @@ namespace OS {
|
||||
});
|
||||
break;
|
||||
case `${this.name}-download`:
|
||||
if (file.type !== "file") {
|
||||
if (!file || file.type !== "file") {
|
||||
return;
|
||||
}
|
||||
file.path
|
||||
|
@ -6,7 +6,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.3-a",
|
||||
"version":"0.1.11-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-hdd-o",
|
||||
"mimes":["dir"],
|
||||
|
@ -1,19 +1,17 @@
|
||||
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="400">
|
||||
<afx-vbox>
|
||||
<afx-hbox data-height = "23" data-id = "nav-bar">
|
||||
<afx-button data-width = "23" data-id = "btback" iconclass = "fa fa-arrow-left"></afx-button>
|
||||
<input type = "text" data-id = "navinput"></input>
|
||||
<div data-width = "2"></div>
|
||||
<afx-button data-width = "23" data-id = "btgrid" iconclass = "fa fa-th"></afx-button>
|
||||
<div data-width = "2"></div>
|
||||
<afx-button data-width = "23" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox>
|
||||
<afx-list-view data-id = "favouri" data-width = "150" min-width="100">
|
||||
</afx-list-view>
|
||||
<afx-resizer data-width = "3" ></afx-resizer>
|
||||
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="500" responsive="true">
|
||||
<afx-tile data-id="container" name="vbox" dir="column">
|
||||
<afx-tile data-height = "35" min-width="120" data-width="180" data-id = "nav-bar" name="hbox" dir="row">
|
||||
<afx-list-view data-id = "favouri" dropdown="true" focusable="true"></afx-list-view>
|
||||
</afx-tile>
|
||||
<afx-resizer data-id="resizer" dir="column" disable="true" data-width="3" data-height="0"></afx-resizer>
|
||||
<afx-vbox>
|
||||
<afx-hbox data-width="120" data-height="35" data-id="nav-bar">
|
||||
<afx-tab-bar data-id = "path-nav" dir="row" ></afx-tab-bar>
|
||||
<afx-button data-width = "40" data-id = "btback" iconclass = "fa fa-arrow-up"></afx-button>
|
||||
<afx-button data-width = "40" data-id = "btgrid" iconclass = "fa fa-th"></afx-button>
|
||||
<afx-button data-width = "40" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
|
||||
</afx-hbox>
|
||||
<afx-file-view data-id = "fileview"></afx-file-view>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-vbox>
|
||||
</afx-tile>
|
||||
</afx-app-window>
|
@ -4,7 +4,7 @@ libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = scheme.html package.json
|
||||
copyfiles = scheme.html package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=MarketPlace
|
||||
|
11
src/packages/MarketPlace/README.md
Normal file
11
src/packages/MarketPlace/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# Market Place
|
||||
|
||||
AntOS original application store.
|
||||
This application is icluded in the AntOS delivery
|
||||
and cannot be removed/uinstalled by regular user
|
||||
|
||||
## Change logs
|
||||
- 0.2.8-b: use system css variables in theme
|
||||
- 0.2.7-b: only launch application
|
||||
- 0.2.6-b: improve install process
|
||||
- v0.2.5-b: add README.md
|
@ -5,23 +5,7 @@ afx-app-window[data-id="marketplace-win"] afx-list-view[data-id='repo'] div.list
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] div[data-id="desc-container"]{
|
||||
overflow-y: auto;
|
||||
overflow-x: none;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] afx-hbox {
|
||||
padding-left: 10px;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-label[data-id='appname'] i.label-text{
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] div[data-id='appname']:before {
|
||||
content: "\f085";
|
||||
font-family: "FontAwesome";
|
||||
font-size: 25px;
|
||||
font-style: normal;
|
||||
margin-right: 10px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] p[data-id='app-desc'] {
|
||||
text-align: justify;
|
||||
@ -38,9 +22,11 @@ afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] {
|
||||
padding-left:10px;
|
||||
display: table;
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] {
|
||||
border-bottom: 1px solid #afafaf;
|
||||
afx-app-window[data-id="marketplace-win"] afx-tab-bar {
|
||||
border-top: 1px solid var(--border-quaternary);
|
||||
border-bottom: 1px solid var(--border-quaternary);
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] input{
|
||||
border: 0;
|
||||
@ -48,13 +34,14 @@ afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] i
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] div[data-id="searchicon"]:before{
|
||||
content: "\f002";
|
||||
display: block;
|
||||
background-color:transparent;
|
||||
color:#afafaf;
|
||||
color: var(--text-disable);
|
||||
font-family: "FontAwesome";
|
||||
padding-top: 3px;
|
||||
padding-left:3px;
|
||||
/* font-size: 25px; */
|
||||
padding-left:5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] li{
|
||||
padding:0;
|
||||
@ -70,9 +57,13 @@ afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] span{
|
||||
afx-app-window[data-id="marketplace-win"] span.info-header{
|
||||
font-weight: bold;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] span.info-detail
|
||||
{
|
||||
word-break: break-word;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-label[data-id="vstat"] {
|
||||
font-size: 11px;
|
||||
color: chocolate;
|
||||
color: var(--text-warning);
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] p.stat {
|
||||
margin: 0;
|
||||
@ -82,4 +73,78 @@ afx-app-window[data-id="marketplace-win"] p.stat {
|
||||
afx-app-window[data-id = "repository-dialog-win"] afx-list-view[data-id="repo-list"] ul.complex-content > li{
|
||||
font-size: 11px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] afx-tab-bar[data-id="catlist"] i.label-text
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container {
|
||||
margin: 0 auto;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul
|
||||
{
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul li{
|
||||
/*display: block;*/
|
||||
width: 90px;
|
||||
text-align: center;
|
||||
font-size: 40px;
|
||||
/*justify-content: normal !important;*/
|
||||
}
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] i{
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] i.label-text{
|
||||
word-break: break-word;
|
||||
font-size: 14px;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] i.icon-style {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] afx-label span
|
||||
{
|
||||
flex-direction: column;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul li:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"]> div.list-container > ul .afx-list-item li
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul .afx-list-item li:hover
|
||||
{
|
||||
background-color: var(--item-bg-hover);
|
||||
color: var(--text-primary);
|
||||
border-radius: 10px;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul .afx-list-item li.selected
|
||||
{
|
||||
background-color: var(--item-bg-active);
|
||||
color:var(--text-tertiary);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="app-header"] afx-button button
|
||||
{
|
||||
border-radius: 0;
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="app-header"]
|
||||
{
|
||||
border-radius: 0;
|
||||
border-bottom: 1px solid var(--border-quaternary);
|
||||
}
|
||||
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="app-header"] afx-button[data-id="appname"] i.label-text
|
||||
{
|
||||
font-weight: bold;
|
||||
}
|
@ -24,9 +24,9 @@ namespace OS {
|
||||
private installdir: string;
|
||||
private apps_meta: GenericObject<any>;
|
||||
private applist: GUI.tag.ListViewTag;
|
||||
private catlist: GUI.tag.ListViewTag;
|
||||
private catlist: GUI.tag.TabBarTag;
|
||||
private container: GUI.tag.VBoxTag;
|
||||
private appname: GUI.tag.LabelTag;
|
||||
private appname: GUI.tag.ButtonTag;
|
||||
private appdetail: HTMLUListElement;
|
||||
private appdesc: HTMLParagraphElement;
|
||||
private btinstall: GUI.tag.ButtonTag;
|
||||
@ -39,21 +39,37 @@ namespace OS {
|
||||
}
|
||||
|
||||
main(): void {
|
||||
const stack_panel = this.find("stack-panel") as GUI.tag.StackPanelTag;
|
||||
this.container = this.find("container") as GUI.tag.VBoxTag;
|
||||
this.appname = this.find("appname") as GUI.tag.ButtonTag;
|
||||
this.appdesc = this.find("app-desc") as HTMLParagraphElement;
|
||||
this.appdetail = this.find("app-detail") as HTMLUListElement;
|
||||
this.btinstall = this.find("bt-install") as GUI.tag.ButtonTag;
|
||||
this.btremove = this.find("bt-remove") as GUI.tag.ButtonTag;
|
||||
this.btexec = this.find("bt-exec") as GUI.tag.ButtonTag;
|
||||
this.searchbox = this.find("searchbox") as HTMLInputElement;
|
||||
|
||||
this.appname.onbtclick = (_) => {
|
||||
this.applist.selected = -1;
|
||||
stack_panel.navigateBack();
|
||||
}
|
||||
this.installdir = this.systemsetting.system.pkgpaths.user;
|
||||
// test repository
|
||||
this.apps_meta = [];
|
||||
this.apps_meta = {};
|
||||
|
||||
this.applist = this.find("applist") as GUI.tag.ListViewTag;
|
||||
this.catlist = this.find("catlist") as GUI.tag.ListViewTag;
|
||||
this.catlist = this.find("catlist") as GUI.tag.TabBarTag;
|
||||
this.applist.onlistselect = (e) => {
|
||||
const data = e.data.item.data;
|
||||
return this.appDetail(data);
|
||||
this.appDetail(data);
|
||||
stack_panel.navigateNext();
|
||||
};
|
||||
|
||||
this.catlist.onlistselect = (e) => {
|
||||
const selected = this.catlist.selected;
|
||||
this.catlist.ontabselect = (e) => {
|
||||
const selected = this.catlist.selected as number;
|
||||
if(selected < 0)
|
||||
return;
|
||||
|
||||
if(selected === 0)
|
||||
{
|
||||
return this.resetAppList();
|
||||
@ -93,14 +109,7 @@ namespace OS {
|
||||
this.applist.data = result;
|
||||
};
|
||||
|
||||
this.container = this.find("container") as GUI.tag.VBoxTag;
|
||||
this.appname = this.find("appname") as GUI.tag.LabelTag;
|
||||
this.appdesc = this.find("app-desc") as HTMLParagraphElement;
|
||||
this.appdetail = this.find("app-detail") as HTMLUListElement;
|
||||
this.btinstall = this.find("bt-install") as GUI.tag.ButtonTag;
|
||||
this.btremove = this.find("bt-remove") as GUI.tag.ButtonTag;
|
||||
this.btexec = this.find("bt-exec") as GUI.tag.ButtonTag;
|
||||
this.searchbox = this.find("searchbox") as HTMLInputElement;
|
||||
|
||||
$(this.container).css("visibility", "hidden");
|
||||
this.btexec.onbtclick = (_e) => {
|
||||
const el = this.applist.selectedItem;
|
||||
@ -108,8 +117,8 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
const app = el.data;
|
||||
if (app.pkgname) {
|
||||
return this._gui.launch(app.pkgname, []);
|
||||
if (app.app) {
|
||||
return this._gui.launch(app.app, []);
|
||||
}
|
||||
};
|
||||
|
||||
@ -117,10 +126,10 @@ namespace OS {
|
||||
try {
|
||||
if (this.btinstall.data.dirty) {
|
||||
await this.updatePackage();
|
||||
return this.notify(__("Package updated"));
|
||||
return this.toast(__("Package updated"));
|
||||
}
|
||||
const n = await this.remoteInstall();
|
||||
return this.notify(__("Package installed: {0}", n));
|
||||
return this.toast(__("Package installed: {0}", n));
|
||||
} catch (error) {
|
||||
return this.error(error.toString(), error);
|
||||
}
|
||||
@ -129,7 +138,7 @@ namespace OS {
|
||||
this.btremove.onbtclick = async () => {
|
||||
try {
|
||||
await this.uninstall();
|
||||
return this.notify(__("Packaged uninstalled"));
|
||||
return this.toast(__("Packaged uninstalled"));
|
||||
} catch (e) {
|
||||
return this.error(e.toString(), e);
|
||||
}
|
||||
@ -173,14 +182,18 @@ namespace OS {
|
||||
switch (e.which) {
|
||||
case 37:
|
||||
return e.preventDefault();
|
||||
/*
|
||||
case 38:
|
||||
this.applist.selectPrev();
|
||||
return e.preventDefault();
|
||||
*/
|
||||
case 39:
|
||||
return e.preventDefault();
|
||||
/*
|
||||
case 40:
|
||||
this.applist.selectNext();
|
||||
return e.preventDefault();
|
||||
*/
|
||||
case 13:
|
||||
return e.preventDefault();
|
||||
default:
|
||||
@ -273,6 +286,7 @@ namespace OS {
|
||||
if (this.apps_meta[name]) {
|
||||
pkg.icon = this.apps_meta[name].icon;
|
||||
pkg.iconclass = this.apps_meta[name].iconclass;
|
||||
pkg.app = this.apps_meta[name].app;
|
||||
}
|
||||
this.apps_meta[name] = pkg;
|
||||
}
|
||||
@ -325,10 +339,28 @@ namespace OS {
|
||||
iconclass: "bi bi-gear-wide"
|
||||
});
|
||||
});
|
||||
this.catlist.data = cat_list_data;
|
||||
this.catlist.items = cat_list_data;
|
||||
this.catlist.selected = 0;
|
||||
}
|
||||
|
||||
private add_meta_from(k:string, v: API.PackageMetaType)
|
||||
{
|
||||
const mt = {
|
||||
pkgname: v.pkgname ? v.pkgname : v.app,
|
||||
app: v.app,
|
||||
name: v.name,
|
||||
text: `${v.name} ${v.version}`,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: `${v.path}/README.md`,
|
||||
dependencies: v.dependencies ? Array.from(v.dependencies) : [],
|
||||
dependBy: []
|
||||
};
|
||||
this.apps_meta[`${k}@${v.version}`] = mt;
|
||||
return mt;
|
||||
}
|
||||
fetchApps(): Promise<GenericObject<any>> {
|
||||
return new Promise((resolve, _reject) => {
|
||||
let v: API.PackageMetaType;
|
||||
@ -336,19 +368,7 @@ namespace OS {
|
||||
const pkgcache = this.systemsetting.system.packages;
|
||||
for (let k in pkgcache) {
|
||||
v = pkgcache[k];
|
||||
this.apps_meta[`${k}@${v.version}`] = {
|
||||
pkgname: v.pkgname ? v.pkgname : v.app,
|
||||
name: v.name,
|
||||
text: `${v.name} ${v.version}`,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: `${v.path}/README.md`,
|
||||
dependencies: v.dependencies ? Array.from(v.dependencies) : [],
|
||||
dependBy: []
|
||||
};
|
||||
this.add_meta_from(k,v);
|
||||
}
|
||||
|
||||
const list: string[] = []
|
||||
@ -368,6 +388,8 @@ namespace OS {
|
||||
this.appname.text = d.name;
|
||||
const status = this.find("vstat") as GUI.tag.LabelTag;
|
||||
status.text = "";
|
||||
$(this.appdesc).empty();
|
||||
$(this.appdetail).empty();
|
||||
if (d.description) {
|
||||
d.description
|
||||
.asFileHandle()
|
||||
@ -379,13 +401,11 @@ namespace OS {
|
||||
);
|
||||
})
|
||||
.catch((_e) => {
|
||||
this.notify(
|
||||
__("Unable to read package description")
|
||||
this.error(
|
||||
__("Unable to read package description"), _e
|
||||
);
|
||||
return $(this.appdesc).empty();
|
||||
});
|
||||
} else {
|
||||
$(this.appdesc).empty();
|
||||
}
|
||||
const pkgcache = this.systemsetting.system.packages;
|
||||
this.btinstall.text = "__(Install)";
|
||||
@ -416,7 +436,6 @@ namespace OS {
|
||||
$(this.btexec).hide();
|
||||
}
|
||||
|
||||
$(this.appdetail).empty();
|
||||
for (let k in d) {
|
||||
const v = d[k];
|
||||
if (k !== "name" && k !== "description" && k !== "domel") {
|
||||
@ -425,7 +444,7 @@ namespace OS {
|
||||
.append(
|
||||
$("<span class= 'info-header'>").html(k)
|
||||
)
|
||||
.append($("<span>").html(v))
|
||||
.append($("<span class = 'info-detail'>").html(v))
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -448,7 +467,7 @@ namespace OS {
|
||||
},
|
||||
],
|
||||
onchildselect: (
|
||||
e: GUI.TagEventType<GUI.tag.MenuEventData>
|
||||
e: GUI.TagEventType<GUI.tag.StackMenuEventData>
|
||||
) => {
|
||||
return this.menuOptionsHandle(e.data.item.data.id);
|
||||
},
|
||||
@ -468,7 +487,7 @@ namespace OS {
|
||||
case "install":
|
||||
this.localInstall()
|
||||
.then((n) => {
|
||||
return this.notify(
|
||||
return this.toast(
|
||||
__("Package installed: {0}", n)
|
||||
);
|
||||
})
|
||||
@ -551,7 +570,8 @@ namespace OS {
|
||||
return reject(this._api.throwe(__("Unable to find package: {0}", pkgname)));
|
||||
}
|
||||
try {
|
||||
const n = await this.install(meta.download + "?_=" + new Date().getTime(), meta);
|
||||
const mt = await this.install(meta.download + "?_=" + new Date().getTime(), meta);
|
||||
meta.app = mt.app;
|
||||
return resolve(meta);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
@ -581,7 +601,7 @@ namespace OS {
|
||||
const dep = this.checkDependencies(pkgname);
|
||||
if (dep.notfound.size != 0) {
|
||||
this.openDialog("TextDialog", {
|
||||
disable: true,
|
||||
disable: false,
|
||||
title: __("Unresolved dependencies"),
|
||||
value: __(
|
||||
"Unable to install: The package `{0}` depends on these packages, but they are not found:\n{1}",
|
||||
@ -621,13 +641,19 @@ namespace OS {
|
||||
mimes: [".*/zip"],
|
||||
});
|
||||
const n = await this.install(d.file.path);
|
||||
const name = n.pkgname?n.pkgname:n.app;
|
||||
const apps = this.applist.data.map(
|
||||
(v) => v.pkgname
|
||||
);
|
||||
const idx = apps.indexOf(n);
|
||||
const idx = apps.indexOf(name);
|
||||
if (idx >= 0) {
|
||||
this.applist.selected = idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
const mt = this.add_meta_from(name,n);
|
||||
this.appDetail(mt);
|
||||
}
|
||||
return resolve(n.name);
|
||||
} catch (error) {
|
||||
reject(__e(error));
|
||||
@ -638,7 +664,7 @@ namespace OS {
|
||||
private install(
|
||||
zfile: string,
|
||||
meta?: GenericObject<any>
|
||||
): Promise<GenericObject<any>> {
|
||||
): Promise<API.PackageMetaType> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
let v: API.PackageMetaType;
|
||||
@ -657,22 +683,6 @@ namespace OS {
|
||||
});
|
||||
|
||||
});
|
||||
const app_meta = {
|
||||
pkgname: v.pkgname ? v.pkgname : v.app,
|
||||
name: v.name,
|
||||
text: v.name,
|
||||
icon: v.icon,
|
||||
iconclass: v.iconclass,
|
||||
category: v.category,
|
||||
author: v.info.author,
|
||||
version: v.version,
|
||||
description: meta
|
||||
? meta.description
|
||||
: undefined,
|
||||
download: meta
|
||||
? meta.download
|
||||
: undefined,
|
||||
};
|
||||
v.text = v.name;
|
||||
v.filename = v.pkgname ? v.pkgname : v.app;
|
||||
v.type = "app";
|
||||
@ -684,11 +694,15 @@ namespace OS {
|
||||
v.iconclass =
|
||||
"fa fa-adn";
|
||||
}
|
||||
if(v.icon)
|
||||
{
|
||||
v.icon = `${pth}/${v.icon}`;
|
||||
}
|
||||
v.path = pth;
|
||||
this.systemsetting.system.packages[
|
||||
v.pkgname ? v.pkgname : v.app
|
||||
] = v;
|
||||
return resolve(app_meta);
|
||||
return resolve(v);
|
||||
} catch (error) {
|
||||
reject(__e(error));
|
||||
}
|
||||
@ -704,22 +718,23 @@ namespace OS {
|
||||
private uninstallPkg(pkgname: string): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const meta = this.apps_meta[pkgname];
|
||||
if (!meta) {
|
||||
return reject(this._api.throwe(__("Unable to find application meta-data: {0}", pkgname)));
|
||||
}
|
||||
const app = this.systemsetting.system.packages[meta.pkgname];
|
||||
if (!app) {
|
||||
return reject(this._api.throwe(__("Application {0} is not installed", pkgname)));
|
||||
}
|
||||
|
||||
// got the app meta
|
||||
try {
|
||||
if (!meta) {
|
||||
throw __("Unable to find application meta-data: {0}", pkgname).__();
|
||||
}
|
||||
const app = this.systemsetting.system.packages[meta.pkgname];
|
||||
if (!app) {
|
||||
throw __("Application {0} is not installed", pkgname).__();
|
||||
}
|
||||
const r = await app.path
|
||||
.asFileHandle()
|
||||
.remove();
|
||||
if (r.error) {
|
||||
return reject(this._api.throwe(__("Cannot uninstall package: {0}", r.error)));
|
||||
throw __("Cannot uninstall package: {0}", r.error).__();
|
||||
}
|
||||
this.notify(__("Package uninstalled"));
|
||||
this.toast(__("Package uninstalled"));
|
||||
// stop all the services if any
|
||||
if (app.services) {
|
||||
for (let srv of Array.from(app.services)) {
|
||||
@ -727,7 +742,7 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
delete this.systemsetting.system.packages[meta.pkgname];
|
||||
this._gui.unloadApp(meta.pkgname);
|
||||
this._gui.unloadApp(meta.pkgname, true);
|
||||
if (meta.download) {
|
||||
this.appDetail(meta);
|
||||
}
|
||||
@ -735,6 +750,7 @@ namespace OS {
|
||||
if (meta.domel)
|
||||
this.applist.delete(meta.domel);
|
||||
$(this.container).css("visibility", "hidden");
|
||||
delete this.apps_meta[pkgname];
|
||||
}
|
||||
return resolve(meta);
|
||||
}
|
||||
@ -771,7 +787,7 @@ namespace OS {
|
||||
}
|
||||
this.bulkUninstall([...dep.uninstall])
|
||||
.then((_b) => {
|
||||
this.notify(__("Uninstall successfully"));
|
||||
this.toast(__("Uninstall successfully"));
|
||||
})
|
||||
.catch((err) => {
|
||||
this.error(__("Unable to uninstall package(s): {0}", err.toString()), err);
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.2.4-a",
|
||||
"version":"0.2.8-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-shopping-bag",
|
||||
"mimes":["none"],
|
||||
|
@ -1,31 +1,39 @@
|
||||
<afx-app-window data-id = "marketplace-win" apptitle="MarketPlace" width="650" height="400">
|
||||
<afx-hbox >
|
||||
<afx-vbox data-width = "350">
|
||||
<afx-hbox data-height= "23" data-id="search-container">
|
||||
<div data-width="17" data-id="searchicon"></div>
|
||||
<input data-id = "searchbox" ></input>
|
||||
</afx-hbox>
|
||||
<afx-hbox>
|
||||
<afx-list-view data-id = "catlist" dropdown = "false" data-width="35%"></afx-list-view>
|
||||
<afx-resizer data-width = "3" ></afx-resizer>
|
||||
<afx-list-view data-id = "applist" dropdown = "false" ></afx-list-view>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<afx-resizer data-width = "3" ></afx-resizer>
|
||||
<afx-vbox data-id = "container">
|
||||
<afx-label data-id = "appname" data-height = "25"></afx-label>
|
||||
<afx-hbox data-height = "50">
|
||||
<div style = "text-align:left;">
|
||||
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button>
|
||||
<afx-button data-id = "bt-exec" text = "__(Launch)"></afx-button>
|
||||
<afx-button data-id = "bt-install" text = "__(Install)" ></afx-button>
|
||||
<p class="stat"><afx-label data-id="vstat"></afx-label></p>
|
||||
</div>
|
||||
<afx-app-window data-id = "marketplace-win" apptitle="MarketPlace" width="650" height="500">
|
||||
<afx-vbox >
|
||||
<afx-stack-panel data-id = "stack-panel" dir = "column" tabbarheight= "40">
|
||||
|
||||
<afx-vbox>
|
||||
<afx-hbox data-height= "30" data-id="search-container">
|
||||
<div data-width="17" data-id="searchicon"></div>
|
||||
<input data-id = "searchbox" ></input>
|
||||
</afx-hbox>
|
||||
<div data-id="desc-container">
|
||||
<p data-id = "app-desc"></p>
|
||||
<ul data-id = "app-detail"></ul>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
<afx-tab-bar data-id = "catlist" data-height="45"></afx-tab-bar>
|
||||
<afx-list-view data-id = "applist" dropdown = "false"></afx-list-view>
|
||||
</afx-vbox>
|
||||
|
||||
<afx-hbox>
|
||||
|
||||
<afx-vbox data-id = "container">
|
||||
<afx-hbox data-height = "35" data-id="app-header">
|
||||
<afx-button data-id = "appname" iconclass = "bi bi-backspace-fill"></afx-button>
|
||||
<div style = "display: flex;justify-content: flex-end;">
|
||||
<afx-button data-id = "bt-remove" text = "__(Uninstall)" iconclass = "bi bi-trash-fill"></afx-button>
|
||||
<afx-button data-id = "bt-exec" text = "__(Launch)" iconclass = "fa fa-cog"></afx-button>
|
||||
<afx-button data-id = "bt-install" text = "__(Install)" iconclass = "bi bi-cloud-download-fill" ></afx-button>
|
||||
</div>
|
||||
</afx-hbox>
|
||||
<p class="stat" data-height="15"><afx-label data-id="vstat"></afx-label></p>
|
||||
<div data-id="desc-container">
|
||||
<p data-id = "app-desc"></p>
|
||||
<ul data-id = "app-detail"></ul>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
|
||||
</afx-stack-panel>
|
||||
|
||||
|
||||
|
||||
|
||||
</afx-hbox>
|
||||
</afx-app-window>
|
@ -4,3 +4,7 @@ Defaut text editor which is included in each AntOS release.
|
||||
It has very barebone features: open/edit/save text file.
|
||||
|
||||
Text/Code editor with fancy features can be optionally installed via the Market Place
|
||||
|
||||
## Change logs
|
||||
- v0.1.2-b: use system css variables in theme
|
||||
- v0.1.1-b: update README
|
||||
|
@ -3,5 +3,5 @@ afx-app-window[data-id="NotePad"] textarea[data-id="editor"]
|
||||
margin: 0;
|
||||
padding:10px;
|
||||
border: 0;
|
||||
background-color: transparent;
|
||||
background-color: var(--background-tertiary);
|
||||
}
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@iohub.dev"
|
||||
},
|
||||
"version": "0.1.0-b",
|
||||
"version": "0.1.2-b",
|
||||
"category": "Utility",
|
||||
"iconclass": "bi bi-pen",
|
||||
"mimes": [
|
||||
|
@ -89,7 +89,7 @@ namespace OS {
|
||||
|
||||
this.applist.buttons = [
|
||||
{
|
||||
text: "+",
|
||||
iconclass: "bi bi-plus",
|
||||
onbtclick: () => {
|
||||
const apps = (() => {
|
||||
const result = [];
|
||||
@ -101,6 +101,7 @@ namespace OS {
|
||||
text: v.name,
|
||||
app: k,
|
||||
iconclass: v.iconclass,
|
||||
icon: v.icon
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -127,7 +128,7 @@ namespace OS {
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
iconclass: "bi bi-dash",
|
||||
onbtclick: () => {
|
||||
const item = this.applist.selectedItem;
|
||||
if (!item) {
|
||||
@ -160,7 +161,7 @@ namespace OS {
|
||||
}
|
||||
return result1;
|
||||
})();
|
||||
announcer.ostrigger("app-pinned", this.applist.data);
|
||||
announcer.ostrigger("APP-PINNED", "APP-PINNED", this.applist.data);
|
||||
}
|
||||
}
|
||||
App.AppAndServiceHandle = AppAndServiceHandle;
|
||||
|
@ -67,7 +67,7 @@ namespace OS {
|
||||
|
||||
this.wplist.buttons = [
|
||||
{
|
||||
text: "+",
|
||||
iconclass: "bi bi-plus",
|
||||
onbtclick: (e) => {
|
||||
return this.parent
|
||||
.openDialog("FileDialog", {
|
||||
|
@ -1,10 +1,10 @@
|
||||
module_files = main.js AppearanceHandle.js AppAndServiceHandle.js VFSHandle.js LocaleHandle.js StartupHandle.js
|
||||
module_files = main.js AppearanceHandle.js AppAndServiceHandle.js VFSHandle.js LocaleHandle.js StartupHandle.js VersionsHandle.js
|
||||
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = scheme.html package.json
|
||||
copyfiles = scheme.html package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=Setting
|
||||
|
16
src/packages/Setting/README.md
Normal file
16
src/packages/Setting/README.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Setting: AntOS system setting and configuration
|
||||
|
||||
GUI based system setting application for AntOS.
|
||||
|
||||
In-depth system settings can be found and modified in .settings.json file stored in HOME directory
|
||||
of current user.
|
||||
|
||||
CAUTION:without using the Setting application, users can modify .settings.json with their own risk.
|
||||
In case of system anormaly after the modification, the system settings can be reset to default
|
||||
by simply removing the setting file
|
||||
|
||||
## Change logs
|
||||
- v0.1.5-b: fix VFS setting dialog bugs
|
||||
- v0.1.4-b: use system css variables in theme
|
||||
- v0.1.2-b: minor bug fix on UI
|
||||
- v0.1.2-b: add README
|
@ -47,7 +47,7 @@ namespace OS {
|
||||
this.applist = this.find("applist") as GUI.tag.ListViewTag;
|
||||
this.srvlist.buttons = [
|
||||
{
|
||||
text: "+",
|
||||
iconclass: "bi bi-plus",
|
||||
onbtclick: () => {
|
||||
let services = [];
|
||||
for (var k in setting.system.packages) {
|
||||
@ -75,7 +75,7 @@ namespace OS {
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
iconclass: "bi bi-dash",
|
||||
onbtclick: () => {
|
||||
const item = this.srvlist.selectedItem;
|
||||
if (!item) {
|
||||
@ -90,7 +90,7 @@ namespace OS {
|
||||
|
||||
this.applist.buttons = [
|
||||
{
|
||||
text: "+",
|
||||
iconclass: "bi bi-plus",
|
||||
onbtclick: () => {
|
||||
const apps = (() => {
|
||||
const result = [];
|
||||
@ -102,6 +102,7 @@ namespace OS {
|
||||
text: v.name,
|
||||
app: k,
|
||||
iconclass: v.iconclass,
|
||||
icon: v.icon
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -126,7 +127,7 @@ namespace OS {
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
iconclass: "bi bi-dash",
|
||||
onbtclick: () => {
|
||||
const item = this.applist.selectedItem;
|
||||
if (!item) {
|
||||
|
@ -96,25 +96,18 @@ namespace OS {
|
||||
}
|
||||
|
||||
VFSSettingDialog.scheme = `\
|
||||
<afx-app-window width='250' height='180' apptitle = "__(Mount Points)">
|
||||
<afx-vbox>
|
||||
<afx-app-window width='250' height='200' apptitle = "__(Mount Points)">
|
||||
<afx-vbox padding="10">
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
<afx-vbox>
|
||||
<div data-height="10" ></div>
|
||||
<afx-label data-height="30" text = "__(Name)" ></afx-label>
|
||||
<input type = "text" data-id= "txtName" ></input>
|
||||
<div data-height="3" ></div>
|
||||
<afx-label data-height="30" text = "__(Path)" ></afx-label>
|
||||
<input type = "text" data-id= "txtPath" ></input>
|
||||
<div data-height="10" ></div>
|
||||
<afx-input label= "__(Name)" data-id="txtName"></afx-input>
|
||||
<afx-input label= "__(Path)" data-id="txtPath"></afx-input>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "content" ></afx-button>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "content" ></afx-button>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-app-window>\
|
||||
@ -144,7 +137,7 @@ namespace OS {
|
||||
this.ppath = this.find("ppath") as GUI.tag.LabelTag;
|
||||
this.mplist.buttons = [
|
||||
{
|
||||
text: "+",
|
||||
iconclass: "bi bi-plus",
|
||||
onbtclick: async () => {
|
||||
const d = await this.parent.openDialog(
|
||||
new VFSSettingDialog(),
|
||||
@ -162,7 +155,7 @@ namespace OS {
|
||||
},
|
||||
},
|
||||
{
|
||||
text: "-",
|
||||
iconclass: "bi bi-dash",
|
||||
onbtclick: async () => {
|
||||
const item = this.mplist.selectedItem;
|
||||
if (!item) {
|
||||
|
80
src/packages/Setting/VersionsHandle.ts
Normal file
80
src/packages/Setting/VersionsHandle.ts
Normal file
@ -0,0 +1,80 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
const App = OS.application.Setting;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @class VFSHandle
|
||||
* @extends {App.SettingHandle}
|
||||
*/
|
||||
class VersionsHandle extends App.SettingHandle {
|
||||
private grid: GUI.tag.GridViewTag;
|
||||
|
||||
/**
|
||||
*Creates an instance of VFSHandle.
|
||||
* @param {HTMLElement} scheme
|
||||
* @param {OS.application.Setting} parent
|
||||
* @memberof VFSHandle
|
||||
*/
|
||||
constructor(scheme: HTMLElement, parent: OS.application.Setting) {
|
||||
super(scheme, parent);
|
||||
this.grid = this.find("grid-version") as GUI.tag.GridViewTag;
|
||||
this.grid.resizable = true;
|
||||
this.grid.header = [{ text: __("Component")}, { text: __("Version")}];
|
||||
this.display_versions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Display versions of all system components
|
||||
*/
|
||||
private async display_versions() : Promise<any>
|
||||
{
|
||||
try {
|
||||
let result = await API.handle.versions();
|
||||
if(result.error)
|
||||
{
|
||||
throw API.throwe(__("Unable to fetch system version information"));
|
||||
}
|
||||
let data = result.result as GenericObject<any>;
|
||||
let records = [[{text:"AntOS"}, {text: `${OS.VERSION.version_string}`}]];
|
||||
for (const key in data) {
|
||||
const element = data[key];
|
||||
records.push([
|
||||
{ text: key },
|
||||
{ text: `${element["version"]}-${element["ref"]}` }
|
||||
])
|
||||
}
|
||||
this.grid.rows = records;
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
this.parent.error(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
App.VersionsHandle = VersionsHandle;
|
||||
}
|
@ -1,32 +1,17 @@
|
||||
afx-app-window[data-id = "setting-window"] afx-tab-container afx-tab-bar afx-list-view > div > ul li{
|
||||
float:none;
|
||||
border-radius: 0;
|
||||
font-weight: bold;
|
||||
padding-top:3px;
|
||||
padding-bottom: 3px;
|
||||
border:0;
|
||||
}
|
||||
afx-app-window[data-id = "setting-window"] afx-tab-bar{
|
||||
border-right: 1px solid #cbcbcb;
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "setting-window"] afx-label.header{
|
||||
afx-app-window[data-id = "setting-window"] afx-label.header i.label-text{
|
||||
font-weight: bold;
|
||||
}
|
||||
afx-app-window[data-id = "setting-window"] div.footer{
|
||||
border-right: 1px solid #cbcbcb;
|
||||
}
|
||||
/*APPEARANCE*/
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] div[data-id = "wp-preview"]{
|
||||
display: block;
|
||||
border:1px solid #cbcbcb;
|
||||
border:1px solid var(--border-quaternary);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-list-view[data-id="wplist"]
|
||||
{
|
||||
border:1px solid #cbcbcb;
|
||||
padding:2px;
|
||||
border:1px solid var(--border-quaternary);
|
||||
}
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-resizer{
|
||||
border:0;
|
||||
@ -35,7 +20,7 @@ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-re
|
||||
/*VFS*/
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-list-view[data-id="mplist"]
|
||||
{
|
||||
border: 1px solid #cbcbcb;
|
||||
border: 1px solid var(--border-quaternary);
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.btnsel button{
|
||||
@ -46,15 +31,20 @@ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.bt
|
||||
/*LANGUAGES*/
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="locale"] afx-list-view[data-id="lglist"]
|
||||
{
|
||||
border: 1px solid #cbcbcb;
|
||||
border: 1px solid var(--border-quaternary);
|
||||
}
|
||||
/*STARTUP*/
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="startup"] afx-list-view
|
||||
{
|
||||
border: 1px solid #cbcbcb;
|
||||
border: 1px solid var(--border-quaternary);
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="app-services"] afx-list-view
|
||||
{
|
||||
border: 1px solid #cbcbcb;
|
||||
border: 1px solid var(--border-quaternary);
|
||||
}
|
||||
|
||||
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="app-about"] afx-grid-view
|
||||
{
|
||||
border: 1px solid var(--border-quaternary);
|
||||
}
|
@ -82,6 +82,7 @@ namespace OS {
|
||||
static StartupHandle: typeof SettingHandle;
|
||||
static SettingHandle: typeof SettingHandle;
|
||||
static AppAndServiceHandle: typeof SettingHandle;
|
||||
static VersionsHandle: typeof SettingHandle;
|
||||
|
||||
/**
|
||||
*Creates an instance of Setting.
|
||||
@ -98,14 +99,15 @@ namespace OS {
|
||||
* @memberof Setting
|
||||
*/
|
||||
main(): void{
|
||||
//this.containter = this.find("container") as GUI.tag.TabContainerTag;
|
||||
const containter = this.find("container") as GUI.tag.TabContainerTag;
|
||||
|
||||
new Setting.AppearanceHandle(this.find("appearance"), this);
|
||||
new Setting.VFSHandle(this.find("vfs"), this);
|
||||
new Setting.LocaleHandle(this.find("locale"), this);
|
||||
new Setting.StartupHandle(this.find("startup"), this);
|
||||
new Setting.AppAndServiceHandle(this.find("app-services"), this);
|
||||
|
||||
new Setting.VersionsHandle(this.find("app-about"), this);
|
||||
containter.selectedIndex = 0;
|
||||
(this.find("btnsave") as GUI.tag.ButtonTag ).onbtclick = (e) => {
|
||||
this._api
|
||||
.setting()
|
||||
@ -130,6 +132,17 @@ namespace OS {
|
||||
);
|
||||
});
|
||||
};
|
||||
this.morphon(GUI.RESPONSIVE.MEDIUM, (fulfilled:boolean) => {
|
||||
|
||||
if(fulfilled)
|
||||
{
|
||||
containter.dir = "row";
|
||||
}
|
||||
else
|
||||
{
|
||||
containter.dir = "column";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Setting.singleton = true;
|
||||
|
@ -6,7 +6,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.1-a",
|
||||
"version":"0.2.2-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-wrench",
|
||||
"mimes":["none"]
|
||||
|
@ -1,100 +1,89 @@
|
||||
<afx-app-window data-id = "setting-window" apptitle="Setting" width="650" height="400">
|
||||
<afx-app-window data-id = "setting-window" apptitle="Setting" width="600" height="500" responsive="true">
|
||||
<afx-vbox>
|
||||
<afx-tab-container data-id = "container" dir = "row" tabbarwidth= "150">
|
||||
<afx-tab-container data-id = "container" dir = "column" tabbarheight= "40" tabbarwidth="160">
|
||||
|
||||
<afx-hbox tabname="__(Appearance)" data-id="appearance" iconclass = "fa fa-paint-brush">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox tabname="__(Appearance)" data-id="appearance" iconclass = "fa fa-paint-brush" padding="10">
|
||||
<afx-vbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="23"></afx-label>
|
||||
<afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="30"></afx-label>
|
||||
<afx-hbox>
|
||||
<afx-list-view data-width="150" data-id="wplist"></afx-list-view>
|
||||
<afx-list-view data-width="150" data-id="wplist" focusable="true"></afx-list-view>
|
||||
<afx-resizer data-width="2"></afx-resizer>
|
||||
<afx-vbox>
|
||||
<div data-id = "wp-preview"></div>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox data-height="25">
|
||||
<afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
|
||||
<div data-width="5"></div>
|
||||
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-id = "wp-preview"></div>
|
||||
</afx-hbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-height="30" data-id="theme-list" dropdown="true"></afx-list-view>
|
||||
<afx-hbox data-height="35">
|
||||
<afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
|
||||
<div data-width="5"></div>
|
||||
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
|
||||
</afx-hbox>
|
||||
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-height="35" data-id="theme-list" dropdown="true"></afx-list-view>
|
||||
<div data-height="5"></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
|
||||
<afx-hbox data-id="vfs" tabname = "__(VFS)" iconclass = "fa fa-inbox">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox data-id="vfs" tabname = "__(VFS)" iconclass = "fa fa-inbox" padding="10">
|
||||
<afx-vbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-id="mplist"></afx-list-view>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="23"></afx-label>
|
||||
<afx-hbox data-height = "25" >
|
||||
<afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-id="mplist" focusable="true"></afx-list-view>
|
||||
<afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="30"></afx-label>
|
||||
<afx-hbox data-height = "40" >
|
||||
<div data-width="16"></div>
|
||||
<afx-label data-id="dpath"></afx-label>
|
||||
<afx-button text="" iconclass = "fa fa-arrow-up" data-id="btndpath" data-width="20" class="btnsel"></afx-button>
|
||||
<afx-button text="" iconclass = "fa fa-arrow-up" data-id="btndpath" data-width="40" class="btnsel"></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Local packages path)" iconclass = "fa fa-cube" class = "header" data-height="23"></afx-label>
|
||||
<afx-hbox data-height = "25" >
|
||||
<afx-label text = "__(Local packages path)" iconclass = "fa fa-cube" class = "header" data-height="30"></afx-label>
|
||||
<afx-hbox data-height = "40" >
|
||||
<div data-width="16"></div>
|
||||
<afx-label data-id="ppath"></afx-label>
|
||||
<afx-button text="" data-id="btnppath" iconclass = "fa fa-arrow-up" data-width="20" class="btnsel"></afx-button>
|
||||
<afx-button text="" data-id="btnppath" iconclass = "fa fa-arrow-up" data-width="40" class="btnsel"></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="10"></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
|
||||
<afx-hbox data-id="locale" tabname = "__(Languages)"iconclass = "fa fa-globe">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox data-id="locale" tabname = "__(Languages)"iconclass = "fa fa-globe" padding="10">
|
||||
<afx-vbox>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-id="lglist"></afx-list-view>
|
||||
<afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-id="lglist" focusable="true"></afx-list-view>
|
||||
<div data-height="10"></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
|
||||
<afx-hbox data-id="startup" tabname = "__(Startup)" iconclass = "fa fa-cog">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox data-id="startup" tabname = "__(Startup)" iconclass = "fa fa-cog" padding="10">
|
||||
<afx-vbox>
|
||||
<afx-label text = "__(Startup services)" iconclass = "fa fa-tasks" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-id="srvlist"></afx-list-view>
|
||||
<afx-label text = "__(Startup services)" iconclass = "fa fa-tasks" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-id="srvlist" focusable="true"></afx-list-view>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Startup applications)" iconclass = "fa fa-adn" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-id="applist"></afx-list-view>
|
||||
<afx-label text = "__(Startup applications)" iconclass = "fa fa-adn" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-id="applist" focusable="true"></afx-list-view>
|
||||
<div data-height="10"></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
|
||||
<afx-hbox data-id="app-services" tabname = "__(Apps. and Services)" iconclass = "fa fa-adn">
|
||||
<div data-width="10"></div>
|
||||
<afx-hbox data-id="app-services" tabname = "__(Apps. and Services)" iconclass = "fa fa-adn" padding="10">
|
||||
<afx-vbox>
|
||||
<afx-label text = "__(Services)" iconclass = "fa fa-tasks" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-id="sys-srvlist"></afx-list-view>
|
||||
<afx-label text = "__(Services)" iconclass = "fa fa-tasks" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-id="sys-srvlist" focusable="true"></afx-list-view>
|
||||
<div data-height="5"></div>
|
||||
<afx-label text = "__(Pinned applications)" iconclass = "fa fa-adn" class = "header" data-height="23"></afx-label>
|
||||
<afx-list-view data-id="sys-applist"></afx-list-view>
|
||||
<afx-label text = "__(Pinned applications)" iconclass = "fa fa-adn" class = "header" data-height="30"></afx-label>
|
||||
<afx-list-view data-id="sys-applist" focusable="true"></afx-list-view>
|
||||
<div data-height="10"></div>
|
||||
</afx-vbox>
|
||||
<div data-width="10"></div>
|
||||
</afx-hbox>
|
||||
|
||||
<afx-hbox data-id="app-about" tabname = "__(Versions)" iconclass = "bi bi-info-square" padding="10">
|
||||
<afx-vbox>
|
||||
<afx-label text = "__(System versions)" iconclass = "bi bi-gear-wide-connected" class = "header" data-height="30"></afx-label>
|
||||
<afx-grid-view data-id="grid-version" focusable="true"></afx-grid-view>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
|
||||
</afx-tab-container>
|
||||
<afx-hbox data-height="35">
|
||||
<div data-width = "150" class = "footer"></div>
|
||||
<afx-hbox data-height="45">
|
||||
<div></div>
|
||||
<div style="text-align:right" >
|
||||
<afx-button text="__(Save)" data-id="btnsave" iconclass="fa fa-save" style="margin-right:10px;" ></afx-button>
|
||||
<afx-button text="__(Save)" data-id="btnsave" iconclass="fa fa-save" style="margin-right:5px;" ></afx-button>
|
||||
</div>
|
||||
</afx-hbox>
|
||||
|
||||
|
@ -1,11 +0,0 @@
|
||||
module_files = Calendar.js PushNotification.js Syslog.js
|
||||
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = package.json scheme.html
|
||||
|
||||
|
||||
PKG_NAME=Syslog
|
||||
include ../pkg.mk
|
@ -1,86 +0,0 @@
|
||||
afx-overlay[data-id = "notifyzone"]{
|
||||
/*opacity: 0.85;*/
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding:3px;
|
||||
margin: 0;
|
||||
}
|
||||
afx-overlay[data-id = "notifyzone"] afx-button button{
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
afx-list-view[data-id = "notifylist"]
|
||||
{
|
||||
padding:0;
|
||||
}
|
||||
afx-list-view[data-id = "notifylist"] > div.list-container > ul li{
|
||||
border:1px solid #464646;
|
||||
border-radius: 3px;
|
||||
margin-bottom: 5px;
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
afx-overlay[data-id = "feedzone"]{
|
||||
overflow: hidden;
|
||||
background-color:transparent;
|
||||
right:5px;
|
||||
margin: 0;
|
||||
padding:0;
|
||||
top:0;
|
||||
}
|
||||
afx-list-view[data-id = "notifeed"]
|
||||
{
|
||||
padding:0;
|
||||
margin:0;
|
||||
}
|
||||
afx-list-view[data-id = "notifeed"] li{
|
||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
||||
border:1px solid #262626;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 2px;
|
||||
z-index: 99999;
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='Syslog'] div[data-id ='container']{
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-error {
|
||||
display: block;
|
||||
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-error i::before {
|
||||
color: chocolate;
|
||||
}
|
||||
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-time{
|
||||
display: block;
|
||||
padding-left: 10px;
|
||||
}
|
||||
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-time i.label-text{
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='Syslog'] afx-bug-list-item li.selected {
|
||||
background-color: #116cd6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='Syslog'] pre {
|
||||
padding: 10px;
|
||||
margin:0;
|
||||
user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
afx-app-window[data-id ='Syslog'] input{
|
||||
height: 100%;
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"app": "Syslog",
|
||||
"pkgname": "Syslog",
|
||||
"services": [
|
||||
"Calendar",
|
||||
"PushNotification"
|
||||
],
|
||||
"name": "System log",
|
||||
"description": "Core services and system log",
|
||||
"info": {
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com",
|
||||
"credit": "dedicated to some one here",
|
||||
"licences": "GPLv3"
|
||||
},
|
||||
"version": "0.1.1-a",
|
||||
"category": "System",
|
||||
"iconclass": "fa fa-bug",
|
||||
"mimes": []
|
||||
}
|
11
src/packages/SystemReport/Makefile
Normal file
11
src/packages/SystemReport/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
module_files = SystemReport.js
|
||||
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = package.json scheme.html README.md
|
||||
|
||||
|
||||
PKG_NAME=SystemReport
|
||||
include ../pkg.mk
|
8
src/packages/SystemReport/README.md
Normal file
8
src/packages/SystemReport/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# SystemReport: System notification management and service
|
||||
|
||||
Provide system wise notification service (Push Notification)
|
||||
|
||||
## Change logs
|
||||
- v0.1.4-b: use system css variables in theme
|
||||
- v0.1.3-b: Rename from Syslog to SystemReport, move all services to SystemServices package
|
||||
- v0.1.2-b: add README
|
@ -110,33 +110,33 @@ detail:
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @class Syslog
|
||||
* @class SystemReport
|
||||
* @extends {BaseApplication}
|
||||
*/
|
||||
export class Syslog extends BaseApplication {
|
||||
export class SystemReport extends BaseApplication {
|
||||
private loglist: TAG.ListViewTag;
|
||||
private logdetail: HTMLElement;
|
||||
private srv: PushNotification;
|
||||
constructor(args: AppArgumentsType[]) {
|
||||
super("Syslog", args);
|
||||
super("SystemReport", args);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @memberof Syslog
|
||||
* @memberof SystemReport
|
||||
*/
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @memberof Syslog
|
||||
* @memberof SystemReport
|
||||
*/
|
||||
main(): void {
|
||||
this.loglist = this.find("loglist") as TAG.ListViewTag;
|
||||
this.logdetail = this.find("logdetail");
|
||||
|
||||
this._gui
|
||||
.pushService("Syslog/PushNotification")
|
||||
.pushService("SystemServices/PushNotification")
|
||||
.then((srv) => {
|
||||
|
||||
this.srv = srv as PushNotification;
|
||||
@ -231,7 +231,7 @@ detail:
|
||||
*
|
||||
*
|
||||
* @param {GenericObject<any>} log
|
||||
* @memberof Syslog
|
||||
* @memberof SystemReport
|
||||
*/
|
||||
addLog(log: GenericObject<any>): void {
|
||||
this.loglist.push(log);
|
||||
@ -241,7 +241,7 @@ detail:
|
||||
*
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof Syslog
|
||||
* @memberof SystemReport
|
||||
*/
|
||||
cleanup(): void {
|
||||
if (this.srv) {
|
||||
@ -250,6 +250,6 @@ detail:
|
||||
}
|
||||
}
|
||||
|
||||
Syslog.singleton = true;
|
||||
SystemReport.singleton = true;
|
||||
}
|
||||
}
|
36
src/packages/SystemReport/main.css
Normal file
36
src/packages/SystemReport/main.css
Normal file
@ -0,0 +1,36 @@
|
||||
afx-app-window[data-id ='SystemReport'] div[data-id ='container']{
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-error {
|
||||
display: block;
|
||||
|
||||
}
|
||||
|
||||
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-error i::before {
|
||||
color: var(--text-error);
|
||||
}
|
||||
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-time{
|
||||
display: block;
|
||||
padding-left: 10px;
|
||||
}
|
||||
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-time i.label-text{
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/*
|
||||
afx-app-window[data-id ='SystemReport'] afx-bug-list-item li.selected {
|
||||
background-color: #116cd6;
|
||||
color: white;
|
||||
}*/
|
||||
|
||||
afx-app-window[data-id ='SystemReport'] pre {
|
||||
padding: 10px;
|
||||
margin:0;
|
||||
user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
afx-app-window[data-id ='SystemReport'] input{
|
||||
height: 100%;
|
||||
}
|
15
src/packages/SystemReport/package.json
Normal file
15
src/packages/SystemReport/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"app": "SystemReport",
|
||||
"pkgname": "SystemReport",
|
||||
"name": "System report",
|
||||
"description": "System reports",
|
||||
"info": {
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com",
|
||||
"licences": "GPLv3"
|
||||
},
|
||||
"version": "0.1.5-b",
|
||||
"category": "System",
|
||||
"iconclass": "fa fa-bug",
|
||||
"mimes": []
|
||||
}
|
@ -1,17 +1,17 @@
|
||||
<afx-app-window data-id="Syslog" width='500' height='350' apptitle = "__(System error log)" >
|
||||
<afx-app-window data-id="SystemReport" width='600' height='450' apptitle = "__(System error log)" responsive="true">
|
||||
<afx-hbox>
|
||||
<afx-list-view data-id = "loglist" data-width="200"> </afx-list-view>
|
||||
<afx-list-view data-id = "loglist" data-width="200" focusable="true"> </afx-list-view>
|
||||
<afx-resizer data-width = "2" ></afx-resizer>
|
||||
<afx-vbox>
|
||||
<div data-id = "container">
|
||||
<pre><code data-id="logdetail"></code></pre>
|
||||
</div>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox style="text-align:right;" data-height = "27">
|
||||
<afx-button data-width ="20"
|
||||
<afx-hbox style="text-align:right;" data-height = "35">
|
||||
<afx-button data-width ="content"
|
||||
tooltip = "ct:__(Clear all logs)" iconclass = "fa fa-trash-o" data-id = "btclean" ></afx-button>
|
||||
<input type = "text" data-id = "txturi" ></input>
|
||||
<afx-button data-width ="80" text = "__(Report)"
|
||||
<afx-button data-width ="content" text = "__(Report)"
|
||||
iconclass = "fa fa-bug" data-id = "btnreport" ></afx-button>
|
||||
<div data-width="10" ></div>
|
||||
</afx-hbox>
|
@ -26,9 +26,8 @@ namespace OS {
|
||||
export class Calendar extends BaseService {
|
||||
constructor(args: AppArgumentsType[]) {
|
||||
super("Calendar", args);
|
||||
//@iconclass = "fa fa-commenting"
|
||||
this.text = "";
|
||||
this.iconclass = "fa fa-calendar";
|
||||
this.iconclass = "bi bi-calendar3";
|
||||
}
|
||||
|
||||
init(): void {
|
||||
@ -36,11 +35,11 @@ namespace OS {
|
||||
this.watch(1000, () => {
|
||||
const now = new Date();
|
||||
this.text = now.toString();
|
||||
(this.domel as GUI.tag.SimpleMenuEntryTag).text = this.text;
|
||||
this.update();
|
||||
});
|
||||
}
|
||||
|
||||
awake(e: GUI.TagEventType<GUI.tag.MenuEventData>): void {
|
||||
awake(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void {
|
||||
this.openDialog("CalendarDialog").then((d) => console.log(d));
|
||||
}
|
||||
// do nothing
|
11
src/packages/SystemServices/Makefile
Normal file
11
src/packages/SystemServices/Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
module_files = Calendar.js PushNotification.js
|
||||
|
||||
libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=SystemServices
|
||||
include ../pkg.mk
|
@ -31,12 +31,10 @@ namespace OS {
|
||||
private cb: (e: JQuery.ClickEvent) => void;
|
||||
private view: boolean;
|
||||
private mlist: TAG.ListViewTag;
|
||||
private mfeed: TAG.ListViewTag;
|
||||
private nzone: TAG.OverlayTag;
|
||||
private fzone: TAG.OverlayTag;
|
||||
|
||||
logs: GenericObject<any>[];
|
||||
logmon: Syslog;
|
||||
logmon: SystemReport;
|
||||
|
||||
/**
|
||||
*Creates an instance of PushNotification.
|
||||
@ -48,6 +46,7 @@ namespace OS {
|
||||
this.iconclass = "fa fa-bars";
|
||||
this.cb = undefined;
|
||||
this.logs = [];
|
||||
this.text = __("Notification");
|
||||
this.logmon = undefined;
|
||||
}
|
||||
|
||||
@ -69,9 +68,7 @@ namespace OS {
|
||||
*/
|
||||
main(): void {
|
||||
this.mlist = this.find("notifylist") as TAG.ListViewTag;
|
||||
this.mfeed = this.find("notifeed") as TAG.ListViewTag;
|
||||
this.nzone = this.find("notifyzone") as TAG.OverlayTag;
|
||||
this.fzone = this.find("feedzone") as TAG.OverlayTag;
|
||||
(this.find("btclear") as TAG.ButtonTag).onbtclick = (e) =>
|
||||
(this.mlist.data = []);
|
||||
(this.find("bterrlog") as TAG.ButtonTag).onbtclick = (e) =>
|
||||
@ -82,18 +79,12 @@ namespace OS {
|
||||
this.subscribe("info", (o) => this.pushout("INFO", o));
|
||||
|
||||
this.nzone.height = "100%";
|
||||
this.fzone.height = "100%";
|
||||
|
||||
$(this.nzone)
|
||||
.css("right", 0)
|
||||
.css("top", "0")
|
||||
.css("bottom", "0")
|
||||
.hide();
|
||||
$(this.fzone)
|
||||
//.css("z-index", 99999)
|
||||
.css("bottom", "0")
|
||||
.css("bottom", "0")
|
||||
.hide();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -104,7 +95,7 @@ namespace OS {
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
private showLogReport(): void {
|
||||
this._gui.launch("Syslog", []);
|
||||
this._gui.launch("SystemReport", []);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -112,19 +103,19 @@ namespace OS {
|
||||
*
|
||||
* @private
|
||||
* @param {string} s
|
||||
* @param {GenericObject<any>} o
|
||||
* @param {API.AnnouncementDataType} o
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
private addLog(s: string, o: GenericObject<any>): void {
|
||||
private addLog(s: string, o: API.AnnouncementDataType<any>): void {
|
||||
const logtime = new Date();
|
||||
const log = {
|
||||
type: s,
|
||||
name: o.name,
|
||||
text: `${o.data.m}`,
|
||||
text: `${o.message}`,
|
||||
id: o.id,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
error: o.data.e,
|
||||
icon: o.icon,
|
||||
iconclass: o.iconclass,
|
||||
error: o.u_data,
|
||||
time: logtime,
|
||||
closable: true,
|
||||
tag: "afx-bug-list-item",
|
||||
@ -141,14 +132,14 @@ namespace OS {
|
||||
*
|
||||
* @private
|
||||
* @param {string} s
|
||||
* @param {GenericObject<any>} o
|
||||
* @param {API.AnnouncementDataType} o
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
private pushout(s: string, o: GenericObject<any>): void {
|
||||
private pushout(s: string, o: API.AnnouncementDataType<any>): void {
|
||||
const d = {
|
||||
text: `[${s}] ${o.name} (${o.id}): ${o.data.m}`,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
text: `[${s}] ${o.name} (${o.id}): ${o.message}`,
|
||||
icon: o.icon,
|
||||
iconclass: o.iconclass,
|
||||
closable: true,
|
||||
};
|
||||
if (s !== "INFO") {
|
||||
@ -166,16 +157,7 @@ namespace OS {
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
private notifeed(d: GenericObject<any>): void {
|
||||
let timer: number;
|
||||
this.mfeed.unshift(d);
|
||||
$(this.fzone).show();
|
||||
timer = window.setTimeout(() => {
|
||||
this.mfeed.delete(d.domel);
|
||||
if (this.mfeed.data.length === 0) {
|
||||
$(this.fzone).hide();
|
||||
}
|
||||
return clearTimeout(timer);
|
||||
}, 3000);
|
||||
GUI.toast(d,{timeout: 3, location: GUI.ANCHOR.NORTH});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,21 +166,25 @@ namespace OS {
|
||||
* @param {GUI.TagEventType} evt
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
awake(evt: GUI.TagEventType<GUI.tag.MenuEventData>): void {
|
||||
awake(evt: GUI.TagEventType<GUI.tag.StackMenuEventData>): void {
|
||||
if (this.view) {
|
||||
$(this.nzone).hide();
|
||||
} else {
|
||||
$(this.nzone).show();
|
||||
}
|
||||
console.log(evt, evt.data.item.aid);
|
||||
this.view = !this.view;
|
||||
if (!this.cb) {
|
||||
this.cb = (e) => {
|
||||
const list_id = $(evt.data.item).attr("list-id");
|
||||
console.log(e.target, list_id);
|
||||
if (
|
||||
!$(e.target).closest($(this.nzone)).length &&
|
||||
!$(e.target).closest(evt.data.item).length
|
||||
!$(e.target).closest(`[list-id="${list_id}"]`).length
|
||||
//!$(e.target).closest($(evt.data.item)).length
|
||||
) {
|
||||
$(this.nzone).hide();
|
||||
$(document).unbind("click", this.cb);
|
||||
$(document).off("click", this.cb);
|
||||
this.view = !this.view;
|
||||
}
|
||||
};
|
||||
@ -206,7 +192,7 @@ namespace OS {
|
||||
if (this.view) {
|
||||
$(document).on("click", this.cb);
|
||||
} else {
|
||||
$(document).unbind("click", this.cb);
|
||||
$(document).off("click", this.cb);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,16 +209,12 @@ namespace OS {
|
||||
const scheme = `\
|
||||
<div>
|
||||
<afx-overlay data-id = "notifyzone" width = "250px">
|
||||
<afx-hbox data-height="30">
|
||||
<afx-hbox data-height="35">
|
||||
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
|
||||
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button>
|
||||
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "40"></afx-button>
|
||||
</afx-hbox>
|
||||
<afx-list-view data-id="notifylist"></afx-list-view>
|
||||
</afx-overlay>
|
||||
<afx-overlay data-id = "feedzone" width = "250">
|
||||
<afx-list-view data-id = "notifeed">
|
||||
</afx-list-view>
|
||||
</afx-overlay>
|
||||
</div>\
|
||||
`;
|
||||
}
|
10
src/packages/SystemServices/README.md
Normal file
10
src/packages/SystemServices/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
## System services
|
||||
|
||||
|
||||
Basic services:
|
||||
- Push notification
|
||||
- Calendar service
|
||||
|
||||
### change logs
|
||||
- 0.1.2-b: fix show/hide animation bug
|
||||
- 0.1.1-a: use system css variables in theme
|
27
src/packages/SystemServices/main.css
Normal file
27
src/packages/SystemServices/main.css
Normal file
@ -0,0 +1,27 @@
|
||||
afx-overlay[data-id = "notifyzone"]{
|
||||
/*opacity: 0.85;*/
|
||||
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding:3px;
|
||||
margin: 0;
|
||||
}
|
||||
afx-overlay[data-id = "notifyzone"] afx-button button{
|
||||
width: 100%;
|
||||
border-radius: 0;
|
||||
}
|
||||
afx-list-view[data-id = "notifylist"]
|
||||
{
|
||||
padding:0;
|
||||
}
|
||||
afx-list-view[data-id = "notifylist"] > div.list-container > ul li{
|
||||
border:1px solid var(--border-quaternary);
|
||||
background-color: var(--item-bg-odd);
|
||||
border-radius: 3px;
|
||||
margin-bottom: 5px;
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
15
src/packages/SystemServices/package.json
Normal file
15
src/packages/SystemServices/package.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"pkgname": "SystemServices",
|
||||
"services": ["PushNotification", "Calendar"],
|
||||
"name": "System services",
|
||||
"description": "System services",
|
||||
"info": {
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com",
|
||||
"licences": "GPLv3"
|
||||
},
|
||||
"version": "0.1.2-b",
|
||||
"category": "System",
|
||||
"iconclass": "fa fa-cog",
|
||||
"mimes": []
|
||||
}
|
@ -1,56 +0,0 @@
|
||||
afx-app-window div.afx-window-wrapper{
|
||||
border:1px solid #262626;
|
||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
||||
border-radius: 0px;
|
||||
background-color:#363636;
|
||||
}
|
||||
afx-app-window.unactive > div.afx-window-wrapper{
|
||||
background-color: #464646;
|
||||
}
|
||||
|
||||
afx-app-window ul.afx-window-top{
|
||||
height: 20px;
|
||||
border-bottom: 1px solid #262626;
|
||||
}
|
||||
afx-app-window div.afx-window-overlay {
|
||||
top: 22px;
|
||||
}
|
||||
afx-app-window ul.afx-window-top li{
|
||||
margin-left: 3px;
|
||||
margin-top:4px;
|
||||
|
||||
}
|
||||
afx-app-window ul.afx-window-top .afx-window-close,.afx-window-minimize,.afx-window-maximize{
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 0;
|
||||
}
|
||||
afx-app-window ul li.afx-window-close{
|
||||
background-color: #Fc605b;
|
||||
float:left;
|
||||
}
|
||||
afx-app-window ul li.afx-window-minimize{
|
||||
background-color: #fec041;
|
||||
float:left;
|
||||
}
|
||||
afx-app-window ul li.afx-window-maximize{
|
||||
background-color: #35cc4b;
|
||||
float:left;
|
||||
}
|
||||
|
||||
afx-app-window ul li.afx-window-title{
|
||||
margin-top:1px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
afx-app-window div.afx-window-content
|
||||
{
|
||||
background-color:#363636;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
afx-app-window.unactive div.afx-window-content
|
||||
{
|
||||
background-color:#464646;
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
afx-button button{
|
||||
padding: 4px;
|
||||
border: 1px solid #262626;
|
||||
background-color: #464646;
|
||||
color: white;
|
||||
border-radius: 3px;
|
||||
font-family: "Ubuntu";
|
||||
}
|
||||
|
||||
afx-button button[disabled]{
|
||||
color: #868686;
|
||||
}
|
||||
afx-button i.icon-style {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
display: inline-block;
|
||||
}
|
||||
afx-button button:active, afx-button button.btactive {
|
||||
background-color: #2786F3;
|
||||
color: white;
|
||||
border: 1px solid #363636;
|
||||
}
|
@ -1,53 +0,0 @@
|
||||
afx-calendar-view div{
|
||||
text-align: center;
|
||||
|
||||
}
|
||||
afx-calendar-view > div {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
afx-calendar-view i.prevmonth, afx-calendar-view i.nextmonth{
|
||||
display: inline-block;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
afx-calendar-view i.prevmonth{
|
||||
margin-right: 20px;
|
||||
}
|
||||
afx-calendar-view i.nextmonth{
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
afx-calendar-view i.prevmonth:before{
|
||||
content: "\f104";
|
||||
font-family: "FontAwesome";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
/*position:absolute;
|
||||
top:25%;
|
||||
right:5px;*/
|
||||
}
|
||||
afx-calendar-view i.nextmonth:before{
|
||||
content: "\f105";
|
||||
font-family: "FontAwesome";
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
margin-left: 20px;
|
||||
/*position:absolute;
|
||||
top:25%;
|
||||
right:5px;*/
|
||||
}
|
||||
|
||||
|
||||
afx-calendar-view afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell
|
||||
{
|
||||
background-color: transparent;
|
||||
}
|
||||
afx-calendar-view afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell.afx-grid-cell-selected
|
||||
{
|
||||
background-color: #116cd6;
|
||||
color:white;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
@ -1,35 +0,0 @@
|
||||
afx-apps-dock{
|
||||
bottom: 0px;
|
||||
top: 0px;
|
||||
width: 32px;
|
||||
background-color:#363636;
|
||||
padding:0;
|
||||
padding-top: 0;
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border:1px solid #262626;
|
||||
box-shadow: none;
|
||||
}
|
||||
afx-apps-dock afx-button button{
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
font-size: 19px;
|
||||
margin-bottom: 0;
|
||||
padding:0px;
|
||||
background-color: transparent;
|
||||
border:0;
|
||||
border-radius: 0;
|
||||
}
|
||||
afx-apps-dock afx-button afx-label i.icon-style{
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin-left: 2px;
|
||||
margin-bottom: 0px;
|
||||
border:0;
|
||||
}
|
||||
|
||||
afx-apps-dock afx-button.selected > button {
|
||||
background-color: #464646;
|
||||
color: white;
|
||||
border: 1px solid #464646;
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
afx-grid-view afx-grid-row:nth-child(even) afx-grid-cell
|
||||
{
|
||||
background-color: #3b3b3b;
|
||||
}
|
||||
|
||||
afx-grid-view afx-grid-row:nth-child(odd) afx-grid-cell
|
||||
{
|
||||
background-color: #363636;
|
||||
}
|
||||
|
||||
afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell
|
||||
{
|
||||
background-color: #116cd6;
|
||||
color:white;
|
||||
}
|
||||
afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell.afx-grid-cell-selected
|
||||
{
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
|
||||
afx-grid-view .grid_row_header afx-grid-cell{
|
||||
border:0;
|
||||
}
|
@ -1,90 +0,0 @@
|
||||
afx-list-view > div.list-container > ul li{
|
||||
padding: 5px;
|
||||
padding-top:3px;
|
||||
padding-bottom: 3px;
|
||||
padding-right: 10px;
|
||||
background-color: #363636;
|
||||
}
|
||||
afx-list-view > div.list-container > ul afx-list-item:nth-child(even) li{
|
||||
background-color:#3b3b3b;
|
||||
}
|
||||
afx-list-view i.closable{
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
afx-list-view i.closable:before{
|
||||
font-size: 10px;
|
||||
margin-left: 10px;
|
||||
color: #868686;
|
||||
}
|
||||
|
||||
afx-list-view > div.list-container > ul li > i {
|
||||
margin-right: 3px;
|
||||
|
||||
}
|
||||
afx-list-view > div.list-container > ul > afx-list-item > li.selected{
|
||||
background-color: #116cd6;
|
||||
color:white;
|
||||
}
|
||||
|
||||
afx-list-view.dropdown > div.list-container > ul{
|
||||
border:1px solid #262626;
|
||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
||||
border-radius: 3px;
|
||||
max-height: 150px;
|
||||
background-color: #363636;
|
||||
border-top-left-radius: 0px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
afx-list-view.dropdown div.list-container div{
|
||||
color: white;
|
||||
padding-top:3px;
|
||||
padding-bottom: 3px;
|
||||
border:1px solid #262626;
|
||||
border-radius: 3px;
|
||||
background-color: transparent;
|
||||
height: 17px;
|
||||
}
|
||||
afx-list-view.dropdown div.list-container div > afx-label{
|
||||
padding-left:3px;
|
||||
}
|
||||
afx-list-view.dropdown div.list-container div:before {
|
||||
content: "\f107";
|
||||
font-family: "FontAwesome";
|
||||
font-size: 11px;
|
||||
font-style: normal;
|
||||
position: absolute;
|
||||
top:25%;
|
||||
right: 5px;
|
||||
}
|
||||
afx-list-view.dropdown > div.list-container > ul li:hover{
|
||||
background-color: #464646;
|
||||
}
|
||||
afx-list-view ul.complex-content{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
}
|
||||
afx-list-view ul.complex-content li{
|
||||
padding:0;
|
||||
background-color: transparent;
|
||||
color:#5e5f59;
|
||||
list-style: none;
|
||||
}
|
||||
afx-list-view > div.list-container > ul li.selected ul.complex-content li{
|
||||
color:white;
|
||||
}
|
||||
|
||||
afx-list-view div.button_container afx-button{
|
||||
margin-right: 2px;
|
||||
}
|
||||
afx-list-view div.button_container afx-button button{
|
||||
border-radius: 0;
|
||||
padding-left:5px;
|
||||
padding-top:1px;
|
||||
padding-bottom: 1px;
|
||||
padding-right: 5px;
|
||||
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
afx-menu afx-switch span{
|
||||
width: 20px;
|
||||
height: 16px;
|
||||
font-size: 16px;
|
||||
/*margin-top:5px;*/
|
||||
}
|
||||
afx-menu span.shortcut{
|
||||
text-align: right;
|
||||
}
|
||||
afx-menu li:hover > a afx-switch span:before{
|
||||
color:white;
|
||||
}
|
||||
|
||||
afx-menu afx-menu ul {
|
||||
padding: 0;
|
||||
border:1px solid #262626;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
/*box-shadow: 2px 2px 2px #cbcbcb;*/
|
||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
||||
background-color: #363636;
|
||||
}
|
||||
afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{
|
||||
padding:3px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
afx-menu afx-menu li{
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
afx-menu li:hover {
|
||||
background-color: #2786F3;
|
||||
}
|
||||
afx-menu li:hover > a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
afx-menu afx-menu .afx_submenu:before, afx-menu ul.context .afx_submenu:before{
|
||||
content: "\f054";
|
||||
font-family: "FontAwesome";
|
||||
font-size: 10px;
|
||||
right:5px;
|
||||
color: #414339;
|
||||
position:absolute;
|
||||
top:25%;
|
||||
}
|
||||
|
||||
afx-menu ul.context{
|
||||
border:1px solid #262626;
|
||||
border-radius: 5px;
|
||||
border-top-left-radius: 0px;
|
||||
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
|
||||
background-color: #363636;
|
||||
}
|
||||
afx-menu ul.context li{
|
||||
min-width: 150px;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
afx-overlay {
|
||||
background-color: rgba(54, 54, 54, 0.7);
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user