341 Commits

Author SHA1 Message Date
13969511a5 Update README.md
Some checks failed
gitea-sync/antos/pipeline/head This commit looks good
gitea-sync/antos-frontend/pipeline/head There was a failure building this commit
2022-12-31 13:22:52 +01:00
dd56643e01 Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:16:40 +01:00
576051aca0 Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:15:20 +01:00
fec05d115f Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:10:32 +01:00
5313f0b224 add screenshot
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:09:42 +01:00
25d1c5fd47 Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 12:18:14 +01:00
2620d2ccb6 add build status
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-10-05 21:32:40 +02:00
d1fdb47ca2 Update Jenkinsfile
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-09-29 19:17:19 +02:00
31d928c977 Delete _config.yml
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-09-16 15:31:42 +02:00
50e74cef5f Delete .travis.yml
All checks were successful
antos-devel/pipeline/head This commit looks good
gitea-sync/antos/pipeline/head This commit looks good
2022-09-16 13:26:12 +02:00
cbb8948dbf Generate build script per branch
All checks were successful
antos-devel/pipeline/head This commit looks good
2022-09-16 13:18:56 +02:00
c377a80d0d export dts to build dir 2022-09-16 12:44:02 +02:00
f466cf1200 Update change logs on v1.2.1 2022-09-16 12:41:38 +02:00
53867c9d03 Separate build based on current branch name 2022-09-16 11:53:15 +02:00
4cd271cc51 Merge pull request #45 from lxsang/1.2.1
release 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
2022-09-16 11:47:05 +02:00
9b5da170e7 App name now can differ from pkgname 2022-08-25 01:20:51 +02:00
b381294064 fix: fix icon display problem when application is installed, remove all related settings when an application is uinstalled 2022-08-24 14:45:11 +02:00
a0ee2d1090 Update Jenkinsfile 2022-08-23 18:20:25 +02:00
b6c90e56cd update image path in readme 2022-08-22 21:27:57 +02:00
5f29d5b382 Update README.md 2022-08-22 12:59:33 +02:00
b806f20bdb change image path 2022-08-22 12:58:50 +02:00
19a8610f1c Update Jenkinsfile 2022-08-22 09:52:10 +02:00
14b72ef425 Fix dragndrop bug on Fileview (grid mod) 2022-08-19 11:15:21 +02:00
c96919ea12 fix: correct jenkins build demo stage 2022-08-04 13:47:55 +02:00
1cf718117f Fix fileview status incorrect, add more build stage to Jenkinsfile 2022-08-04 13:40:02 +02:00
e76a3f4ef1 Update Jenkinsfile 2022-08-02 11:59:21 +02:00
bb8991c1a1 Update Jenkinsfile 2022-08-02 11:57:43 +02:00
558ef1b3ef Update Jenkinsfile 2022-08-02 11:56:56 +02:00
ef95e55d50 Update README.md 2022-08-01 18:18:31 +02:00
4a2f4483d6 Update README.md 2022-08-01 18:12:27 +02:00
a03feba430 Update README.md 2022-08-01 18:08:13 +02:00
5014cc730f Update README.md 2022-08-01 17:15:43 +02:00
b3a4dd4bd1 Update README.md 2022-08-01 17:09:08 +02:00
1e230c9f9d Update Jenkinsfile 2022-08-01 16:48:52 +02:00
2a4a68b902 Create Jenkinsfile 2022-08-01 16:38:54 +02:00
969222b687 Update README.md 2022-07-29 02:02:30 +02:00
76b16f54a3 regen ar 2022-07-22 20:35:54 +02:00
255f9dc285 update readme file, and include it to delivery 2022-07-22 20:35:21 +02:00
b26a62464d regen archive 2022-07-22 20:19:46 +02:00
d08b33a0b9 fix ar generation problem: with new version format 2022-07-22 20:19:10 +02:00
09e12437a8 fix ar generation problem: with new version format 2022-07-22 20:17:47 +02:00
e4ccfedbd8 regen archive 2022-07-22 20:13:45 +02:00
da5bbdab60 Allow to set version number and build ID to the current Antos build 2022-07-22 20:13:08 +02:00
699c697344 update login form style 2022-07-22 18:04:56 +02:00
2fd4bb5c96 Bug fix + improvement:
- About dialog now can display package README file
- Fix: double click handling on grid view
- add README to packages
- support source map in release version
2022-07-19 20:57:12 +02:00
6cbb463f6f Fileview: list view display modified date instead of mime 2022-07-17 22:21:10 +02:00
f7081ae48a Include current Antos version to login screen 2022-07-17 15:05:59 +02:00
5d17c429f7 Makefile read current version from gcode 2022-07-17 14:29:39 +02:00
583a0c0349 update version number in code 2022-07-17 14:20:56 +02:00
c0603cd47d Minor style fixes on menus and dropdown list 2022-07-17 13:10:02 +02:00
66af4d63f6 regen archive 2022-07-05 20:02:26 +02:00
8b029c2e0a fix minor visual bug on grid view, list view and tree view 2022-07-05 20:02:01 +02:00
86bcaf9dea visual bug fix on label: inline block by default 2022-07-05 15:25:19 +02:00
084c377bcf visual bug fix on label 2022-07-05 15:10:17 +02:00
61de95788c Visual improvements 2022-07-05 15:04:18 +02:00
52af4b6940 fix visualize bug after style changes 2022-07-05 14:41:34 +02:00
e63cae1550 style improvement on Label, FileView, GridView, system menu and app Panel 2022-07-05 13:22:51 +02:00
b2ce9ec978 Minor improvement
- Use arrow up key on File widget to navigate to parent directory
- Application will trigger a "launched" event when opened
2022-07-04 19:59:34 +02:00
f97a45be15 Add more control to mem file + bug fix on File 2022-07-02 21:36:07 +02:00
fdcc5ce784 allow to create memory-based temporal VFS file system 2022-07-02 18:52:50 +02:00
81d78aa8e5 robusify VFS mem file handling 2022-07-02 16:28:21 +02:00
5734ce8ca9 regen ar 2022-05-30 12:18:17 +02:00
7f0ae264fe regen ar 2022-05-30 12:14:10 +02:00
d109d6af39 fix: file name display inconsitent between views 2022-05-30 12:13:29 +02:00
c26e27d7ec Fix multiple dialogs focus bug 2022-05-24 17:35:27 +02:00
c2e72a067e regen ar 2022-05-24 15:41:25 +02:00
8b23ebeeff Loading animation is now based on the current context (global or application context) 2022-05-24 15:39:58 +02:00
deae7bbc57 regen archive 2022-03-31 09:27:30 +02:00
9c5272757a update archive 2022-03-29 18:26:04 +02:00
2cdd8f9a43 support dnd and grid sort 2022-03-29 18:25:07 +02:00
079af3b0ce fix type conversion error in gridview tag 2022-02-23 14:33:21 +01:00
6af1260918 regen release for test 2021-11-26 18:25:37 +01:00
a1d26a6069 regen release 2021-11-26 18:24:09 +01:00
dc2a7c36d8 regen release 2021-11-26 18:22:42 +01:00
15f8de8910 regen release 2021-11-26 18:17:44 +01:00
f912e57e3b regen release 2021-11-26 18:14:10 +01:00
a6d725ea71 User a custom tag to manage the desktop instead of GUI 2021-11-26 00:03:01 +01:00
0624f421f7 API improvement & bug fix:
- subscribed global event doesnt unsubcribed when process is killed
- process killall API doesnt work as expected
- improve core API
2021-11-25 01:13:16 +01:00
64094c29d5 update 2021-11-24 23:42:55 +01:00
d208aea1b9 update 2021-11-24 23:37:06 +01:00
149566ec5d update 2021-11-24 23:21:23 +01:00
3c67b1c134 fix bug 2021-11-24 22:37:49 +01:00
3a24df169c update announcement system 2021-11-24 22:15:25 +01:00
e345a61269 update bootstrap icons to v.1.7.1 2021-11-21 19:45:02 +01:00
b3d38ccb05 allow multiple files upload in single request 2021-11-21 19:19:09 +01:00
66e96cc0db update VFS API 2021-10-24 20:56:18 +02:00
86a94a817a update GUI API 2021-10-24 20:37:19 +02:00
27ac7c06fc Minor bug fix on desktop handling 2021-10-24 19:44:21 +02:00
99e0d02581 enable setting blur overlay window 2021-10-24 18:34:08 +02:00
52709d5da4 improve Window GUI API 2021-10-24 00:27:17 +02:00
b399958f69 Update README.md 2021-10-15 18:17:26 +02:00
aab242c1f7 cleanup 2021-10-12 22:36:20 +02:00
043214c001 cleanup 2021-10-12 22:35:47 +02:00
9c06d88713 AntOS load automatically custom VFS handles if available 2021-10-12 22:26:40 +02:00
5b125c288d update drone file 2021-10-12 22:04:33 +02:00
8af672748f Update .drone.yml 2021-10-12 22:04:10 +02:00
facf51fe8c Update .drone.yml 2021-10-12 22:03:42 +02:00
90b6783568 Update .drone.yml 2021-10-12 22:02:27 +02:00
c23cb1bfa8 Improve core API:
- improve OS exit API
- improve VFS API
2021-10-12 21:58:57 +02:00
e6e727246d Update README.md 2021-06-13 12:41:13 +02:00
89add530bc Update README.md 2021-06-13 12:39:17 +02:00
27ef66eca8 Update README.md 2021-06-13 12:39:02 +02:00
cf19a79309 Update README.md 2021-06-13 12:35:06 +02:00
81a04675dd Update README.md 2021-06-13 12:34:10 +02:00
18a46bf436 Update README.md 2021-06-13 12:30:10 +02:00
6d078ce5e3 Merge pull request #36 from lxsang/next-1.2.0
Next 1.2.0
2021-06-13 12:12:07 +02:00
df52baf408 Modify readme for release 2021-06-13 12:09:33 +02:00
cd34857753 update default repository 2021-06-13 12:08:39 +02:00
509125d1a3 regen archive 2021-06-13 12:08:39 +02:00
0af320e4c2 fix etract zip API bug 2021-06-13 12:08:38 +02:00
34771ab3ae regen archive 2021-06-13 12:08:38 +02:00
4c96fac5e5 replace CodePad by NotePad 2021-06-13 12:08:37 +02:00
99e3e6308c updata maket place 2021-06-13 12:08:37 +02:00
2be50053a8 enable cache jquery 2021-06-13 12:08:37 +02:00
8b0196a21b force jquery to disable cache 2021-06-13 12:08:37 +02:00
11b8d0b2de force jquery to disable cache 2021-06-13 12:08:36 +02:00
3f05659940 force close xhr after finish 2021-06-13 12:08:36 +02:00
a7cb8e06e1 add timestamp to request 2021-06-13 12:08:36 +02:00
7b0adc5f39 disable keep alive 2021-06-13 12:08:35 +02:00
6c59bbd5d6 disable keep alive 2021-06-13 12:08:35 +02:00
54152e1d9c disable cache in ajax qery 2021-06-13 12:08:35 +02:00
44f4ad1840 disable cache in $.ajax 2021-06-13 12:08:35 +02:00
cac548edf9 disable cache in $.ajax 2021-06-13 12:08:34 +02:00
cbfba153dc regen archive 2021-06-13 12:08:34 +02:00
b8671cbab0 ACE and CodePad now moved to MarketPlace 2021-06-13 12:08:33 +02:00
15011086f7 revert changes 2021-06-13 12:08:33 +02:00
eb27b330a7 update core 2021-06-13 12:08:32 +02:00
f8c19e2a4c minor bug fix on SDK 2021-06-13 12:08:31 +02:00
1568cddafd regen archive 2021-06-13 12:08:30 +02:00
0d9f5efc40 used libantosdk for compiling 2021-06-13 12:08:29 +02:00
5d41281706 used libantosdk for pompiling 2021-06-13 12:08:27 +02:00
b4fb5473e4 update terser setting 2021-06-13 12:08:27 +02:00
096890c136 regen release 2021-06-13 12:08:26 +02:00
a5d93fcd72 enhance vfs path handling 2021-06-13 12:08:25 +02:00
2316994b94 enhance vfs path handling 2021-06-13 12:08:25 +02:00
fd4474d598 Improvement on core API:
- improvement performance of some VFS utility API
- Allow compress/extract zip file in Files
- CodePad/Files/MarketPlace use some common system APIs instead of defining in-package functions
2021-06-13 12:08:23 +02:00
e657c98688 move some API from CodePad to system API 2021-06-13 12:08:22 +02:00
b2e2cbeda5 fix compile bug when coffie files are not available 2021-06-13 12:08:20 +02:00
c57ebdccac regen the latest archive 2021-06-13 12:08:19 +02:00
e2d1a90f73 regen archive 2021-06-13 12:08:18 +02:00
3ca7b91c37 Add support to typescript in CodePad 2021-06-13 12:08:16 +02:00
cdc11089fa fix global lib load bug 2021-06-13 12:08:16 +02:00
b69103107c create a new input file for each upload 2021-06-13 12:08:15 +02:00
25f95360d6 clean file input before upload 2021-06-13 12:08:14 +02:00
d9c372515d fix: upload file caching problem 2021-06-13 12:08:13 +02:00
9f9db3076f fix: upload file caching problem 2021-06-13 12:08:12 +02:00
7123098049 update icon alignment 2021-06-13 12:08:12 +02:00
808623b0d6 update sysmenu style 2021-06-13 12:08:11 +02:00
207480f87f regen distrib 2021-06-13 12:08:10 +02:00
77f643a206 fix iframe window move problem 2021-06-13 12:08:09 +02:00
d670bd3b3a fix resize problem when iframe is inside of window 2021-06-13 12:08:08 +02:00
ac76a5f967 Allow window horizotal and vertical resize 2021-06-13 12:08:07 +02:00
1a132178c3 fix setting save bug 2021-06-13 12:08:05 +02:00
445f25bdbf fix setting save bug 2021-06-13 12:08:04 +02:00
ee03e07325 fix setting save bug 2021-06-13 12:08:03 +02:00
62c56eac9f fix setting save bug 2021-06-13 12:08:03 +02:00
ebb74ebcbc fix setting save bug 2021-06-13 12:08:02 +02:00
e764cef4ac fix setting save bug 2021-06-13 12:08:02 +02:00
af570fecf5 fix key input 2021-06-13 12:08:02 +02:00
726de16035 change category name 2021-06-13 12:08:02 +02:00
51bf3e3df5 Add features:
- add bootstrap icon font support
- classing applications by categories in start menu
2021-06-13 12:08:02 +02:00
78f09934d2 fix open with bug 2021-06-13 12:08:01 +02:00
ebfa49254e drone deploy for next-1.2.0 2021-06-13 12:08:00 +02:00
330028c185 drone deploy for next-1.2.0 2021-06-13 12:08:00 +02:00
20840d09b0 Improvements on GUI + API:
- Add generic key-value dialog
- Allow multiple file upload
- Add ESC and enter key handle to dialog
- Improve File application
2021-06-13 12:08:00 +02:00
f7a3373d23 update make file 2021-06-13 12:07:59 +02:00
5ca29f5103 fix max recent entries number in codepad 2021-06-13 12:07:58 +02:00
738eb1f831 Remove loading animation in push notification service 2021-06-13 12:07:57 +02:00
d384a65b73 Add features: 2021-06-13 12:07:57 +02:00
26a35a6c70 improve css resizer style 2021-06-13 12:07:56 +02:00
26e5e9bb53 Fix recent file bug in CodePad 2021-06-13 12:07:54 +02:00
bc30261bda Fix shortcut bug 2021-06-13 12:07:52 +02:00
7cd2f97b75 Add features:
- allow pinning apps in Setting
- pinned apps in system pannel
- Services manager in Setting
- Fix and impprove some CSS bug
2021-06-13 12:07:52 +02:00
26c397838b fix MarketPlace scheme bug 2021-06-13 12:07:52 +02:00
d067fa4f21 self-closing xml should not be used in custom tag scheme 2021-06-13 12:07:52 +02:00
9a12e9176f minor improvement on GUI API 2021-06-13 12:07:52 +02:00
de5878c349 Add features:
- Improvement application list in market place
- Allow triplet keyboard shortcut in GUI
- CodePad allows setting shortcut in CommandPalette commands
- CodePad should have recent menu entry that remember top n file opened
- Improve File application grid view
- Label text should be selectable
2021-06-13 12:07:51 +02:00
94a0c097a8 Add minor features:
- File dialog should remember last opened folder
- Add dynamic key-value dialog that work on any object
- Window list panel should show window title in tooltip when mouse hovering on application icon
- Improvement application list in market place
2021-06-13 12:07:51 +02:00
4ee88d0243 Update README.md 2021-03-27 18:56:08 +01:00
78c9cbff7d Update README.md 2021-03-27 18:54:33 +01:00
5c9fd5ef79 Update README.md 2021-03-25 23:39:26 +01:00
bb3df6fedd Update README.md 2021-03-25 23:35:38 +01:00
3de67da568 Update README.md 2021-03-25 23:35:23 +01:00
8183b9fa21 Update README.md 2021-03-25 22:58:53 +01:00
5ba938c6a4 Update .drone.yml 2021-03-25 22:57:26 +01:00
ca69b3ec81 Update .drone.yml 2021-01-31 19:45:08 +01:00
3f5d415198 Update README.md 2021-01-27 20:25:47 +01:00
24a69c1585 Update README.md 2021-01-27 20:11:31 +01:00
1f1f4fee5d Update .drone.yml 2021-01-27 19:44:51 +01:00
ca5031090b Update .drone.yml 2021-01-27 19:00:44 +01:00
efc4651037 Update .drone.yml 2021-01-27 18:58:31 +01:00
24aa3f2071 Update .drone.yml 2021-01-27 18:44:53 +01:00
792897a81d Update .drone.yml 2021-01-27 18:41:02 +01:00
f9ee54b763 Update .drone.yml 2021-01-27 18:39:03 +01:00
d474b2dcb4 Update .drone.yml 2021-01-27 18:35:31 +01:00
7073a0ee3b Update .drone.yml 2021-01-27 18:33:29 +01:00
70ef8895f5 Update .drone.yml 2021-01-27 18:31:33 +01:00
cdec805b07 Update .drone.yml 2021-01-27 18:26:03 +01:00
1c8ab443e3 Update .drone.yml 2021-01-27 18:22:22 +01:00
7a5f15fd03 Update .drone.yml 2021-01-27 18:19:27 +01:00
c559b72193 Update .drone.yml 2021-01-27 18:18:04 +01:00
7c01e58a8d Update .drone.yml 2021-01-27 18:13:03 +01:00
e6d2305a38 Update .drone.yml 2021-01-27 18:08:25 +01:00
1791e581df Update .drone.yml 2021-01-27 18:06:34 +01:00
54054fc48a Update .drone.yml 2021-01-27 18:04:38 +01:00
7cd49d1bb3 Update .drone.yml 2021-01-27 17:59:07 +01:00
7bedac9f98 Create .drone.yml 2021-01-27 17:58:31 +01:00
08c3fd8ffb Delete TODO 2021-01-02 13:56:22 +01:00
7488f4625c sort file in fileview tag 2021-01-02 13:47:59 +01:00
5b6e6f8d94 sort file in fileview tag 2021-01-02 13:35:56 +01:00
f3f3559393 regenerate ar 2020-12-25 22:38:52 +01:00
f70686d9ca codepad extension is now stored on user home 2020-12-21 18:22:34 +01:00
9fe744fde7 Improvement antos API + bugs fixed 2020-12-21 15:55:02 +01:00
235a9334d6 fix codepad extension API bugs 2020-12-20 19:38:34 +01:00
7d3ff0f206 add comments to new code 2020-12-20 17:51:51 +01:00
05cea66870 Improvement + bug fix
- Some minor bug fix
- Major change: allow split view in CodePad, make CodePad API portable so that it is easy to use another editor
other than ACE in the futures (such as monaco editor)
2020-12-20 16:45:51 +01:00
6ee3861be6 remove simpleMDE from base library 2020-12-18 21:51:12 +01:00
566592dc8e Improvement + fix bug:
- improve Tag API
- Improve CodePad UI + Extension API
- Add package dependencies to Market Place
2020-12-18 19:51:19 +01:00
6c935e88ee Improve some tags + add features to codepad
- Improve Resizer + TabContainer tag
- Add bottom bar to the CodePad editor
2020-12-16 00:57:29 +01:00
9d5a0b404c fix path bug 2020-11-19 21:35:49 +01:00
43f96af920 add release archive 2020-11-19 17:39:30 +01:00
cc4081f9a0 allow enable/disable grid resize feature 2020-09-24 18:41:30 +02:00
45d425d5db add resizer to grid view 2020-09-24 18:22:16 +02:00
99a029bdd3 allow building standarlone afx GUI API 2020-09-18 13:20:22 +02:00
5ef3bfe7f0 fix notification error on service 2020-08-06 23:15:38 +02:00
2b67084392 fix path error on PackageFileHandle 2020-08-04 20:00:34 +02:00
b1649016f4 add PackageFileHandle + support for library definition + minor fix 2020-08-04 19:51:32 +02:00
b86565212a minor fix on float list 2020-07-14 23:20:15 +02:00
4cb01bff05 Update .travis.yml 2020-07-14 22:33:11 +02:00
1d2792a06b Update .travis.yml 2020-07-14 22:31:35 +02:00
5dd9c85125 Update .travis.yml 2020-07-14 22:29:19 +02:00
56ae39d020 minor fix on button event 2020-07-14 17:43:45 +02:00
47a22559d8 Merge branch 'master' of https://github.com/lxsang/antos 2020-07-10 16:07:03 +02:00
ec90e010dc add input type to prompt dialog 2020-07-10 16:06:49 +02:00
644105c8de Update README.md 2020-06-26 15:57:04 +02:00
64207df15e Enhance CodePad 2020-06-26 15:03:22 +02:00
edfcc72080 Merge branch 'master' of https://github.com/lxsang/antos 2020-06-26 14:41:58 +02:00
1bbbd49ce9 Enhance REST URI 2020-06-26 14:41:44 +02:00
63c1b74514 Delete screenshot.png 2020-06-26 10:36:56 +02:00
d43da56adf Update README.md 2020-06-26 01:38:00 +02:00
dcf544904c Update README.md 2020-06-26 01:35:19 +02:00
d05b3474c0 fix make file 2020-06-26 01:17:02 +02:00
af767e57fe Update README.md 2020-06-26 00:26:29 +02:00
160bb1281f Update README.md 2020-06-25 22:24:51 +02:00
f00fdb2424 enhance core tag API 2020-06-25 22:10:48 +02:00
c19fc26686 Update BaseModel.ts 2020-06-25 14:56:07 +02:00
b54fdbedf6 Update NSpinnerTag.ts 2020-06-25 14:54:38 +02:00
cb318c0547 export slider tag to API 2020-06-24 21:49:05 +02:00
352a1c8c17 Enhance File 2020-06-24 19:58:15 +02:00
3d0595eab4 update showdown to new lib 2020-06-24 19:53:28 +02:00
52bc2df658 add a test on string trimming API 2020-06-22 13:11:31 +02:00
c3452a6bbe Update .travis.yml 2020-06-22 13:01:17 +02:00
32a86090c0 Merge branch 'master' of https://github.com/lxsang/antos 2020-06-22 12:58:24 +02:00
377edcffb4 fix API overriden bug 2020-06-22 12:58:10 +02:00
1f7fbbc015 Update .travis.yml 2020-06-22 12:49:34 +02:00
090884d765 Update .travis.yml 2020-06-22 12:47:22 +02:00
764d44388e Update Makefile 2020-06-22 12:43:33 +02:00
83bfb86469 Merge branch 'master' of https://github.com/lxsang/antos 2020-06-22 12:37:57 +02:00
ae11223c0c fix some travis bug 2020-06-22 12:37:34 +02:00
e7e7006cdf Update .travis.yml 2020-06-22 12:29:54 +02:00
9281cfb8db fix some travis bug 2020-06-22 12:27:02 +02:00
297aecc53c Update .travis.yml 2020-06-22 12:26:13 +02:00
1c4a744486 Update .travis.yml 2020-06-22 12:17:20 +02:00
f9196641de Update .travis.yml 2020-06-22 12:14:40 +02:00
31fe990fe6 Update .travis.yml 2020-06-22 12:10:25 +02:00
684c08a36a add test to travis job 2020-06-22 12:07:23 +02:00
6d85427225 add test to travis job 2020-06-22 12:01:05 +02:00
b72d5a5de3 Update .travis.yml 2020-06-22 11:53:52 +02:00
7a619ac3a8 Merge pull request #4 from lxsang/antos-1.0.0a
Antos 1.0.0a
2020-06-22 11:33:33 +02:00
a2acafa3d0 v1.0.0a ready 2020-06-22 11:28:44 +02:00
4a941f0467 add more docs 2020-06-18 17:09:00 +02:00
023e61c01e fix unescape 2020-06-17 23:56:08 +02:00
5ff637138c add docs 2020-06-17 21:06:55 +02:00
1812f25d8a add docs on coreapi 2020-06-16 18:02:17 +02:00
977bf0c62f add more docs, fix minor bug on dialog registration 2020-06-15 18:10:13 +02:00
218f6142c6 add more doc 2020-06-12 20:24:39 +02:00
d5df803c2c Finish tag documenting 2020-06-11 18:43:20 +02:00
6ea3be79f4 add some JSDoc to the code 2020-06-10 11:20:41 +02:00
6365081f56 add some JSDoc to the code 2020-06-10 11:15:01 +02:00
edbffeb2b8 Enhance core API
- Add trim function to core API
- Minor bug fix
2020-06-06 18:41:43 +02:00
598c11b2c3 fix codepad close error when file is not saved 2020-06-06 00:33:21 +02:00
473d9c6575 fix safari compatibility 2020-06-05 23:42:15 +02:00
f84da77825 enhance the core API and default application 2020-06-05 17:46:04 +02:00
fb462fe31b add more type, all defaults apps are now in typescript 2020-06-04 17:49:48 +02:00
6e95994892 add more test, convert CodePad to type scipt 2020-06-03 23:43:08 +02:00
af496b0aac add test and fix some tags 2020-05-31 23:37:36 +02:00
ced3777d30 fix old system dialog is not destroyed after closed 2020-05-29 23:34:35 +02:00
1c32f2010c Switch from coffee script to typescrit
- Better tooling support
- Stronger type checking
- Unit test with jest
- JSDocc support
2020-05-29 22:22:00 +02:00
759cd1fc6f switch to es6 from coffeescript 2020-05-24 13:17:59 +02:00
f11509120e refactory code 2020-05-24 11:52:22 +02:00
0c93ff7c9c Enhance dialog api 2020-05-23 20:07:32 +02:00
9f184fc19b Some major changes:
- Enhance the error report system
- The SDK now allows to create sub-directory when building and releasing
2020-05-22 18:31:06 +02:00
0abb49dadc fix spotlight behavior 2020-05-21 22:22:46 +02:00
0b27af3e9d fix Files bug 2020-05-21 22:16:24 +02:00
5c08a189cc minor fix bug 2020-05-21 21:54:43 +02:00
61a0196c79 add dialog + enhance clipboard api 2020-05-21 17:26:59 +02:00
b9ad064928 disable cache when downloading packagds 2020-05-21 15:18:49 +02:00
f1c4201270 fix system loading handle 2020-05-21 14:49:55 +02:00
52ac96c18a add wait cursor when system is loading 2020-05-21 13:10:35 +02:00
a3960766d1 fix repository loading 2020-05-20 23:47:31 +02:00
9008396ac9 core services renamed to syslog 2020-05-20 23:11:58 +02:00
33eab1d30a support drag and drop in file 2020-05-19 23:29:31 +02:00
53df50f196 add draggable treeview 2020-05-19 20:00:56 +02:00
ff6f480f08 add market place 2020-05-19 01:03:49 +02:00
d1e8fd91dc support locale at application level using package.json 2020-05-18 19:41:11 +02:00
d1496c6530 move non basic app to another repository 2020-05-18 19:04:44 +02:00
40b06f8f1a add darkmode themes, clean up 2020-05-18 18:53:59 +02:00
c94cb0963d add extension maker 2020-05-17 23:58:21 +02:00
a29a449eaf minor fix 2020-05-17 17:08:10 +02:00
fa22fc5413 use fat arrow for this reference 2020-05-17 17:05:12 +02:00
95cfddb389 add extension to codepad 2020-05-17 01:40:18 +02:00
e20ee262fc finish CodePad 2020-05-15 20:55:13 +02:00
74534e976c functional command palette 2020-05-14 23:08:57 +02:00
d2de593974 set ace as defaut editor 2020-05-14 01:03:44 +02:00
e1591087d5 adapt more apps 2020-05-13 19:03:39 +02:00
04e5a4d50c adapt apps 2020-05-12 21:08:39 +02:00
c53fd8bd88 refactory code 2020-05-11 19:52:31 +02:00
f5e5d8501b adapt the system with the new api 2020-05-10 21:39:42 +02:00
ae54c9a10c remove riot and use the new tag engine 2020-05-09 23:32:26 +02:00
6c0f304767 appdock 2020-05-05 21:39:45 +02:00
710491bc66 add more tags 2020-05-05 17:52:54 +02:00
fe36693879 calendar, color picker, tabcontainer 2020-04-28 15:59:18 +02:00
c46049fd0c add float list 2020-04-27 18:50:52 +02:00
8a441e5a59 tab container 2020-04-26 21:08:26 +02:00
56a79e2db0 add more tags 2020-04-14 20:58:00 +02:00
64f055e7ce add tree-view 2020-04-05 22:52:49 +02:00
9924c642c5 add tree-view 2020-04-05 22:51:31 +02:00
8b44c3b3c0 add tab bar 2020-04-03 16:34:52 +02:00
5b21696fa1 grid view update 2020-04-03 13:49:12 +02:00
60e50d2ceb fi 2020-04-02 19:13:45 +02:00
3a47785a47 add tags 2020-04-02 18:33:07 +02:00
4fda59a964 fix 2020-04-02 10:29:52 +02:00
328a37320e menu update 2020-03-13 18:36:00 +01:00
45ec9e4792 add more tag 2020-03-11 18:36:38 +01:00
ed03c72e3b add tag 2020-03-02 17:59:38 +01:00
5d987f8fb6 multiselect 2020-02-28 18:59:41 +01:00
0de9f88dcf add more tagesr 2020-02-28 18:35:20 +01:00
2a990491d9 add more tag 2020-02-27 18:06:05 +01:00
ffdb5b5f8a some tag 2020-02-25 18:28:08 +01:00
2656c21cdb gui 2020-02-21 17:56:45 +01:00
d50d884733 cleanup 2020-02-20 18:28:03 +01:00
743 changed files with 101315 additions and 53891 deletions

20
.drone.yml Normal file
View File

@ -0,0 +1,20 @@
---
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/1.2.1/release/antos-1.2.1.tar.gz
- name: build
commands:
- cd /opt/www/htdocs/os && tar xvzf antos-1.2.1.tar.gz
- rm /opt/www/htdocs/os/antos-1.2.1.tar.gz
trigger:
branch:
- 1.2.1

7
.gitignore vendored
View File

@ -1,3 +1,8 @@
build
node_modules
.DS_Store
.DS_Store
package-lock.json
dist
docs
coffees
.vscode

View File

@ -1,11 +0,0 @@
language: javascript
node_js:
- 7
install:
- npm install -g coffeescript
- npm install -g uglify-es
- npm install -g uglifycss
- npm install -g riot-cli
script:
- make
- make release

44
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,44 @@
pipeline{
agent { node{ label'workstation' }}
options {
// Limit build history with buildDiscarder option:
// daysToKeepStr: history is only kept up to this many days.
// numToKeepStr: only this many build logs are kept.
// artifactDaysToKeepStr: artifacts are only kept up to this many days.
// artifactNumToKeepStr: only this many builds have their artifacts kept.
buildDiscarder(logRotator(numToKeepStr: "1"))
// Enable timestamps in build log console
timestamps()
// Maximum time to run the whole pipeline before canceling it
timeout(time: 1, unit: 'HOURS')
// Use Jenkins ANSI Color Plugin for log console
ansiColor('xterm')
// Limit build concurrency to 1 per branch
disableConcurrentBuilds()
}
stages
{
stage('Build release') {
steps {
sh'''
cd $WORKSPACE
npm install terser
npm install uglifycss
npm install typescript
npm install @types/jquery
buildir="build"
[ -d "$buildir" ] && rm -rf "$buildir"
export BUILDDIR="$WORKSPACE/$buildir/opt/www/htdocs/os"
make release
'''
script {
// only useful for any master branch
//if (env.BRANCH_NAME =~ /^master/) {
archiveArtifacts artifacts: 'd.ts/, build/', fingerprint: true
//}
}
}
}
}
}

236
Makefile
View File

@ -1,8 +1,16 @@
BUILDDIR?=build/htdocs/os
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.1
BRANCH = b
BUILDID=$(shell git rev-parse --short HEAD)
GSED=sed
UNAME_S := $(shell uname -s)
@ -10,89 +18,152 @@ ifeq ($(UNAME_S),Darwin)
GSED=gsed
endif
tags = dist/core/tags/tag.js \
dist/core/tags/WindowTag.js \
dist/core/tags/TileLayoutTags.js \
dist/core/tags/ResizerTag.js \
dist/core/tags/LabelTag.js \
dist/core/tags/ButtonTag.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 \
dist/core/tags/TreeViewTag.js \
dist/core/tags/SliderTag.js \
dist/core/tags/FloatListTag.js \
dist/core/tags/CalendarTag.js \
dist/core/tags/ColorPickerTag.js \
dist/core/tags/FileViewTag.js \
dist/core/tags/OverlayTag.js \
dist/core/tags/AppDockTag.js \
dist/core/tags/SystemPanelTag.js \
dist/core/tags/DesktopTag.js
coffees= src/core/core.coffee\
src/core/api.coffee\
src/core/settings.coffee\
src/core/handlers/RemoteHandler.coffee\
src/core/vfs.coffee\
src/core/vfs/GoogleDriveHandler.coffee\
src/core/db.coffee\
src/core/gui.coffee\
src/core/BaseModel.coffee\
src/core/BaseApplication.coffee\
src/core/BaseService.coffee\
src/core/BaseEvent.coffee\
src/core/BaseDialog.coffee\
src/antos.coffee
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 \
dist/core/BaseDialog.js \
$(tags) \
dist/core/gui.js \
dist/core/pm.js \
dist/bootstrap.js
antfx = $(tags) \
dist/core/Announcerment.js
packages = Syslog Files MarketPlace Setting NotePad
packages = CoreServices NotePad wTerm ActivityMonitor Files MarkOn MarketPlace Preview Setting
main: initd build_coffees build_tags build_themes schemes libs build_packages languages
main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/
- cp README.md $(BUILDDIR)/
initd:
- mkdir -p $(BUILDDIR)
lite: build_coffees build_tags build_themes schemes build_packages
lite: build_javascripts build_themes build_packages
#%.js: %.coffee
# coffee --compile $<
# coffee --compile $<
build_coffees:
@echo "$(BLUE)Building coffee files$(NC)"
- mkdir $(BUILDDIR)/scripts
ts:
-rm -rf dist
$(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
#-rm src/packages/CodePad/libs/corelib.d.ts.zip
#zip -j src/packages/CodePad/libs/corelib.d.ts.zip /tmp/corelib.d.ts
standalone_tags: ts
@echo "$(BLUE)Bundling standalone tags files$(NC)"
- mkdir -p $(BUILDDIR)
- rm $(BUILDDIR)/afx*
#echo "(function() {" > $(BUILDDIR)/scripts/antos.js
for f in $(antfx); do \
(cat "$${f}"; echo) >> dist/afx.js;\
rm "$${f}";\
done
echo "var Ant=this;" >> dist/afx.js
$(UGLIFYJS) dist/afx.js --compress --mangle --output $(BUILDDIR)/afx.js
# standalone theme
@for f in src/themes/system/afx-*.css; do \
if [ "$$f" != "src/themes/system/antos.css" ]; then \
echo "$$f"; \
(cat "$${f}"; echo) >> $(BUILDDIR)/afx.css; \
fi;\
done
@for f in src/themes/antos_light/afx-*.css; do \
if [ "$$f" != "src/themes/antos_light/antos.css" ]; then \
echo "$$f"; \
(cat "$${f}"; echo) >> $(BUILDDIR)/afx.css; \
fi;\
done
# $(UGLIFYCSS) --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css
rm -r dist/core
build_javascripts: ts
@echo "$(BLUE)Bundling javascript files$(NC)"
- mkdir -p $(BUILDDIR)/scripts
- rm $(BUILDDIR)/scripts/antos.js
- rm $(BUILDDIR)/scripts/antos.coffee
for f in $(coffees); do (cat "$${f}"; echo) >> $(BUILDDIR)/scripts/antos.coffee; done
coffee --compile $(BUILDDIR)/scripts/antos.coffee
- rm $(BUILDDIR)/scripts/antos.coffee
#echo "(function() {" > $(BUILDDIR)/scripts/antos.js
for f in $(javascripts); do \
(cat "$${f}"; echo) >> dist/antos.js;\
rm "$${f}";\
done
echo 'OS.VERSION.version_string = "$(VERSION)-$(BRANCH)-$(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
libs:
@echo "$(BLUE)Copy lib files$(NC)"
cp -rf src/libs/* $(BUILDDIR)/scripts/
schemes:
@echo "$(BLUE)Copy schemes files$(NC)"
- mkdir -p $(BUILDDIR)/resources/schemes
cp src/core/schemes/* $(BUILDDIR)/resources/schemes/
testdata:
@echo "$(BLUE)Copy JSON test files$(NC)"
- mkdir -p $(BUILDDIR)/resources/jsons
cp src/core/handlers/jsons/* $(BUILDDIR)/resources/jsons
build_tags:
@echo "$(BLUE)Building tag files$(NC)"
-mkdir $(BUILDDIR)/resources
-rm $(BUILDDIR)/resources/antos_tags.js
for f in src/core/tags/*; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/antos_tags.js; done
languages:
-mkdir $(BUILDDIR)/resources
-mkdir $(BUILDDIR)/resources/languages
-mkdir -p $(BUILDDIR)/resources
-mkdir -p $(BUILDDIR)/resources/languages
cp src/core/languages/*.json $(BUILDDIR)/resources/languages/
genlang:
read -r -p "Enter locale: " LOCAL;\
./src/core/languages/gen.sh ./src ./src/core/languages/$$LOCAL.json
build_themes: antos_themes_build
cp -r src/themes/system $(BUILDDIR)/resources/themes/
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/wp $(BUILDDIR)/resources/themes/system
for f in src/themes/system/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/system.css;done
antos_themes_build:
@echo "$(BLUE)Building themes name: antos$(NC)"
-rm -rf $(BUILDDIR)/resources/themes/antos/*
-mkdir -p $(BUILDDIR)/resources/themes/antos
for f in src/themes/antos/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/antos/antos.css;done
-mkdir -p $(BUILDDIR)/resources/themes/antos/fonts
cp -rf src/themes/antos/fonts/* $(BUILDDIR)/resources/themes/antos/fonts
cp src/themes/antos/wp* $(BUILDDIR)/resources/themes/antos/
antos_light:
@echo "$(BLUE)Building themes name: antos-light$(NC)"
-rm -rf $(BUILDDIR)/resources/themes/antos_light/*
-mkdir -p $(BUILDDIR)/resources/themes/antos_light
for f in src/themes/antos_light/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/antos_light/antos_light.css;done
antos_dark:
@echo "$(BLUE)Building themes name: antos-dark$(NC)"
-rm -rf $(BUILDDIR)/resources/themes/antos_dark/*
-mkdir -p $(BUILDDIR)/resources/themes/antos_dark
for f in src/themes/antos_dark/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css;done
build_packages:
- mkdir $(BUILDDIR)/packages
- mkdir -p $(BUILDDIR)/packages
- for d in $(packages); do ( test -d $(BUILDDIR)/packages/$$d && rm -rf $(BUILDDIR)/packages/$$d/* ); done
for d in $(packages); do (cd src/packages/$$d; make);done
for d in $(packages); do ( test -d $(BUILDDIR)/packages/$$d || mkdir -p $(BUILDDIR)/packages/$$d && cp -rf src/packages/$$d/build/* $(BUILDDIR)/packages/$$d/);done
@ -104,42 +175,61 @@ package:
cd src/packages/$$PKG && make;\
cd ../../../;\
test -d $(BUILDDIR)/packages/$$PKG || mkdir -p $(BUILDDIR)/packages/$$PKG;\
cp -rf src/packages/$$PKG/build/* $(BUILDDIR)/packages/$$PKG/;\
cp -rfv src/packages/$$PKG/build/* $(BUILDDIR)/packages/$$PKG/;\
test -d src/packages/$$PKG/build && rm -r src/packages/$$PKG/build;
pkgar:
read -r -p "Enter package name: " PKG;\
echo $$PKG | make package &&\
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;\
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:
# uglify antos.js
# npm install uglify-es -g
# npm install uglify-js -g
uglifyjs $(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 riot-cli -g
riot --ext js $(BUILDDIR)/resources/antos_tags.js $(BUILDDIR)/resources/antos_tags.js
uglifyjs $(BUILDDIR)/resources/antos_tags.js --compress --mangle --output $(BUILDDIR)/resources/antos_tags.js
$(GSED) -i 's/resources\/antos_tags.js/scripts\/riot.min.js/g' $(BUILDDIR)/index.html
$(GSED) -i 's/scripts\/riot.compiler.min.js/resources\/antos_tags.js/g' $(BUILDDIR)/index.html
$(GSED) -i 's/type=\"riot\/tag\"/ /g' "$(BUILDDIR)/index.html"
# npm install uglifycss -g
# npm install $(UGLIFYCSS) -g
# uglify the css
uglifycss --output $(BUILDDIR)/resources/themes/antos/antos.css $(BUILDDIR)/resources/themes/antos/antos.css
uglifycss --output $(BUILDDIR)/resources/themes/system/font-awesome.css $(BUILDDIR)/resources/themes/system/font-awesome.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 && 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;\
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:
-[ -d /tmp/antos-$(VERSION) ] && rm -r /tmp/antos-$(VERSION)
-[ -f /tmp/antos-$(VERSION).tar.gz ] && rm /tmp/antos-$(VERSION).tar.gz
mkdir /tmp/antos-$(VERSION)
BUILDDIR=/tmp/antos-$(VERSION) make release
cd /tmp/antos-$(VERSION) && tar cvzf ../antos-$(VERSION).tar.gz .
mv /tmp/antos-$(VERSION).tar.gz release/
echo -n $(VERSION) > release/latest
release: main uglify
doc:
./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS API" --out $(DOCDIR)
test: build_javascripts
jest
clean:
rm -rf $(BUILDDIR)/*
rm -rf $(BUILDDIR)/resources
rm -rf $(BUILDDIR)/scripts
rm -rf $(BUILDDIR)/packages
rm -rf $(BUILDDIR)/index.html

117
README.md
View File

@ -1,34 +1,116 @@
# antOS
[![Build Status](https://travis-ci.org/lxsang/antos.svg?branch=master)](https://travis-ci.org/lxsang/antos)
# antOS v1.2.1
[![Build Status](https://ci.iohub.dev/buildStatus/icon?job=gitea-sync%2Fantos%2Fmaster)](https://ci.iohub.dev/job/gitea-sync/job/antos/job/master/)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flxsang%2Fantos.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
Server or Embedded Linux are often headless, so accessing the resource on these systems is not always obvious. The aim of this project is to develop a client core API that enables desktop like experience to remote server's resource accessing using web technologies. AntOS is based on jQuery and Riot, it is designed to be used along with our [**antd**](https://github.com/lxsang/ant-http) server and Lua based server side app, but it can also be adapted to any server side languages (PHP, etc) and server, by implementing all the system calls API defined in core/handlers/RemoteHandler.coffee. Basically, application design for the web os relies on these system calls to communicating with the server. The API defines the core UI, system calls (to server), Virtual File system, virtual database and the necessary libraries for easing the development of webOS's applications. Applications can be developped with coffee/javascript/css without the need of a server side script.
AntOS is a web-based desktop platform that features a window manager, application APIs, GUI toolkit, file system abstractions, application store, and an API and SDK for in-browser application development. The purpose of this project is to enable users to easily set up a self-hosted, cloud-based working environment using only a web browser. The front-end can connect to a remote server and act as a virtual desktop environment (VDE).
Note that, the development of the project is in early alpha state, so bugs are very welcome :)
The WebOS is tested on recent Firefox, Chrome and Safari, however i did not test it on IE or Edge since i have no Windows device :)
AntOS can be used in several application contexts, such as:
- Providing visual tools to access and control resources on remote servers and embedded Linux environments
- Providing and developing SaaS web-based applications
- Self-hosting a cloud-based working environment
- Creating a customized, user-friendly interface for managing and interacting with cloud-based resources and services
- Setting up a collaborative, online workspace for remote teams and distributed organizations
- Building a web-based operating system that can run on various devices, including laptops, tablets, and smartphones
- Creating a virtualized environment for testing and deploying web-based applications in a sandboxed environment
- Building a platform for creating and hosting web-based educational or training content
- Setting up a web-based development environment for prototyping and building web-based applications quickly and easily
- Etc, You name it!
With the provided application API and SDK, AntOS facilitates the development and deployment of user-specific applications inside the VDE environment
![https://github.com/lxsang/antos/raw/master/antos-shot.png](https://github.com/lxsang/antos/raw/master/antos-shot.png)
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
## Demo
A demo of the web desktop is available at my page [https://os.lxsang.me](https://os.lxsang.me) using username: demo and password: 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.
![Screenshot](screenshot.png "Screenshot")
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
## AntOS applications (Available on the MarketPlace)
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
## Build
Note that this is only the client API, to make it work for your application, you need to implement all the system calls in core/handlers/RemoteHandler.coffee using a server side scripting language (e.g. PHP). I'm planning to release an API documentation which describes what need to be sent and what will be returned for each system call in near future (i'm kind of very busy right now :) ).
## Documentation
I'm a big fan of the Make system, so i use it as a build system for all of my projects. So, to build AntOS:
1. You need to have *make* installed. Then since most of the API is written in Coffee script, you will need it to be installed too.
2. Edit the BUILDDIR variable in the Makefile file to point to where you want to put the built API
3. Type `make` then you are good to go.
- Documentation: [https://doc.iohub.dev/antos](https://doc.iohub.dev/antos)
- API: [https://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
It you have any problem, please contact me or open an issue, i'll try to response ASAP.
## Change logs
* 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
- [x] Allow pinning application to system panel
- [x] Improvement application list in market place
- [x] Allow triplet keyboard shortcut in GUI
- [x] CodePad allows setting shortcut in CommandPalette commands
- [ ] Improvement multi-window application support
- [x] CodePad should have recent menu entry that remember top n file opened
- [x] Improve File application grid view
- [x] Label text should be selectable
- [x] switch window using shortcut
- [x] Loading bar animation on system pannel
- [x] Multiple file upload support
- [x] Generic key-value dialog
- [x] Add bootstrap font support for icons
- [x] Class applications by categories in start menu
- [x] Support vertical and horizontal resize window
* Market place now classifies application by categories
* CodePad is no longer default system application, it has been moved to MarketPlace
* More applications added to MarketPlace
* Antos SDK
- SDK is no longer included in base Antos release, it can be installed via MarketPlace
- The SDK now has a generic API that can be used in different development tasks other than AntOS application
- Heavy SDK tasks are now offloaded to workers
- 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)
## Licence
Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
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
@ -45,3 +127,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**

2
TODO
View File

@ -1,2 +0,0 @@
# Remove riot dependecies
# Rewrite the UI system

View File

@ -1 +0,0 @@
theme: jekyll-theme-slate

BIN
antos-shot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

10503
d.ts/antos.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

27299
d.ts/core.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

33477
d.ts/jquery.d.ts vendored Normal file

File diff suppressed because it is too large Load Diff

8
jest.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
roots: ['<rootDir>'],
transform : {
'^.+\\.ts$': 'ts-jest'
},
testRegex: '(/tests/test.*|(\\.|/)(test|spec))\\.[tj]s?$',
moduleFileExtensions: ['js', 'ts'],
}

BIN
release/antos-1.0.0.tar.gz Normal file

Binary file not shown.

BIN
release/antos-1.1.2.tar.gz Normal file

Binary file not shown.

BIN
release/antos-1.2.0.tar.gz Normal file

Binary file not shown.

BIN
release/antos-1.2.1.tar.gz Normal file

Binary file not shown.

1
release/latest Normal file
View File

@ -0,0 +1 @@
1.2.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -1,26 +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/.
_GUI = self.OS.GUI
_API = self.OS.API
_PM = self.OS.PM
_OS = self.OS
_courrier = self.OS.courrier
this.onload = () ->
($ document).on 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', ()->
_GUI.fullscreen = not _GUI.fullscreen
self.OS.boot()

25
src/bootstrap.ts Normal file
View File

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

416
src/core/Announcerment.ts Normal file
View File

@ -0,0 +1,416 @@
// 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 {
/**
* 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
*
* @export
* @interface ObservableEntryType
*/
export interface ObservableEntryType {
/**
* A Set of callbacks that should be called only once.
* These callbacks will be removed after the first
* occurrence of the corresponding event
*
* @memberof ObservableEntryType
*/
one: Set<(d: any) => void>;
/**
* A Set of callbacks that should be called
* every time the corresponding event is triggered
*
* @memberof ObservableEntryType
*/
many: Set<(d: any) => void>;
}
/**
* Announcement listener type definition
*
* @export
* @interface AnnouncerListenerType
*/
export interface AnnouncerListenerType {
[index: number]: {
/**
* The event name
*
* @type {string}
*/
e: string;
/**
* The event callback
*
*/
f: (d: any) => void;
}[];
}
/**
* This class is the based class used in AntOS event
* announcement system.
* It implements the observer pattern using simple
* subscribe/publish mechanism
* @export
* @class Announcer
*/
export class Announcer {
/**
* The observable object that stores event name
* and its corresponding callback in [[ObservableEntryType]]
*
* @type {GenericObject<ObservableEntryType>}
* @memberof Announcer
*/
observable: GenericObject<ObservableEntryType>;
/**
* Enable/disable the announcer
*
* @type {boolean}
* @memberof Announcer
*/
enable: boolean;
/**
*Creates an instance of Announcer.
* @memberof Announcer
*/
constructor() {
this.observable = {};
this.enable = true;
}
/**
* Disable the announcer, when this function is called
* all events and their callbacks will be removed
*
* @returns
* @memberof Announcer
*/
disable() {
this.off("*");
return (this.enable = false);
}
/**
* Subscribe to an event, the callback will be called
* every time the corresponding event is trigged
*
* @param {string} evtName event name
* @param {(d: any) => void} callback The corresponding callback
* @returns {void}
* @memberof Announcer
*/
on(evtName: string, callback: (d: any) => void): void {
if (!this.enable) {
return;
}
if (!this.observable[evtName]) {
this.observable[evtName] = {
one: new Set(),
many: new Set(),
};
}
this.observable[evtName].many.add(callback);
}
/**
* Subscribe to an event, the callback will
* be called only once and then removed from the announcer
*
* @param {string} evtName event name
* @param {(d: any) => void} callback the corresponding callback
* @returns {void}
* @memberof Announcer
*/
one(evtName: string, callback: (d: any) => void): void {
if (!this.enable) {
return;
}
if (!this.observable[evtName]) {
this.observable[evtName] = {
one: new Set(),
many: new Set(),
};
}
this.observable[evtName].one.add(callback);
}
/**
* Unsubscribe the callback from an event
*
* @param {string} evtName event name
* @param {(d: any) => void} [callback] the callback to be unsubscribed.
* When the `callback` is `*`, all callbacks related to `evtName` will be
* removed
* @memberof Announcer
*/
off(evtName: string, callback?: (d: any) => void): void {
const fn = (evt: string, cb: (d: any) => void) => {
if (!this.observable[evt]) {
return;
}
if (cb) {
this.observable[evt].one.delete(cb);
return this.observable[evt].many.delete(cb);
} else {
if (this.observable[evt]) {
return delete this.observable[evt];
}
}
};
if (evtName === "*") {
for (let k in this.observable) {
fn(k, callback);
}
} else {
fn(evtName, callback);
}
}
/**
* Trigger an event
*
* @param {string} evtName event name
* @param {*} data data object that will be send to all related callback
* @returns {void}
* @memberof Announcer
*/
trigger(evtName: string, data: any): void {
const trig = (name: string, d: any) => {
const names = [name, "*"];
for (let evt of names) {
if (!this.observable[evt]) {
continue;
}
this.observable[evt].one.forEach((f) => f(d));
this.observable[evt].one = new Set();
this.observable[evt].many.forEach((f) => f(d));
}
};
if (evtName === "*") {
for (let k in this.observable) {
const v = this.observable[k];
if (k !== "*") {
trig(k, data);
}
}
} else {
return trig(evtName, data);
}
}
}
}
/**
* This namespace defines every thing related to the system announcement.
*
* The system announcement provides a global way to communicate between
* processes (applications/services) using the subscribe/publish
* mechanism
*/
export namespace announcer {
/**
* The global announcer object that manages global events
* 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 = {};
/**
* Subscribe to a global event
*
* @export
* @param {string} e event name
* @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: API.AnnouncementDataType<any>) => void, a: BaseModel): void {
if (!announcer.listeners[a.pid]) {
announcer.listeners[a.pid] = [];
}
announcer.listeners[a.pid].push({ e, f });
announcer.observable.on(e, f);
}
/**
* Trigger a global event
*
* @export
* @param {string} e event name
* @param {*} d data passing to all related callback
*/
export function trigger(e: string, d: any): void {
announcer.observable.trigger(e, d);
}
/**
* Report system fail. This will trigger the global `fail`
* event
*
* @export
* @param {(string | FormattedString)} m message string
* @param {Error} e error to be reported
*/
export function osfail(m: string | FormattedString, e: Error): void {
announcer.ostrigger("fail", m, e );
}
/**
* Report system error. This will trigger the global `error`
* event
*
* @export
* @param {(string | FormattedString)} m message string
* @param {Error} e error to be reported
*/
export function oserror(m: string | FormattedString, e: Error): void {
announcer.ostrigger("error", m, e );
}
/**
* Trigger system notification (`info` event)
*
* @export
* @param {(string | FormattedString)} m notification message
*/
export function osinfo(m: string | FormattedString): void {
announcer.ostrigger("info", m);
}
/**
*
*
* @export
* @param {string} e event name
* @param {(string| FormattedString)} m event message
* @param {*} [d] user data
*/
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);
}
/**
* Unregister a process (application/service) from
* the global announcement system
*
* @export
* @param {GUI.BaseModel} app reference to the process
* @returns {void}
*/
export function unregister(app: BaseModel): void {
if (
!announcer.listeners[app.pid] ||
!(announcer.listeners[app.pid].length > 0)
) {
return;
}
for (let i of announcer.listeners[app.pid]) {
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;
}
}
}

View File

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

513
src/core/BaseApplication.ts Normal file
View File

@ -0,0 +1,513 @@
// 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 {
/**
* This namespace is dedicated to application and service definition.
* When an application is loaded, its prototype definition will be
* inserted to this namespace for reuse lately
*/
export namespace application {
/**
* Abstract prototype of all AntOS applications.
* Any new application definition should extend
* this prototype
*
* @export
* @abstract
* @class BaseApplication
* @extends {BaseModel}
*/
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
*
* @type {GenericObject<any>}
* @memberof BaseApplication
*/
setting: GenericObject<any>;
/**
* Hotkeys (shortcuts) defined for this application
*
* @protected
* @type {GUI.ShortcutType}
* @memberof BaseApplication
*/
protected keycomb: GUI.ShortcutType;
/**
* Reference to the system dock
*
* @type {GUI.tag.AppDockTag}
* @memberof BaseApplication
*/
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;
/**
* Loading animation check timeout
*
* @private
* @memberof BaseApplication
*/
private _loading_toh: any;
/**
* Store pending loading task
*
* @private
* @type {number[]}
* @memberof BaseApplication
*/
private _pending_task: number[];
/**
*Creates an instance of BaseApplication.
* @param {string} name application name
* @param {AppArgumentsType[]} args application arguments
* @memberof BaseApplication
*/
constructor(name: string, args: AppArgumentsType[]) {
super(name, args);
if (!setting.applications[this.name]) {
setting.applications[this.name] = {};
}
this.setting = setting.applications[this.name];
this.keycomb = {};
this._loading_toh = undefined;
this._pending_task = [];
}
/**
* Init the application, this function is called when the
* application process is created and docked in the application
* dock.
*
* The application UI will be rendered after the execution
* of this function.
*
* @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() || [];
OS.PM.pidactive = this.pid;
this.appmenu.onmenuselect = (
d: GUI.tag.MenuEventData
): void => {
return this.trigger("menuselect", d);
};
this.trigger("focused", undefined);
if (this.dialog) {
return this.dialog.show();
}
});
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.subscribe("appregistry", (m) => {
if (m.name === this.name) {
this.applySetting(m.message as string);
}
});
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != this.pid)
{
return;
}
this._pending_task.push(o.id);
this.trigger("loading", undefined);
});
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
this._pending_task.splice(i, 1);
}
if (this._pending_task.length === 0) {
// set time out
if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000);
}
});
this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme();
}
/**
* Render the application UI by first loading its scheme
* and then mount this scheme to the DOM tree
*
* @protected
* @returns {void}
* @memberof BaseApplication
*/
protected loadScheme(): void {
//now load the scheme
const path = `${this.meta().path}/scheme.html`;
return this.render(path);
}
/**
* 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
*
* @protected
* @param {Promise<any>} promise the promise on a task to be performed
* @returns {Promise<void>}
* @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);
try {
await promise;
this._api.loaded(q, this.name, "OK");
return resolve();
} catch (e) {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e));
}
});
}
/**
* Bind a hotkey to the application, this function
* is used to define application keyboard shortcut
*
* @protected
* @param {string} k the hotkey to bind, should be in the following
* format: `[ALT|SHIFT|CTRL|META]-KEY`, e.g. `CTRL-S`
* @param {(e: JQuery.KeyboardEventBase) => void} f the callback function
* @returns {void}
* @memberof BaseApplication
*/
protected bindKey(
k: string,
f: (e: JQuery.KeyboardEventBase) => void
): void {
const arr = k.toUpperCase().split("-");
const c = arr.pop();
let fnk = "";
if (arr.includes("META")) {
fnk += "META";
}
if (arr.includes("CTRL")) {
fnk += "CTRL";
}
if (arr.includes("ALT")) {
fnk += "ALT";
}
if (arr.includes("SHIFT")) {
fnk += "SHIFT";
}
if ( fnk == "") {
return;
}
fnk = `fn_${fnk.hash()}`;
if (!this.keycomb[fnk]) {
this.keycomb[fnk] = {};
}
this.keycomb[fnk][c] = f;
}
/**
* Update the application local from the system
* locale or application specific locale configuration
*
* @param {string} name locale name e.g. `en_GB`
* @returns {void}
* @memberof BaseApplication
*/
updateLocale(name: string): void {
const meta = this.meta();
if (!meta || !meta.locales) {
return;
}
if (!meta.locales[name]) {
return;
}
const result = [];
for (let k in meta.locales[name]) {
const v = meta.locales[name][k];
result.push((this._api.lang[k] = v));
}
}
/**
* Execute the callback subscribed to a
* keyboard shortcut
*
* @param {string} fnk meta or modifier key e.g. `CTRL`, `ALT`, `SHIFT` or `META`
* @param {string} c a regular key
* @param {JQuery.KeyDownEvent} e JQuery keyboard event
* @returns {boolean} return whether the shortcut is executed
* @memberof BaseApplication
*/
shortcut(fnk: string, c: string, e: JQuery.KeyDownEvent): boolean {
if (!this.keycomb[fnk]) {
return true;
}
if (!this.keycomb[fnk][c]) {
return true;
}
this.keycomb[fnk][c](e);
return false;
}
/**
* Apply a setting to the application
*
* @protected
* @param {string} k the setting name
* @memberof BaseApplication
*/
protected applySetting(k: string): void {}
/**
* Apply all settings to the application
*
* @protected
* @memberof BaseApplication
*/
protected applyAllSetting(): void {
for (let k in this.setting) {
const v = this.setting[k];
this.applySetting(k);
}
}
/**
* 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
*
* @returns {void}
* @memberof BaseApplication
*/
show(): void {
this.trigger("focus", undefined);
}
/**
* Blur the application
*
* @returns {void}
* @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();
}
}
/**
* Hide the application
*
* @returns {void}
* @memberof BaseApplication
*/
hide(): void {
return this.trigger("hide", undefined);
}
/**
* Maximize or restore the application window size
* and its position
*
* @returns {void}
* @memberof BaseApplication
*/
toggle(): void {
return this.trigger("toggle", undefined);
}
/**
* Get the application title
*
* @returns {(string| FormattedString)}
* @memberof BaseApplication
*/
title(): string | FormattedString {
return (this.scheme as GUI.tag.WindowTag).apptitle;
}
/**
* Function called when the application exit.
* If the input exit event is prevented, the application
* process will not be killed
*
*
* @protected
* @param {BaseEvent} evt exit event
* @memberof BaseApplication
*/
protected onexit(evt: BaseEvent): void {
this.cleanup(evt);
if (!evt.prevent) {
if (this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
$(this.scheme).remove();
}
}
/**
* Get the application meta-data
*
* @returns {API.PackageMetaType}
* @memberof BaseApplication
*/
meta(): API.PackageMetaType {
return application[this.name].meta;
}
/**
* Base menu definition. This function
* returns the based menu definition of all applications.
* Other application specific menu entries
* should be defined in [[menu]] function
*
* @protected
* @returns {GUI.BasicItemType[]}
* @memberof BaseApplication
*/
protected baseMenu(): GUI.BasicItemType[] {
let mn: GUI.BasicItemType[] = [
{
text: application[this.name].meta.name,
nodes: [
{ text: "__(About)", dataid: `${this.name}-about` },
{ text: "__(Exit)", dataid: `${this.name}-exit` },
],
},
];
mn = mn.concat(this.menu() || []);
return mn;
}
/**
* The main application entry that is called after
* the application UI is rendered. This application
* must be implemented by all subclasses
*
* @abstract
* @memberof BaseApplication
*/
abstract main(): void;
/**
* Application specific menu definition
*
* @protected
* @returns {GUI.BasicItemType[]}
* @memberof BaseApplication
*/
protected menu(): GUI.BasicItemType[] {
// implement by subclasses
// to add menu to application
return [];
}
/**
* The cleanup function that is called by [[onexit]] function.
* Application need to override this function to perform some
* specific task before exiting or to prevent the application
* to be exited
*
* @protected
* @param {BaseEvent} e
* @memberof BaseApplication
*/
protected cleanup(e: BaseEvent): void {}
/**
* Check if the loading tasks ended,
* if it the case, stop the animation
*
* @private
* @memberof BaseApplication
*/
private animation_check(): void {
if(this._pending_task.length === 0)
{
this.trigger("loaded", undefined);
}
if(this._loading_toh)
clearTimeout(this._loading_toh);
this._loading_toh = undefined;
}
}
BaseApplication.type = ModelType.Application;
}
}

View File

@ -1,339 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class SubWindow extends this.OS.GUI.BaseModel
constructor: (name) ->
super name, null
@parent = undefined
@modal = false
quit: () ->
evt = new _GUI.BaseEvent("exit")
@onexit(evt)
if not evt.prevent
delete @.observable
($ @scheme).remove() if @scheme
@dialog.quit() if @dialog
init: () ->
main: () ->
meta: () ->
@parent.meta()
show: () ->
@trigger 'focus'
($ @scheme).css "z-index", window._zindex + 2
hide: () ->
@trigger 'hide'
SubWindow.type = 3
this.OS.GUI.SubWindow = SubWindow
class BaseDialog extends SubWindow
constructor: (name) ->
super name
@handler = undefined
onexit: (e) ->
@parent.dialog = undefined if @parent
this.OS.GUI.BaseDialog = BaseDialog
###
this dialog rende a tag as main content
and a list of buttons, the behaviour of
the button is specified by user. The conf
object is in the follow form
{
tag: <tag_name>,
buttons:[
{
label: 'buton label',
onclick: function(d){...}
}, ...
]
}
###
class BasicDialog extends BaseDialog
constructor: ( name, @conf, @title) ->
super name
init: () ->
@title = @name if not @title
html = "<afx-app-window data-id = '#{@name}' width='#{@conf.width}' height='#{@conf.height}'>"
html += "<afx-hbox><div data-width='7'></div><afx-vbox><div data-height='5'></div>"
html += "<#{v.tag} #{v.att} data-id = 'content#{k}'></#{v.tag}>" for k,v of @conf.tags
html += "<div data-height = '35' style=' text-align:right;padding-top:3px;'>"
html += "<afx-button data-id = 'bt#{k}' text = '#{v.label}' style='margin-left:5px;'></afx-button>" for k,v of @conf.buttons
html += "</div><div data-height='5'></div></afx-vbox><div data-width='7'></div></afx-hbox></afx-app-window>"
#render the html
_GUI.htmlToScheme html, @, @host
main: () ->
@scheme.set "apptitle", @title
@scheme.set "minimizable", false
@scheme.set "resizable", @conf.resizable if @conf.resizable isnt undefined
me = @
f = (_v) -> () -> _v.onclick me
# bind action to button
( (me.find "bt#{k}").set "onbtclick", f(v) ) for k, v of @conf.buttons
@conf.filldata @ if @conf.filldata
@conf.xtra @ if @conf.xtra
this.OS.GUI.BasicDialog = BasicDialog
class PromptDialog extends BasicDialog
constructor: () ->
super "PromptDialog", {
tags: [
{ tag: "afx-label" },
{ tag: "input", att: "type = 'text' data-height='25'" }
],
width: 200,
height: 120,
resizable: false,
buttons: [
{
label: "__(Ok)",
onclick: (d) ->
txt = (d.find "content1").value
return d.quit() if txt is ""
d.handler txt if d.handler
d.quit()
},
{
label: "__(Cancel)",
onclick: (d) -> d.quit()
}
],
filldata: (d) ->
return unless d.data
(d.find "content0").set "text", d.data.label
(d.find "content1").value = d.data.value if d.data.value
$(d.find "content1").attr("type", d.data.type) if d.data.type
xtra: (d) ->
$( d.find "content1" ).keyup (e) ->
(d.find "bt0").trigger() if e.which is 13
}
this.OS.register "PromptDialog", PromptDialog
class CalendarDialog extends BasicDialog
constructor: () ->
super "CalendarDialog", {
tags: [{ tag: 'afx-calendar-view' }],
width: 300,
height: 230,
resizable: false,
buttons: [
{
label: "__(Ok)",
onclick: (d) ->
date = (d.find "content0").get "selectedDate"
if date
d.handler date if d.handler
d.quit()
else
d.notify __("Please select a date")
},
{
label: "__(Cancel)",
onclick: (d) -> d.quit()
}
]
}
this.OS.register "CalendarDialog", CalendarDialog
class ColorPickerDialog extends BasicDialog
constructor: () ->
super "ColorPickerDialog", {
tags: [{ tag: 'afx-color-picker' }, {tag:'div', att: 'data-height="5"' }],
width: 313,
height: 250,
resizable: false,
buttons: [
{
label: "__(Ok)",
onclick: (d) ->
c = (d.find "content0").get "selectedColor"
if c
d.handler c if d.handler
d.quit()
else
d.notify "Please select a color"
},
{
label: "__(Cancel)",
onclick: (d) -> d.quit()
}
]
}
this.OS.register "ColorPickerDialog", ColorPickerDialog
class InfoDialog extends BasicDialog
constructor: () ->
super "InfoDialog", {
tags: [{ tag: 'afx-grid-view' }],
width: 250,
height: 300,
resizable: true,
buttons: [ { label: "__(Cancel)", onclick: (d) -> d.quit() } ],
filldata: (d) ->
return unless d.data
rows = []
rows.push [ { value: k }, { value: v } ] for k, v of d.data
(d.find "content0").set "rows", rows
}
this.OS.register "InfoDialog", InfoDialog
class YesNoDialog extends BasicDialog
constructor: () ->
super "YesNoDialog", {
tags: [{ tag: "afx-label" }],
width: 300,
height: 100,
resizable: true,
buttons: [
{
label: "__(Yes)", onclick: (d) ->
d.handler true if d.handler
d.quit()
},
{
label: "__(No)", onclick: (d) ->
d.handler false if d.handler
d.quit()
}
],
filldata: (d) ->
return unless d.data
l = d.find "content0"
for k, v of d.data
l.set k, v
}
this.OS.register "YesNoDialog", YesNoDialog
class SelectionDialog extends BasicDialog
constructor: () ->
super "SelectionDialog", {
tags: [{ tag: "afx-list-view" }],
width: 250,
height: 300,
resizable: false,
buttons: [
{
label: "__(Ok)", onclick: (d) ->
el = d.find "content0"
it = el.get "selected"
return unless it
d.handler it if d.handler
d.quit()
},
{ label: "__(Cancel)", onclick: (d) -> d.quit() }
],
filldata: (d) ->
return unless d.data
(d.find "content0").set "items", d.data
xtra: (d) ->
( d.find "content0" ).set "onlistdbclick", (e) ->
(d.find "bt0").trigger()
}
this.OS.register "SelectionDialog", SelectionDialog
class AboutDialog extends BaseDialog
constructor: () ->
super "AboutDialog"
init: () ->
@render "os://resources/schemes/about.html"
main: () ->
mt = @meta()
@scheme.set "apptitle", __("About: {0}",mt.name)
(@find "mylabel").set "*", {icon:mt.icon, iconclass:mt.iconclass, text:"#{mt.name}(v#{mt.version})"}
($ @find "mydesc").html mt.description
# grid data for author info
return unless mt.info
rows = []
rows.push [ { value: k }, { value: v } ] for k, v of mt.info
(@find "mygrid").set "rows", rows
this.OS.register "AboutDialog", AboutDialog
class FileDiaLog extends BaseDialog
constructor: () ->
super "FileDiaLog"
init: () ->
@render "os://resources/schemes/filedialog.html"
main: () ->
fileview = @find "fileview"
location = @find "location"
filename = @find "filename"
me = @
@scheme.set "apptitle", @title
fileview.set "fetch", (e, f) ->
return unless e.child
e.child.path.asFileHandler().read (d) ->
return me.error __("Resource not found: {0}", e.child.path) if d.error
f d.result
setroot = (path) ->
path.asFileHandler().read (d) ->
if(d.error)
return me.error __("Resource not found: {0}", path)
fileview.set "path", path
fileview.set "data", d.result
if not @data or not @data.root
location.set "onlistselect", (e) ->
return unless e and e.data.path
setroot e.data.path
location.set "items", ( i for i in @systemsetting.VFS.mountpoints when i.type isnt "app" )
location.set "selected", 0 unless location.get "selected"
else
$(location).hide()
@trigger "calibrate"
setroot @data.root
fileview.set "onfileselect", (f) ->
($ filename).val f.filename if f.type is "file"
(@find "bt-ok").set "onbtclick", (e) ->
f = fileview.get "selectedFile"
return me.notify __("Please select a file/fofler") unless f
return me.notify __("Please select {0} only", me.data.type) if me.data and me.data.type and me.data.type isnt f.type
if me.data and me.data.mimes
#verify the mime
m = false
if f.mime
for v in me.data.mimes
if f.mime.match (new RegExp v, "g")
m = true
break
return me.notify __("Only {0} could be selected", me.data.mimes.join(",")) unless m
d = f.path
d = f.path.asFileHandler().parent() if f.type is "file"
me.handler d, ($ filename).val(), f.path, f if me.handler
#sel = if me.data and me.data.selection then me.data.selection else "file"
#me.handler f, ($ filename).val() if me.handler and ((f.type is sel) or (sel is "*"))
me.quit()
(@find "bt-cancel").set "onbtclick", (e) ->
me.quit()
if @data and @data.file
($ filename).css("display", "block").val @data.file.basename or "Untitled"
@trigger "resize"
fileview.set "showhidden", @data.hidden if @data and @data.hidden
this.OS.register "FileDiaLog", FileDiaLog

1529
src/core/BaseDialog.ts Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

711
src/core/BaseModel.ts Normal file
View File

@ -0,0 +1,711 @@
// 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 {
/**
* Application argument type definition
*
* @export
* @interface AppArgumentsType
*/
export interface AppArgumentsType {
/**
* File type to be open by the app
*
* @type {string}
* @memberof AppArgumentsType
*/
type?: string;
/**
* File path to be opened
*
* @type {string}
* @memberof AppArgumentsType
*/
path: string;
/**
* Any other object
*/
[propName: string]: any;
}
/**
* Enum definition of different model types
*
* @export
* @enum {number}
*/
export enum ModelType {
/**
* Applications
*/
Application,
/**
* Services
*/
Service,
/**
* Sub-window such as dialogs
*/
SubWindow,
}
/**
* Base AntOS event definition
*
* @export
* @class BaseEvent
*/
export class BaseEvent {
/**
* The event name placeholder
*
* @type {string}
* @memberof BaseEvent
*/
name: string;
/**
* Placeholder indicates whether the event is forced to
* be happen
*
* @private
* @type {boolean}
* @memberof BaseEvent
*/
private force: boolean;
/**
* Placeholder indicates whether the event is prevented.
* This value has not effect if `force` is set to `true`
*
* @type {boolean}
* @memberof BaseEvent
*/
prevent: boolean;
/**
*Creates an instance of BaseEvent.
* @param {string} name event name
* @param {boolean} force indicates whether the event is forced
* @memberof BaseEvent
*/
constructor(name: string, force: boolean) {
this.name = name;
this.force = force;
this.prevent = false;
}
/**
* Prevent the current event. This function
* has no effect if `force` is set to true
*
* @memberof BaseEvent
*/
preventDefault(): void {
if (!this.force) {
this.prevent = true;
}
}
}
/**
* The root model of all applications, dialogs or services
* in the system
*
* @export
* @abstract
* @class BaseModel
*/
export abstract class BaseModel {
/**
* The class name
*
* @type {string}
* @memberof BaseModel
*/
name: string;
/**
* The argument of the model
*
* @type {AppArgumentsType[]}
* @memberof BaseModel
*/
args: AppArgumentsType[];
/**
* Each model has its own local announcement system
* to handle all local events inside that model.
*
* This observable object is propagate to all the
* UI elements ([[AFXTag]]) inside the model
*
* @protected
* @type {API.Announcer}
* @memberof BaseModel
*/
protected _observable: API.Announcer;
/**
* Reference to the core API namespace
*
* @protected
* @type {typeof API}
* @memberof BaseModel
*/
protected _api: typeof API;
/**
* Reference to the core GUI namespace
*
* @protected
* @type {typeof GUI}
* @memberof BaseModel
*/
protected _gui: typeof GUI;
/**
* Reference to the model's dialog
*
* @type {GUI.BaseDialog}
* @memberof BaseModel
*/
dialog: GUI.BaseDialog;
/**
* The HTML element ID of the virtual desktop
*
* @protected
* @type {HTMLElement}
* @memberof BaseModel
*/
protected host: HTMLElement;
/**
* The process number of the current model.
* For sub-window this number is the number
* of the parent window
*
* @type {number}
* @memberof BaseModel
*/
pid: number;
/**
* Reference the DOM element of the UI scheme belong to
* this model
*
* @type {HTMLElement}
* @memberof BaseModel
*/
scheme: HTMLElement;
/**
* Reference to the system setting
*
* @protected
* @type {typeof setting}
* @memberof BaseModel
*/
protected systemsetting: typeof setting;
/**
* Placeholder for the process creation timestamp
*
* @type {number}
* @memberof BaseModel
*/
birth: number;
/**
* Different model type
*
* @static
* @type {ModelType}
* @memberof BaseModel
*/
static type: ModelType;
/**
* Allow singleton on this model
*
* @static
* @type {boolean}
* @memberof BaseModel
*/
static singleton: boolean;
/**
* The javascript or css files that the model depends on. All dependencies
* will be loaded before the model is rendered
*
* @static
* @type {string[]} list of VFS paths of dependencies
* @memberof BaseModel
*/
static dependencies: string[];
/**
* Reference to the CSS Element of the model
*
* @static
* @type {(HTMLElement | string)}
* @memberof BaseModel
*/
static style: HTMLElement | string;
/**
* Place holder for model meta-data
*
* @static
* @type {API.PackageMetaType}
* @memberof BaseModel
*/
static meta: API.PackageMetaType;
/**
*Creates an instance of BaseModel.
* @param {string} name class name
* @param {AppArgumentsType[]} args arguments
* @memberof BaseModel
*/
constructor(name: string, args: AppArgumentsType[]) {
this.name = name;
this.args = args;
this._observable = new API.Announcer();
this._api = API;
this._gui = GUI;
this.systemsetting = setting;
this.on("exit", () => this.quit(false));
this.host = this._gui.desktop();
this.dialog = undefined;
}
/**
* Getter: get the local announcer object
*
* @readonly
* @type {API.Announcer}
* @memberof BaseModel
*/
get observable(): API.Announcer {
return this._observable;
}
/**
* Update the model locale
*
* @param {string} name
* @memberof BaseModel
*/
updateLocale(name: string) {}
/**
* Render the model's UI
*
* @protected
* @param {string} p VFS path to the UI scheme definition
* @returns {void}
* @memberof BaseModel
*/
protected render(p: string): void {
return GUI.loadScheme(p, this, this.host);
}
/**
* Exit the model
*
* @param {boolean} force set this value to `true` will bypass the prevented exit event by user
* @returns {void}
* @memberof BaseModel
*/
quit(force: boolean): void {
const evt = new BaseEvent("exit", force);
this.onexit(evt);
if (!evt.prevent) {
if(this.observable)
{
this.observable.off("*");
delete this._observable;
}
if (this.dialog) {
this.dialog.quit();
}
return PM.kill(this);
}
}
/**
* Model meta data, need to be implemented by
* subclasses
*
* @abstract
* @returns {API.PackageMetaType}
* @memberof BaseModel
*/
abstract meta(): API.PackageMetaType;
/**
* VFS path to the model asset
*
* @returns {string}
* @memberof BaseModel
*/
path(): string {
const mt = this.meta();
if (mt && mt.path) {
return mt.path;
}
return null;
}
/**
* Execute a server side script and get back the result
*
* @protected
* @param {GenericObject<any>} cmd execution indication, should be:
*
* ```
* {
* path?: string, // VFS path to the server side script
* code: string, // or server side code to be executed
* parameters: any // the parameters of the server side execution
* }
* ```
*
* @returns {Promise<any>}
* @memberof BaseModel
*/
protected call(cmd: GenericObject<any>): Promise<any> {
return this._api.apigateway(cmd, false);
}
/**
* Connect to the server side api using a websocket connection
*
* Server side script can be execute inside the stream by writing
* data in JSON format with the following interface
*
* ```
* {
* path?: string, // VFS path to the server side script
* code: string, // or server side code to be executed
* parameters: any // the parameters of the server side execution
* }
* ```
*
* @protected
* @returns {Promise<WebSocket>}
* @memberof BaseModel
*/
protected stream(): Promise<WebSocket> {
return this._api.apigateway(null, true) as Promise<WebSocket>;
}
/**
* Init the model before UI rendering
*
* @abstract
* @memberof BaseModel
*/
abstract init(): void;
/**
* Main entry point after UI rendering
*
* @abstract
* @memberof BaseModel
*/
abstract main(): void;
/**
* Show the model
*
* @abstract
* @memberof BaseModel
*/
abstract show(): void;
/**
* Hide the model
*
* @abstract
* @memberof BaseModel
*/
abstract hide(): void;
/**
* Function called when the model exits
*
* @protected
* @abstract
* @param {BaseEvent} e exit event
* @memberof BaseModel
*/
protected abstract onexit(e: BaseEvent): void;
/**
* subscribe once to a local event
*
* @protected
* @param {string} e name of the event
* @param {(d: any) => void} f event callback
* @returns {void}
* @memberof BaseModel
*/
protected one(e: string, f: (d: any) => void): void {
return this.observable.one(e, f);
}
/**
* Subscribe to a local event
*
* @protected
* @param {string} e event name
* @param {(d: any) => void} f event callback
* @returns {void}
* @memberof BaseModel
*/
protected on(e: string, f: (d: any) => void): void {
return this.observable.on(e, f);
}
/**
* Unsubscribe an event
*
* @protected
* @param {string} e event name or `*` (all events)
* @param {(d: any) => void} [f] callback to be unsubscribed, can be `undefined`
* @returns {void}
* @memberof BaseModel
*/
protected off(e: string, f?: (d: any) => void): void {
if (!f) {
return this.observable.off(e);
}
return this.observable.off(e, f);
}
/**
* trigger a local event
*
* @param {string} e event name
* @param {*} [d] event data
* @returns {void}
* @memberof BaseModel
*/
trigger(e: string, d?: any): void {
if (!this.observable) return;
this.observable.trigger(e, d);
}
/**
* subscribe to an event on the global announcement system
*
* @protected
* @param {string} e event name
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
* @returns {void}
* @memberof BaseModel
*/
subscribe(e: string, f: (d: API.AnnouncementDataType<any>) => void): void {
return announcer.on(e, f, this);
}
/**
* Open a dialog
*
* @param {(GUI.BaseDialog | string)} d a dialog object or a dialog class name
* @param {GenericObject<any>} [data] input data of the dialog, refer to each
* dialog definition for the format of the input data
* @returns {Promise<any>} A promise on the callback data of the dialog, refer
* to each dialog definition for the format of the callback data
* @memberof BaseModel
*/
openDialog(
d: GUI.BaseDialog | string,
data?: GenericObject<any>
): Promise<any> {
return new Promise((resolve, reject) => {
if (this.dialog) {
this.dialog.show();
return;
}
if (typeof d === "string") {
if (!GUI.dialogs[d]) {
this.error(__("Dialog {0} not found", d));
return;
}
this.dialog = new OS.GUI.dialogs[d as string]();
} else {
this.dialog = d;
}
//@dialog.observable = riot.observable() unless @dialog
this.dialog.parent = this;
this.dialog.handle = resolve;
this.dialog.pid = this.pid;
this.dialog.data = data;
return this.dialog.init();
});
}
/**
* Open a [[YesNoDialog]] to confirm a task
*
* @protected
* @param {GenericObject<any>} data [[YesNoDialog]] input data
* @returns {Promise<boolean>}
* @memberof BaseModel
*/
protected ask(data: GenericObject<any>): Promise<boolean> {
return this._gui.openDialog("YesNoDialog", data);
}
/**
* Trigger a global event
*
* @protected
* @param {string} t event name
* @param {(string | FormattedString)} m event message
* @param {any} u_data user data object if any
* @returns {void}
* @memberof BaseModel
*/
protected publish(
t: string,
m: string | FormattedString,
u_data?: any
): void {
const mt = this.meta();
const data: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
data.icon = undefined;
if (mt && mt.icon) {
data.icon = `${mt.path}/${mt.icon}`;
}
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, data?: any): void {
return this.publish("notification", m, data);
}
/**
* Publish a global warning
*
* @param {(string | FormattedString)} m warning string
* @returns {void}
* @memberof BaseModel
*/
warn(m: string | FormattedString): void {
return this.publish("warning", m);
}
/**
* Report a global error
*
* @param {(string | FormattedString)} m error message
* @param {Error} [e] error object if any
* @returns
* @memberof BaseModel
*/
error(m: string | FormattedString, e?: Error) {
return this.publish("error", m, e ? e : this._api.throwe(m));
}
/**
* Report a global fail event
*
* @param {string} m fail message
* @param {Error} [e] error object if any
* @returns
* @memberof BaseModel
*/
fail(m: string, e?: Error) {
return this.publish("fail", m, e ? e : this._api.throwe(m));
}
/**
* Throw an error inside the model
*
* @returns {Error}
* @memberof BaseModel
*/
throwe(): Error {
return this._api.throwe(this.name);
}
/**
* Update the model, this will update all its UI elements
*
* @returns {void}
* @memberof BaseModel
*/
update(): void {
if (this.scheme) {
this.scheme.update();
}
if(this.dialog)
{
this.dialog.update();
}
}
/**
* Find a HTMLElement in the UI of the model
* using the `data-id` attribute of the element
*
* @protected
* @param {string} id
* @returns {HTMLElement}
* @memberof BaseModel
*/
protected find(id: string): HTMLElement {
if (this.scheme) {
return $(`[data-id='${id}']`, this.scheme)[0];
}
}
/**
* Select all DOM Element inside the UI of the model
* using JQuery selector
*
* @protected
* @param {string} sel
* @returns {HTMLElement}
* @memberof BaseModel
*/
protected select(sel: string): JQuery<HTMLElement> {
if (this.scheme) {
return $(sel, this.scheme);
}
}
}
}

View File

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

252
src/core/BaseService.ts Normal file
View File

@ -0,0 +1,252 @@
// 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 application {
/**
* Services are processes that run in the background and
* are waken up in certain circumstances such as by global
* events or user interactions.
*
* Each service takes an entry in the system tray menu
* located on the system panel. This menu entry is used
* to access to service visual contents such as: options,
* task performing based on user interaction, etc.
*
* Services are singleton processes, there is only
* one process of a service at a time
*
* @export
* @abstract
* @class BaseService
* @extends {BaseModel}
*/
export abstract class BaseService extends BaseModel {
/**
* The service icon shown in the system tray
*
* @type {string}
* @memberof BaseService
*/
icon: string;
/**
* CSS class of the service icon shown in the system tray
*
* @type {string}
* @memberof BaseService
*/
iconclass: string;
/**
* Text of the service shown in the system tray
*
* @type {string}
* @memberof BaseService
*/
text: string;
/**
* Reference to the menu entry DOM element attached
* to the service
*
* @type {HTMLElement}
* @memberof BaseService
*/
domel: HTMLElement;
/**
* Reference to the timer that periodically executes the callback
* defined in [[watch]].
*
* @private
* @type {number}
* @memberof BaseService
*/
private timer: number;
/**
* Reference to the system tray menu
*
* @type {HTMLElement}
* @memberof BaseService
*/
holder: HTMLElement;
/**
* Placeholder for service select callback
*
* @memberof BaseService
*/
onmenuselect: (
d: OS.GUI.TagEventType<GUI.tag.MenuEventData>
) => void;
/**
*Creates an instance of BaseService.
* @param {string} name service class name
* @param {AppArgumentsType[]} args service arguments
* @memberof BaseService
*/
constructor(name: string, args: AppArgumentsType[]) {
super(name, args);
this.icon = undefined;
this.iconclass = "fa fa-paper-plane-o";
this.text = "";
this.timer = undefined;
this.holder = undefined;
this.onmenuselect = (d) => {
return this.awake(d);
};
}
/**
* Do nothing
*
* @memberof BaseService
*/
hide(): void {}
/**
* Init the service before attaching it to
* the system tray: event subscribe, scheme
* loading.
*
* Should be implemented by all subclasses
*
* @abstract
* @memberof BaseService
*/
abstract init(): void;
/**
* Refresh the service menu entry in the
* system tray
*
* @memberof BaseService
*/
update(): void {
(this.domel as GUI.tag.MenuEntryTag).data = this;
}
/**
* Get the service meta-data
*
* @returns {API.PackageMetaType}
* @memberof BaseService
*/
meta(): API.PackageMetaType {
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.
*
* Each service should only have at most one watcher
*
* @protected
* @param {number} t period time in seconds
* @param {() => void} f callback function
* @returns {number}
* @memberof BaseService
*/
protected watch(t: number, f: () => void): number {
var func = () => {
f();
if (this.timer) {
clearTimeout(this.timer);
}
return (this.timer = window.setTimeout(() => func(), t));
};
return func();
}
/**
* This function is called when the service
* is exited
*
* @protected
* @param {BaseEvent} evt exit event
* @returns
* @memberof BaseService
*/
protected onexit(evt: BaseEvent) {
if (this.timer) {
console.log("clean timer");
}
if (this.timer) {
clearTimeout(this.timer);
}
this.cleanup(evt);
if (this.scheme) {
return $(this.scheme).remove();
}
}
/**
* Do nothing
*
* @memberof BaseService
*/
main(): void {}
/**
* Do nothing
*
* @memberof BaseService
*/
show(): void {}
/**
* Awake the service, this function is usually called when
* the system tray menu entry attached to the service is
* selected.
*
* This function should be implemented by all subclasses
*
* @abstract
* @param {GUI.TagEventType} e
* @memberof BaseService
*/
abstract awake(e: GUI.TagEventType<GUI.tag.MenuEventData>): void;
/**
* Do nothing
*
* @protected
* @param {BaseEvent} evt
* @memberof BaseService
*/
protected cleanup(evt: BaseEvent) {}
}
BaseService.type = ModelType.Service;
BaseService.singleton = true;
}
}

View File

@ -1,403 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
class FormatedString
constructor: (@fs, args) ->
@values = []
return unless args
@values[i] = args[i] for i in [0..args.length - 1]
toString: () ->
@__()
__: () ->
me = @
return @fs.l().replace /{(\d+)}/g, (match, number) ->
return if typeof me.values[number] != 'undefined' then me.values[number].__() else match
hash: () ->
@__().hash()
asBase64: () ->
@__().asBase64()
unescape: () ->
@__().unescape()
asUint8Array: () ->
@__().asUint8Array()
format: () ->
args = arguments
@values[i] = args[i] for i in [0..args.length - 1]
class Version
constructor:(@string) ->
arr = @string.split "-"
br =
"r": 3,
"b": 2,
"a": 1
@branch = 3
@branch = br[arr[1]] if arr.length is 2 and br[arr[1]]
mt = arr[0].match /\d+/g
throw new Error __("Version string is in invalid format: {0}", @string) if not mt
@major = 0
@minor = 0
@patch = 0
@major = Number mt[0] if mt.length >= 1
@minor = Number mt[1] if mt.length >= 2
@patch = Number mt[2] if mt.length >= 3
# this function return
# 0 if the version is unchanged
# 1 if the current version is newer
# -1 if the current version is older
compare: (o) ->
other = o.__v()
return 1 if @branch > other.branch
return -1 if @branch < other.branch
return 0 if @major is other.major and @minor is other.minor and @patch is other.patch
return 1 if @major > other.major
return -1 if @major < other.major
return 1 if @minor > other.minor
return -1 if @minor < other.minor
return 1 if @patch > other.patch
return -1
nt: (o) ->
return (@compare o) is 1
ot: (o) ->
return (@compare o) is -1
__v: () -> @
toString: () -> @string
Object.defineProperty Object.prototype, '__',
value: () ->
return @toString()
enumerable: false
writable: true
String.prototype.hash = () ->
hash = 5381
i = this.length
hash = (hash * 33) ^ this.charCodeAt(--i) while i
hash >>> 0
String.prototype.__v = () ->
return new Version @
String.prototype.asBase64 = () ->
tmp = encodeURIComponent this
return btoa ( tmp.replace /%([0-9A-F]{2})/g, (match, p1) ->
return String.fromCharCode (parseInt p1, 16)
)
String.prototype.unescape = () ->
d = @
d = d.replace /\\\\/g, "\\"
d = d.replace /\\"/g, '"'
d = d.replace /\\n/g, "\n"
d = d.replace /\\t/g, "\t"
d = d.replace /\\b/g, "\b"
d = d.replace /\\f/g, "\f"
d = d.replace /\\r/g, "\r"
d
String.prototype.asUint8Array = () ->
bytes = []
for i in [0..(@length - 1)]
bytes.push @charCodeAt i
bytes = new Uint8Array(bytes)
return bytes
if not String.prototype.format
String.prototype.format = () ->
args = arguments
return @replace /{(\d+)}/g, (match, number) ->
return if typeof args[number] != 'undefined' then args[number].__() else match
String.prototype.f = () ->
args = arguments
return new FormatedString(@, args)
String.prototype.__ = () ->
match = @match(/^__\((.*)\)$/)
return match[1].l() if match
return @
String.prototype.l = () ->
_API = window.OS.API
_API.lang[@] = @ unless _API.lang[@]
return _API.lang[@]
# language directive
this.__ = () ->
_API = window.OS.API
args = arguments
return "Undefined" unless args.length > 0
d = args[0]
d.l()
return new FormatedString d, (args[i] for i in [1 .. args.length - 1])
Date.prototype.toString = () ->
dd = @getDate()
mm = @getMonth() + 1
yyyy = @getFullYear()
hh = @getHours()
mi = @getMinutes()
se = @getSeconds()
dd = "0#{dd}" if dd < 10
mm = "0#{mm}" if mm < 10
hh = "0#{hh}" if hh < 10
mi = "0#{mi}" if mi < 10
se = "0#{se}" if se < 10
return "#{dd}/#{mm}/#{yyyy} #{hh}:#{mi}:#{se}"
Date.prototype.timestamp = () ->
return @getTime() / 1000 | 0
self.OS.API =
# the handler object could be a any remote or local handle to
# fetch user data, used by the API to make requests
# handlers are defined in /src/handlers
handler: {}
shared: {} # shared libraries
searchHandler:{}
lang:{}
#request a user data
mid: () ->
return _courrier.getMID()
post: (p, d, c, f) ->
q = _courrier.getMID()
_API.loading q, p
$.ajax {
type: 'POST',
url: p,
contentType: 'application/json',
data: JSON.stringify d,
dataType: 'json',
success: null
}
#$.getJSON p, d
.done (data) ->
_API.loaded q, p, "OK"
c(data)
.fail (e, s) ->
_API.loaded q, p, "FAIL"
f(e, s)
blob: (p, c, f) ->
q = _courrier.getMID()
r = new XMLHttpRequest()
r.open "GET", p, true
r.responseType = "arraybuffer"
r.onload = (e) ->
if @status is 200 and @readyState is 4
c @response
_API.loaded q, p, "OK"
else
f e, @
_API.loaded q, p, "FAIL"
_API.loading q, p
r.send()
upload: (p, d, c, f) ->
q = _courrier.getMID()
#insert a temporal file selector
o = ($ '<input>').attr('type', 'file').css("display", "none")
o.change () ->
_API.loading q, p
formd = new FormData()
formd.append 'path', d
# TODO: only one file is selected at this time
formd.append 'upload', o[0].files[0]
$.ajax {
url: p,
data: formd,
type: 'POST',
contentType: false,
processData: false,
}
.done (data) ->
_API.loaded q, p, "OK"
c(data)
o.remove()
.fail (e, s) ->
_API.loaded q, p, "FAIL"
f(e, s)
o.remove()
o.click()
saveblob: (name, b) ->
url = window.URL.createObjectURL b
o = ($ '<a>')
.attr("href", url)
.attr("download", name)
.css("display", "none")
.appendTo("body")
o[0].click()
window.URL.revokeObjectURL(url)
o.remove()
systemConfig: ->
_API.request 'config', (result) ->
console.log result
loading: (q, p) ->
_courrier.trigger "loading", { id: q, data: { m: "#{p}", s: true }, name: "OS" }
loaded: (q, p, m ) ->
_courrier.trigger "loaded", { id: q, data: { m: "#{m}: #{p}", s: false }, name: "OS" }
get: (p, c, f, t) ->
conf =
type: 'GET',
url: p,
conf.dataType = t if t
q = _courrier.getMID()
_API.loading q, p
$.ajax conf
.done (data) ->
_API.loaded q, p, "OK"
c(data)
.fail (e, s) ->
_API.loaded q, p, "FAIL"
f(e, s)
script: (p, c, f) ->
q = _courrier.getMID()
_API.loading q, p
$.getScript p
.done (data) ->
_API.loaded q, p, "OK"
c(data)
.fail (e, s) ->
_API.loaded q, p, "FAIL"
f(e, s)
resource: (r, c, f) ->
path = "resources/#{r}"
_API.get path, c, f
libready: (l) ->
return _API.shared[l] || false
require: (l,f) ->
if not _API.shared[l]
if l.match /^(https?:\/\/[^\s]+)/g
_API.script l, () ->
_API.shared[l] = true
_courrier.trigger "sharedlibraryloaded", l
f() if f
, (e, s) ->
_courrier.oserror __("Cannot load 3rd library at: {0}", l), e, r
else
path = "os://scripts/"
js = "#{path}#{l}.js"
js.asFileHandler().onready (d) ->
_API.shared[l] = true
el = $ '<script>', { src: "#{_API.handler.get}/#{js}" }
.appendTo 'head'
#load css file
css = "#{path}#{l}.css"
css.asFileHandler().onready (d) ->
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{_API.handler.get}/#{css}" }
.appendTo 'head'
, () ->
console.log "loaded", l
_courrier.trigger "sharedlibraryloaded", l
f() if f
else
console.log l, "Library exist, no need to load"
f() if f
_courrier.trigger "sharedlibraryloaded", l
requires:(libs, f) ->
return f() unless libs.length > 0
_courrier.observable.one "sharedlibraryloaded", (l) ->
libs.splice 0, 1
_API.requires libs, f
_API.require libs[0], null
packages:
fetch: (f) ->
_API.handler.packages {
command: "list", args: { paths: (v for k, v of _OS.setting.system.pkgpaths) }
}, f
cache: (f) ->
_API.handler.packages {
command: "cache", args: { paths: (v for k, v of _OS.setting.system.pkgpaths) }
}, f
setting: (f) ->
_API.handler.setting f
apigateway: (d, ws, c) ->
return _API.handler.apigateway d, ws, c
search: (text) ->
r = []
for k, v of _API.searchHandler
ret = _API.searchHandler[k](text)
if ret.length > 0
ret.unshift { text: k, class: "search-header", dataid: "header" }
r = r.concat ret
return r
onsearch: (name, fn) ->
self.OS.API.searchHandler[name] = fn unless self.OS.API.searchHandler[name]
setLocale: (name, f) ->
path = "resources/languages/#{name}.json"
_API.get path, (d) ->
_OS.setting.system.locale = name
_API.lang = d
if f then f() else _courrier.trigger "systemlocalechange", name
, (e, s) ->
#_OS.setting.system.locale = "en_GB"
_courrier.oserror __("Language file {0} not found", path), e, s
f() if f
, "json"
throwe: (n) ->
err = undefined
try
throw new Error(n)
catch e
err = e
return "" if not err
return err
# utilities functioncs
switcher: () ->
o = {}
p = {}
p[arguments[i]] = false for i in [0..arguments.length - 1 ]
Object.defineProperty o, "__p", {
enumerable: false,
value: p
}
fn = (o, v) ->
Object.defineProperty o, v, {
enumerable: true,
set: (value) ->
for k,l of @__p
@__p[k] = false
o.__p[v] = value
, get: () ->
return o.__p[v]
}
for k, v of o.__p
fn o, k
Object.defineProperty o, "selected", {
configurable: true,
enumerable: false,
get: () ->
for k,v of o.__p
return k if v
}
return o

View File

@ -1,152 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
'use strict'
#define the OS object
self = this
self.OS or=
API: {}
GUI: {}
APP: {}
setting:
user: {}
applications: {}
desktop: {}
appearance: {}
VFS: {}
system: {}
courrier:
observable: riot.observable()
quota: 0
listeners: {}
on: (e, f, a) ->
_courrier.listeners[a.pid] = [] unless _courrier.listeners[a.pid]
_courrier.listeners[a.pid].push { e: e, f: f }
_courrier.observable.on e, f
trigger: (e, d) -> _courrier.observable.trigger e, d
osfail: (m, e, s) ->
_courrier.ostrigger "fail", { m: m, e: e, s: s }
oserror: (m, e, s) ->
_courrier.ostrigger "error", { m: m, e: e, s: s }
osinfo: (m) ->
_courrier.ostrigger "info", { m: m, e: null, s: null }
ostrigger: (e, d) ->
_courrier.trigger e, { id: 0, data: d, name: "OS" }
unregister: (app) ->
return unless _courrier.listeners[app.pid] and _courrier.listeners[app.pid].length > 0
_courrier.observable.off i.e, i.f for i in _courrier.listeners[app.pid]
delete _courrier.listeners[app.pid]
# _courrier.listeners[app.pid]
getMID: () ->
_courrier.quota += 1
_courrier.quota
register: (name, x) ->
if x.type is 3 then self.OS.GUI.subwindows[name] = x else _OS.APP[name] = x
PM:
pidalloc: 0
processes: {}
createProcess: (app, cls, args) ->
f = () ->
#if it is single ton
# and a process is existing
# just return it
if cls.singleton and _PM.processes[app] and _PM.processes[app].length == 1
_PM.processes[app][0].show()
else
_PM.processes[app] = [] if not _PM.processes[app]
obj = new cls(args)
obj.birth = (new Date).getTime()
_PM.pidalloc++
obj.pid = _PM.pidalloc
_PM.processes[app].push obj
if cls.type is 1 then _GUI.dock obj, cls.meta else _GUI.attachservice obj
if cls.type is 2
_courrier.trigger "srvroutineready", app
if cls.dependencies
libs = (v for v in cls.dependencies)
_API.requires libs, f
else
f()
appByPid: (pid) ->
app = undefined
find = (l) ->
return a for a in l when a.pid is pid
for k, v of _PM.processes
app = find v
break if app
app
kill: (app) ->
return if not app.name or not _PM.processes[app.name]
i = _PM.processes[app.name].indexOf app
if i >= 0
if _OS.APP[app.name].type == 1 then _GUI.undock app else _GUI.detachservice app
_courrier.unregister app
delete _PM.processes[app.name][i]
_PM.processes[app.name].splice i, 1
killAll: (app, force) ->
return unless _PM.processes[app]
a.quit(force) for a in _PM.processes[app]
cleanup: ->
console.log "Clean up system"
_PM.killAll a, true for a, v of _PM.processes
_courrier.observable.off("*") if _courrier.observable
$(window).off('keydown')
($ "#workspace").off("mouseover")
delete _courrier.observable
($ "#wrapper").empty()
_GUI.clearTheme()
_courrier.observable = riot.observable()
_courrier.quota = 0
_OS.APP = {}
_OS.setting =
user: {}
applications: {}
desktop: {}
appearance: {}
VFS: {}
system: {}
_PM.processes = {}
_PM.pidalloc = 0
boot: ->
#first login
console.log "Booting sytem"
_API.handler.auth (d) ->
# in case someone call it more than once :)
if d.error
# show login screen
_GUI.login()
else
# startX :)
_GUI.startAntOS d.result
cleanupHandlers: {}
exit: ->
#do clean up first
f() for n, f of _OS.cleanupHandlers
_API.handler.setting (r) ->
_OS.cleanup()
_API.handler.logout()
onexit: (n, f) ->
self.OS.cleanupHandlers[n] = f unless self.OS.cleanupHandlers[n]

1787
src/core/core.ts Normal file

File diff suppressed because it is too large Load Diff

View File

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

202
src/core/db.ts Normal file
View File

@ -0,0 +1,202 @@
// 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));
}
});
}
}
}
}

View File

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

1127
src/core/gui.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,125 +0,0 @@
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
# AnTOS Web desktop is is licensed under the GNU General Public
# License v3.0, see the LICENCE file for more information
# This program is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
# You should have received a copy of the GNU General Public License
#along with this program. If not, see https://www.gnu.org/licenses/.
self.OS.API.HOST = self.location.hostname+ (if self.location.port then":#{self.location.port}" else "")
self.OS.API.REST = "#{self.location.protocol}//#{self.OS.API.HOST}"
_REST = self.OS.API.REST
_HOST = self.OS.API.HOST
self.OS.API.handler =
# get file, require authentification
get: "#{_REST}/VFS/get"
# get shared file with publish
shared: "#{_REST}/VFS/shared"
scandir: (p, c ) ->
path = "#{_REST}/VFS/scandir"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail __("Fail to scan directory: {0}", p), e, s
mkdir: (p, c ) ->
path = "#{_REST}/VFS/mkdir"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail __("Fail to create directory: {0}", p), e, s
sharefile: (p, pub , c) ->
path = "#{_REST}/VFS/publish"
_API.post path, { path: p , publish: pub }, c, (e, s) ->
_courrier.osfail __("Fail to publish file: {0}", p), e, s
fileinfo: (p, c) ->
path = "#{_REST}/VFS/fileinfo"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail __("Fail to get file meta data: {0}", p), e, s
readfile: (p, c, t) ->
path = "#{_REST}/VFS/get/"
_API.get path + p, c, (e, s) ->
_courrier.osfail __("Fail to read file: {0}", p), e, s
, t
move: (s, d, c) ->
path = "#{_REST}/VFS/move"
_API.post path, { src: s, dest: d }, c, (e, s) ->
_courrier.osfail __("Fail to move file: {0} -> {1}", s, d), e, s
delete: (p , c) ->
path = "#{_REST}/VFS/delete"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail __("Fail to delete: {0}", p), e, s
fileblob: (p, c) ->
path = "#{_REST}/VFS/get/"
_API.blob path + p, c, (e, s) ->
_courrier.osfail "Fail to read file: #{p}", e, s
packages: (d, c) ->
path = "#{_REST}/system/packages"
_API.post path, d, c, (e, s) ->
_courrier.osfail __("Fail to {0} package", d.command), e, s
upload: (d, c) ->
path = "#{_REST}/VFS/upload"
_API.upload path, d, c, (e, s) ->
_courrier.osfail __("Fail to upload file to: {0}", d), e, s
write: (p, d , c) ->
path = "#{_REST}/VFS/write"
_API.post path, { path: p, data: d }, c, (e, s) ->
_courrier.osfail __("Fail to write to file: {0}", p), e, s
scanapp: (p, c ) ->
path = "#{_REST}/system/application"
apigateway: (d, ws, c) ->
if ws
path = "#{_HOST}/system/apigateway?ws=1"
proto = if window.location.protocol is "https:" then "wss://" else "ws://"
socket = new WebSocket proto + path
if c then c(socket)
return socket
else
path = "#{_REST}/system/apigateway?ws=0"
_API.post path, d, c, (e, s) ->
_courrier.osfail __("Fail to invoke gateway api"), e, s
auth: (c) ->
p = "#{_REST}/user/auth"
_API.post p, {}, c, (e, s) ->
console.log e, s
alert __("Resource not found: {0}", p)
login: (d, c) ->
p = "#{_REST}/user/login"
_API.post p, d, c, () ->
alert __("Resource not found: {0}", p)
logout: () ->
p = "#{_REST}/user/logout"
_API.post p, {}, (d) ->
_OS.boot()
, () ->
alert __("Resource not found: {0}", p)
setting: (f) ->
p = "#{_REST}/system/settings"
_API.post p, _OS.setting, (d) ->
_courrier.oserror __("Cannot save system setting"), d.error if d.error
f(d) if f
, (e, s) ->
m = __("Fail to make request: {0}", p)
_courrier.osfail m , e, s
f({ error: m }) if f
dbquery: (cmd, d, c) ->
path = "#{_REST}/VDB/#{cmd}"
_API.post path, d, c, (e, s) ->
_courrier.osfail __("Fail to query data from database: {0}", path), e, s

View File

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

View File

@ -0,0 +1,471 @@
// Copyright 2017-2020 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 {
/**
* Interface for user login data
*
* @export
* @interface UserLoginType
*/
export interface UserLoginType {
/**
* The user credential
*
* @type {string}
* @memberof UserLoginType
*/
username: string;
/**
* The user password
*
* @type {string}
* @memberof UserLoginType
*/
password: string;
}
/**
* Interface for a command sent to
* server side package manage, it contains two field:
*
* @export
* @interface PackageCommandType
*/
export interface PackageCommandType {
/**
* Command name, should be: `init`, `cache`, `install`,
* `uninstall` or `list`
*
* @type {string}
* @memberof PackageCommandType
*/
command: string;
/**
* Parameter object of each command
*
* @type {GenericObject<any>}
* @memberof PackageCommandType
*/
args: GenericObject<any>;
}
/**
*
* Interface for basic request result returned
* from the server-side. A valid server-side response should
* be in the following format
* ```json
* {
* "error": boolean or string_err,
* "result": JSON result object
* }
* ```
*
* @export
* @interface RequestResult
*/
export interface RequestResult {
/**
* Indicate whether the response is error
*
* @type {(boolean | string)}
* @memberof RequestResult
*/
error: boolean | string;
/**
* The response result, this value must be
* set when `error` is false
*
* @type {(string
* | boolean
* | GenericObject<any>
* | any[]
* | FileInfoType
* | FileInfoType[]
* | setting.UserSettingType)}
* @memberof RequestResult
*/
result:
| string
| boolean
| GenericObject<any>
| any[]
| FileInfoType
| FileInfoType[]
| setting.UserSettingType;
}
let loc: any = { hostname: "localhost", port: "80", protocol: "http" };
if (Ant.location) loc = Ant.location;
/**
* The host name of the server-side
*/
export var HOST: string =
loc.hostname + (loc.port ? `:${loc.port}` : "");
/**
* The base URI of the server-side API
*/
export var BASE_URI: string = `${HOST}${loc.pathname}`;
/**
* The base REST URI of the server-side API
*/
export var REST: string = `${loc.protocol}//${BASE_URI}`;
/**
* The namespace `handle` contains some low level API to
* communicate with the server side API. It is the only
* API layer that communicate directly with the server.
* To make AntOS compatible with any server side API,
* all exported variable unctions defined in the `handle`
* namespace should be re-implemented
*/
export namespace handle {
/**
* Base URI for reading content of VFS file
*/
export var get: string = `${REST}/VFS/get`;
/**
* Base URI for VFS file sharing
*/
export var shared: string = `${REST}/VFS/shared`;
/**
* Send a request to the server-side API for a directory scanning
* operation
*
* @export
* @param {string} p a VFS file path e.g. home://test/
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* which contains an error or a list of FileInfoType
*/
export function scandir(p: string): Promise<RequestResult> {
const path = `${REST}/VFS/scandir`;
return API.post(path, { path: p });
}
/**
*
* Send a request to the server-side API for directory creation
*
* @export
* @param {string} p VFS path of the directory to be created
* @returns {Promise<RequestResult>} A promise on a RequestResult
* which contains an error or true on success
*/
export function mkdir(p: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/mkdir`;
return API.post(path, { path: p });
}
/**
* Send a request to the server-side API for sharing/unsharing a VFS file,
* once shared a VFS file will be publicly visible by everyone
*
* @export
* @param {string} p VFS file path to be shared
* @param {boolean} pub flag: share (true) or unshare (false)
* @returns {Promise<RequestResult>} A promise on a RequestResult
* which contains an error or true on success
*/
export function sharefile(
p: string,
pub: boolean
): Promise<RequestResult> {
const path = `${API.REST}/VFS/publish`;
return API.post(path, { path: p, publish: pub });
}
/**
* Get VFS file meta-data
*
* @export
* @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* which contains an error or an object of FileInfoType
*/
export function fileinfo(p: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/fileinfo`;
return API.post(path, { path: p });
}
/**
* Read a VFS file content. There are many ways a VFS file can be read:
* - Read as a raw text content
* - Read as a javascript file, in this case the content of the
* file will be executed
* - Read as JSON object
*
* @export
* @param {string} p path of the VFS file
* @param {string} t return 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
*
* @returns {Promise<any>} A promise on a [[RequestResult]]
* which contains an error or an object of [[FileInfoType]]
*/
export function readfile(p: string, t: string): Promise<any> {
const path = `${API.REST}/VFS/get/`;
return API.get(path + p, t);
}
/**
* Move a file to another location on server-side
*
* @export
* @param {string} s VFS source file path
* @param {string} d VFS destination file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* which contains an error or a success response
*/
export function move(s: string, d: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/move`;
return API.post(path, { src: s, dest: d });
}
/**
* Delete a VFS file on the server-side
*
* @export
* @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* which contains an error or a success response
*/
export function remove(p: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/delete`;
return API.post(path, { path: p });
}
/**
* Read the file as binary data
*
* @export
* @param {string} p VFS file to be read
* @returns {Promise<ArrayBuffer>} a Promise on an array buffer
*/
export function fileblob(p: string): Promise<ArrayBuffer> {
const path = `${API.REST}/VFS/get/`;
return API.blob(path + p);
}
/**
* Send a command to the serverside package manager
*
* @export
* @param {PackageCommandType} d a package command of type PackageCommandType
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
*/
export function packages(
d: PackageCommandType
): Promise<RequestResult> {
const path = `${API.REST}/system/packages`;
return API.post(path, d);
}
/**
* Upload file to the server via VFS interface
*
* @export
* @param {string} d VFS destination directory path
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
*/
export function upload(d: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/upload`;
return API.upload(path, d);
}
/**
* Write Base 64 encoded data to a VFS file
*
* @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]]
*/
export function write(
p: string,
d: string
): Promise<RequestResult> {
const path = `${API.REST}/VFS/write`;
return API.post(path, { path: p, data: d });
}
/**
* An apigateway allows client side to execute a custom server-side
* script and get back the result. This gateway is particularly
* useful in case of performing a task that is not provided by the core
* API
*
* @export
* @param {GenericObject<any>} d execution indication, provided only when ws is `false`
* otherwise, `d` should be written directly to the websocket stream as JSON object.
* Two possible formats of `d`:
* ```text
* execute an server-side script file:
*
* {
* path: [VFS path],
* parameters: [parameters of the server-side script]
* }
*
* or, execute directly a snippet of server-side script:
*
* { code: [server-side script code snippet as string] }
*
* ```
*
* @param {boolean} ws flag indicate whether to use websocket for the connection
* to the gateway API. In case of streaming data, the websocket is preferred
* @returns {Promise<any>} a promise on the result object (any)
*/
export function apigateway(
d: GenericObject<any>,
ws: boolean
): Promise<any> {
if (ws) {
return new Promise(function (resolve, reject) {
try {
const path = `${API.BASE_URI}/system/apigateway?ws=1`;
const proto =
window.location.protocol === "https:"
? "wss://"
: "ws://";
const socket = new WebSocket(proto + path);
return resolve(socket);
} catch (e) {
return reject(__e(e));
}
});
} else {
const path = `${API.REST}/system/apigateway?ws=0`;
return API.post(path, d);
}
}
/**
* Check if a user is logged in
*
* @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
* contains an error or a [[UserSettingType]] object
*/
export function auth(): Promise<RequestResult> {
const p = `${API.REST}/user/auth`;
return API.post(p, {});
}
/**
* 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
*/
export function login(d: UserLoginType): Promise<RequestResult> {
const p = `${API.REST}/user/login`;
return API.post(p, d);
}
/**
* Perform a logout operation
*
* @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
*/
export function logout(): Promise<RequestResult> {
const p = `${API.REST}/user/logout`;
return API.post(p, {});
}
/**
* Save the current user settings
*
* @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
*/
export function setting(): Promise<RequestResult> {
const p = `${API.REST}/system/settings`;
return API.post(p, OS.setting);
}
/**
* This is the low level function of AntOS VDB API.
* It requests the server API to perform some simple
* SQL query.
*
* @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"
* }
* }
* ```
*/
export function dbquery(
cmd: string,
d: GenericObject<any>
): Promise<RequestResult> {
const path = `${API.REST}/VDB/${cmd}`;
return API.post(path, d);
}
}
}
}

View File

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

View File

@ -214,4 +214,153 @@
"Wrong format: it should be [name] url":"Wrong format: it should be [name] url",
"Yes":"Yes",
"{0} is not a directory":"{0} is not a directory"
,
"{0}: {1}":"{0}: {1}",
"{0} is not a file":"{0} is not a file",
"{0} is not an application":"{0} is not an application",
"Add application":"Add application",
"Add mount point":"Add mount point",
"Add service":"Add service",
"All fields should be filled":"All fields should be filled",
"Appearance":"Appearance",
"Application {0} is not installed":"Application {0} is not installed",
"Application meta data isnt found":"Application meta data isnt found",
"Application not found":"Application not found",
"Applications and services setting":"Applications and services setting",
"Apps. and Services":"Apps. and Services",
"Cannot fetch system locales: {0}":"Cannot fetch system locales: {0}",
"Cannot load extension meta data":"Cannot load extension meta data",
"Cannot load scheme: {0}":"Cannot load scheme: {0}",
"Cannot read folder: {0}":"Cannot read folder: {0}",
"Change language mode":"Change language mode",
"Change theme":"Change theme",
"Command palete":"Command palete",
"Command palette":"Command palette",
"Command Palette":"Command Palette",
"Compiled successful":"Compiled successful",
"Confirm install":"Confirm install",
"ct:Logout":"ct:Logout",
"ct:Toggle fullscreen":"ct:Toggle fullscreen",
"Current folder is not found":"Current folder is not found",
"Desktop path":"Desktop path",
"Duplicate key: {0}":"Duplicate key: {0}",
"Edit mount point":"Edit mount point",
"Enter key-value data":"Enter key-value data",
"Enter URI":"Enter URI",
"Error reported":"Error reported",
"example string":"example string",
"Extension installed":"Extension installed",
"ExtensionName":"ExtensionName",
"Fail to create: {0}":"Fail to create: {0}",
"Fail to download: {0}":"Fail to download: {0}",
"Fail to publish: {0}":"Fail to publish: {0}",
"Fail to rename: {0}":"Fail to rename: {0}",
"Fail to upload: {0}":"Fail to upload: {0}",
"hello {0}":"hello {0}",
"Hide":"Hide",
"Install from zip":"Install from zip",
"Installing...":"Installing...",
"Invalid library: {0}":"Invalid library: {0}",
"Invalid package name: {0}":"Invalid package name: {0}",
"Invalid package path":"Invalid package path",
"Key cannot be empty":"Key cannot be empty",
"Languages":"Languages",
"Local packages path":"Local packages path",
"Mount points":"Mount points",
"Mount Points":"Mount Points",
"New CodePad extension at":"New CodePad extension at",
"New Project at":"New Project at",
"New window":"New window",
"No meta-data found":"No meta-data found",
"Open folder":"Open folder",
"Open Folder":"Open Folder",
"Open Recent":"Open Recent",
"Output":"Output",
"Packaged uninstalled":"Packaged uninstalled",
"Package installed: {0}":"Package installed: {0}",
"Package not found {0}":"Package not found {0}",
"Package updated":"Package updated",
"Path":"Path",
"Pinned applications":"Pinned applications",
"Please enter extension URI:":"Please enter extension URI:",
"Please enter mount point name":"Please enter mount point name",
"Please select {0} only":"Please select {0} only",
"Please select a day":"Please select a day",
"Please select a directory":"Please select a directory",
"Please select a file/fofler":"Please select a file/fofler",
"Please select an item":"Please select an item",
"Please select color":"Please select color",
"Preparing for release":"Preparing for release",
"ProjectName":"ProjectName",
"Remove: {0}?":"Remove: {0}?",
"Remove":"Remove",
"Report":"Report",
"Running {0}...":"Running {0}...",
"Select a directory":"Select a directory",
"Select build directory":"Select build directory",
"Select extension archive":"Select extension archive",
"Select package archive":"Select package archive",
"Service not found: {0}":"Service not found: {0}",
"Services":"Services",
"Show":"Show",
"Start":"Start",
"Startup applications":"Startup applications",
"Startup services":"Startup services",
"Startup":"Startup",
"System error log":"System error log",
"System locale":"System locale",
"System setting saved":"System setting saved",
"The folder is not empty: {0}":"The folder is not empty: {0}",
"Theme":"Theme",
"Toggle bottom bar":"Toggle bottom bar",
"Toggle split view":"Toggle split view",
"Unable to build extension: {0}":"Unable to build extension: {0}",
"Unable to build project: {0}":"Unable to build project: {0}",
"Unable to create archive: {0}":"Unable to create archive: {0}",
"Unable to create extension directories: {0}":"Unable to create extension directories: {0}",
"Unable to create extension template: {0}":"Unable to create extension template: {0}",
"Unable to create package archive: {0}":"Unable to create package archive: {0}",
"Unable to create project directory: {0}":"Unable to create project directory: {0}",
"Unable to create template files: {0}":"Unable to create template files: {0}",
"Unable to disable split view: Please save changes of modified files on the right panel":"Unable to disable split view: Please save changes of modified files on the right panel",
"Unable to find: {0}":"Unable to find: {0}",
"Unable to find action: {0}":"Unable to find action: {0}",
"Unable to find application meta-data: {0}":"Unable to find application meta-data: {0}",
"Unable to find dialog scheme":"Unable to find dialog scheme",
"Unable to find extension: {0}":"Unable to find extension: {0}",
"Unable to find package: {0}":"Unable to find package: {0}",
"Unable to get blob: {0}":"Unable to get blob: {0}",
"Unable to install extension: {0}":"Unable to install extension: {0}",
"Unable to install package":"Unable to install package",
"Unable to launch: {0}":"Unable to launch: {0}",
"Unable to load: {0}":"Unable to load: {0}",
"unable to load extension: {0}":"unable to load extension: {0}",
"Unable to load push notification service":"Unable to load push notification service",
"Unable to load repository: {0}: {1}":"Unable to load repository: {0}: {1}",
"Unable to move file/folder":"Unable to move file/folder",
"Unable to open: {0}":"Unable to open: {0}",
"Unable to preload extension":"Unable to preload extension",
"Unable to read: {0}":"Unable to read: {0}",
"Unable to read meta-data: {0}":"Unable to read meta-data: {0}",
"Unable to read meta-data:{0}":"Unable to read meta-data:{0}",
"Unable to read meta-data":"Unable to read meta-data",
"Unable to read package description":"Unable to read package description",
"Unable to report error: {0}":"Unable to report error: {0}",
"Unable to run extension: {0}":"Unable to run extension: {0}",
"Unable to run project: {0}":"Unable to run project: {0}",
"Unable to save file: {0}":"Unable to save file: {0}",
"Unable to uninstall package(s): {0}":"Unable to uninstall package(s): {0}",
"Uninstall successfully":"Uninstall successfully",
"Unknown extension action: {0}":"Unknown extension action: {0}",
"Unresolved dependencies on: {0}":"Unresolved dependencies on: {0}",
"Unresolved dependencies":"Unresolved dependencies",
"Update":"Update",
"Value":"Value",
"Verifying: {0}":"Verifying: {0}",
"Version string is in invalid format: {0}":"Version string is in invalid format: {0}",
"VFS unknown handle: {0}":"VFS unknown handle: {0}",
"VFS":"VFS",
"Wallpaper":"Wallpaper"
}

View File

@ -1,217 +0,0 @@
{
"About":"About",
"About: {0}":"About: {0}",
"Add category":"Add category",
"Add repository":"Add repository",
"Address":"Address",
"Alive (ms)":"Alive (ms)",
"Application installed":"Application installed",
"Application {0} is not executable":"Application {0} is not executable",
"Application":"Application",
"Applications":"Applications",
"April":"April",
"August":"August",
"Authentication":"Authentication",
"Cancel":"Cancel",
"Cannot Edit category":"Cannot Edit category",
"Cannot add new category":"Cannot add new category",
"Cannot create {0}":"Cannot create {0}",
"Cannot delete all content of: {0} [{1}]":"Cannot delete all content of: {0} [{1}]",
"Cannot delete the category: {0} [{1}]":"Cannot delete the category: {0} [{1}]",
"Cannot delete the section: {0}":"Cannot delete the section: {0}",
"Cannot delete: {0}":"Cannot delete: {0}",
"Cannot down load the app {0}":"Cannot down load the app {0}",
"Cannot export file for embedding to text":"Cannot export file for embedding to text",
"Cannot fetch CV categories":"Cannot fetch CV categories",
"Cannot fetch the entry content":"Cannot fetch the entry content",
"Cannot fetch user data":"Cannot fetch user data",
"Cannot install {0}":"Cannot install {0}",
"Cannot load 3rd library at: {0}":"Cannot load 3rd library at: {0}",
"Cannot move section":"Cannot move section",
"Cannot read service script: {0}":"Cannot read service script: {0}",
"Cannot render the PDF file":"Cannot render the PDF file",
"Cannot save blog: {0}":"Cannot save blog: {0}",
"Cannot save section: {0}":"Cannot save section: {0}",
"Cannot save system setting":"Cannot save system setting",
"Cannot save user data":"Cannot save user data",
"Cannot share file: {0}":"Cannot share file: {0}",
"Cannot uninstall package: {0}":"Cannot uninstall package: {0}",
"Categories":"Categories",
"Clear all":"Clear all",
"Close tab":"Close tab",
"Close without saving ?":"Close without saving ?",
"Close":"Close",
"Copy not yet implemented":"Copy not yet implemented",
"Copy":"Copy",
"Created: {0}":"Created: {0}",
"Cut":"Cut",
"December":"December",
"Delete a post":"Delete a post",
"Delete category":"Delete category",
"Delete section":"Delete section",
"Delete":"Delete",
"Desktop":"Desktop",
"Dialog {0} not found":"Dialog {0} not found",
"Do you really want to delete this post ?":"Do you really want to delete this post ?",
"Do you really want to delete: {0}?":"Do you really want to delete: {0}?",
"Download":"Download",
"Edit category":"Edit category",
"Edit repository":"Edit repository",
"Edit":"Edit",
"Email":"Email",
"Error find app by mimes {0}":"Error find app by mimes {0}",
"Error reading package meta data: {0}":"Error reading package meta data: {0}",
"Error saving file {0}":"Error saving file {0}",
"Exit":"Exit",
"Fail to create directory: {0}":"Fail to create directory: {0}",
"Fail to create {0}: {1}":"Fail to create {0}: {1}",
"Fail to delete {0}: {1}":"Fail to delete {0}: {1}",
"Fail to delete: {0}":"Fail to delete: {0}",
"Fail to fetch packages list from: {0}":"Fail to fetch packages list from: {0}",
"Fail to get file meta data: {0}":"Fail to get file meta data: {0}",
"Fail to make request: {0}":"Fail to make request: {0}",
"Fail to move file: {0} -> {1}":"Fail to move file: {0} -> {1}",
"Fail to paste: {0}":"Fail to paste: {0}",
"Fail to publish file: {0}":"Fail to publish file: {0}",
"Fail to query data from database: {0}":"Fail to query data from database: {0}",
"Fail to read file: {0}":"Fail to read file: {0}",
"Fail to rename to {0}: {1}":"Fail to rename to {0}: {1}",
"Fail to scan directory: {0}":"Fail to scan directory: {0}",
"Fail to upload file to: {0}":"Fail to upload file to: {0}",
"Fail to upload to {0}: {1}":"Fail to upload to {0}: {1}",
"Fail to write to file: {0}":"Fail to write to file: {0}",
"Fail to {0} package":"Fail to {0} package",
"February":"February",
"File name":"File name",
"File not found {0}":"File not found {0}",
"File {0} copied":"File {0} copied",
"File {0} cut":"File {0} cut",
"File":"File",
"Folder name":"Folder name",
"Format : [name] url":"Format : [name] url",
"Found {0} sections":"Found {0} sections",
"Fri":"Fri",
"From":"From",
"Full name must be entered":"Full name must be entered",
"Full name":"Full name",
"Google Drive":"Google Drive",
"Hidden files":"Hidden files",
"Home":"Home",
"Icon view":"Icon view",
"Ignore all {0} unsaved files ?":"Ignore all {0} unsaved files ?",
"Install":"Install",
"Invalid package: Meta data file not found":"Invalid package: Meta data file not found",
"January":"January",
"July":"July",
"June":"June",
"Kill process":"Kill process",
"Language file {0} not found":"Language file {0} not found",
"Launch":"Launch",
"List view":"List view",
"Location":"Location",
"Log out":"Log out",
"Logout":"Logout",
"March":"March",
"May":"May",
"Mime type {0} is not supported":"Mime type {0} is not supported",
"Modify section entry":"Modify section entry",
"Mon":"Mon",
"Move to":"Move to",
"Name":"Name",
"Navigation bar":"Navigation bar",
"New file":"New file",
"New folder":"New folder",
"New section entry for {0}":"New section entry for {0}",
"New":"New",
"No application available to open {0}":"No application available to open {0}",
"No post found: {0}":"No post found: {0}",
"No":"No",
"November":"November",
"OS":"OS",
"October":"October",
"Ok":"Ok",
"Only {0} could be selected":"Only {0} could be selected",
"Open file":"Open file",
"Open with":"Open with",
"Open":"Open",
"Options":"Options",
"Package uninstalled":"Package uninstalled",
"Parent can not be the category itself":"Parent can not be the category itself",
"Paste":"Paste",
"Phone":"Phone",
"Pid":"Pid",
"Please enter category name":"Please enter category name",
"Please enter tags":"Please enter tags",
"Please insert a title in the text: beginning with heading":"Please insert a title in the text: beginning with heading",
"Please select a category":"Please select a category",
"Please select a date":"Please select a date",
"Please select a file":"Please select a file",
"Please select a parent category":"Please select a parent category",
"Please select a section to edit":"Please select a section to edit",
"Please select a section to move":"Please select a section to move",
"Preview":"Preview",
"Properties":"Properties",
"Quit without saving ?":"Quit without saving ?",
"Quit":"Quit",
"Read more":"Read more",
"Refresh":"Refresh",
"Rename":"Rename",
"Repositories":"Repositories",
"Resource not found {0}":"Resource not found {0}",
"Resource not found: {0}":"Resource not found: {0}",
"Row {0}, col {1}, lines: {2}":"Row {0}, col {1}, lines: {2}",
"Sat":"Sat",
"Save as":"Save as",
"Save":"Save",
"Section list is empty, please add one":"Section list is empty, please add one",
"Select image file":"Select image file",
"Selected: {0} ({1} bytes)":"Selected: {0} ({1} bytes)",
"September":"September",
"Service":"Service",
"Share file":"Share file",
"Shared url: {0}":"Shared url: {0}",
"Shared":"Shared",
"Short biblio":"Short biblio",
"Sidebar":"Sidebar",
"Size":"Size",
"Subtitle":"Subtitle",
"Sun":"Sun",
"System fail: Cannot init desktop manager":"System fail: Cannot init desktop manager",
"System fail: Cannot init login screen":"System fail: Cannot init login screen",
"Tags":"Tags",
"This feature is not implemented yet":"This feature is not implemented yet",
"Thu":"Thu",
"Title or content must not be blank":"Title or content must not be blank",
"Title":"Title",
"Toggle Full screen":"Toggle Full screen",
"Tree view":"Tree view",
"Tue":"Tue",
"Type":"Type",
"Uninstall : {0}?":"Uninstall : {0}?",
"Uninstall":"Uninstall",
"Unknown API setting for {0}":"Unknown API setting for {0}",
"Updated: {0}":"Updated: {0}",
"Upload":"Upload",
"Url":"Url",
"User abort the authentication":"User abort the authentication",
"User data updated":"User data updated",
"VDB Unknown condition for delete command":"VDB Unknown condition for delete command",
"VFS Cannot encode file: {0}":"VFS Cannot encode file: {0}",
"VFS cannot create : {0}":"VFS cannot create : {0}",
"VFS cannot delete : {0}":"VFS cannot delete : {0}",
"VFS cannot download file : {0}":"VFS cannot download file : {0}",
"VFS cannot get meta data for {0}":"VFS cannot get meta data for {0}",
"VFS cannot init {0}: {1}":"VFS cannot init {0}: {1}",
"VFS cannot move : {0}":"VFS cannot move : {0}",
"VFS cannot read : {0}":"VFS cannot read : {0}",
"VFS cannot save : {0}":"VFS cannot save : {0}",
"VFS cannot write : {0}":"VFS cannot write : {0}",
"VFS unknown action: {0}":"VFS unknown action: {0}",
"VFS unknown handler: {0}":"VFS unknown handler: {0}",
"View":"View",
"Wed":"Wed",
"Would you like to login to {0}?":"Would you like to login to {0}?",
"Wrong format: it should be [name] url":"Wrong format: it should be [name] url",
"Yes":"Yes",
"{0} is not a directory":"{0} is not a directory"
}

View File

@ -304,4 +304,116 @@
"Welcome to AntOSDK":"Welcome to AntOSDK",
"Your application version is older ({0} < {1})":"Your application version is older ({0} < {1})",
"zip file generated in release folder":"zip file generated in release folder"
,
"{0}: {1}":"{0}: {1}",
"{0} is not a file":"{0} is not a file",
"{0} is not an application":"{0} is not an application",
"All fields should be filled":"All fields should be filled",
"Application {0} is not installed":"Application {0} is not installed",
"Application meta data isnt found":"Application meta data isnt found",
"Application not found":"Application not found",
"Applications and services setting":"Applications and services setting",
"Apps. and Services":"Apps. and Services",
"Cannot load extension meta data":"Cannot load extension meta data",
"Cannot load scheme: {0}":"Cannot load scheme: {0}",
"Cannot read folder: {0}":"Cannot read folder: {0}",
"Change language mode":"Change language mode",
"Change theme":"Change theme",
"Command palete":"Command palete",
"Command palette":"Command palette",
"Command Palette":"Command Palette",
"Confirm install":"Confirm install",
"ct:Logout":"ct:Logout",
"ct:Toggle fullscreen":"ct:Toggle fullscreen",
"Current folder is not found":"Current folder is not found",
"Duplicate key: {0}":"Duplicate key: {0}",
"Enter key-value data":"Enter key-value data",
"Enter URI":"Enter URI",
"Error reported":"Error reported",
"example string":"example string",
"Extension installed":"Extension installed",
"ExtensionName":"ExtensionName",
"Fail to create: {0}":"Fail to create: {0}",
"Fail to download: {0}":"Fail to download: {0}",
"Fail to publish: {0}":"Fail to publish: {0}",
"Fail to rename: {0}":"Fail to rename: {0}",
"Fail to upload: {0}":"Fail to upload: {0}",
"hello {0}":"hello {0}",
"Install from zip":"Install from zip",
"Invalid library: {0}":"Invalid library: {0}",
"Invalid package name: {0}":"Invalid package name: {0}",
"Invalid package path":"Invalid package path",
"Key cannot be empty":"Key cannot be empty",
"Mount Points":"Mount Points",
"New CodePad extension at":"New CodePad extension at",
"New window":"New window",
"No meta-data found":"No meta-data found",
"Open folder":"Open folder",
"Open Folder":"Open Folder",
"Open Recent":"Open Recent",
"Packaged uninstalled":"Packaged uninstalled",
"Package installed: {0}":"Package installed: {0}",
"Package not found {0}":"Package not found {0}",
"Package updated":"Package updated",
"Pinned applications":"Pinned applications",
"Please enter extension URI:":"Please enter extension URI:",
"Please select a day":"Please select a day",
"Please select an item":"Please select an item",
"Please select color":"Please select color",
"Report":"Report",
"Select build directory":"Select build directory",
"Select extension archive":"Select extension archive",
"Select package archive":"Select package archive",
"Service not found: {0}":"Service not found: {0}",
"Services":"Services",
"Start":"Start",
"System error log":"System error log",
"The folder is not empty: {0}":"The folder is not empty: {0}",
"Toggle bottom bar":"Toggle bottom bar",
"Toggle split view":"Toggle split view",
"Unable to build extension: {0}":"Unable to build extension: {0}",
"Unable to build project: {0}":"Unable to build project: {0}",
"Unable to create archive: {0}":"Unable to create archive: {0}",
"Unable to create extension directories: {0}":"Unable to create extension directories: {0}",
"Unable to create extension template: {0}":"Unable to create extension template: {0}",
"Unable to create package archive: {0}":"Unable to create package archive: {0}",
"Unable to create project directory: {0}":"Unable to create project directory: {0}",
"Unable to create template files: {0}":"Unable to create template files: {0}",
"Unable to disable split view: Please save changes of modified files on the right panel":"Unable to disable split view: Please save changes of modified files on the right panel",
"Unable to find: {0}":"Unable to find: {0}",
"Unable to find action: {0}":"Unable to find action: {0}",
"Unable to find application meta-data: {0}":"Unable to find application meta-data: {0}",
"Unable to find dialog scheme":"Unable to find dialog scheme",
"Unable to find extension: {0}":"Unable to find extension: {0}",
"Unable to find package: {0}":"Unable to find package: {0}",
"Unable to get blob: {0}":"Unable to get blob: {0}",
"Unable to install extension: {0}":"Unable to install extension: {0}",
"Unable to install package":"Unable to install package",
"Unable to launch: {0}":"Unable to launch: {0}",
"Unable to load: {0}":"Unable to load: {0}",
"unable to load extension: {0}":"unable to load extension: {0}",
"Unable to load push notification service":"Unable to load push notification service",
"Unable to load repository: {0}: {1}":"Unable to load repository: {0}: {1}",
"Unable to move file/folder":"Unable to move file/folder",
"Unable to open: {0}":"Unable to open: {0}",
"Unable to preload extension":"Unable to preload extension",
"Unable to read: {0}":"Unable to read: {0}",
"Unable to read meta-data: {0}":"Unable to read meta-data: {0}",
"Unable to read meta-data:{0}":"Unable to read meta-data:{0}",
"Unable to read meta-data":"Unable to read meta-data",
"Unable to read package description":"Unable to read package description",
"Unable to report error: {0}":"Unable to report error: {0}",
"Unable to run extension: {0}":"Unable to run extension: {0}",
"Unable to run project: {0}":"Unable to run project: {0}",
"Unable to save file: {0}":"Unable to save file: {0}",
"Unable to uninstall package(s): {0}":"Unable to uninstall package(s): {0}",
"Uninstall successfully":"Uninstall successfully",
"Unknown extension action: {0}":"Unknown extension action: {0}",
"Unresolved dependencies on: {0}":"Unresolved dependencies on: {0}",
"Unresolved dependencies":"Unresolved dependencies",
"Value":"Value",
"Verifying: {0}":"Verifying: {0}",
"VFS unknown handle: {0}":"VFS unknown handle: {0}"
}

View File

@ -245,4 +245,63 @@
"Theme":"Thème",
"VFS":"VFS",
"Wallpaper":"Fond d'écran"
,
"add {0} to zip":"add {0} to zip",
"Add files to build target":"Add files to build target",
"and unsaved project":"and unsaved project",
"Build and Run":"Build and Run",
"Build":"Build",
"Build done":"Build done",
"Build Options":"Build Options",
"Build release":"Build release",
"Cannot create file: {0}":"Cannot create file: {0}",
"Cannot export: {0}":"Cannot export: {0}",
"Cannot export to {0}: {1}":"Cannot export to {0}: {1}",
"Cannot export to PNG in this browser: {0}":"Cannot export to PNG in this browser: {0}",
"Cannot save project: {0}":"Cannot save project: {0}",
"Cannot save the zip file {0} : {1}":"Cannot save the zip file {0} : {1}",
"Coffees":"Coffees",
"Compiled successful":"Compiled successful",
"Copied {0} -> {1}":"Copied {0} -> {1}",
"Copied files":"Copied files",
"Created directory: {0}":"Created directory: {0}",
"Created file: {0}":"Created file: {0}",
"Css":"Css",
"Error when create directory: {0}":"Error when create directory: {0}",
"Export as":"Export as",
"File exported":"File exported",
"Generated {0}":"Generated {0}",
"Graph editor":"Graph editor",
"Hide":"Hide",
"Ignore: {0} unsaved files {1}?":"Ignore: {0} unsaved files {1}?",
"Ignore unsaved project ?":"Ignore unsaved project ?",
"Installing...":"Installing...",
"Javascripts":"Javascripts",
"Metadata found...":"Metadata found...",
"New Project at":"New Project at",
"New project":"New project",
"Opening {0}":"Opening {0}",
"Open project":"Open project",
"Open Project":"Open Project",
"Output":"Output",
"Please select {0} only":"Please select {0} only",
"Please select a file/fofler":"Please select a file/fofler",
"Preparing for release":"Preparing for release",
"ProjectName":"ProjectName",
"Project":"Project",
"project saved":"project saved",
"Render":"Render",
"Running {0}...":"Running {0}...",
"Select a file":"Select a file",
"Show":"Show",
"Syntax error: {0}":"Syntax error: {0}",
"Uninstall: {0}?":"Uninstall: {0}?",
"Unsaved project":"Unsaved project",
"Update":"Update",
"Verifying {0}":"Verifying {0}",
"Version string is in invalid format: {0}":"Version string is in invalid format: {0}",
"Welcome to AntOSDK":"Welcome to AntOSDK",
"Your application version is older ({0} < {1})":"Your application version is older ({0} < {1})",
"zip file generated in release folder":"zip file generated in release folder"
}

4
src/core/languages/gen.sh Normal file → Executable file
View File

@ -20,7 +20,7 @@
ord() {
LC_CTYPE=C printf '%d' "'$1"
}
grep --include=\*.{coffee,tag} -roh "$1" -e '__("[^"]*"' | while read -r line ; do
grep --include=\*.ts -roh "$1" -e '__("[^"]*"' | while read -r line ; do
SUBSTRING=$(echo $line| cut -d'"' -f 2)
if test -f "$2" && [ ! -z "$(grep -F "\"$SUBSTRING\":" "$2")" ]
then
@ -29,7 +29,7 @@ grep --include=\*.{coffee,tag} -roh "$1" -e '__("[^"]*"' | while read -r line ;
echo -e "\t\"$SUBSTRING\":\"$SUBSTRING\"," >> "tmp.json"
fi
done
grep --include=\*.{coffee,html,tag} -roh "$1" -e '\"__([^\"]*)\"' | while read -r line; do
grep --include=\*.{ts,html} -roh "$1" -e '\"__([^\"]*)\"' | while read -r line; do
len=$(( ${#line} - 6 ))
#echo $len
#echo $line

View File

@ -318,4 +318,141 @@
"Welcome to AntOSDK":"Welcome to AntOSDK",
"Your application version is older ({0} < {1})":"Your application version is older ({0} < {1})",
"zip file generated in release folder":"zip file generated in release folder"
,
"Cancels":"Cancels",
"Command Palette":"Command Palette",
"Mount Points":"Mount Points",
"New CodePad extension at":"New CodePad extension at",
"Report":"Report",
"Select extension archive":"Select extension archive",
"System error log":"System error log"
,
"[^":"[^",
"{0} is not a file":"{0} is not a file",
"Action {0} is unsupported on: {1}":"Action {0} is unsupported on: {1}",
"Application meta data isnt found":"Application meta data isnt found",
"Cannot load extension meta data":"Cannot load extension meta data",
"Cannot load scheme: {0}":"Cannot load scheme: {0}",
"Cannot read folder: {0}":"Cannot read folder: {0}",
"Change language mode":"Change language mode",
"Change theme":"Change theme",
"Command palete":"Command palete",
"Command palette":"Command palette",
"ct:Logout":"ct:Logout",
"ct:Toggle fullscreen":"ct:Toggle fullscreen",
"ct:User: {0}":"ct:User: {0}",
"Current folder is not found":"Current folder is not found",
"Error reported":"Error reported",
"Error saving file {0}: {1}":"Error saving file {0}: {1}",
"Example action":"Example action",
"Extension installed":"Extension installed",
"ExtensionName":"ExtensionName",
"Fail to create: {0}":"Fail to create: {0}",
"Fail to download: {0}":"Fail to download: {0}",
"Fail to publish: {0}":"Fail to publish: {0}",
"Fail to read: {0}":"Fail to read: {0}",
"Fail to rename: {0}":"Fail to rename: {0}",
"Fail to upload: {0}":"Fail to upload: {0}",
"Install extension":"Install extension",
"Invalid library: {0}":"Invalid library: {0}",
"New Extension":"New Extension",
"New project from current folder":"New project from current folder",
"New Project":"New Project",
"No meta-data found":"No meta-data found",
"Open folder":"Open folder",
"Open Folder":"Open Folder",
"Package is generated in release folder":"Package is generated in release folder",
"Please select a day":"Please select a day",
"Please select an item":"Please select an item",
"Please select color":"Please select color",
"Start":"Start",
"The folder is not empty: {0}":"The folder is not empty: {0}",
"Unable to build extension":"Unable to build extension",
"Unable to build project":"Unable to build project",
"Unable to create archive":"Unable to create archive",
"Unable to create extension directories":"Unable to create extension directories",
"Unable to create extension template":"Unable to create extension template",
"Unable to create package archive":"Unable to create package archive",
"Unable to create project directory":"Unable to create project directory",
"Unable to create template files":"Unable to create template files",
"Unable to find action: {0}":"Unable to find action: {0}",
"Unable to find extension: {0}":"Unable to find extension: {0}",
"Unable to install extension":"Unable to install extension",
"unable to load extension: {0}":"unable to load extension: {0}",
"Unable to load libraries":"Unable to load libraries",
"Unable to open: {0}":"Unable to open: {0}",
"Unable to preload extension":"Unable to preload extension",
"Unable to read: {0}":"Unable to read: {0}",
"Unable to read meta-data":"Unable to read meta-data",
"Unable to report error: {0}":"Unable to report error: {0}",
"Unable to run extension":"Unable to run extension",
"Unable to run project":"Unable to run project",
"Unable to save file: {0}":"Unable to save file: {0}",
"Value":"Value",
"Verifying: {0}":"Verifying: {0}",
"VFS unknown handle: {0}":"VFS unknown handle: {0}"
,
"{0}: {1}":"{0}: {1}",
"{0} is not an application":"{0} is not an application",
"All fields should be filled":"All fields should be filled",
"Application {0} is not installed":"Application {0} is not installed",
"Application not found":"Application not found",
"Applications and services setting":"Applications and services setting",
"Apps. and Services":"Apps. and Services",
"Confirm install":"Confirm install",
"Duplicate key: {0}":"Duplicate key: {0}",
"Enter key-value data":"Enter key-value data",
"Enter URI":"Enter URI",
"example string":"example string",
"hello {0}":"hello {0}",
"Install from zip":"Install from zip",
"Invalid package name: {0}":"Invalid package name: {0}",
"Invalid package path":"Invalid package path",
"Key cannot be empty":"Key cannot be empty",
"New window":"New window",
"Open Recent":"Open Recent",
"Packaged uninstalled":"Packaged uninstalled",
"Package installed: {0}":"Package installed: {0}",
"Package not found {0}":"Package not found {0}",
"Package updated":"Package updated",
"Pinned applications":"Pinned applications",
"Please enter extension URI:":"Please enter extension URI:",
"Select build directory":"Select build directory",
"Select package archive":"Select package archive",
"Service not found: {0}":"Service not found: {0}",
"Toggle bottom bar":"Toggle bottom bar",
"Toggle split view":"Toggle split view",
"Unable to build extension: {0}":"Unable to build extension: {0}",
"Unable to build project: {0}":"Unable to build project: {0}",
"Unable to create archive: {0}":"Unable to create archive: {0}",
"Unable to create extension directories: {0}":"Unable to create extension directories: {0}",
"Unable to create extension template: {0}":"Unable to create extension template: {0}",
"Unable to create package archive: {0}":"Unable to create package archive: {0}",
"Unable to create project directory: {0}":"Unable to create project directory: {0}",
"Unable to create template files: {0}":"Unable to create template files: {0}",
"Unable to disable split view: Please save changes of modified files on the right panel":"Unable to disable split view: Please save changes of modified files on the right panel",
"Unable to find: {0}":"Unable to find: {0}",
"Unable to find application meta-data: {0}":"Unable to find application meta-data: {0}",
"Unable to find dialog scheme":"Unable to find dialog scheme",
"Unable to find package: {0}":"Unable to find package: {0}",
"Unable to get blob: {0}":"Unable to get blob: {0}",
"Unable to install extension: {0}":"Unable to install extension: {0}",
"Unable to install package":"Unable to install package",
"Unable to launch: {0}":"Unable to launch: {0}",
"Unable to load: {0}":"Unable to load: {0}",
"Unable to load push notification service":"Unable to load push notification service",
"Unable to load repository: {0}: {1}":"Unable to load repository: {0}: {1}",
"Unable to move file/folder":"Unable to move file/folder",
"Unable to read meta-data: {0}":"Unable to read meta-data: {0}",
"Unable to read meta-data:{0}":"Unable to read meta-data:{0}",
"Unable to read package description":"Unable to read package description",
"Unable to run extension: {0}":"Unable to run extension: {0}",
"Unable to run project: {0}":"Unable to run project: {0}",
"Unable to uninstall package(s): {0}":"Unable to uninstall package(s): {0}",
"Uninstall successfully":"Uninstall successfully",
"Unknown extension action: {0}":"Unknown extension action: {0}",
"Unresolved dependencies on: {0}":"Unresolved dependencies on: {0}",
"Unresolved dependencies":"Unresolved dependencies"
}

View File

@ -267,4 +267,130 @@
,
"Graph editor":"Graph editor",
"Render":"Render"
,
"add {0} to zip":"add {0} to zip",
"Add files to build target":"Add files to build target",
"and unsaved project":"and unsaved project",
"Build and Run":"Build and Run",
"Build":"Build",
"Build done":"Build done",
"Build Options":"Build Options",
"Build release":"Build release",
"Cannot create file: {0}":"Cannot create file: {0}",
"Cannot save project: {0}":"Cannot save project: {0}",
"Cannot save the zip file {0} : {1}":"Cannot save the zip file {0} : {1}",
"Coffees":"Coffees",
"Compiled successful":"Compiled successful",
"Copied {0} -> {1}":"Copied {0} -> {1}",
"Copied files":"Copied files",
"Created directory: {0}":"Created directory: {0}",
"Created file: {0}":"Created file: {0}",
"Css":"Css",
"Error when create directory: {0}":"Error when create directory: {0}",
"Generated {0}":"Generated {0}",
"Hide":"Hide",
"Ignore: {0} unsaved files {1}?":"Ignore: {0} unsaved files {1}?",
"Ignore unsaved project ?":"Ignore unsaved project ?",
"Installing...":"Installing...",
"Javascripts":"Javascripts",
"Metadata found...":"Metadata found...",
"New Project at":"New Project at",
"New project":"New project",
"Opening {0}":"Opening {0}",
"Open project":"Open project",
"Open Project":"Open Project",
"Output":"Output",
"Please select {0} only":"Please select {0} only",
"Please select a file/fofler":"Please select a file/fofler",
"Preparing for release":"Preparing for release",
"ProjectName":"ProjectName",
"Project":"Project",
"project saved":"project saved",
"Running {0}...":"Running {0}...",
"Select a file":"Select a file",
"Show":"Show",
"Uninstall: {0}?":"Uninstall: {0}?",
"Unsaved project":"Unsaved project",
"Update":"Update",
"Verifying {0}":"Verifying {0}",
"Version string is in invalid format: {0}":"Version string is in invalid format: {0}",
"Welcome to AntOSDK":"Welcome to AntOSDK",
"Your application version is older ({0} < {1})":"Your application version is older ({0} < {1})",
"zip file generated in release folder":"zip file generated in release folder"
,
"Cancels":"Cancels",
"Command Palette":"Command Palette",
"Mount Points":"Mount Points",
"New CodePad extension at":"New CodePad extension at",
"Report":"Report",
"Select extension archive":"Select extension archive",
"System error log":"System error log"
,
"[^":"[^",
"{0} is not a file":"{0} is not a file",
"Action {0} is unsupported on: {1}":"Action {0} is unsupported on: {1}",
"Application meta data isnt found":"Application meta data isnt found",
"Cannot load extension meta data":"Cannot load extension meta data",
"Cannot load scheme: {0}":"Cannot load scheme: {0}",
"Cannot read folder: {0}":"Cannot read folder: {0}",
"Change language mode":"Change language mode",
"Change theme":"Change theme",
"Command palete":"Command palete",
"Command palette":"Command palette",
"ct:Logout":"ct:Logout",
"ct:Toggle fullscreen":"ct:Toggle fullscreen",
"ct:User: {0}":"ct:User: {0}",
"Current folder is not found":"Current folder is not found",
"Error reported":"Error reported",
"Error saving file {0}: {1}":"Error saving file {0}: {1}",
"Example action":"Example action",
"Extension installed":"Extension installed",
"ExtensionName":"ExtensionName",
"Fail to create: {0}":"Fail to create: {0}",
"Fail to download: {0}":"Fail to download: {0}",
"Fail to publish: {0}":"Fail to publish: {0}",
"Fail to read: {0}":"Fail to read: {0}",
"Fail to rename: {0}":"Fail to rename: {0}",
"Fail to upload: {0}":"Fail to upload: {0}",
"Install extension":"Install extension",
"Invalid library: {0}":"Invalid library: {0}",
"New Extension":"New Extension",
"New project from current folder":"New project from current folder",
"New Project":"New Project",
"No meta-data found":"No meta-data found",
"Open folder":"Open folder",
"Open Folder":"Open Folder",
"Package is generated in release folder":"Package is generated in release folder",
"Please select a day":"Please select a day",
"Please select an item":"Please select an item",
"Please select color":"Please select color",
"Start":"Start",
"The folder is not empty: {0}":"The folder is not empty: {0}",
"Unable to build extension":"Unable to build extension",
"Unable to build project":"Unable to build project",
"Unable to create archive":"Unable to create archive",
"Unable to create extension directories":"Unable to create extension directories",
"Unable to create extension template":"Unable to create extension template",
"Unable to create package archive":"Unable to create package archive",
"Unable to create project directory":"Unable to create project directory",
"Unable to create template files":"Unable to create template files",
"Unable to find action: {0}":"Unable to find action: {0}",
"Unable to find extension: {0}":"Unable to find extension: {0}",
"Unable to install extension":"Unable to install extension",
"unable to load extension: {0}":"unable to load extension: {0}",
"Unable to load libraries":"Unable to load libraries",
"Unable to open: {0}":"Unable to open: {0}",
"Unable to preload extension":"Unable to preload extension",
"Unable to read: {0}":"Unable to read: {0}",
"Unable to read meta-data":"Unable to read meta-data",
"Unable to report error: {0}":"Unable to report error: {0}",
"Unable to run extension":"Unable to run extension",
"Unable to run project":"Unable to run project",
"Unable to save file: {0}":"Unable to save file: {0}",
"Value":"Value",
"Verifying: {0}":"Verifying: {0}",
"VFS unknown handle: {0}":"VFS unknown handle: {0}"
}

181
src/core/pm.ts Normal file
View File

@ -0,0 +1,181 @@
namespace OS {
/**
* This namespace dedicated to all operations related to system
* process management
*/
export namespace PM {
/**
* A process is either an instance of an application or a service
*/
export type ProcessType =
| application.BaseApplication
| application.BaseService;
/**
* Alias to all classes that extends [[BaseModel]]
*/
export type ModelTypeClass = {
new <T extends BaseModel>(args: AppArgumentsType[]): T;
};
/**
* Process id allocator, when a new process is created, the value of
* this variable is increased
*/
export var pidalloc: number = 0;
/**
* 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
*
* @export
* @param {string} app class name string
* @param {ProcessTypeClass} cls prototype class
* @param {GUI.AppArgumentsType[]} [args] process arguments
* @returns {Promise<ProcessType>} a promise on the created process
*/
export function createProcess(
app: string,
cls: ModelTypeClass,
args?: AppArgumentsType[]
): Promise<ProcessType> {
return new Promise(function (resolve, reject) {
let metaclass = (cls as any) as typeof BaseModel;
const f = function () {
//if it is single ton
// and a process is existing
// just return it
let obj: ProcessType;
if (
metaclass.singleton &&
PM.processes[app] &&
PM.processes[app].length === 1
) {
obj = PM.processes[
app
][0] as application.BaseApplication;
obj.show();
} else {
if (!PM.processes[app]) {
PM.processes[app] = [];
}
obj = new cls(args);
obj.birth = new Date().getTime();
PM.pidalloc++;
obj.pid = PM.pidalloc;
obj.subscribe("systemlocalechange", (d) => {
obj.updateLocale(d.message as string);
return obj.update();
});
PM.processes[app].push(obj);
if (metaclass.type === ModelType.Application) {
GUI.dock(
obj as application.BaseApplication,
metaclass.meta
);
} else {
GUI.attachservice(obj as application.BaseService);
}
}
return obj;
};
if (metaclass.dependencies) {
const libs = metaclass.dependencies;
return API.require(libs)
.then(() => resolve(f()))
.catch((e: Error) => reject(__e(e)));
} else {
return resolve(f());
}
});
}
/**
* Get the reference to a process using its id
*
* @export
* @param {number} pid
* @returns {BaseModel}
*/
export function appByPid(pid: number): BaseModel {
let app = undefined;
const find = function (l: Array<any>) {
for (let a of l) {
if (a.pid === pid) {
return a;
}
}
};
for (let k in PM.processes) {
const v = PM.processes[k];
app = find(v);
if (app) {
break;
}
}
return app;
}
/**
* Kill a process
*
* @export
* @param {OS.GUI.BaseModel} app reference to the process
* @returns {void}
*/
export function kill(app: BaseModel): void {
if (!app.name || !PM.processes[app.name]) {
return;
}
const i = PM.processes[app.name].indexOf(app);
if (i >= 0) {
if (application[app.name].type === ModelType.Application) {
GUI.undock(app as application.BaseApplication);
} else {
GUI.detachservice(app as application.BaseService);
}
announcer.unregister(app);
delete PM.processes[app.name][i];
PM.processes[app.name].splice(i, 1);
}
}
/**
* Kill all process of an application or service
*
* @export
* @param {string} app process class name
* @param {boolean} force force exit all process
* @returns {void}
*/
export function killAll(app: string, force: boolean): void {
if (!PM.processes[app]) {
return;
}
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);
}
}
}

View File

@ -1,11 +0,0 @@
<afx-app-window data-id = 'about-window' width='300' height='200'>
<afx-vbox>
<div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel'></afx-label>
</h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-vbox>
</afx-app-window>

View File

@ -1,10 +0,0 @@
<afx-sys-panel id = "syspanel"></afx-sys-panel>
<!--div class = "afx-clear"></div-->
<div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-float-list id = "desktop" ></afx-float-list>
<!--div id = "workingenv"></div-->
</div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu>
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>

View File

@ -1,13 +0,0 @@
<afx-app-window data-id = 'file-dialog-window' width='400' height='300'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox>
<afx-file-view data-id = "fileview" view='tree' status = false></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" />
<div data-height = '30' style=' text-align:right;padding:3px;'>
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
</afx-vbox>
</afx-hbox>
</afx-app-window>

View File

@ -1,78 +0,0 @@
<!--
Since nothing is loaded, the login form is a standalone element,
it deserves it own unique style
For now, it can not be themed !!!
-->
<style>
body, html{
background-color: rgba(215,215,215,0.7);
}
#login_form{
width:300px;
height: 200px;
display: block;
border:1px solid #a6a6a6;
border-radius: 6px;
box-shadow: 1px 1px 1px #9f9F9F;
position: absolute;
margin: auto;
top:0;
right: 0;
bottom: 0;
left: 0;
font-family:Verdana, Geneva, Tahoma, sans-serif;
font-size: 13px;
text-align: center;
background-color: white;
color: #414339;
}
#login_form p{
display: inline-block;
background-color:#dfdfdf;
border-bottom: 1px solid #a6a6a6;
padding:10px;
width: calc(100% - 20px);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
font-weight: bold;
margin:0;
}
#login_form input {
width: 250px;
outline: none;
margin-top:10px;
border-radius: 6px;
box-sizing: border-box;
font-size: 13px;
padding: 5px;
border: 1px solid #a6a6a6;
background-color: white;
color: #414339;
}
#login_form button{
margin-top:10px;
width: 250px;
height: 30px;
background-color: #2786F3;
color: white;
border: 1px solid #dedede;
border-radius: 6px;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 13px;
padding:5px;
}
#login_error{
padding:3px;
color:red;
font-weight: normal;
}
</style>
<div id = "login_form">
<p>Welcome to AntOS, please identify</p>
<input id = "txtuser" type = "text" value = "demo" />
<input id = "txtpass" type = "password" value = "demo" />
<button id = "btlogin">Login</button>
<span id = "login_error"></span>
</div>

View File

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

626
src/core/settings.ts Normal file
View File

@ -0,0 +1,626 @@
// 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 {
/**
* This namespace is dedicated to everything related to the
* global system settings
*/
export namespace setting {
/**
* User setting type definition
*
* @export
* @interface UserSettingType
*/
export interface UserSettingType {
/**
* User full name
*
* @type {string}
* @memberof UserSettingType
*/
name: string;
/**
* User name
*
* @type {string}
* @memberof UserSettingType
*/
username: string;
/**
* User id
*
* @type {number}
* @memberof UserSettingType
*/
id: number;
/**
* User groups
*
* @type {{ [index: number]: string }}
* @memberof UserSettingType
*/
group?: { [index: number]: string };
[propName: string]: any;
}
/**
* Virtual desktop setting data type
*
* @export
* @interface DesktopSettingType
*/
export interface DesktopSettingType {
/**
* Desktop VFS path
*
* @type {string}
* @memberof DesktopSettingType
*/
path: string;
/**
* Desktop menu, can be added automatically by applications
*
* @type {GUI.BasicItemType[]}
* @memberof DesktopSettingType
*/
menu: GUI.BasicItemType[];
/**
* Show desktop hidden files
*
* @type {boolean}
* @memberof DesktopSettingType
*/
showhidden: boolean;
[propName: string]: any;
}
/**
* Wallpaper setting data type
*
* @export
* @interface WPSettingType
*/
export interface WPSettingType {
/**
* Repeat wallpaper:
* - `repeat`
* - `repeat-x`
* - `repeat-y`
* - `no-repeat`
*
* @type {string}
* @memberof WPSettingType
*/
repeat: string;
/**
* Wallpaper size
* - `contain`
* - `cover`
* - `auto`
*
* @type {string}
* @memberof WPSettingType
*/
size: string;
/**
* VFS path to the wallpaper image
*
* @type {string}
* @memberof WPSettingType
*/
url: string;
}
/**
* Theme setting data type
*
* @export
* @interface ThemeSettingType
*/
export interface ThemeSettingType {
/**
* Theme name, this value is used for looking
* theme file in system asset
*
* @type {string}
* @memberof ThemeSettingType
*/
name: string;
/**
* Theme user-friendly text
*
* @type {string}
* @memberof ThemeSettingType
*/
text: string;
}
/**
* Appearance setting data type
*
* @export
* @interface AppearanceSettingType
*/
export interface AppearanceSettingType {
/**
* Current theme name
*
* @type {string}
* @memberof AppearanceSettingType
*/
theme: string;
/**
* All themes available in the system
*
* @type {ThemeSettingType[]}
* @memberof AppearanceSettingType
*/
themes: ThemeSettingType[];
/**
* Current wallpaper setting
*
* @type {WPSettingType}
* @memberof AppearanceSettingType
*/
wp: WPSettingType;
/**
* All wallpapers available in the system
*
* @type {string[]}
* @memberof AppearanceSettingType
*/
wps: string[];
}
/**
* VFS Mount points setting data type
*
* @export
* @interface VFSMountPointSettingType
*/
export interface VFSMountPointSettingType {
/**
* Path to the mount point
*
* @type {string}
* @memberof VFSMountPointSettingType
*/
path: string;
/**
* User friendly mount point name
*
* @type {string}
* @memberof VFSMountPointSettingType
*/
text: string;
[propName: string]: any;
}
/**
* VFS setting data type
*
* @export
* @interface VFSSettingType
*/
export interface VFSSettingType {
/**
* mount points setting
*
* @type {VFSMountPointSettingType[]}
* @memberof VFSSettingType
*/
mountpoints: VFSMountPointSettingType[];
[propName: string]: any;
}
/**
* Global system setting data type
*
* @export
* @interface SystemSettingType
*/
export interface SystemSettingType {
/**
* System error report URL
*
* @type {string}
* @memberof SystemSettingType
*/
error_report: string;
/**
* Current system locale e.g. `en_GB`
*
* @type {string}
* @memberof SystemSettingType
*/
locale: string;
/**
* System menus
*
* @type {API.PackageMetaType[]}
* @memberof API.PackageMetaType
*/
menu: API.PackageMetaType[];
/**
* Packages meta-data
*
* @type {{ [index: string]: API.PackageMetaType }}
* @memberof SystemSettingType
*/
packages: { [index: string]: API.PackageMetaType };
/**
* Path to the installed packages
*
* @type {{
* user: string;
* system: string;
* }}
* @memberof SystemSettingType
*/
pkgpaths: {
/**
* User specific packages install location
*
* @type {string}
*/
user: string;
/**
* System packages install location
*
* @type {string}
*/
system: string;
};
/**
* Package repositories setting.
* This configuration is used by [[MarketPlace]]
* for package management
*
* @type {{
* text: string;
* url: string;
* }[]}
* @memberof SystemSettingType
*/
repositories: {
/**
* Repository name
*
* @type {string}
*/
text: string;
/**
* Repository uri
*
* @type {string}
*/
url: string;
}[];
/**
* Startup applications and services
*
* @type {{
* apps: string[];
* services: string[];
* }}
* @memberof SystemSettingType
*/
startup: {
/**
* List of application names
*
* @type {string[]}
*/
apps: string[];
/**
* List of service names
*
* @type {string[]}
*/
services: string[];
/**
* List of pinned applications
*
* @type {string[]}
*/
pinned: string[];
};
}
/**
* User settings
*/
export var user: UserSettingType;
/**
* Application settings
*/
export var applications: GenericObject<any> = {};
/**
* Desktop settings
*/
export var desktop: DesktopSettingType;
/**
* Appearance settings
*/
export var appearance: AppearanceSettingType;
/**
* VFS settings
*/
export var VFS: VFSSettingType;
/**
* System settings
*/
export var system: SystemSettingType;
}
/**
* Reset the system settings to default values
*
* @export
*/
export function resetSetting(): void {
setting.desktop = {
path: "home://.desktop",
menu: [],
showhidden: false,
};
setting.user = {
name: undefined,
username: undefined,
id: 0,
};
setting.appearance = {
theme: "antos_dark",
themes: [
{
text: "AntOS light",
name: "antos_light",
},
{
text: "AntOS dark",
name: "antos_dark",
},
],
wp: {
url: "os://resources/themes/system/wp/wp3.jpg",
size: "cover",
repeat: "repeat",
},
wps: [],
};
setting.VFS = {
mountpoints: [
//TODO: multi app try to write to this object, it need to be cloned
{
text: "__(Applications)",
path: "app://",
iconclass: "fa fa-adn",
type: "app",
},
{
text: "__(Home)",
path: "home://",
iconclass: "fa fa-home",
type: "fs",
},
{
text: "__(Desktop)",
path: setting.desktop.path,
iconclass: "fa fa-desktop",
type: "fs",
},
{
text: "__(OS)",
path: "os://",
iconclass: "fa fa-inbox",
type: "fs",
},
{
text: "__(Google Drive)",
path: "gdv://",
iconclass: "fa fa-inbox",
type: "fs",
},
{
text: "__(Shared)",
path: "shared://",
iconclass: "fa fa-share-square",
type: "fs",
},
],
};
OS.setting.system = {
error_report: "https://os.iohub.dev/report",
locale: "en_GB",
menu: [],
packages: {},
pkgpaths: {
user: "home://.packages",
system: "os://packages",
},
repositories: [],
startup: {
apps: [],
services: ["Syslog/PushNotification", "Syslog/Calendar"],
pinned: [],
},
};
}
/**
* Apply the input parameter object to system settings.
* This object could be an object loaded from
* setting JSON file saved on the server.
*
* @export
* @param {*} conf
*/
export function systemSetting(conf: any) {
resetSetting();
if (conf.desktop) {
setting.desktop = conf.desktop;
}
if (conf.applications) {
setting.applications = conf.applications;
}
if (conf.appearance) {
setting.appearance = conf.appearance;
}
if (conf.user) {
setting.user = conf.user;
}
if (conf.VFS) {
setting.VFS = conf.VFS;
}
if (conf.system) {
setting.system = conf.system;
}
if (!setting.VFS.gdrive) {
setting.VFS.gdrive = {
CLIENT_ID: "",
API_KEY: "",
apilink: "https://apis.google.com/js/api.js",
DISCOVERY_DOCS: [
"https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
],
SCOPES: "https://www.googleapis.com/auth/drive",
};
}
// default repo
if(!setting.system.repositories || setting.system.repositories.length === 0)
{
setting.system.repositories = [
{
text: "Github",
url: "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/packages.json"
}
]
}
setting.applications.categories = [
{
text: "__(Media)",
iconclass: "bi bi-disc"
},
{
text: "__(Development)",
iconclass: "bi bi-hammer"
},
{
text: "__(Education)",
iconclass: "fa fa-graduation-cap"
},
{
text: "__(Game)",
iconclass: "fa fa-gamepad"
},
{
text: "__(Graphics)",
iconclass: "bi bi-palette-fill"
},
{
text: "__(Internet)",
iconclass: "fa fa-globe"
},
{
text: "__(Office)",
iconclass: "bi bi-building"
},
{
text: "__(System)",
iconclass: "fa bi-gear-wide-connected"
},
{
text: "__(Utility)",
iconclass: "bi bi-tools"
},
]
}
// Register handle for application search
API.onsearch("__(Applications)", function (t) {
const ar = [];
const term = new RegExp(t, "i");
for (let k in setting.system.packages) {
const v = setting.system.packages[k];
if (v.app) {
var e: any, k1: string, v1: { [x: string]: any; detail?: any; path?: any; complex?: any; };
if (
v.name.match(term) ||
(v.description && v.description.match(term))
) {
v1 = {};
for (k1 in v) {
e = v[k1];
if (k1 !== "selected") {
v1[k1] = e;
}
}
v1.detail = [{ text: v1.path }];
v1.complex = true;
ar.push(v1);
} else if (v.mimes) {
for (let m of v.mimes) {
if (t.match(new RegExp(m, "g"))) {
v1 = {};
for (k1 in v) {
e = v[k1];
if (k1 !== "selected") {
v1[k1] = v[k1];
}
}
v1.detail = [{ text: v1.path }];
v1.complex = true;
ar.push(v1);
break;
}
}
}
}
}
return ar;
});
}

326
src/core/tags/AppDockTag.ts Normal file
View File

@ -0,0 +1,326 @@
namespace OS {
export namespace GUI {
/**
*
* Interface for an application dock item
*
* @export
* @interface AppDockItemType
*/
export interface AppDockItemType {
/**
* Reference to the application process represented
* by the dock item
*
* @type {application.BaseApplication}
* @memberof AppDockItemType
*/
app: application.BaseApplication;
/**
* Reference to the DOM element of
* the owner dock item
*
* @type {AFXTag}
* @memberof AppDockItemType
*/
domel?: AFXTag;
[propName: string]: any;
}
export namespace tag {
/**
* This class define the AntOS system application dock tag
*
* @export
* @class AppDockTag
* @extends {AFXTag}
*/
export class AppDockTag extends AFXTag {
/**
* variable holds the application select event
* callback handle
*
* @private
* @type {TagEventCallback<any>}
* @memberof AppDockTag
*/
private _onappselect: TagEventCallback<any>;
/**
* Items data of the dock
*
* @private
* @type {AppDockItemType[]}
* @memberof AppDockTag
*/
private _items: AppDockItemType[];
/**
* Reference to the currently select application
* process in the dock
*
* @private
* @type {AppDockItemType}
* @memberof AppDockTag
*/
private _selectedItem: AppDockItemType;
/**
*Creates an instance of AppDockTag.
* @memberof AppDockTag
*/
constructor() {
super();
this._onappselect = (e) => {};
}
/**
* Implementation of the abstract function: Update the current tag.
* It do nothing for this tag
*
* @protected
* @param {*} [d]
* @memberof AppDockTag
*/
protected reload(d?: any): void {
let app: application.BaseApplication = d as application.BaseApplication;
if(!app)
{
return;
}
let i = -1;
const iterable = this.items;
for (let k = 0; k < iterable.length; k++) {
const v = iterable[k];
if (v.app.pid === app.pid) {
i = k;
break;
}
}
if (i !== -1) {
$(this.items[i].domel).attr("tooltip", `cr:${app.title()}`);
}
}
/**
* Init the tag before mounting
*
* @protected
* @memberof AppDockTag
*/
protected init(): void {
this._items = [];
}
/**
* The tag layout, it is empty on creation but elements will
* be added automatically to it in operation
*
* @protected
* @returns {TagLayoutType[]}
* @memberof AppDockTag
*/
protected layout(): TagLayoutType[] {
return [];
}
/**
* getter to get the dock items
*
* @readonly
* @type {AppDockItemType[]}
* @memberof AppDockTag
*/
get items(): AppDockItemType[] {
return this._items;
}
/**
* Setter:
*
* set the selected application in the dock
* this will trigger two event:
* - `focus`: on the selected application
* - `blur`: on all other applications on the dock
*
* Getter:
*
* Get the current selected application
* on the dock
*
* @memberof AppDockTag
*/
set selectedApp(v: application.BaseApplication) {
let el = undefined;
for (let it of this.items) {
it.app.blur();
$(it.domel).removeClass();
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();
}
get selectedApp(): application.BaseApplication {
if(!this._selectedItem)
return undefined;
return this._selectedItem.app;
}
/**
* Get selected item of the dock
*
* @readonly
* @type {AppDockItemType}
* @memberof AppDockTag
*/
get selectedItem(): AppDockItemType
{
return this._selectedItem;
}
/**
* When a new application process is created, this function
* will be called to add new application entry to the dock.
* The added application will becomes the current selected
* application
*
* @param {AppDockItemType} item an application dock item entry
* @memberof AppDockTag
*/
newapp(item: AppDockItemType): void {
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;
}
/**
* Delete and application entry from the dock.
* This function will be called when an application
* is exit
*
* @param {BaseApplication} a the application to be removed from the dock
* @memberof AppDockTag
*/
removeapp(a: application.BaseApplication): void {
let i = -1;
const iterable = this.items;
for (let k = 0; k < iterable.length; k++) {
const v = iterable[k];
if (v.app.pid === a.pid) {
i = k;
break;
}
}
if (i !== -1) {
delete this.items[i].app;
this.items.splice(i, 1);
$($(this).children()[i]).remove();
}
}
/**
* Mount the current dock tag
*
* @protected
* @memberof AppDockTag
*/
protected mount(): void {
this.contextmenuHandle = (e, m) => {
if (e.target === this) {
return;
}
const bt = ($(e.target).closest(
"afx-button"
)[0] as any) as ButtonTag;
const app = bt.data as application.BaseApplication;
m.items = [
{ text: "__(New window)", dataid: "new" },
{ text: "__(Show)", dataid: "show" },
{ text: "__(Hide)", dataid: "hide" },
{ text: "__(Close)", 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;
}
}
};
return m.show(e);
};
announcer.trigger("sysdockloaded", undefined);
GUI.bindKey("CTRL-ALT-2", (e) =>{
if(!this.items || this.items.length === 0)
{
return;
}
let index = this.items.indexOf(this.selectedItem);
if(index < 0)
{
index = 0;
}
else
{
index++;
}
if(index >= this.items.length)
index = 0;
this.items[index].app.trigger("focus");
});
GUI.bindKey("CTRL-ALT-1", (e) =>{
if(!this.items || this.items.length === 0)
{
return;
}
let index = this.items.indexOf(this.selectedItem);
index--;
if(index < 0)
{
index = this.items.length - 1;
}
if(index < 0)
{
return;
}
this.items[index].app.trigger("focus");
});
}
}
define("afx-apps-dock", AppDockTag);
}
}
}

198
src/core/tags/ButtonTag.ts Normal file
View File

@ -0,0 +1,198 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* This tag define a basic button and its behavior
*
* @export
* @class ButtonTag
* @extends {AFXTag}
*/
export class ButtonTag extends AFXTag {
/**
* Variable hold the button click callback handle
*
* @private
* @type {TagEventCallback<JQuery.MouseEventBase>}
* @memberof ButtonTag
*/
private _onbtclick: TagEventCallback<JQuery.MouseEventBase>;
/**
* Custom user data
*
* @type {GenericObject<any>}
* @memberof ButtonTag
*/
data: GenericObject<any>;
/**
*Creates an instance of ButtonTag.
* @memberof ButtonTag
*/
constructor() {
super();
}
/**
* Set the click callback handle for the target button
*
* @memberof ButtonTag
*/
set onbtclick(v: TagEventCallback<JQuery.MouseEventBase>) {
this._onbtclick = v;
}
/**
* Set the path to the button icon, the path should be
* a VFS file path
*
* @memberof ButtonTag
*/
set icon(v: string) {
$(this).attr("icon", v);
(this.refs.label as LabelTag).icon = v;
}
/**
* Set the icon class to the button, this property
* allows to style the button icon using CSS
*
* @memberof ButtonTag
*/
set iconclass(v: string) {
$(this).attr("iconclass", v);
(this.refs.label as LabelTag).iconclass = v;
}
/**
* Setter: Set the text of the button
*
* Getter: Get the current button test
*
* @memberof ButtonTag
*/
set text(v: string | FormattedString) {
(this.refs.label as LabelTag).text = v;
}
get text(): string | FormattedString {
return (this.refs.label as LabelTag).text;
}
/**
* Setter: Enable or disable the button
*
* Getter: Get the `enable` property of the button
*
* @memberof ButtonTag
*/
set enable(v: boolean) {
$(this.refs.button).prop("disabled", !v);
}
get enable(): boolean {
return !$(this.refs.button).prop("disabled");
}
/**
* Setter: set or remove the attribute `selected` of the button
*
* Getter: check whether the attribute `selected` of the button is set
*
* @memberof ButtonTag
*/
set selected(v: boolean) {
$(this.refs.button).removeClass();
this.attsw(v, "selected");
if (v) {
$(this.refs.button).addClass("selected");
}
}
get selected(): boolean {
return this.hasattr("selected");
}
/**
* Setter: activate or deactivate the toggle mode of the button
*
* Getter: Check whether the button is in toggle mode
*
* @memberof ButtonTag
*/
set toggle(v: boolean) {
this.attsw(v, "toggle");
}
get toggle(): boolean {
return this.hasattr("toggle");
}
/**
* Mount the tag
*
* @protected
* @memberof ButtonTag
*/
protected mount() {
$(this.refs.button).on("click", (e) => {
if (this.toggle) {
this.selected = !this.selected;
}
const evt: TagEventType<JQuery.MouseEventBase> = {
id: this.aid,
data: e,
};
this._onbtclick(evt);
this.observable.trigger("btclick", evt);
});
}
/**
* Init the tag before mounting
*
* @protected
* @memberof ButtonTag
*/
protected init(): void {
this.enable = true;
this.toggle = false;
this._onbtclick = (e) => {};
}
/**
* Re-calibrate the button, do nothing in this tag
*
* @protected
* @memberof ButtonTag
*/
protected calibrate(): void {}
/**
* Update the current tag, do nothing in this tag
*
* @param {*} [d]
* @memberof ButtonTag
*/
reload(d?: any): void {}
/**
* Button layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof ButtonTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "Button",
ref: "button",
children: [{ el: "afx-label", ref: "label" }],
},
];
}
}
define("afx-button", ButtonTag);
}
}
}

View File

@ -0,0 +1,335 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Tag that define system calendar widget
*
* @export
* @class CalendarTag
* @extends {AFXTag}
*/
export class CalendarTag extends AFXTag {
/**
* The current selected day
*
* @private
* @type {number}
* @memberof CalendarTag
*/
private _day: number;
/**
* The current selected month
*
* @private
* @type {number}
* @memberof CalendarTag
*/
private _month: number;
/**
* The current selected year
*
* @private
* @type {number}
* @memberof CalendarTag
*/
private _year: number;
/**
* The current selected date object
*
* @private
* @type {Date}
* @memberof CalendarTag
*/
private _selectedDate: Date;
/**
* placeholder for date select event callback
*
* @private
* @type {TagEventCallback<Date>}
* @memberof CalendarTag
*/
private _ondateselect: TagEventCallback<Date>;
/**
*Creates an instance of CalendarTag.
* @memberof CalendarTag
*/
constructor() {
super();
this._day = 0;
this._month = 0;
this._year = 0;
this._ondateselect = (e) => {};
}
/**
* Init the tag before mounting
*
* @protected
* @memberof CalendarTag
*/
protected init(): void {
$(this).css("height", "100%");
$(this.refs.grid).css("width", "100%");
}
/**
* Update the current tag, doing nothing in this tag
*
* @protected
* @param {*} [d] any data object
* @memberof CalendarTag
*/
protected reload(d?: any): void {}
/**
* Get the current selected date in the widget
*
* @readonly
* @type {Date}
* @memberof CalendarTag
*/
get selectedDate(): Date {
return this._selectedDate;
}
/**
* Set the date select event callback handle for the widget
*
* @memberof CalendarTag
*/
set ondateselect(v: TagEventCallback<Date>) {
this._ondateselect = v;
}
/**
* Mount the current widget to the DOM tree
*
* @protected
* @memberof CalendarTag
*/
protected mount(): void {
$(this.refs.prev).on("click",(e) => this.prevmonth());
$(this.refs.next).on("click",(e) => this.nextmonth());
const grid = this.refs.grid as GridViewTag;
grid.header = [
{ text: "__(Sun)" },
{ text: "__(Mon)" },
{ text: "__(Tue)" },
{ text: "__(Wed)" },
{ text: "__(Thu)" },
{ text: "__(Fri)" },
{ text: "__(Sat)" },
];
grid.oncellselect = (e) => {
this.dateselect(e);
};
this.observable.on("resize", (e) => this.calibrate());
this.calibrate();
this.calendar(null);
}
/**
* This function triggers the date select event
*
* @private
* @param {TagEventType} e AFX tag event data [[TagEventType]]
* @returns {void}
* @memberof CalendarTag
*/
private dateselect(
e: TagEventType<TagEventDataType<tag.GridCellPrototype>>
): void {
if (!e.data.item) {
return;
}
const value = e.data.item.data.text;
if (value === "") {
return;
}
const evt = {
id: this.aid,
data: new Date(
this._year,
this._month,
parseInt(value)
),
};
this._ondateselect(evt);
this._selectedDate = evt.data;
return this.observable.trigger("dateselect", evt);
}
/**
* Calibrate the layout of the tag
*
* @protected
* @memberof CalendarTag
*/
protected calibrate(): void {
$(this.refs.grid).css(
"height",
`${$(this).height() - $(this.refs.ctrl).height()}px`
);
}
/**
* Display the previous month of the current month
*
* @private
* @memberof CalendarTag
*/
private prevmonth(): void {
this._selectedDate = undefined;
this._month--;
if (this._month < 0) {
this._month = 11;
this._year--;
}
this.calendar(new Date(this._year, this._month, 1));
}
/**
* Display the next month of the current month
*
* @private
* @returns
* @memberof CalendarTag
*/
private nextmonth() {
this._selectedDate = undefined;
this._month++;
if (this._month > 11) {
this._month = 0;
this._year++;
}
return this.calendar(new Date(this._year, this._month, 1));
}
/**
* Visualize the calendar base on input date
*
* @private
* @param {Date} date
* @memberof CalendarTag
*/
private calendar(date: Date) {
let week_day: number;
let asc: any, end: any;
if (!date) {
date = new Date();
}
this._day = date.getDate();
this._month = date.getMonth();
this._year = date.getFullYear();
const now = {
d: new Date().getDate(),
m: new Date().getMonth(),
y: new Date().getFullYear(),
};
const months = [
__("January"),
__("February"),
__("March"),
__("April"),
__("May"),
__("June"),
__("July"),
__("August"),
__("September"),
__("October"),
__("November"),
__("December"),
];
const this_month = new Date(this._year, this._month, 1);
const next_month = new Date(this._year, this._month + 1, 1);
// Find out when this month starts and ends.
const first_week_day = this_month.getDay();
const days_in_this_month = Math.round(
(next_month.getTime() - this_month.getTime()) /
(1000 * 60 * 60 * 24)
);
//self.mtext = months[self.month]
const rows = [];
let row = [];
// Fill the first week of the month with the appropriate number of blanks.
for (
week_day = 0, end = first_week_day - 1, asc = 0 <= end;
asc ? week_day <= end : week_day >= end;
asc ? week_day++ : week_day--
) {
row.push({ text: "" });
}
week_day = first_week_day;
for (
let day_counter = 1,
end1 = days_in_this_month,
asc1 = 1 <= end1;
asc1 ? day_counter <= end1 : day_counter >= end1;
asc1 ? day_counter++ : day_counter--
) {
week_day %= 7;
if (week_day === 0) {
rows.push(row);
row = [];
}
// Do something different for the current day.
if (
now.d === day_counter &&
this._month === now.m &&
this._year === now.y
) {
row.push({ text: day_counter, selected: true });
} else {
row.push({ text: day_counter });
}
week_day++;
}
for (
let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2;
asc2 ? i <= end2 : i >= end2;
asc2 ? i++ : i--
) {
row.push({ text: "" });
}
rows.push(row);
const grid = this.refs.grid as GridViewTag;
grid.rows = rows;
(this.refs.mlbl as LabelTag).text = `${
months[this._month]
} ${this._year}`;
}
/**
* Layout definition of the widget
*
* @protected
* @returns {TagLayoutType[]}
* @memberof CalendarTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div",
ref: "ctrl",
children: [
{ el: "i", class: "prevmonth", ref: "prev" },
{ el: "afx-label", ref: "mlbl" },
{ el: "i", class: "nextmonth", ref: "next" },
],
},
{ el: "afx-grid-view", ref: "grid" },
];
}
}
define("afx-calendar-view", CalendarTag);
}
}
}

View File

@ -0,0 +1,327 @@
namespace OS {
export namespace GUI {
/**
* Color type used by AFX API
*
* @export
* @interface ColorType
*/
export interface ColorType {
/**
* Red chanel
*
* @type {number}
* @memberof ColorType
*/
r: number;
/**
* Green chanel
*
* @type {number}
* @memberof ColorType
*/
g: number;
/**
* Blue chanel
*
* @type {number}
* @memberof ColorType
*/
b: number;
/**
* Alpha chanel
*
* @type {number}
* @memberof ColorType
*/
a?: number;
/**
* color text in CSS format
*
* @type {string}
* @memberof ColorType
*/
text?: string;
/**
* Color in hex format
*
* @type {string}
* @memberof ColorType
*/
hex?: string;
}
export namespace tag {
/**
* Class definition of Color picker widget
*
* @export
* @class ColorPickerTag
* @extends {AFXTag}
*/
export class ColorPickerTag extends AFXTag {
/**
* The current selected color object
*
* @private
* @type {ColorType}
* @memberof ColorPickerTag
*/
private _selectedColor: ColorType;
/**
* placeholder for the color select event callback
*
* @private
* @type {TagEventCallback<ColorType>}
* @memberof ColorPickerTag
*/
private _oncolorselect: TagEventCallback<ColorType>;
/**
* Creates an instance of ColorPickerTag.
* @memberof ColorPickerTag
*/
constructor() {
super();
this._oncolorselect = (e) => {};
}
/**
* Init tag before mounting, do nothing
*
* @protected
* @memberof ColorPickerTag
*/
protected init(): void {}
/**
* Reload tag, do nothing
*
* @protected
* @param {*} [d]
* @memberof ColorPickerTag
*/
protected reload(d?: any): void {}
/**
* Get selected color value
*
* @readonly
* @type {ColorType}
* @memberof ColorPickerTag
*/
get selectedColor(): ColorType {
return this._selectedColor;
}
/**
* Set the color select event handle
*
* @memberof ColorPickerTag
*/
set oncolorselect(v: TagEventCallback<ColorType>) {
this._oncolorselect = v;
}
/**
* Mount the widget to DOM tree
*
* @protected
* @memberof ColorPickerTag
*/
protected mount(): void {
$(this.refs.wrapper)
.css("width", "310px")
.css("height", "190px")
.css("display", "block")
.css("padding", "3px");
$(this.refs.palette)
.css("width", "284px")
.css("height", "155px")
.css("float", "left");
$(this.refs.colorval)
.css("width", "15px")
.css("height", "155px")
.css("text-align", "center")
.css("margin-left", "3px")
.css("display", "block")
.css("float", "left");
$(this.refs.inputwrp).css("margin-top", "3px");
$(this.refs.hextext)
.css("width", "70px")
.css("margin-left", "3px")
.css("margin-right", "5px");
this.build_palette();
}
/**
* Build the color palette
*
* @private
* @memberof ColorPickerTag
*/
private build_palette(): void {
const colorctx = ($(this.refs.palette).get(
0
) as HTMLCanvasElement).getContext("2d");
let gradient = colorctx.createLinearGradient(
0,
0,
$(this.refs.palette).width(),
0
);
// fill color
gradient.addColorStop(0, "rgb(255, 0, 0)");
gradient.addColorStop(0.15, "rgb(255, 0, 255)");
gradient.addColorStop(0.33, "rgb(0, 0, 255)");
gradient.addColorStop(0.49, "rgb(0, 255, 255)");
gradient.addColorStop(0.67, "rgb(0, 255, 0)");
gradient.addColorStop(0.84, "rgb(255, 255, 0)");
gradient.addColorStop(1, "rgb(255, 0, 0)");
gradient.addColorStop(0, "rgb(0, 0, 0)");
// Apply gradient to canvas
colorctx.fillStyle = gradient;
colorctx.fillRect(
0,
0,
colorctx.canvas.width,
colorctx.canvas.height
);
// Create semi transparent gradient (white -> trans. -> black)
gradient = colorctx.createLinearGradient(
0,
0,
0,
$(this.refs.palette).width()
);
gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)");
gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)");
gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
// Apply gradient to canvas
colorctx.fillStyle = gradient;
colorctx.fillRect(
0,
0,
colorctx.canvas.width,
colorctx.canvas.height
);
// now add mouse move event
const getHex = function (c) {
let s = c.toString(16);
if (s.length === 1) {
s = "0" + s;
}
return s;
};
const pick_color = (e) => {
$(this.refs.palette).css("cursor", "crosshair");
const offset = $(this.refs.palette).offset();
const x = e.pageX - offset.left;
const y = e.pageY - offset.top;
const color = colorctx.getImageData(x, y, 1, 1);
const data: ColorType = {
r: color.data[0],
g: color.data[1],
b: color.data[2],
text:
"rgb(" +
color.data[0] +
", " +
color.data[1] +
", " +
color.data[2] +
")",
hex:
"#" +
getHex(color.data[0]) +
getHex(color.data[1]) +
getHex(color.data[2]),
};
return data;
};
const mouse_move_h = (e) => {
const data = pick_color(e);
return $(this.refs.colorval).css(
"background-color",
data.text
);
};
$(this.refs.palette).on("mouseenter",(e) => {
return $(this.refs.palette).on(
"mousemove",
mouse_move_h
);
});
$(this.refs.palette).on("mouseout", (e) => {
$(this.refs.palette).off("mousemove", mouse_move_h);
if (this.selectedColor) {
return $(this.refs.colorval).css(
"background-color",
this.selectedColor.text
);
}
});
$(this.refs.palette).on("click", (e) => {
const data = pick_color(e);
$(this.refs.rgbtext).html(data.text);
$(this.refs.hextext).val(data.hex);
this._selectedColor = data;
const evt = { id: this.aid, data };
this._oncolorselect(evt);
return this.observable.trigger("colorselect", data);
});
}
/**
* layout definition of the widget
*
* @protected
* @returns {TagLayoutType[]}
* @memberof ColorPickerTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div",
ref: "wrapper",
children: [
{
el: "canvas",
class: "color-palette",
ref: "palette",
},
{ el: "color-sample", ref: "colorval" },
{ el: "div", class: "afx-clear" },
{
el: "div",
ref: "inputwrp",
children: [
{ el: "input", ref: "hextext" },
{ el: "span", ref: "rgbtext" },
],
},
],
},
];
}
}
define("afx-color-picker", ColorPickerTag);
}
}
}

272
src/core/tags/DesktopTag.ts Normal file
View File

@ -0,0 +1,272 @@
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 {
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 = () => {
announcer.trigger("desktopresize", undefined);
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.items = menu;
m.onmenuselect = (evt: TagEventType<tag.MenuEventData>) => {
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.observable.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("desktoploaded", 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);
}
}
}

View File

@ -0,0 +1,772 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Definition of system file view widget
*
* @export
* @class FileViewTag
* @extends {AFXTag}
*/
export class FileViewTag extends AFXTag {
/**
* placeholder for file select event callback
*
* @private
* @type {TagEventCallback<API.FileInfoType>}
* @memberof FileViewTag
*/
private _onfileselect: TagEventCallback<API.FileInfoType>;
/**
* placeholder for file open event callback
*
* @private
* @type {TagEventCallback<API.FileInfoType>}
* @memberof FileViewTag
*/
private _onfileopen: TagEventCallback<API.FileInfoType>;
/**
* 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
*
* @private
* @type {API.FileInfoType[]}
* @memberof FileViewTag
*/
private _data: API.FileInfoType[];
/**
* The path of the current working directory
*
* @private
* @type {string}
* @memberof FileViewTag
*/
private _path: string;
/**
* Header definition of the widget grid view
*
* @private
* @type {(GenericObject<any>[])}
* @memberof FileViewTag
*/
private _header: GenericObject<any>[];
/**
* placeholder for the user-specified meta-data fetch function
*
* @private
* @memberof FileViewTag
*/
private _fetch: (p: string) => Promise<API.FileInfoType[]>;
/**
*Creates an instance of FileViewTag.
* @memberof FileViewTag
*/
constructor() {
super();
}
/**
* Init the widget before mounting
*
* @protected
* @memberof FileViewTag
*/
protected init(): void {
this.data = [];
this.status = true;
this.showhidden = false;
this.chdir = true;
this.view = "list";
this._onfileopen = this._onfileselect = (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.__f)
{
this.desc = ! this.desc;
if(t1 < t2) { return -1; }
if(t1 > t2) { return 1; }
}
else
{
this.desc = ! this.desc;
if(t1 > t2) { return -1; }
if(t1 < t2) { return 1; }
}
return 0;
};
this._header = [
{
text: "__(File name)",
sort: fn
},
{
text: "__(Modified)",
sort: fn
},
{
text: "__(Size)",
sort: fn
},
];
}
/**
* Update the current widget, do nothing
*
* @protected
* @param {*} [d]
* @memberof FileViewTag
*/
protected reload(d?: any): void { }
/**
* set the function that allows to fetch file entries.
* This handle function should return a promise on
* an arry of [[API.FileInfoType]]
*
* @memberof FileViewTag
*/
set fetch(v: (p: string) => Promise<API.FileInfoType[]>) {
this._fetch = v;
}
/**
* 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]]
*
* @memberof FileViewTag
*/
set onfileselect(e: TagEventCallback<API.FileInfoType>) {
this._onfileselect = 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]]
*
* @memberof FileViewTag
*/
set onfileopen(e: TagEventCallback<API.FileInfoType>) {
this._onfileopen = e;
}
/**
* Setter:
*
* chang the view of the widget, there are three different views
* - `icon`
* - `list`
* - `tree`
*
* Getter:
*
* Get the current view setting of the widget
*
* @memberof FileViewTag
*/
set view(v: string) {
$(this).attr("view", v);
this.switchView();
}
get view(): string {
return $(this).attr("view");
}
/**
* Setter:
*
* 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 content of the selected directory
*
* Getter:
*
* check whether changing current working directory feature
* is enabled
*
* @memberof FileViewTag
*/
set chdir(v: boolean) {
this.attsw(v, "chdir");
}
get chdir(): boolean {
return this.hasattr("chdir");
}
/**
* Setter : Enable or disable the status bar of the widget
*
* Getter: Check whether the status bar is enabled
*
* @memberof FileViewTag
*/
set status(v: boolean) {
this.attsw(v, "status");
if (v) {
$(this.refs.status).show();
return;
}
$(this.refs.status).hide();
}
get status(): boolean {
return this.hasattr("status");
}
/**
* Setter:
*
* Allow the widget to show or hide hidden file
*
* Getter:
*
* Check whether the hidden file should be shown in
* the widget
*
* @memberof FileViewTag
*/
set showhidden(v: boolean) {
this.attsw(v, "showhidden");
if (!this.data) {
return;
}
this.switchView();
}
get showhidden(): boolean {
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
*
* @readonly
* @type {API.FileInfoType}
* @memberof FileViewTag
*/
get selectedFile(): API.FileInfoType {
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;
}
/**
* Setter:
*
* Set the path of the current working directory.
* When called the widget will refresh the current
* working directory using the configured [[fetch]]
* function
*
* Getter:
*
* Get the path of the current working directory
*
* @memberof FileViewTag
*/
set path(v: string) {
if (!v) {
return;
}
this._path = v;
if (!this._fetch) {
return;
}
this._fetch(v)
.then((data: API.FileInfoType[]) => {
if (!data) {
return;
}
this.data = data.sort(this.sortByName).sort(this.sortByType);
if (this.status) {
(this.refs.status as LabelTag).text = " ";
}
})
.catch((e: Error) =>
announcer.oserror(e.toString(), e)
);
}
get path(): string {
return this._path;
}
/**
* Setter: Set the data of the current working directory
*
* Getter: Get the data of the current working directory
*
* @memberof FileViewTag
*/
set data(v: API.FileInfoType[]) {
if (!v) {
return;
}
this._data = v;
this.refreshData();
}
get data(): API.FileInfoType[] {
return this._data;
}
/**
* Set the file drag and drop event handle. This allows application
* to define custom behavior of the event
*
* @memberof FileViewTag
*/
set ondragndrop(
v: TagEventCallback<
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);
};
}
/**
* Sort file by its type
*
* @private
* @param {API.FileInfoType} a
* @param {API.FileInfoType} b
* @return {*} {number}
* @memberof FileViewTag
*/
private sortByType(
a: API.FileInfoType,
b: API.FileInfoType
): number {
if (!a.type) {
a.type = "none";
}
if (!b.type) {
b.type = "none";
}
return a.type.toLowerCase().localeCompare(b.type.toLowerCase());
}
/**
* sort file by its name
*
* @private
* @param {API.FileInfoType} a first file meta-data
* @param {API.FileInfoType} b second file meta-data
* @returns {number}
* @memberof FileViewTag
*/
private sortByName(
a: API.FileInfoType,
b: API.FileInfoType
): number {
// sort file by type, then by name
if (a.filename) {
a.name = a.filename;
}
if (b.filename) {
b.name = b.filename;
}
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
}
/**
* calibrate the widget layout
*
* @memberof FileViewTag
*/
calibrate(): void {
let h = $(this).outerHeight();
const w = $(this).width();
if (this.status) {
h -= $(this.refs.status).height() + 10;
}
$(this.refs.listview).css("height", h + "px");
$(this.refs.gridview).css("height", h + "px");
$(this.refs.treecontainer).css("height", h + "px");
$(this.refs.listview).css("width", w + "px");
$(this.refs.gridview).css("width", w + "px");
$(this.refs.treecontainer).css("width", w + "px");
}
/**
* Refresh the list view of the widget. This function
* is called when the view of the widget changed to `icon`
*
* @private
* @memberof FileViewTag
*/
private refreshList(): void {
const items = [];
$.each(this.data, (i, v) => {
if (v.filename[0] === "." && !this.showhidden) {
return;
}
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;
if(v.icon)
v.iconclass = undefined;
items.push(v);
});
(this.refs.listview as ListViewTag).data = items;
}
/**
* Refresh the grid view of the widget, this function is called
* when the view of the widget set to `list`
*
* @private
* @memberof FileViewTag
*/
private refreshGrid(): void {
const rows = [];
$.each(this.data, (i, v) => {
if (v.filename[0] === "." && !this.showhidden) {
return;
}
v.iconclass = v.iconclass ? v.iconclass : v.type;
if(v.icon)
v.iconclass = undefined;
const row = [
{
text: v.filename,
icon: v.icon,
iconclass: v.iconclass,
path: v.path,
data: v
},
{
text: v.mtime,
data: v,
},
{
text: v.size,
data: v,
},
];
return rows.push(row);
});
(this.refs.gridview as GridViewTag).rows = rows;
}
/**
* Refresh the Treeview of the widget, this function is called
* when the view of the widget set to `tree`
*
* @private
* @memberof FileViewTag
*/
private refreshTree(): void {
//@treeview.root.set("selectedItem", null)
const tdata: TreeViewDataType = {
text: this.path,
path: this.path,
open: true,
nodes: this.getTreeData(this.data),
};
(this.refs.treeview as TreeViewTag).data = tdata;
// (this.refs.treeview as TreeViewTag).expandAll();
}
/**
* Create the tree data from the list of input
* file meta-data
*
* @private
* @param {API.FileInfoType[]} data list of file meta-data
* @returns {TreeViewDataType[]}
* @memberof FileViewTag
*/
private getTreeData(
data: API.FileInfoType[]
): TreeViewDataType[] {
const nodes = [];
const me = this;
$.each(data, (i, v) => {
if (v.filename[0] === "." && !this.showhidden) {
return undefined;
}
if(!v.text)
v.text = v.filename;
if (v.type === "dir") {
v.nodes = [];
v.open = false;
}
v.iconclass = v.iconclass ? v.iconclass : v.type;
if(v.icon)
v.iconclass = undefined;
return nodes.push(v);
});
return nodes;
}
/**
* Refresh data of the current widget view
*
* @private
* @returns {void}
* @memberof FileViewTag
*/
private refreshData(): void {
if (!this.data) {
return;
}
this.data.sort(this.sortByName).sort(this.sortByType);
switch (this.view) {
case "icon":
return this.refreshList();
case "list":
return this.refreshGrid();
default:
return this.refreshTree();
}
}
/**
* Switch between three view options
*
* @private
* @memberof FileViewTag
*/
private switchView(): void {
$(this.refs.listview).hide();
$(this.refs.gridview).hide();
$(this.refs.treecontainer).hide();
this._selectedFiles = [];
switch (this.view) {
case "icon":
$(this.refs.listview).show();
break;
case "list":
$(this.refs.gridview).show();
break;
default:
$(this.refs.treecontainer).show();
}
this.refreshData();
this.calibrate();
if (this.status) {
(this.refs.status as LabelTag).text = " ";
}
}
/**
* This function triggers the file select event
*
* @private
* @param {API.FileInfoType} e selected file meta-data
* @memberof FileViewTag
*/
private fileselect(e: API.FileInfoType): void {
if (e.path === this.path) {
e.type = "dir";
e.mime = "dir";
}
if (this.status) {
(this.refs.status as LabelTag).text = __(
"Selected: {0} ({1} bytes)",
e.filename,
e.size ? e.size : "0"
);
}
const evt = { id: this.aid, data: e };
this._onfileselect(evt);
this.observable.trigger("fileselect", evt);
}
/**
* This function triggers the file open event
*
* @private
* @param {API.FileInfoType} e selected file meta-data
* @memberof FileViewTag
*/
private filedbclick(e: API.FileInfoType): void {
if (e.path === this.path) {
e.type = "dir";
e.mime = "dir";
}
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);
}
}
/**
* Mount the widget in the DOM tree
*
* @protected
* @memberof FileViewTag
*/
protected mount(): void {
this.observable.on("resize", (e) => this.calibrate());
const tree = this.refs.treeview as TreeViewTag;
tree.fetch = (v) => {
return new Promise((resolve, reject) => {
if (!this._fetch) {
return resolve(undefined);
}
if (!v.data.path) {
return resolve(undefined);
}
return this._fetch(v.data.path)
.then((d: API.FileInfoType[]) =>
resolve(
this.getTreeData(
d.sort(this.sortByName).sort(this.sortByType)
)
)
)
.catch((e: Error) => reject(__e(e)));
});
};
const grid = this.refs.gridview as GridViewTag;
const list = this.refs.listview as ListViewTag;
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] 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.data as API.FileInfoType);
};
tree.ontreedbclick = (e) => {
this.filedbclick(e.data.item.data as API.FileInfoType);
};
this.switchView();
}
/**
* Layout definition of the widget
*
* @protected
* @returns {TagLayoutType[]}
* @memberof FileViewTag
*/
protected layout(): TagLayoutType[] {
return [
{ el: "afx-list-view", ref: "listview" },
{
el: "div",
class: "treecontainer",
ref: "treecontainer",
children: [
{ el: "afx-tree-view", ref: "treeview" },
],
},
{ el: "afx-grid-view", ref: "gridview" },
{ el: "afx-label", class: "status", ref: "status" },
];
}
}
define("afx-file-view", FileViewTag);
}
}
}

View File

@ -0,0 +1,238 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A float list is a list of items in which each
* item can be moved (drag and drop) freely
*
* @export
* @class FloatListTag
* @extends {ListViewTag}
*/
export class FloatListTag extends ListViewTag {
/**
* Update the current tag, do nothing
*
* @protected
* @param {*} [d]
* @memberof FloatListTag
*/
protected reload(d?: any): void {}
/**
* Variable that hold the onready callback of
* the tag. This callback will be called after
* the tag is mounted
*
* @private
* @memberof FloatListTag
*/
private _onready: (e: FloatListTag) => void;
/**
*Creates an instance of FloatListTag.
* @memberof FloatListTag
*/
constructor() {
super();
}
/**
* set the onready callback function to the tag.
* This callback will be called after
* the tag is mounted
*
* @memberof FloatListTag
*/
set onready(v: (e: FloatListTag) => void) {
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
*
* @memberof FloatListTag
*/
set dropdown(v: boolean) {}
/**
* Disable the list buttons configuration in this
* list
*
* @memberof FloatListTag
*/
set buttons(v: GenericObject<any>[]) {}
/**
* Disable the `showlist` behavior in this list
*
* @protected
* @param {*} e
* @memberof FloatListTag
*/
protected showlist(e: any) {}
/**
* Disable the `dropoff` behavior in this list
*
* @protected
* @param {*} e
* @memberof FloatListTag
*/
protected dropoff(e: any) {}
/**
* Function called when the data of the list
* is changed
*
* @protected
* @memberof FloatListTag
*/
protected ondatachange(): void {
this.calibrate();
}
/**
* Mount the list to the DOM tree
*
* @protected
* @returns {void}
* @memberof FloatListTag
*/
protected mount(): void {
$(this.refs.container)
.css("width", "100%")
.css("height", "100%");
$(this.refs.mlist)
.css("position", "absolute")
.css("display", "block")
.css("width", "100%");
this.observable.on("resize", (e) => this.calibrate());
if (this._onready) {
return this._onready(this);
}
}
/**
* Push an element to the list
*
* @param {GenericObject<any>} v an element data
* @returns
* @memberof FloatListTag
*/
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);
});
}
/**
* Calibrate the view of the list
*
* @memberof FloatListTag
*/
calibrate(): void {
let ctop = 20;
let cleft = 20;
$(this.refs.mlist).css(
"height",
`${$(this.refs.container).height()}px`
);
const gw = $(this.refs.mlist).width();
const gh = $(this.refs.mlist).height();
$(this.refs.mlist)
.children()
.each((i, e) => {
$(e)
.css("top", `${ctop}px`)
.css("left", `${cleft}px`);
const w = $(e).width();
const h = $(e).height();
if (this.dir === "vertical") {
ctop += h + 20;
if (ctop + h > gh) {
ctop = 20;
cleft += w + 20;
}
} else {
cleft += w + 20;
if (cleft + w > gw) {
cleft = 20;
ctop += h + 20;
}
}
});
}
}
define("afx-float-list", FloatListTag);
}
}
}

1235
src/core/tags/GridViewTag.ts Normal file

File diff suppressed because it is too large Load Diff

186
src/core/tags/LabelTag.ts Normal file
View File

@ -0,0 +1,186 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* This class defines basic AFX label tag.
* A label contains a text and an icon (optional)
*
* @export
* @class LabelTag
* @extends {AFXTag}
*/
export class LabelTag extends AFXTag {
/**
* placeholder of the text to be displayed
*
* @private
* @type {(string | FormattedString)}
* @memberof LabelTag
*/
private _text: string | FormattedString;
/**
*Creates an instance of LabelTag.
* @memberof LabelTag
*/
constructor() {
super();
}
/**
* this implementation does nothing in this tag
*
* @protected
* @memberof LabelTag
*/
protected mount() {
$(this.refs.container)
.css("display", "flex");
$(this.refs.iclass)
.css("flex-shrink",0);
$(this.refs.i)
.css("flex-shrink",0);
$(this.refs.text)
.css("flex",1);
}
/**
* Refresh the text in the label
*
* @protected
* @param {*} d
* @memberof LabelTag
*/
protected reload(d: any): void {
this.text = this.text;
}
/**
* Reset to default some property value
*
* @protected
* @memberof LabelTag
*/
protected init(): void {
this.icon = undefined;
this.iconclass = undefined;
this.text = undefined;
this.selectable = false;
}
/**
* This implementation of the function does nothing
*
* @protected
* @memberof LabelTag
*/
protected calibrate(): void {}
/**
* Set the VFS path of the label icon
*
* @memberof LabelTag
*/
set icon(v: string) {
$(this.refs.i).attr("style", "");
$(this).attr("icon", v);
if (v) {
$(this.refs.i)
.css("background", `url(${API.handle.get}/${v})`)
.css("background-size", "100% 100%")
.css("background-repeat", "no-repeat");
$(this.refs.i).show();
} else {
$(this.refs.i).hide();
}
}
/**
* Set the CSS class of the label icon
*
* @memberof LabelTag
*/
set iconclass(v: string) {
$(this).attr("iconclass", v);
$(this.refs.iclass).removeClass();
if (v) {
$(this.refs.iclass).addClass(v);
$(this.refs.iclass).show();
} else {
$(this.refs.iclass).hide();
}
}
/**
* Setter: Set the text of the label
*
* Getter: Get the text displayed on the label
*
* @memberof LabelTag
*/
set text(v: string | FormattedString) {
this._text = v;
if (v && v !== "") {
$(this.refs.text).show();
$(this.refs.text).html(v.__());
} else {
$(this.refs.text).hide();
}
}
get text(): string | FormattedString {
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
*
* @protected
* @returns {TagLayoutType[]}
* @memberof LabelTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "span",
ref: "container",
children: [
{ el: "i", ref: "iclass" },
{ el: "i", ref: "i", class: "icon-style" },
{ el: "i", ref: "text", class: "label-text" },
],
},
];
}
}
define("afx-label", LabelTag);
}
}
}

1249
src/core/tags/ListViewTag.ts Normal file

File diff suppressed because it is too large Load Diff

826
src/core/tags/MenuTag.ts Normal file
View File

@ -0,0 +1,826 @@
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);
}
}
}

View File

@ -0,0 +1,211 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A simple number sinner tag
*
* @export
* @class NSpinnerTag
* @extends {AFXTag}
*/
export class NSpinnerTag extends AFXTag {
/**
* Placeholder for value change event handle
*
* @private
* @type {TagEventCallback<number>}
* @memberof NSpinnerTag
*/
private _onchange: TagEventCallback<number>;
/**
* Placeholder for the spinner data
*
* @private
* @type {number}
* @memberof NSpinnerTag
*/
private _value: number;
/**
* Place holder for the spinner step
*
* @type {number}
* @memberof NSpinnerTag
*/
step: number;
/**
*Creates an instance of NSpinnerTag.
* @memberof NSpinnerTag
*/
constructor() {
super();
this._onchange = (e) => {};
}
/**
* Init the spinner value to `0` and step to `1`
*
* @protected
* @memberof NSpinnerTag
*/
protected init(): void {
this._value = 0;
this.step = 1;
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof NSpinnerTag
*/
protected reload(d?: any): void {}
/**
* Set the value change event handle
*
* @memberof NSpinnerTag
*/
set onvaluechange(f: TagEventCallback<number>) {
this._onchange = f;
}
/**
* Mount the tag and bind basic events
*
* @protected
* @memberof NSpinnerTag
*/
protected mount(): void {
$(this.refs.holder).attr("type", "text");
$(this.refs.incr).on("click",(e) => {
this.value = this.value + this.step;
});
$(this.refs.decr).on("click",(e) => {
this.value = this.value - this.step;
});
// @observable.on "calibrate", () -> @calibrate()
this.observable.on("resize", () => this.calibrate());
$(this.refs.holder).on("keyup", (e) => {
if (e.which === 13) {
let val = parseInt(
(this.refs.holder as HTMLInputElement).value
);
if (!isNaN(val)) {
if (val < 0) {
val = this.value;
}
return (this.value = val);
}
}
});
this.calibrate();
}
/**
* Calibrate the layout of the spinner
*
* @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");
}
/**
* Setter: Set the spinner value
*
* Getter: Get the spinner value
*
* @memberof NSpinnerTag
*/
set value(v: number) {
if(this._value === v || isNaN(v))
{
return;
}
this._value = v;
$(this.refs.holder).val(this._value);
const evt = { id: this.aid, data: v };
this._onchange(evt);
this.observable.trigger("nspin", evt);
}
get value(): number {
return this._value;
}
/**
* Spinner layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof NSpinnerTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "input",
ref: "holder",
},
{
el: "ul",
ref: "spinner",
children: [
{
el: "li",
class: "incr",
ref: "incr",
children: [{ el: "i" }],
},
{
el: "li",
class: "decr",
ref: "decr",
children: [{ el: "i" }],
},
],
},
];
}
}
define("afx-nspinner", NSpinnerTag);
}
}
}

159
src/core/tags/OverlayTag.ts Normal file
View File

@ -0,0 +1,159 @@
namespace OS {
export namespace GUI {
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]])
* can be used inside this tag to compose elements
*
* @export
* @class OverlayTag
* @extends {AFXTag}
*/
export class OverlayTag extends AFXTag {
/**
* Tag width placeholder
*
* @private
* @type {string}
* @memberof OverlayTag
*/
private _width: string;
/**
* Tag height place holder
*
* @private
* @type {string}
* @memberof OverlayTag
*/
private _height: string;
/**
*Creates an instance of OverlayTag.
* @memberof OverlayTag
*/
constructor() {
super();
}
//.css "display", "flex"
//.css "flex-direction", "column"
//$(@refs.yield).css "flex", "1"
/**
* Put the tag on top of the virtual desktop environment
*
* @protected
* @memberof OverlayTag
*/
protected init(): void {
$(this.refs.yield)
.css("position", "relative")
.css("width", "100%")
.css("height", "100%");
$(this).css("position", "absolute").css("z-index", 1000000);
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof OverlayTag
*/
protected reload(d?: any): void {}
/**
* Setter:
*
* Set the width of the tag, the tag width should be in form of:
* `100px` of `80%`
*
* Getter:
*
* Get the tag width
*
* @memberof OverlayTag
*/
set width(v: string) {
if (!v) {
return;
}
this._width = v;
this.calibrate();
}
get width(): string {
return this._width;
}
/**
* Setter:
*
* Set the tag height, the tag height should be in form of:
* `100px` of `80%`
*
* Getter:
*
* Get the tag height
*
* @memberof OverlayTag
*/
set height(v: string) {
if (!v) {
return;
}
this._height = v;
this.calibrate();
}
get height(): string {
return this._height;
}
/**
* Calibrate the element when mounting
*
* @protected
* @returns {void}
* @memberof OverlayTag
*/
protected mount(): void {
return this.calibrate();
}
/**
* Calibrate the width and height of the tag
*
* @returns {void}
* @memberof OverlayTag
*/
calibrate(): void {
$(this).css("width", this.width).css("height", this.height);
return this.observable.trigger("resize", {
id: this.aid,
data: {
w: this.width,
h: this.height,
},
});
}
/**
* Layout definition of the tag
*
* @protected
* @returns {TagLayoutType[]}
* @memberof OverlayTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "afx-vbox",
ref: "yield",
},
];
}
}
define("afx-overlay", OverlayTag);
}
}
}

316
src/core/tags/ResizerTag.ts Normal file
View File

@ -0,0 +1,316 @@
namespace OS {
export namespace GUI {
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:
*
* The resizer tag in the following example will be attached to the first `afx-vbox`,
* and allows to resize this element using mouse
*
* ```xml
* <afx-hbox>
* <afx-vbox>...</afx-vbox>
* <afx-resizer></afx-resizer>
* <afx-vbox>...</afx-vbox>
* </afx-hbox>
* ```
*
* @export
* @class ResizerTag
* @extends {AFXTag}
*/
export class ResizerTag extends AFXTag {
/**
* Reference to the element that this tag is attached to
*
* @private
* @type {*}
* @memberof ResizerTag
*/
private _resizable_el: any;
/**
* Reference to the resize event callback
*
* @private
* @type {TagEventCallback<any>}
* @memberof ResizerTag
*/
private _onresize: TagEventCallback<any>;
/**
* Reference to the parent tag of the current tag.
* The parent tag should be an instance of a [[TileLayoutTag]]
* such as [[VBoxTag]] or [[HBoxTag]]
*
* @private
* @type {*}
* @memberof ResizerTag
*/
private _parent: any;
/**
* Placeholder of the minimum value that
* the attached element can be resized
*
* @private
* @type {number}
* @memberof ResizerTag
*/
private _minsize: number;
/**
*Creates an instance of ResizerTag.
* @memberof ResizerTag
*/
constructor() {
super();
}
/**
* Set the properties of the tag to default values
*
* @protected
* @memberof ResizerTag
*/
protected init(): void {
this._resizable_el = undefined;
this._parent = $(this).parent().parent()[0];
this._minsize = 0;
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof ResizerTag
*/
protected reload(d?: any): void { }
/**
* Setter:
*
* Set resize direction, two possible values:
* - `hz` - horizontal direction, resize by width
* - `ve` - vertical direction, resize by height
*
* Getter:
*
* Get the resize direction
*
* @memberof ResizerTag
*/
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");
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");
if (this._resizable_el) {
att = $(this._resizable_el).attr("min-height");
if (att) {
this._minsize = parseInt(att);
}
}
}
if (this._minsize === 0) {
this._minsize = 10;
}
this.make_draggable();
}
get dir(): string {
return $(this).attr("dir");
}
/**
* Getter : Check whether the resizer should attach to its next or previous element
*
* Setter: if `v=true` select next element as attached element of the resizer, otherwise
* select the previous element
* @readonly
* @type {boolean}
* @memberof ResizerTag
*/
get attachnext(): boolean {
return this.hasattr("attachnext");
}
set attachnext(v: boolean) {
this.attsw(v, "attachnext");
}
/**
* Setter:
* - set the resize event callback
*
* Getter:
* - get the resize event callback
*
* @memberof ResizerTag
*/
set onelresize(v: TagEventCallback<any>) {
this._onresize = v;
}
get onelresize(): TagEventCallback<any> {
return this._onresize;
}
/**
* Mount the tag to the DOM tree
*
* @protected
* @memberof ResizerTag
*/
protected mount(): void {
$(this).css(" display", "block");
const tagname = $(this._parent).prop("tagName");
if (this.attachnext) {
this._resizable_el =
$(this).next().length === 1
? $(this).next()[0]
: undefined;
}
else {
this._resizable_el =
$(this).prev().length === 1
? $(this).prev()[0]
: undefined;
}
if (tagname === "AFX-HBOX") {
this.dir = "hz";
} else if (tagname === "AFX-VBOX") {
this.dir = "ve";
} else {
this.dir = "hz";
}
}
/**
* Enable draggable on the element
*
* @private
* @memberof ResizerTag
*/
private make_draggable(): void {
$(this).css("user-select", "none");
if (!this.dir || this.dir == "none") {
return;
}
$(this).on("mousedown", (e) => {
e.preventDefault();
$(window).on("mousemove", (evt) => {
if (!this._resizable_el) {
return;
}
if (this.dir === "hz") {
return this.horizontalResize(evt);
} else if (this.dir === "ve") {
return this.verticalResize(evt);
}
});
return $(window).on("mouseup", function (evt) {
$(window).off("mousemove", null);
$(window).off("mouseup", null);
return $(window).off("mouseup", null);
});
});
}
/**
* Resize the attached element in the horizontal direction (width)
*
* @private
* @param {JQuery.MouseEventBase} e JQuery mouse event
* @returns {void}
* @memberof ResizerTag
*/
private horizontalResize(e: JQuery.MouseEventBase): void {
if (!this._resizable_el) {
return;
}
const offset = $(this._resizable_el).offset();
let w = 0;
if (this.attachnext) {
w = Math.round(offset.left + $(this._resizable_el).width() - e.clientX);
}
else {
w = Math.round(e.clientX - offset.left);
}
if (w < this._minsize) {
w = this._minsize;
}
$(this._resizable_el).attr("data-width", w.toString());
let evt = {
id: this.aid,
data: { w },
};
if (this.onelresize) {
this.onelresize(evt);
}
this.observable.trigger("resize", evt);
}
/**
* Resize the attached element in the vertical direction (height)
*
* @protected
* @param {JQuery.MouseEventBase} e JQuery mouse event
* @returns {void}
* @memberof ResizerTag
*/
protected verticalResize(e: JQuery.MouseEventBase): void {
if (!this._resizable_el) {
return;
}
const offset = $(this._resizable_el).offset();
let h = 0;
if (this.attachnext) {
h = Math.round(offset.top + $(this._resizable_el).height() - e.clientY);
}
else {
h = Math.round(e.clientY - offset.top);
}
if (h < this._minsize) {
h = this._minsize;
}
$(this._resizable_el).attr("data-height", h.toString());
let evt = {
id: this.aid,
data: { h },
};
if (this.onelresize) {
this.onelresize(evt);
}
return this.observable.trigger("resize", evt);
}
/**
* Layout definition of the tag, empty layout
*
* @protected
* @returns {TagLayoutType[]}
* @memberof ResizerTag
*/
protected layout(): TagLayoutType[] {
return [];
}
}
define("afx-resizer", ResizerTag);
}
}
}

275
src/core/tags/SliderTag.ts Normal file
View File

@ -0,0 +1,275 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A slider or track bar is a graphical control element with which
* a user may set a value by moving an indicator, usually horizontally
*
* @class SliderTag
* @extends {AFXTag}
*/
export class SliderTag extends AFXTag {
/**
* Slider max value placeholder
*
* @private
* @type {number}
* @memberof SliderTag
*/
private _max: number;
/**
* Current slider value placeholder
*
* @private
* @type {number}
* @memberof SliderTag
*/
private _value: number;
/**
* Placeholder of the on change event handle
*
* @private
* @type {TagEventCallback<number>}
* @memberof SliderTag
*/
private _onchange: TagEventCallback<number>;
/**
* Placeholder of the on changing event handle
*
* @private
* @type {TagEventCallback<number>}
* @memberof SliderTag
*/
private _onchanging: TagEventCallback<number>;
/**
* Creates an instance of SliderTag.
* @memberof SliderTag
*/
constructor() {
super();
}
/**
* Init the default value of the slider:
* - `max`: 100
* - `value`: 0
*
* @protected
* @memberof SliderTag
*/
protected init(): void {
this.enable = true;
this._max = 100;
this._value = 0;
this._onchange = this._onchanging = () => {};
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof SliderTag
*/
protected reload(d?: any): void {}
/**
* Set value change event handle.
* This handle will be triggered when the
* slider indicator is released
*
* @memberof SliderTag
*/
set onvaluechange(f: TagEventCallback<number>) {
this._onchange = f;
}
/**
* Set value changing event handle.
* This handle is triggered when moving the
* slider indicator
*
* @memberof SliderTag
*/
set onvaluechanging(f: TagEventCallback<number>) {
this._onchanging = f;
}
/**
* Setter: Enable/disable the slider
*
* Getter: Check whether the slider is enabled
*
* @memberof SliderTag
*/
set enable(v: boolean) {
this.attsw(v, "enable");
if (v) {
$(this)
.on("mouseover",() => {
return $(this.refs.point).show();
})
.on("mouseout",() => {
return $(this.refs.point).hide();
});
} else {
$(this.refs.point).hide();
$(this).off("mouseover").off("mouseout");
}
}
get enable(): boolean {
return this.hasattr("enable");
}
/**
* Setter: Set the slider value
*
* Getter: Get the current slider value
*
* @memberof SliderTag
*/
set value(v: number) {
this._value = v;
this.calibrate();
}
get value(): number {
return this._value;
}
/**
* Setter: Set the maximum value of the slider
*
* Getter: Get the maximum value of the slider
*
* @memberof SliderTag
*/
set max(v: number) {
this._max = v;
this.calibrate();
}
get max(): number {
return this._max;
}
/**
* Mount the tag and bind some basic events
*
* @protected
* @memberof SliderTag
*/
protected mount(): void {
this.enable_dragging();
$(this.refs.point).css("position", "absolute");
$(this.refs.point).hide();
this.observable.on("resize", (e) => {
return this.calibrate();
});
$(this.refs.container).on("click",(e) => {
const offset = $(this.refs.container).offset();
const left = e.clientX - offset.left;
const maxw = $(this.refs.container).width();
this.value = (left * this.max) / maxw;
this.calibrate();
const evt = { id: this.aid, data: this.value };
this._onchange(evt);
return this._onchanging(evt);
});
this.calibrate();
}
/**
* Calibrate the slide based on its value and max value
*
* @memberof SliderTag
*/
calibrate(): void {
if (this.value > this.max) {
this.value = this.max;
}
$(this.refs.container).css("width", $(this).width() + "px");
const w =
($(this.refs.container).width() * this.value) /
this.max;
$(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");
}
}
/**
* enable dragging on the slider indicator
*
* @private
* @memberof SliderTag
*/
private enable_dragging(): void {
$(this.refs.point)
.css("user-select", "none")
.css("cursor", "default");
$(this.refs.point).on("mousedown", (e) => {
e.preventDefault();
const offset = $(this.refs.container).offset();
$(window).on("mousemove", (e) => {
let left = e.clientX - offset.left;
left = left < 0 ? 0 : left;
const maxw = $(this.refs.container).width();
left = left > maxw ? maxw : left;
this.value = (left * this.max) / maxw;
this.calibrate();
return this._onchanging({
id: this.aid,
data: this.value,
});
});
$(window).on("mouseup", (e) => {
this._onchange({
id: this.aid,
data: this.value,
});
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
});
});
}
/**
* Layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof SliderTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div",
class: "container",
ref: "container",
children: [
{ el: "div", class: "progress", ref: "prg" },
{ el: "div", class: "dragpoint", ref: "point" },
],
},
];
}
}
define("afx-slider", SliderTag);
}
}
}

144
src/core/tags/SwitchTag.ts Normal file
View File

@ -0,0 +1,144 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A switch tag is basically used to visualize an boolean data value.
*
* @export
* @class SwitchTag
* @extends {AFXTag}
*/
export class SwitchTag extends AFXTag {
/**
* Placeholder for the onchange event handle
*
* @private
* @type {TagEventCallback<boolean>}
* @memberof SwitchTag
*/
private _onchange: TagEventCallback<boolean>;
/**
* Setter: Turn on/off the switch
*
* Getter: Check whether the switch is turned on
*
* @memberof SwitchTag
*/
set swon(v: boolean) {
this.attsw(v, "swon");
$(this.refs.switch).removeClass();
if (v) {
$(this.refs.switch).addClass("swon");
}
}
get swon(): boolean {
return this.hasattr("swon");
}
/**
* Setter: Enable the switch
*
* Getter: Check whether the switch is enabled
*
* @memberof SwitchTag
*/
set enable(v: boolean) {
this.attsw(v, "enable");
}
get enable(): boolean {
return this.hasattr("enable");
}
/**
* Set the onchange event handle
*
* @memberof SwitchTag
*/
set onswchange(v: TagEventCallback<boolean>) {
this._onchange = v;
}
/**
* Mount the tag and bind the click event to the switch
*
* @protected
* @memberof SwitchTag
*/
protected mount(): void {
$(this.refs.switch).on("click",(e) => {
return this.makechange(e);
});
}
/**
* This function will turn the switch (on/off)
* and trigger the onchange event
*
* @private
* @param {JQuery.ClickEvent} e
* @returns
* @memberof SwitchTag
*/
private makechange(e: JQuery.ClickEvent) {
if (!this.enable) {
return;
}
this.swon = !this.swon;
const evt = { id: this.aid, data: this.swon };
this._onchange(evt);
return this.observable.trigger("switch", evt);
}
/**
* Tag layout definition
*
* @protected
* @returns
* @memberof SwitchTag
*/
protected layout() {
return [
{
el: "span",
ref: "switch",
},
];
}
/**
* Init the tag:
* - switch is turn off
* - switch is enabled
*
* @protected
* @memberof SwitchTag
*/
protected init(): void {
this.swon = false;
this.enable = true;
this._onchange = (e) => {};
}
/**
* Do nothing
*
* @protected
* @memberof SwitchTag
*/
protected calibrate(): void {}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof SwitchTag
*/
protected reload(d?: any): void {}
}
define("afx-switch", SwitchTag);
}
}
}

View File

@ -0,0 +1,572 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A system panel contains the following elements:
* - Spotlight to access to applications menu
* - Current focused application menu
* - System tray for all running services running in background
*
* @export
* @class SystemPanelTag
* @extends {AFXTag}
*/
export class SystemPanelTag extends AFXTag {
/**
* Reference to spotlight data
*
* @private
* @type {(GenericObject<string | FormattedString>)}
* @memberof SystemPanelTag
*/
private _osmenu: GenericObject<string | FormattedString>;
/**
* Placeholder indicates whether the spotlight is currently shown
*
* @private
* @type {boolean}
* @memberof SystemPanelTag
*/
private _view: boolean;
/**
* Store pending loading task
*
* @private
* @type {number[]}
* @memberof SystemPanelTag
*/
private _pending_task: number[];
/**
* Loading animation check timeout
*
* @memberof SystemPanelTag
*/
private _loading_toh: any;
/**
* Place holder for a private callback function
*
* @private
* @memberof SystemPanelTag
*/
private _cb: (e: JQuery.MouseEventBase) => void;
/**
* Place holder for system app list
*
* @private
* @type {GenericObject<any>[]}
* @memberof SystemPanelTag
*/
private app_list: GenericObject<any>[];
/**
*Creates an instance of SystemPanelTag.
* @memberof SystemPanelTag
*/
constructor() {
super();
this._osmenu = {
text: __("Start"),
iconclass: "fa fa-circle",
};
this._view = false;
this._pending_task = [];
this._loading_toh = undefined;
this.app_list= [];
}
/**
* Do nothing
*
* @protected
* @memberof SystemPanelTag
*/
protected init(): void { }
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof SystemPanelTag
*/
protected reload(d?: any): void { }
/**
* Attach a service to the system tray on the pannel,
* this operation is performed when a service is started
*
* @param {BaseService} s
* @returns
* @memberof SystemPanelTag
*/
attachservice(s: application.BaseService) {
(this.refs.systray as MenuTag).unshift(s);
return s.attach(this.refs.systray);
}
/**
* Launch the selected application from the spotlight
* applications list
*
* @private
* @returns {void}
* @memberof SystemPanelTag
*/
private open(): void {
const applist = this.refs.applist as ListViewTag;
const el = applist.selectedItem;
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);
applist.unselect();
}
/**
* Perform spotlight search operation on keyboard event
*
* @private
* @param {JQuery.KeyboardEventBase} e
* @returns {void}
* @memberof SystemPanelTag
*/
private search(e: JQuery.KeyboardEventBase): void {
const applist = this.refs.applist as ListViewTag;
const catlist = this.refs.catlist as ListViewTag;
switch (e.which) {
case 27:
// escape key
return this.toggle(false);
case 37:
return e.preventDefault();
case 38:
applist.selectPrev();
return e.preventDefault();
case 39:
return e.preventDefault();
case 40:
applist.selectNext();
return e.preventDefault();
case 13:
e.preventDefault();
return this.open();
default:
catlist.selected = 0;
var text = (this.refs.search as HTMLInputElement)
.value;
if (!(text.length >= 3)) {
(this.refs.applist as ListViewTag).data = this.app_list;
(this.refs.applist as ListViewTag).selected = -1;
return;
}
var result = Ant.OS.API.search(text);
if (result.length === 0) {
return;
}
applist.data = result;
}
}
/**
* detach a service from the system tray of the panel.
* This function is called when the corresponding running
* service is killed
*
* @param {BaseService} s
* @memberof SystemPanelTag
*/
detachservice(s: application.BaseService): void {
(this.refs.systray as MenuTag).delete(
s.domel as MenuEntryTag
);
}
/**
* Layout definition of the panel
*
* @protected
* @returns {TagLayoutType[]}
* @memberof SystemPanelTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div",
ref: "panel",
children: [
{
el: "afx-menu",
ref: "osmenu",
class: "afx-panel-os-menu",
},
{
el: "afx-menu",
ref: "pinned",
class: "afx-panel-os-pinned-app",
},
{
el: "afx-menu",
id: "appmenu",
ref: "appmenu",
class: "afx-panel-os-app",
},
{
el: "afx-menu",
id: "systray",
ref: "systray",
class: "afx-panel-os-stray",
},
],
},
{
el: "afx-overlay",
id: "start-panel",
ref: "overlay",
children: [
{
el: "afx-hbox",
height: 30,
children: [
{
el: "div",
width: 30,
id: "searchicon",
},
{ el: "input", ref: "search" },
],
},
{
el: "afx-hbox",
children: [
{
el: "afx-list-view",
id: "catlist",
ref: "catlist",
width:"40%"
},
{
el: "afx-resizer",
width: 3,
},
{
el: "afx-list-view",
id: "applist",
ref: "applist",
}
]
},
{
el: "afx-hbox",
id: "btlist",
height: 30,
children: [
{
el: "afx-button",
ref: "btscreen",
tooltip: __("ct:Toggle fullscreen"),
},
{
el: "afx-button",
ref: "btuser",
tooltip: __(
"ct:User: {0}",
Ant.OS.setting.user.username
),
},
{
el: "afx-button",
ref: "btlogout",
tooltip: __("ct:Logout"),
},
],
},
],
},
];
}
/**
* Refresh applications list on the spotlight widget
* from system packages meta-data
*
* @private
* @memberof SystemPanelTag
*/
private refreshAppList(): void {
let catlist_el = (this.refs.catlist as tag.ListViewTag);
let k: string, v: API.PackageMetaType;
const catlist = new Set();
this.app_list = [];
for (k in Ant.OS.setting.system.packages) {
v = Ant.OS.setting.system.packages[k];
if (v && v.app) {
this.app_list.push(v);
catlist.add(v.category.__());
}
}
for (k in Ant.OS.setting.system.menu) {
v = Ant.OS.setting.system.menu[k];
this.app_list.push(v);
}
this.app_list.sort(function (a, b) {
if (a.text < b.text) {
return -1;
} else if (a.text > b.text) {
return 1;
} else {
return 0;
}
});
// build up the category menu
const cat_list_data = [];
cat_list_data.push({
text: "__(All)",
iconclass: "bi bi-gear-wide"
});
(OS.setting.applications.categories as Array<GenericObject<any>>)
.forEach((v) =>{
if(catlist.has(v.text.__()))
{
cat_list_data.push(v);
catlist.delete(v.text.__());
}
})
// put the remainder to the data
catlist.forEach((c) => {
cat_list_data.push({
text: c,
iconclass: "bi bi-gear-wide"
});
});
catlist_el.data = cat_list_data;
catlist_el.selected = 0;
}
/**
* Show/hide the spotlight
*
* @private
* @param {boolean} flag
* @memberof SystemPanelTag
*/
private toggle(flag: boolean): void {
this._view = flag;
if (flag) {
$(this.refs.overlay).show();
this.refreshAppList();
this.calibrate();
$(document).on("click", this._cb);
(this.refs.search as HTMLInputElement).value = "";
$(this.refs.search).trigger("focus");
} else {
$(this.refs.overlay).hide();
$(document).off("click", this._cb);
}
}
/**
* Calibrate the spotlight widget
*
* @memberof SystemPanelTag
*/
calibrate(): void {
(this.refs.overlay as OverlayTag).height = `${$(window).height() - $(this.refs.panel).height()
}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
*
* @private
* @memberof SystemPanelTag
*/
private animation_check(): void {
if(this._pending_task.length === 0)
{
$(this.refs.panel).removeClass("loading");
$(GUI.workspace).css("cursor", "auto");
}
if(this._loading_toh)
clearTimeout(this._loading_toh);
this._loading_toh = undefined;
}
/**
* Mount the tag bind some basic event
*
* @protected
* @memberof SystemPanelTag
*/
protected mount(): void {
(this.refs.osmenu as MenuTag).items = [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) => {
this.toggle(false);
return Ant.OS.GUI.toggleFullscreen();
},
});
(this.refs.btuser as ButtonTag).set({
iconclass: "fa fa-user-circle-o",
onbtclick: (e) => {
this.toggle(false);
return Ant.OS.GUI.openDialog(
"InfoDialog",
Ant.OS.setting.user
);
},
});
(this.refs.btlogout as ButtonTag).set({
iconclass: "fa fa-power-off",
onbtclick: (e) => {
this.toggle(false);
return Ant.OS.exit();
},
});
(this.refs.osmenu as MenuTag).onmenuselect = (e) => {
return this.toggle(true);
};
$(this.refs.search).on("keyup", (e) => {
return this.search(e);
});
$(this.refs.applist).on("click", (e) => {
return this.open();
});
Ant.OS.GUI.bindKey("CTRL- ", (e) => {
if (this._view === false) {
return this.toggle(true);
} else {
return this.toggle(false);
}
});
const catlist = (this.refs.catlist as tag.ListViewTag);
catlist.onlistselect = (e) => {
const applist = (this.refs.applist as ListViewTag);
if(catlist.selected === 0)
{
applist.data = this.app_list;
}
else
{
// filter app by data
applist.data = this.app_list.filter((el) =>{
return el.category.__() === e.data.item.data.text.__();
})
}
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", (_) => {
this.RefreshPinnedApp();
});
announcer.observable.on("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != 0)
{
return;
}
this._pending_task.push(o.id);
if(!$(this.refs.panel).hasClass("loading"))
$(this.refs.panel).addClass("loading");
$(GUI.workspace).css("cursor", "wait");
});
announcer.observable.on("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
this._pending_task.splice(i, 1);
}
if (this._pending_task.length === 0) {
// set time out
if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000);
}
});
this.RefreshPinnedApp();
Ant.OS.announcer.trigger("syspanelloaded", undefined);
}
}
define("afx-sys-panel", SystemPanelTag);
}
}
}

204
src/core/tags/TabBarTag.ts Normal file
View File

@ -0,0 +1,204 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Tag event data type definition
*/
type TabEventData = TagEventDataType<ListViewItemTag>;
/**
* a TabBar allows to control a collection of tabs
*
* @export
* @class TabBarTag
* @extends {AFXTag}
*/
export class TabBarTag extends AFXTag {
/**
* Placeholder of currently selected tab index
*
* @private
* @type {number}
* @memberof TabBarTag
*/
private _selected: number;
/**
* Placeholder of tab close event handle
*
* @private
* @memberof TabBarTag
*/
private _ontabclose: (e: TagEventType<TabEventData>) => boolean;
/**
* Placeholder of tab select event handle
*
* @private
* @type {TagEventCallback<TabEventData>}
* @memberof TabBarTag
*/
private _ontabselect: TagEventCallback<TabEventData>;
/**
*Creates an instance of TabBarTag.
* @memberof TabBarTag
*/
constructor() {
super();
this._ontabclose = (e) => true;
this._ontabselect = (e) => {};
}
/**
* Init the tag
*
* @protected
* @memberof TabBarTag
*/
protected init(): void {
this.selected = -1;
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof TabBarTag
*/
protected reload(d?: any): void {}
/**
* Setter: Enable/disable a tab to be closed
*
* Getter: Check whether tabs can be closed
*
* @memberof TabBarTag
*/
set closable(v: boolean) {
this.attsw(v, "closable");
}
get closable(): boolean {
return this.hasattr("closable");
}
/**
* Add a tab in the end of the tab bar
*
* @param {GenericObject<any>} item tab data
* @memberof TabBarTag
*/
push(item: GenericObject<any>): ListViewItemTag {
item.closable = this.closable;
return (this.refs.list as ListViewTag).push(item);
}
/**
* Delete a tab
*
* @param {ListViewItemTag} el reference to DOM element of a tab
* @memberof TabBarTag
*/
delete(el: ListViewItemTag) {
(this.refs.list as ListViewTag).delete(el);
}
/**
* Add a tab to the beginning of the tab bar
*
* @param {GenericObject<any>} item tab data
* @memberof TabBarTag
*/
unshift(item: GenericObject<any>): ListViewItemTag {
item.closable = this.closable;
return (this.refs.list as ListViewTag).unshift(item);
}
/**
* Setter: Set tabs data
*
* Getter: Get all tabs data
*
* @memberof TabBarTag
*/
set items(v: GenericObject<any>[]) {
for (let i of v) {
i.closable = this.closable;
}
(this.refs.list as ListViewTag).data = v;
}
get items(): GenericObject<any>[] {
return (this.refs.list as ListViewTag).data;
}
/**
* Setter: Select a tab by its index
*
* Getter: Get the currently selected tab
*
* @memberof TabBarTag
*/
set selected(v: number | number[]) {
(this.refs.list as ListViewTag).selected = v;
}
get selected(): number | number[] {
return (this.refs.list as ListViewTag).selected;
}
/**
* Set the tab close event handle
*
* @memberof TabBarTag
*/
set ontabclose(v: (e: TagEventType<TabEventData>) => boolean) {
this._ontabclose = v;
}
/**
* Set the tab select event handle
*
* @memberof TabBarTag
*/
set ontabselect(v: TagEventCallback<TabEventData>) {
this._ontabselect = v;
}
/**
* Mount the tab bar and bind some basic events
*
* @protected
* @memberof TabBarTag
*/
protected mount(): void {
$(this.refs.list).css("height", "100%");
(this.refs.list as ListViewTag).onitemclose = (e) => {
e.id = this.aid;
return this._ontabclose(e);
};
(this.refs.list as ListViewTag).onlistselect = (e) => {
this._ontabselect(e);
return this.observable.trigger("tabselect", e);
};
}
/**
* TabBar layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof TabBarTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "afx-list-view",
ref: "list",
},
];
}
}
define("afx-tab-bar", TabBarTag);
}
}
}

View File

@ -0,0 +1,308 @@
namespace OS {
export namespace GUI {
/**
* Tab container data type definition
*
* @export
* @interface TabContainerTabType
*/
export interface TabContainerTabType {
/**
* Reference to the DOM element of the current container
*
* @type {HTMLElement}
* @memberof TabContainerTabType
*/
container: HTMLElement;
[propName: string]: any;
}
export namespace tag {
/**
* A tab container allows to attach each tab on a [[TabBarTag]]
* with a container widget. The attached container widget should be
* composed inside a [[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
*
* Once a tab is selected, its attached container will be shown
*
* @export
* @class TabContainerTag
* @extends {AFXTag}
*/
export class TabContainerTag extends AFXTag {
/**
* Reference to the currently selected tab DOM element
*
* @private
* @type {TabContainerTabType}
* @memberof TabContainerTag
*/
private _selectedTab: TabContainerTabType;
/**
* Placeholder of the tab select event handle
*
* @private
* @type {TagEventCallback<TabContainerTabType>}
* @memberof TabContainerTag
*/
private _ontabselect: TagEventCallback<TabContainerTabType>;
/**
*Creates an instance of TabContainerTag.
* @memberof TabContainerTag
*/
constructor() {
super();
this._ontabselect = (e) => { };
}
/**
* Init the tab bar direction to vertical (column)
*
* @protected
* @memberof TabContainerTag
*/
protected init(): void {
this.dir = "column"; // or row
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof TabContainerTag
*/
protected reload(d?: any): void { }
/**
* Set the tab select event handle
*
* @memberof TabContainerTag
*/
set ontabselect(f: TagEventCallback<TabContainerTabType>) {
this._ontabselect = f;
}
/**
* Get all tab items in the container
*
* @readonly
* @type {TabContainerTabType[]}
* @memberof TabContainerTag
*/
get tabs(): TabContainerTabType[]
{
return (this.refs.bar as TabBarTag).items as TabContainerTabType[];
}
/**
* Select a tab by its index
*
* @memberof TabContainerTag
*/
set selectedIndex(i: number) {
(this.refs.bar as TabBarTag).selected = i;
}
/**
* Setter:
*
* Set the tab bar direction:
* - `row`: horizontal direction
* - `column`: vertical direction
*
* Getter:
*
* Get the tab bar direction
*
* @memberof TabContainerTag
*/
set dir(v: "row" | "column") {
$(this).attr("dir", v);
if (!v) {
return;
}
(this.refs.wrapper as TileLayoutTag).dir = v;
}
get dir(): "row" | "column" {
return $(this).attr("dir") as any;
}
/**
* Setter:
*
* Select a tab using the its tab data type.
* This will show the attached container to the tab
*
* Getter:
*
* Get the tab data of the currently selected Tab
*
* @memberof TabContainerTag
*/
set selectedTab(v: TabContainerTabType) {
if (!v) {
return;
}
const selected = this._selectedTab;
this._selectedTab = v;
if (selected) {
$(selected.container).hide();
}
$(v.container).show();
this.observable.trigger("resize", undefined);
$(v.container).attr("tabindex",-1).css("outline", "none").trigger("focus");
}
get selectedTab(): TabContainerTabType {
return this._selectedTab;
}
/**
* Set the tab bar width, this function only
* works when the tab bar direction is set to
* `row`
*
* @memberof TabContainerTag
*/
set tabbarwidth(v: number) {
if (!v) {
return;
}
$(this.refs.bar).attr("data-width", `${v}`);
(this.refs.wrapper as TileLayoutTag).calibrate();
}
/**
* Set the tab bar height, this function only works
* when the tab bar direction is set to `column`
*
* @memberof TabContainerTag
*/
set tabbarheight(v: number) {
$(this.refs.bar).attr("data-height", `${v}`);
(this.refs.wrapper as TileLayoutTag).calibrate();
}
/**
* Add a new tab with container to the container
*
* item should be in the following format:
*
* ```ts
* {
* text: string,
* icon?: string,
* iconclass?: string,
* container: HTMLElement
* }
* ```
*
* @param {GenericObject<any>} item tab descriptor
* @param {boolean} insert insert the tab content to the container ?
* @returns {ListViewItemTag} the tab DOM element
* @memberof TabContainerTag
*/
public addTab(item: GenericObject<any>, insert: boolean): ListViewItemTag {
if (insert) {
$(this.refs.yield).append(item.container);
}
$(item.container)
.css("width", "100%")
.css("height", "100%")
.hide();
const el = (this.refs.bar as TabBarTag).push(
item
);
el.selected = true;
return el;
}
/**
* Remove a tab from the container
*
* @param {ListViewItemTag} tab the tab item to be removed
* @memberof TabContainerTag
*/
public removeTab(tab: ListViewItemTag): void {
if (tab.data.container) {
$(tab.data.container).remove();
}
(this.refs.bar as TabBarTag).delete(tab);
}
/**
* Mount the tag and bind basic events
*
* @protected
* @memberof TabContainerTag
*/
protected mount(): void {
(this.refs.bar as TabBarTag).ontabselect = (e) => {
const data = (e.data.item as ListViewItemTag)
.data as TabContainerTabType;
this.selectedTab = data;
return this._ontabselect({ data: data, id: this.aid });
};
this.observable.one("mounted", (id) => {
$(this.refs.yield)
.children()
.each((i, e) => {
const item = {} as GenericObject<any>;
if ($(e).attr("tabname")) {
item.text = $(e).attr("tabname");
}
if ($(e).attr("icon")) {
item.icon = $(e).attr("icon");
}
if ($(e).attr("iconclass")) {
item.iconclass = $(e).attr("iconclass");
}
item.container = e;
this.addTab(item, false);
});
});
this.observable.on("resize", (e) => this.calibrate());
this.calibrate();
}
/**
* calibrate the tab container
*
* @memberof TabContainerTag
*/
calibrate(): void {
$(this.refs.wrapper).css("height", `${$(this).height()}px`);
}
/**
* Layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof TabContainerTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "afx-tile",
ref: "wrapper",
children: [
{ el: "afx-tab-bar", ref: "bar" },
{ el: "div", ref: "yield" },
],
},
];
}
}
define("afx-tab-container", TabContainerTag);
}
}
}

View File

@ -0,0 +1,298 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A tile layout organize it child elements
* in a fixed horizontal or vertical direction.
*
* The size of each child element is attributed based
* on its configuration of automatically based on the
* remaining space in the layout
*
*
* @export
* @class TileLayoutTag
* @extends {AFXTag}
*/
export class TileLayoutTag extends AFXTag {
/**
*C reates an instance of TileLayoutTag.
* @memberof TileLayoutTag
*/
constructor() {
super();
}
/**
* Do nothing
*
* @protected
* @memberof TileLayoutTag
*/
protected init(): void {}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof TileLayoutTag
*/
protected reload(d?: any): void {}
/**
* Setter: Set the name of the tile container, should be: `hbox` or `vbox`
*
* Getter: Get the name of the tile container
*
* @memberof TileLayoutTag
*/
set name(v: string) {
if (!v) {
return;
}
$(this).attr("name", v);
$(this.refs.yield)
.removeClass()
.addClass(`afx-${v}-container`);
this.calibrate();
}
get name(): string {
return $(this).attr("name");
}
/**
* Setter:
*
* SET the layout direction, should be:
* - `row`: horizontal direction
* - `column`: vertical direction
*
* Getter:
*
* Get layout direction
*
* @memberof TileLayoutTag
*/
set dir(v: "row" | "column") {
if (!v) {
return;
}
$(this).attr("dir", v);
$(this.refs.yield).css("flex-direction", v);
this.calibrate();
}
get dir(): "row" | "column" {
return $(this).attr("dir") as any;
}
/**
* Mount the element
*
* @protected
* @returns {void}
* @memberof TileLayoutTag
*/
protected mount(): void {
$(this).css("display", "block");
$(this.refs.yield)
.css("display", "flex")
.css("width", "100%")
.css("height", "100%");
this.observable.on("resize", (e) => this.calibrate());
return this.calibrate();
}
/**
* re-organize the layout
*
* @returns {void}
* @memberof TileLayoutTag
*/
calibrate(): void {
if (this.dir === "row") {
return this.hcalibrate();
}
if (this.dir === "column") {
return this.vcalibrate();
}
}
/**
* Organize the layout in horizontal direction, only work when
* the layout direction set to horizontal
*
* @private
* @returns {void}
* @memberof TileLayoutTag
*/
private hcalibrate(): void {
const auto_width = [];
let ocwidth = 0;
const avaiheight = $(this).height();
const avaiWidth = $(this).width();
//$(this.refs.yield).css("height", `${avaiheight}px`);
$(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 {
$(this).css("flex-grow", "1");
auto_width.push(this);
}
});
const csize = (avaiWidth - ocwidth) / auto_width.length;
if (csize > 0) {
$.each(auto_width, (i, v) =>
$(v).css("width", `${csize}px`)
);
}
return this.observable.trigger("hboxchange", {
id: this.aid,
data: { w: avaiWidth, h: avaiheight },
});
}
/**
* Organize the layout in vertical direction, only work when
* the layout direction set to vertical
*
* @private
* @returns {void}
* @memberof TileLayoutTag
*/
private vcalibrate(): void {
const auto_height = [];
let ocheight = 0;
const avaiheight = $(this).height();
const avaiwidth = $(this).width();
//$(this.refs.yield).css("height", `${avaiheight}px`);
$(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 {
$(this).css("flex-grow", "1");
auto_height.push(this);
}
});
const csize = (avaiheight - ocheight) / auto_height.length;
if (csize > 0) {
$.each(auto_height, (i, v) =>
$(v).css("height", `${csize}px`)
);
}
return this.observable.trigger("vboxchange", {
id: this.aid,
data: { w: avaiwidth, h: avaiheight },
});
}
/**
* Layout definition
*
* @returns
* @memberof TileLayoutTag
*/
layout() {
return [
{
el: "div",
ref: "yield",
},
];
}
}
/**
* A HBox organize its child elements in horizontal direction
*
* @export
* @class HBoxTag
* @extends {TileLayoutTag}
*/
export class HBoxTag extends TileLayoutTag {
/**
* Creates an instance of HBoxTag.
* @memberof HBoxTag
*/
constructor() {
super();
}
/**
* Mount the tag
*
* @protected
* @memberof HBoxTag
*/
protected mount(): void {
super.mount();
this.dir = "row";
this.name = "hbox";
}
}
/**
* A VBox organize its child elements in vertical direction
*
* @export
* @class VBoxTag
* @extends {TileLayoutTag}
*/
export class VBoxTag extends TileLayoutTag {
/**
*Creates an instance of VBoxTag.
* @memberof VBoxTag
*/
constructor() {
super();
}
/**
* Mount the tag
*
* @protected
* @memberof VBoxTag
*/
protected mount(): void {
super.mount();
this.dir = "column";
this.name = "vbox";
}
}
define("afx-tile", TileLayoutTag);
define("afx-hbox", HBoxTag);
define("afx-vbox", VBoxTag);
}
}
}

View File

@ -0,0 +1,998 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Tree view data type definition
*
* @export
* @interface TreeViewDataType
*/
export interface TreeViewDataType {
/**
* The child nodes data of the current tree node
*
* @type {TreeViewDataType[]}
* @memberof TreeViewDataType
*/
nodes?: TreeViewDataType[];
/**
* Boolean indicates whether the current node is opened.
* Only work when the current node is not a leaf node
*
* @type {boolean}
* @memberof TreeViewDataType
*/
open?: boolean;
/**
* The node's path from the root node
*
* @type {string}
* @memberof TreeViewDataType
*/
path?: string;
/**
* Indicates whether this node should be selected
*
* @type {boolean}
* @memberof TreeViewDataType
*/
selected?: boolean;
[propName: string]: any;
}
/**
* Tree node event data type definition
*/
export type TreeItemEventData = TagEventDataType<
TreeViewItemPrototype
>;
/**
* Abstract prototype of a tree node. All tree node definition should
* extend this class
*
* @class TreeViewItemPrototype
* @extends {AFXTag}
*/
export abstract class TreeViewItemPrototype extends AFXTag {
/**
* Node data placeholder
*
* @private
* @type {TreeViewDataType}
* @memberof TreeViewItemPrototype
*/
private _data: TreeViewDataType;
/**
* Placeholder for the indent level of the current node from root node
*
* @private
* @type {number}
* @memberof TreeViewItemPrototype
*/
private _indent: number;
/**
* private event object used by current node event
*
* @private
* @type {TagEventType<TreeItemEventData>}
* @memberof TreeViewItemPrototype
*/
private _evt: TagEventType<TreeItemEventData>;
/**
* Reference to the root node
*
* @type {TreeViewTag}
* @memberof TreeViewItemPrototype
*/
treeroot: TreeViewTag;
/**
* The tree path from the root node
*
* @type {string}
* @memberof TreeViewItemPrototype
*/
treepath: string;
/**
* Reference to the parent node of the current node
*
* @type {TreeViewTag}
* @memberof TreeViewItemPrototype
*/
parent: TreeViewTag;
/**
* 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]]
*
* @memberof TreeViewItemPrototype
*/
fetch: (
d: TreeViewItemPrototype
) => Promise<TreeViewDataType[]>;
/**
*Creates an instance of TreeViewItemPrototype.
* @memberof TreeViewItemPrototype
*/
constructor() {
super();
}
/**
* Update the tree, this function
* is used to refresh/expand/collapse the
* current node based on the input parameter
*
* @protected
* @param {*} p string indication, the value should be:
* - `expand`: expand the current node
* - `collapse`: collapse the current node
* - other string: this string is considered as a tree path of a node. If this value
* is the value of current node tree path, the node will be refreshed. Otherwise, nothing
* happens
* @returns {void}
* @memberof TreeViewItemPrototype
*/
protected reload(p: any): void {
if (!p || typeof p !== "string") {
return;
}
switch (p) {
case "expand":
this.open = true;
break;
case "collapse":
this.open = false;
break;
default:
if (p !== this.treepath) {
return;
}
this.open = true;
}
}
/**
* Setter:
*
* Set the data of the current node. This will trigger the
* [[ondatachange]] function
*
* Getter:
*
* Get the current node's data
*
* @memberof TreeViewItemPrototype
*/
set data(v: TreeViewDataType) {
this._data = v;
if (!v) {
return;
}
this.open = v.open;
if (v.path) {
this.treepath = v.path;
}
this.selected = v.selected;
v.domel = this;
this.ondatachange();
}
get data(): TreeViewDataType {
return this._data;
}
/**
* Setter:
*
* Select or unselect the current node.
* This will trigger the item select event
* on the tree root if the parameter is `true`
*
* Getter:
*
* Check whether the current node is selected
*
* @memberof TreeViewItemPrototype
*/
set selected(v: boolean) {
if (!this._data) {
return;
}
this.attsw(v, "selected");
$(this.refs.wrapper).removeClass();
this._data.selected = v;
if (v) {
this.treeroot.unselect();
// set selectedItem but not trigger the update
this.treeroot.itemclick(this._evt);
this._evt.data.dblclick = false;
$(this.refs.wrapper).addClass("afx_tree_item_selected");
}
}
get selected(): boolean {
return this.hasattr("selected");
}
/**
* Setter:
*
* Refresh the current node and expands its sub tree.
* This function only works if the current node is not
* a leaf node
*
* Getter:
*
* Check whether the current node is expanded
*
* @memberof TreeViewItemPrototype
*/
set open(v: boolean) {
if (!this.is_folder()) {
return;
}
this.attsw(v, "open");
$(this.refs.toggle).removeClass();
if (v) {
if (this.fetch) {
this.fetch(this)
.then((d: TreeViewDataType[]) => {
if (!d) {
return;
}
return (this.nodes = d);
})
.catch((e: Error) =>
announcer.oserror(e.toString(), e)
);
} else {
this.nodes = this.nodes;
}
$(this.refs.childnodes).show();
} else {
$(this.refs.childnodes).hide();
}
if (v) {
$(this.refs.toggle).addClass(
"afx-tree-view-folder-open"
);
} else {
$(this.refs.toggle).addClass(
"afx-tree-view-folder-close"
);
}
}
get open(): boolean {
return this.hasattr("open");
}
/**
* Setter: Set the current indent level of this node from the root node
*
* Getter: Get the current indent level
*
* @type {number}
* @memberof TreeViewItemPrototype
*/
get indent(): number {
return this._indent;
}
set indent(v: number) {
if (!v) {
return;
}
this._indent = v;
$(this.refs.padding)
.css("display", "inline-block")
.css("height", "1px")
.css("padding", 0)
.css("margin", 0)
.css("background-color", "transparent")
.css("width", v * 15 + "px")
.css("flex-shrink", 0);
}
/**
* Check whether the current node is not a leaf node
*
* @private
* @returns {boolean}
* @memberof TreeViewItemPrototype
*/
private is_folder(): boolean {
if (this.nodes) {
return true;
} else {
return false;
}
}
/**
* Getter: Get the child nodes data of the current node
*
* Setter: Set the child nodes data of the current node
*
* @type {TreeViewDataType[]}
* @memberof TreeViewItemPrototype
*/
get nodes(): TreeViewDataType[] {
if (!this._data) return undefined;
return this._data.nodes;
}
set nodes(nodes: TreeViewDataType[]) {
if (!nodes || !this.data) {
return;
}
this._data.nodes = nodes;
// return unless @get("nodes") and @get("nodes").length > 0
$(this.refs.childnodes).empty();
$(this.refs.wrapper).addClass("afx_folder_item");
const root = this.treeroot;
const result = [];
for (let v of nodes) {
const el = $("<afx-tree-view>").appendTo(
this.refs.childnodes
);
el[0].uify(this.observable);
const element = el[0] as TreeViewTag;
element.treeroot = root;
element.indent = this.indent + 1;
element.open = this.open;
element.parent = this.parent;
element.treepath = `${this.treepath}/${element.aid}`;
element.fetch = this.fetch;
element.data = v;
}
}
/**
* Init the tag with default properties data
*
* @protected
* @memberof TreeViewItemPrototype
*/
protected init(): void {
this.treeroot = undefined;
this.treepath = this.aid.toString();
this._evt = {
id: this.aid,
data: { item: this, dblclick: false },
};
this._indent = 0;
}
/**
* Mount the tag and bind basic events
*
* @protected
* @memberof TreeViewItemPrototype
*/
protected mount(): void {
$(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.wrapper).on("click",(e) => {
this.selected = true;
});
$(this.refs.wrapper).on("dblclick", (e) => {
this._evt.data.dblclick = true;
this.selected = true;
});
$(this.refs.toggle)
.css("display", "inline-block")
.css("width", "15px")
.css("flex-shrink", 0)
.addClass("afx-tree-view-item")
.on("click",(e) => {
this.open = !this.open;
e.preventDefault();
return e.stopPropagation();
});
}
/**
* 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
*
* @protected
* @returns {TagLayoutType[]}
* @memberof TreeViewItemPrototype
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div",
ref: "wrapper",
children: [
{
el: "ul",
ref: "container",
children: [
{ el: "li", ref: "padding" },
{ el: "li", ref: "toggle" },
{
el: "li",
ref: "itemholder",
class: "itemname",
children: this.itemlayout(),
},
],
},
],
},
{
el: "ul",
ref: "childnodes",
},
];
}
/**
* This function need to be implemented by all subclasses
* to define the inner layout of the node
*
* @protected
* @abstract
* @returns {TagLayoutType[]}
* @memberof TreeViewItemPrototype
*/
protected abstract itemlayout(): TagLayoutType[];
/**
* This function is called when the node data change.
* It needs to be implemented on all subclasses of this
* class
*
* @protected
* @abstract
* @memberof TreeViewItemPrototype
*/
protected abstract ondatachange(): void;
}
/**
* SimpleTreeViewItem extends [[TreeViewItemPrototype]] and
* define it inner layout using a [[LabelTag]]
*
* @export
* @class SimpleTreeViewItem
* @extends {TreeViewItemPrototype}
*/
export class SimpleTreeViewItem extends TreeViewItemPrototype {
/**
*Creates an instance of SimpleTreeViewItem.
* @memberof SimpleTreeViewItem
*/
constructor() {
super();
}
/**
* Refresh the label when data changed
*
* @protected
* @returns {void}
* @memberof SimpleTreeViewItem
*/
protected ondatachange(): void {
if (!this.data) {
return;
}
const v = this.data;
const label = this.refs.label as LabelTag;
label.set(v);
}
/**
* Inner layout definition
*
* @protected
* @returns
* @memberof SimpleTreeViewItem
*/
protected itemlayout() {
return [{ el: "afx-label", ref: "label" }];
}
}
/**
* A tree view widget presents a hierarchical list of nodes.
*
* @export
* @class TreeViewTag
* @extends {AFXTag}
*/
export class TreeViewTag extends AFXTag {
/**
* Reference to the selected node
*
* @private
* @type {TreeViewItemPrototype}
* @memberof TreeViewTag
*/
private _selectedItem: TreeViewItemPrototype;
/**
* Placeholder for tree select event handle
*
* @private
* @type {TagEventCallback<TreeItemEventData>}
* @memberof TreeViewTag
*/
private _ontreeselect: TagEventCallback<TreeItemEventData>;
/**
* Place holder for tree double click event handle
*
* @private
* @type {TagEventCallback<TreeItemEventData>}
* @memberof TreeViewTag
*/
private _ontreedbclick: TagEventCallback<TreeItemEventData>;
/**
* Placeholder for drag and drop event handle
*
* @private
* @type {TagEventCallback<DnDEventDataType<TreeViewTag>>}
* @memberof TreeViewTag
*/
private _ondragndrop: TagEventCallback<
DnDEventDataType<TreeViewTag>
>;
/**
* Tree data placeholder
*
* @private
* @type {TreeViewDataType}
* @memberof TreeViewTag
*/
private _data: TreeViewDataType;
/**
* Placeholder for private dragndrop mouse down event handle
*
* @private
* @memberof TreeViewTag
*/
private _treemousedown: (e: JQuery.MouseEventBase) => void;
/**
* Placeholder for private dragndrop mouse up event handle
*
* @private
* @memberof TreeViewTag
*/
private _treemouseup: (e: JQuery.MouseEventBase) => void;
/**
* Placeholder for private dragndrop mouse move event handle
*
* @private
* @memberof TreeViewTag
*/
private _treemousemove: (e: JQuery.MouseEventBase) => void;
/**
* Private data object passing between dragndrop mouse event
*
* @private
* @type {{ from: TreeViewTag[]; to: TreeViewTag }}
* @memberof TreeViewTag
*/
private _dnd: { from: TreeViewTag[]; to: TreeViewTag };
/**
* Reference to parent tree of the current tree.
* This value is undefined if the current tree is the root
*
* @type {TreeViewTag}
* @memberof TreeViewTag
*/
parent: TreeViewTag;
/**
* Reference to the root tree, this value is undefined
* if the curent tree is root
*
* @type {TreeViewTag}
* @memberof TreeViewTag
*/
treeroot: TreeViewTag;
/**
* tree path of the current tree from the root
*
* @type {string}
* @memberof TreeViewTag
*/
treepath: string;
/**
* Indent level of the current tree
*
* @type {number}
* @memberof TreeViewTag
*/
indent: number;
/**
* Indicates whether the tree should be expanded
*
* @type {boolean}
* @memberof TreeViewTag
*/
open: boolean;
/**
* 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]]
*
* @memberof TreeViewTag
*/
fetch: (
d: TreeViewItemPrototype
) => Promise<TreeViewDataType[]>;
/**
*Creates an instance of TreeViewTag.
* @memberof TreeViewTag
*/
constructor() {
super();
}
/**
* Init the tree view before mounting:
*
* @protected
* @memberof TreeViewTag
*/
protected init(): void {
this.itemtag = "afx-tree-view-item";
this._ontreeselect = this._ondragndrop = this._ontreedbclick = (
e
) => {};
this.indent = 0;
this.open = true;
this.treepath = this.aid.toString();
}
/**
* Layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof TreeViewTag
*/
protected layout(): TagLayoutType[] {
return [];
}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof TreeViewTag
*/
protected reload(d?: any): void {}
/**
* Setter: Enable/disable drag and drop event on the tree
*
* Getter: Check whether the drag and drop event is enabled
*
* @memberof TreeViewTag
*/
set dragndrop(v: boolean) {
this.attsw(v, "dragndrop");
}
get dragndrop(): boolean {
return this.hasattr("dragndrop");
}
/**
* Set the tree select event handle
*
* @memberof TreeViewTag
*/
set ontreeselect(v: TagEventCallback<TreeItemEventData>) {
this._ontreeselect = v;
}
/**
* Set the tree double click event handle
*
* @memberof TreeViewTag
*/
set ontreedbclick(v: TagEventCallback<TreeItemEventData>) {
this._ontreedbclick = v;
}
/**
* Setter:
*
* Set the default tag name of the tree node.
* If there is no tag name in the node data,
* this value will be used when creating node.
*
* Defaut to `afx-tree-view-item`
*
* Getter:
*
* Get the default node tag name
*
* @memberof TreeViewTag
*/
set itemtag(v: string) {
$(this).attr("itemtag", v);
}
get itemtag(): string {
return $(this).attr("itemtag");
}
/**
* Unselect the selected element in the tree
*
* @memberof TreeViewTag
*/
unselect(): void {
if (this.selectedItem) {
this._selectedItem.selected = false;
}
}
/**
* Setter: Set the selected node using its DOM element
*
* Getter: Get the DOM element of the selected node
*
* @type {TreeViewItemPrototype}
* @memberof TreeViewTag
*/
get selectedItem(): TreeViewItemPrototype {
return this._selectedItem;
}
set selectedItem(v: TreeViewItemPrototype) {
if (!v) {
return;
}
if (v === this.selectedItem) {
return;
}
v.selected = true;
}
/**
* Expand all nodes in the tree
*
* @returns {void}
* @memberof TreeViewTag
*/
expandAll(): void {
if (this.is_leaf()) {
return;
}
return this.update("expand");
}
/**
* Collapse all nodes in the tree
*
* @returns {void}
* @memberof TreeViewTag
*/
collapseAll(): void {
if (this.is_leaf()) {
return;
}
return this.update("collapse");
}
/**
* This function will trigger the tree select or tree double click
* event
*
* @param {TagEventType} e
* @returns {void}
* @memberof TreeViewTag
*/
itemclick(e: TagEventType<TreeItemEventData>): void {
if (!e || !e.data) {
return;
}
if (e.data.item === this.selectedItem && !e.data.dblclick) {
return;
}
this._selectedItem = e.data.item;
const evt = { id: this.aid, data: e.data };
if (e.data.dblclick) {
this._ontreedbclick(evt);
return this.observable.trigger("treedbclick", evt);
} else {
this._ontreeselect(evt);
return this.observable.trigger("treeselect", evt);
}
}
/**
* Check whether the current tree is a root tree
*
* @returns {boolean}
* @memberof TreeViewTag
*/
is_root(): boolean {
return this.treeroot === undefined;
}
/**
* Check whether the current tree tag is a leaf
*
* @returns {boolean}
* @memberof TreeViewTag
*/
is_leaf(): boolean {
const data = this.data;
if (!data) {
return true;
}
if (data.nodes) {
return false;
} else {
return true;
}
}
/**
* Set drag and drop event handle
*
* @memberof TreeViewTag
*/
set ondragndrop(
v: TagEventCallback<DnDEventDataType<TreeViewTag>>
) {
this._ondragndrop = v;
}
/**
* Setter:
*
* Set the tree data. This operation will create
* all tree node elements of the current tree
*
* Getter:
*
* Get the tree data
*
* @memberof TreeViewTag
*/
set data(v: TreeViewDataType) {
this._selectedItem = undefined;
if (!v) {
return;
}
this._data = v;
$(this).empty();
if (v.path) {
this.treepath = v.path;
}
let tag = this.itemtag;
if (v.tag) {
tag = v.tag;
}
const el = $(`<${tag}>`).appendTo(this);
el[0].uify(this.observable);
const element = el[0] as TreeViewItemPrototype;
element.treeroot = this.is_root() ? this : this.treeroot;
element.indent = this.indent;
element.treepath = this.treepath;
element.open = this.open;
element.fetch = this.fetch;
element.parent = this;
element.data = v;
if (this.is_root()) {
$(this).off("mousedown", this._treemousedown);
if (this.dragndrop) {
$(this).on("mousedown", this._treemousedown);
}
}
}
get data(): TreeViewDataType {
return this._data;
}
/**
* Mount the tree view
*
* @protected
* @memberof TreeViewTag
*/
protected mount(): void {
this._dnd = {
from: [],
to: undefined,
};
this._treemousedown = (e) => {
let obj: any = $(e.target).closest("afx-tree-view");
if (obj.length === 0) {
return;
}
let el = obj[0] as TreeViewTag;
if (el === this) {
return;
}
this._dnd.from = [el];
this._dnd.to = undefined;
$(window).on("mouseup", this._treemouseup);
return $(window).on("mousemove", this._treemousemove);
};
this._treemouseup = (e) => {
$(window).off("mouseup", this._treemouseup);
$(window).off("mousemove", this._treemousemove);
$("#systooltip").hide();
let obj = $(e.target).closest("afx-tree-view");
if (obj.length === 0) {
return;
}
let el = obj[0] as TreeViewTag;
if (el.is_leaf()) {
el = el.parent;
}
if (
el === this._dnd.from[0] ||
el === this._dnd.from[0].parent
) {
return;
}
this._dnd.to = el;
this._ondragndrop({
id: this.aid,
data: this._dnd,
});
this._dnd = {
from: [],
to: undefined,
};
};
this._treemousemove = (e) => {
if (!e) {
return;
}
if (!this._dnd.from) {
return;
}
const data = this._dnd.from[0].data;
const $label = $("#systooltip");
const top = e.clientY + 5;
const left = e.clientX + 5;
$label.show();
const label = $label[0] as LabelTag;
label.set(data);
$label.css("top", top + "px").css("left", left + "px");
};
}
}
define("afx-tree-view", TreeViewTag);
define("afx-tree-view-item", SimpleTreeViewItem);
}
}
}

602
src/core/tags/WindowTag.ts Normal file
View File

@ -0,0 +1,602 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A WindowTag represents a virtual window element
* used by AntOS applications and dialogs.
*
* @export
* @class WindowTag
* @extends {AFXTag}
*/
export class WindowTag extends AFXTag {
/**
* The element ID of the virtual desktop element
*
* @type {string}
* @memberof WindowTag
*/
desktop: string;
/**
* Window width placeholder
*
* @private
* @type {number}
* @memberof WindowTag
*/
private _width: number;
/**
* Window height placeholder
*
* @private
* @type {number}
* @memberof WindowTag
*/
private _height: number;
/**
* Placeholder indicates whether the current window is shown
*
* @private
* @type {boolean}
* @memberof WindowTag
*/
private _shown: boolean;
/**
* Placeholder indicates whether the current window is maximized
*
* @private
* @type {boolean}
* @memberof WindowTag
*/
private _isMaxi: boolean;
/**
* This placeholder stores the latest offset of the current window.
*
* @private
* @type {GenericObject<any>}
* @memberof WindowTag
*/
private _history: GenericObject<any>;
/**
* This placeholder stores the offset of the virtual desktop element
*
* @private
* @type {GenericObject<any>}
* @memberof WindowTag
*/
private _desktop_pos: GenericObject<any>;
/**
* Creates an instance of WindowTag.
* @memberof WindowTag
*/
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");
}
/**
* Init window tag
* - `shown`: false
* - `isMaxi`: false
* - `minimizable`: false
* - `resizable`: true
* - `apptitle`: Untitled
*
* @protected
* @memberof WindowTag
*/
protected init(): void {
this._shown = false;
this._isMaxi = false;
this._history = {};
this.desktop = GUI.workspace;
this._desktop_pos = $(this.desktop).offset();
this.minimizable = true;
this.resizable = true;
this.apptitle = "Untitled";
}
/**
* Do nothing
*
* @protected
* @memberof WindowTag
*/
protected calibrate(): void { }
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof WindowTag
*/
protected reload(d?: any): void { }
/**
* Setter: Set the window width
*
* Getter: Get the window width
*
* @memberof WindowTag
*/
set width(v: number) {
this._width = v;
if (!v) {
return;
}
this.setsize({ w: v, h: this.height });
}
get width(): number {
return this._width;
}
/**
* Setter: Set the window height
*
* Getter: Get the window height
*
* @memberof WindowTag
*/
set height(v: number) {
this._height = v;
if (!v) {
return;
}
this.setsize({
w: this.width,
h: v,
});
}
get height(): number {
return this._height;
}
/**
* Setter: enable/disable window minimizable
*
* getter: Check whether the window is minimizable
*
* @memberof WindowTag
*/
set minimizable(v: boolean) {
this.attsw(v, "minimizable");
if (v) {
$(this.refs["minbt"]).show();
} else {
$(this.refs["minbt"]).hide();
}
}
get minimizable(): boolean {
return this.hasattr("minimizable");
}
/**
* Setter: enable/disable widow resizable
*
* Getter: Check whether the current window is resizable
*
* @memberof WindowTag
*/
set resizable(v: boolean) {
this.attsw(v, "resizable");
if (v) {
$(this.refs["maxbt"]).show();
$(this.refs["grip"]).show();
$(this.refs["grip_bottom"]).show();
$(this.refs["grip_right"]).show();
} else {
$(this.refs["maxbt"]).hide();
$(this.refs["grip"]).hide();
$(this.refs["grip_bottom"]).hide();
$(this.refs["grip_right"]).hide();
}
}
get resizable(): boolean {
return this.hasattr("resizable");
}
/**
* Setter: Set the window title
*
* Getter: Get window title
*
* @memberof WindowTag
*/
set apptitle(v: string | FormattedString) {
$(this).attr("apptitle", v.__());
if (v) {
(this.refs["txtTitle"] as LabelTag).text = v;
this.observable.trigger("apptitlechange", this);
}
}
get apptitle(): string | FormattedString {
return $(this).attr("apptitle");
}
/**
* Resize all the children of the window based on its width and height
*
* @private
* @memberof WindowTag
*/
private resize(): void {
const ch =
$(this.refs["yield"]).height() /
$(this.refs["yield"]).children().length;
$(this.refs["yield"])
.children()
.each(function (e) {
$(this).css("height", `${ch}px`);
});
}
/**
* Mount the window tag and bind basic events
*
* @protected
* @returns {void}
* @memberof WindowTag
*/
protected mount(): void {
this.contextmenuHandle = function (e) { };
$(this.refs["minbt"]).on("click", (e) => {
return this.observable.trigger("hide", {
id: this.aid,
});
});
$(this.refs["maxbt"]).on("click", (e) => {
return this.toggle_window();
});
$(this.refs["closebt"]).on("click", (e) => {
return this.observable.trigger("exit", {
id: this.aid,
});
});
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", 10);
$(this).on("mousedown", (e) => {
if (this._shown) {
return;
}
return this.observable.trigger("focus", {
id: this.aid,
});
});
//$(this.refs.win_overlay).css("background-color", "red");
$(this.refs["dragger"]).on("dblclick", (e) => {
return this.toggle_window();
});
this.observable.on("resize", (e) => this.resize());
this.observable.on("focus", () => {
$(this)
.show()
.removeClass("unactive");
this._shown = true;
$(this.refs.win_overlay).hide();
$(this).trigger("focus");
});
this.observable.on("blur", () => {
this._shown = false;
$(this).addClass("unactive");
if(this.blur_overlay)
$(this.refs.win_overlay).show();
});
this.observable.on("hide", () => {
$(this).hide();
return (this._shown = false);
});
this.observable.on("toggle", () => {
if (this._shown) {
return this.observable.trigger("hide", {
id: this.aid,
});
} else {
return this.observable.trigger("focus", {
id: this.aid,
});
}
});
this.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,
});
$(this).attr("tabindex", 0).css("outline", "none");
return this.observable.trigger("rendered", {
id: this.aid,
});
}
/**
* Set the window size
*
* @private
* @param {GenericObject<any>} o format: `{ w: window_width, h: window_height }`
* @returns {void}
* @memberof WindowTag
*/
private setsize(o: GenericObject<any>): void {
if (!o) {
return;
}
this._width = o.w;
this._height = o.h;
$(this).css("width", `${o.w}px`).css("height", `${o.h}px`);
$(this.refs.winwrapper).css("height", `${o.h}px`);
this.observable.trigger("resize", {
id: this.aid,
data: o,
});
}
/**
* Enable to drag window on the virtual desktop
*
* @private
* @memberof WindowTag
*/
private enable_dragging(): void {
$(this.refs["dragger"])
.css("user-select", "none")
.css("cursor", "default");
$(this.refs["dragger"]).on("mousedown", (e) => {
e.preventDefault();
const offset = $(this).offset();
offset.top = e.clientY - offset.top;
offset.left = e.clientX - offset.left;
$(window).on("mousemove", (e) => {
$(this.refs.win_overlay).show();
let left: number, top: number;
if (this._isMaxi) {
this.toggle_window();
top = 0;
const letf = e.clientX - $(this).width() / 2;
offset.top = 10;
offset.left = $(this).width() / 2;
} else {
top =
e.clientY -
offset.top -
this._desktop_pos.top;
left =
e.clientX -
this._desktop_pos.top -
offset.left;
left = left < 0 ? 0 : left;
top = top < 0 ? 0 : top;
}
return $(this)
.css("top", `${top}px`)
.css("left", `${left}px`);
});
return $(window).on("mouseup", (e) => {
$(this.refs.win_overlay).hide();
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
});
});
}
/**
* Enable window resize, this only works if the window
* is resizable
*
* @private
* @memberof WindowTag
*/
private enable_resize(): void {
const offset = { top: 0, left: 0 };
let target = undefined;
const mouse_move_hdl = (e) => {
let w = $(this).width();
let h = $(this).height();
$(this.refs.win_overlay).show();
if (target != this.refs.grip_bottom) {
w += e.clientX - offset.left;
}
if (target != this.refs.grip_right) {
h += e.clientY - offset.top;
}
w = w < 100 ? 100 : w;
h = h < 100 ? 100 : h;
offset.top = e.clientY;
offset.left = e.clientX;
this._isMaxi = false;
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);
}
$(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);
});
}
/**
* Maximize the window or restore its previous width, height,
* and position
*
* @private
* @returns {void}
* @memberof WindowTag
*/
private toggle_window(): void {
let h: number, w: number;
if (!this.resizable) {
return;
}
if (this._isMaxi === false) {
this._history = {
top: $(this).css("top"),
left: $(this).css("left"),
width: $(this).css("width"),
height: $(this).css("height"),
};
w = $(this.desktop).width();
h = $(this.desktop).height();
$(this).css("top", "0").css("left", "0");
this.setsize({ w, h });
this._isMaxi = true;
} else {
this._isMaxi = false;
$(this)
.css("top", this._history.top)
.css("left", this._history.left);
this.setsize({
w: parseInt(this._history.width),
h: parseInt(this._history.height),
});
}
}
/**
* Layout definition of the window tag
*
* @protected
* @returns {TagLayoutType[]}
* @memberof WindowTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div",
class: "afx-window-wrapper",
ref: "winwrapper",
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",
},
{
el: "li",
class: "afx-window-title",
ref: "dragger",
children: [
{
el: "afx-label",
ref: "txtTitle",
},
],
},
],
},
{ el: "div", class: "afx-clear" },
{
el: "div",
ref: "yield",
class: "afx-window-content",
},
{
el: "div",
ref: "grip",
class: "afx-window-grip",
},
{
el: "div",
ref: "grip_bottom",
class: "afx-window-grip-bottom",
},
{
el: "div",
ref: "grip_right",
class: "afx-window-grip-right",
},
{
el: "div",
ref: "win_overlay",
class: "afx-window-overlay",
},
],
},
];
}
}
define("afx-app-window", WindowTag);
}
}
}

View File

@ -1,246 +0,0 @@
<afx-app-window ref = "window" >
<div class = "afx-window-wrapper">
<ul class= "afx-window-top" >
<li class = "afx-window-close" onclick = {close}></li>
<li if = {minimizable == true} class = "afx-window-minimize" onclick = {minimize}></li>
<li if = {resizable == true} class = "afx-window-maximize" onclick={maximize}></li>
<li ref = "dragger" class = "afx-window-title">{ apptitle?apptitle.__():apptitle }</li>
</ul>
<div class = "afx-clear"></div>
<div ref = "content" class = "afx-window-content">
<yield/>
</div>
<div if = {resizable == true} ref = "grip" class = "afx-window-grip">
</div>
<script>
this.apptitle = opts.apptitle || ""
if(opts.minimizable == undefined)
this.minimizable = true
else
this.minimizable = eval(opts.minimizable)
if(opts.resizable == undefined)
this.resizable = true
else
this.resizable = eval(opts.resizable)
var self = this
var offset = {top:0,left:0}
var desktop_pos = $("#desktop").offset()
var isMaxi = false
var history = {}
var width = opts.width || 400
var height = opts.height || 300
this.root.observable = opts.observable || riot.observable()
if(!window._zindex) window._zindex = 10
this.shown = false
self.root.contextmenuHandler = function (e) {}
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
if(k == "apptitle")
self.root.observable.trigger("apptitlechange")
self.update()
}
self.root.get = function(k)
{
return self[k]
}
self.root.update = function()
{
self.update()
}
minimize()
{
this.root.observable.trigger("hide")
}
close()
{
this.root.observable.trigger("exit")
}
this.on('mount', function() {
var left,top
//left = 20 + Math.floor(Math.random() * ($("#desktop").width() - width))
//top = 20 + Math.floor(Math.random() * ($("#desktop").height() - height))
left = ($("#desktop").width() - width)/2
top = ($("#desktop").height() - height)/2
$(self.refs.window)
.css("position",'absolute')
.css("left",left + "px")
.css("top",top + "px")
.css("width",width + "px")
.css("height", height + "px")
.css("z-index",window._zindex++)
$(self.refs.window).on("mousedown", function(e){
if(self.shown == false)
self.root.observable.trigger("focus")
})
$(self.refs.window).click(function(e) {
//e.stopPropagation()
//e.windowactive = true
//self.root.observable.trigger("windowselect")
})
enable_dragging()
if(self.resizable)
enable_resize()
$(self.refs.dragger).dblclick(function(e){
toggle_window()
})
$(self.refs.content).children().each(function(e){
this.observable = self.root.observable
})
var fn = function()
{
var ch = $(self.refs.content).height()/ $(self.refs.content).children().length
$(self.refs.content).children().each(function(e){
$(this).css("height",ch+"px")
})
}
fn()
self.root.observable.on("resize", function(){ fn()})
self.root.observable.on("focus",function(){
window._zindex++
$(self.refs.window)
.show()
.css("z-index",window._zindex)
.removeClass("unactive")
self.shown = true
})
self.root.observable.on("blur", function(){
self.shown = false
$(self.refs.window)
.addClass("unactive")
// add css to blur app :)
})
self.root.observable.on("hide", function()
{
$(self.refs.window).hide()
self.shown = false
})
self.root.observable.on("toggle", function(){
if(self.shown)
self.root.observable.trigger("hide")
else
self.root.observable.trigger("focus")
})
self.root.observable.trigger("rendered", self.root)
})
var enable_dragging = function()
{
$(self.refs.dragger)
.css("user-select","none")
.css("cursor","default")
$(self.refs.dragger).on("mousedown", function(e){
e.preventDefault()
offset = $(self.refs.window).offset()
offset.top = e.clientY - offset.top
offset.left = e.clientX - offset.left
$(window).on("mousemove", function(e){
var top,left
if(isMaxi)
{
toggle_window()
top = 0
letf = e.clientX - $(self.refs.window).width()/2
offset.top = 10 //center
offset.left = $(self.refs.window).width()/2
} else
{
top = e.clientY - offset.top - desktop_pos.top
left = e.clientX - desktop_pos.top - offset.left
left = left < 0?0:left;
top = top < 0?0:top;
}
$(self.refs.window).css("top", top +"px")
.css("left",left + "px")
})
$(window).on("mouseup", function(e){
//console.log("unbind mouse up")
$(window).unbind("mousemove", null)
})
})
}
var enable_resize = function()
{
if(!self.resizable) return
$(self.refs.grip)
.css("user-select","none")
.css("cursor","default")
.css("position","absolute")
.css("bottom","0")
.css("right","0")
.css("cursor","nwse-resize")
$(self.refs.grip).on("mousedown", function(e){
e.preventDefault()
offset.top = e.clientY
offset.left = e.clientX
$(window).on("mousemove", function(e){
var w,h
w = $(self.refs.window).width() + e.clientX - offset.left
h = $(self.refs.window).height() + e.clientY - offset.top
w = w < 100 ? 100:w
h = h < 100 ?100:h
offset.top = e.clientY
offset.left = e.clientX
$(self.refs.window)
.css("width", w +"px")
.css("height",h + "px")
isMaxi = false
self.root.observable.trigger('resize',
{id:$(self.root).attr("data-id"),w:w,h:h})
})
$(window).on("mouseup", function(e){
$(window).unbind("mousemove", null)
})
})
}
var toggle_window = function()
{
if(!self.resizable) return
if(isMaxi == false)
{
history = {
top: $(self.refs.window).css("top"),
left:$(self.refs.window).css("left"),
width:$(self.refs.window).css("width"),
height:$(self.refs.window).css("height")
}
var w,h
w = ($("#desktop").width() - 5)
h = ($("#desktop").height() - 10)
$(self.refs.window)
.css("width", w + "px")
.css("height", h + "px")
.css("top","0").css("left","0")
self.root.observable.trigger('resize',
{id:$(self.root).attr("data-id"),w:w,h:h})
isMaxi = true
}
else
{
isMaxi = false
$(self.refs.window)
.css("width",history.width)
.css("height",history.height)
.css("top",history.top).css("left",history.left)
self.root.observable.trigger('resize',
{id:$(self.root).attr("data-id"),w:history.width,h:history.height} )
}
}
maximize()
{
toggle_window()
}
</script>
</afx-app-window>

View File

@ -1,82 +0,0 @@
<afx-apps-dock>
<afx-button class = {selected: parent.selectedApp && it.app.pid == parent.selectedApp.pid} each={ it,i in items} iconclass = {it.iconclass} icon = {it.icon} appindex = {i} text = {it.text} onbtclick = {it.onbtclick} tooltip= {"cr:" + it.app.title()} >
</afx-button>
<script>
this.items = opts.items || []
var self = this
self.selectedApp = null
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
{
self[k] = v
if(k == "selectedApp")
{
for(var i in self.items)
self.items[i].app.blur()
if(v)
$("#desktop")[0].set("selected", -1)
}
}
self.update()
}
self.root.newapp = function(i)
{
self.items.push(i)
self.selectedApp = i.app
self.update()
for(var i in self.items)
self.items[i].app.blur()
}
self.root.removeapp = function(a)
{
var i = -1;
for(var k in self.items)
if(self.items[k].app.pid == a.pid)
{
i = k; break;
}
if(i != -1)
{
delete self.items[i].app
self.items.splice(i,1)
self.update()
}
}
self.root.update = function()
{
self.update()
}
self.root.get = function(k)
{
return self[k]
}
this.on("mount", function(){
window.OS.courrier.trigger("sysdockloaded")
})
self.root.contextmenuHandler = function(e, m)
{
if(e.target == self.root) return;
var appidx = $(e.target).closest( "afx-button" ).attr("appindex")
var app = self.items[appidx].app
m.set("items", [
{ text: "__(Show)", dataid:"show" },
{ text: "__(Hide)", dataid:"hide" },
{ text: "__(Close)", dataid:"quit" }
])
m.set("onmenuselect", function(evt)
{
if(app[evt.item.data.dataid])
app[evt.item.data.dataid]()
})
m.show(e)
}
</script>
</afx-apps-dock>

View File

@ -1,47 +0,0 @@
<afx-button >
<button class= { btactive: selected } disabled={ enable == false } onclick="{ _onbtclick }" ref = "mybtn" >
<afx-label color = {color} icon={icon} iconclass = {iconclass} text = {text} ></afx-label>
</button>
<script>
opts.enable = opts.enable || "true"
this.enable = eval(opts.enable) || false
this.icon = opts.icon
this.iconclass = opts.iconclass
this.color = opts.color
this.text = opts.text || ""
this.selected = eval(opts.selected) || false
this.toggle = eval(opts.toggle) || false
var self = this
this.onbtclick = opts.onbtclick
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.trigger = function()
{
$(self.refs.mybtn).trigger("click")
}
self.root.get = function(k)
{
return self[k]
}
this._onbtclick = function(e)
{
if(typeof self.onbtclick == 'string')
eval(self.onbtclick)
else if(self.onbtclick)
self.onbtclick(e)
if(self.root.observable)
{
self.root.observable.trigger("btclick",{id:$(self.root).attr("data-id"),data:self.root})
}
if(self.toggle)
self.root.set("selected",!self.selected)
}
</script>
</afx-button>

View File

@ -1,107 +0,0 @@
<afx-calendar-view>
<div><i class ="prevmonth" onclick={prevmonth}></i>
<afx-label text = {mtext}></afx-label>
<afx-label text = {year}></afx-label>
<i onclick={nextmonth} class="nextmonth"></i></div>
<afx-grid-view data-id ={"grid_" + rid} style = "height:100%;" ref = "grid" header = {header}> </afx-grid-view>
<script >
this.header = [{value:"__(Sun)"},{value:"__(Mon)"},{value:"__(Tue)"},{value:"__(Wed)"},{value:"__(Thu)"},{value:"__(Fri)"},{value:"__(Sat)"}]
this.root.observable = opts.observable
var self = this
this.day = 0
this.month = 0
this.year = 0
this.ondayselect = opts.ondayselect
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
this.selectedDate = undefined
self.root.get = function(k)
{
return self[k]
}
this.on("mount", function (e) {
self.refs.grid.root.observable = self.root.observable
calendar(null)
self.root.observable.on("gridcellselect", function(d){
if(d.id != "grid_" + self.rid) return
if(d.data.value == "") return
var data = {id:self.rid, data:new Date(self.year, self.month,d.data.value)};
if(self.ondayselect)
self.ondayselect(data)
self.selectedDate = data.data
self.root.observable.trigger("dayselect",data)
})
})
prevmonth()
{
self.selectedDate = undefined
this.month--
if(this.month < 0)
{
this.month = 11
this.year--
}
calendar(new Date(this.year, this.month,1))
}
nextmonth()
{
self.selectedDate = undefined
this.month++
if(this.month > 11)
{
this.month = 0
this.year++
}
calendar(new Date(this.year, this.month,1))
}
var calendar = function (date) {
if (date === null)
date = new Date()
self.day = date.getDate()
self.month = date.getMonth()
self.year = date.getFullYear()
var now ={ d:(new Date()).getDate(), m:(new Date()).getMonth(), y:(new Date()).getFullYear()}
months = ["__(January)", "__(February)", "__(March)", "__(April)", "__(May)", "__(June)", "__(July)", "__(August)", "__(September)", "__(October)", "__(November)", "__(December)"]
this_month = new Date(self.year, self.month, 1)
next_month = new Date(self.year, self.month + 1, 1)
// Find out when this month starts and ends.
first_week_day = this_month.getDay()
days_in_this_month = Math.round((next_month.getTime() - this_month.getTime()) / (1000 * 60 * 60 * 24))
self.mtext = months[self.month]
var rows = []
var row = []
// Fill the first week of the month with the appropriate number of blanks.
for (week_day = 0; week_day < first_week_day; week_day++)
row.push({value:""})
week_day = first_week_day;
for (day_counter = 1; day_counter <= days_in_this_month; day_counter++) {
week_day %= 7
if (week_day == 0)
{
rows.push(row)
row =[]
}
// Do something different for the current day.
if (now.d == day_counter && self.month == now.m && self.year == now.y)
row.push({value:day_counter, selected:true})
else
row.push({value:day_counter})
week_day++;
}
for(var i = 0; i <= 7 - row.length;i++)
row.push({value:""})
rows.push(row)
self.refs.grid.root.set("rows",rows)
}
</script>
</afx-calendar-view>

View File

@ -1,110 +0,0 @@
<afx-color-picker>
<div style = "width:310px; height:190px;display:block; padding:3px;">
<canvas class = "color-palette" width="284" height="155" style ="float:left;" ref = "palette" ></canvas>
<div class = "color-sample" style= "width:15px; height:155px; text-align:center; margin-left:3px; display:block;float:left;" ref = "colorval"></div>
<div class = "afx-clear"></div>
<div style ="margin-top:3px;">
<span>Hex:</span><input type = "text" ref = "hextext" style = "width:70px; margin-left:3px;margin-right:5px;"></input>
<span ref = 'rgbtext'></span>
</div>
</div>
<script>
var self = this
var colorctx = undefined
self.root.observable = opts.observable
self.oncolorsetect = opts.oncolorsetect
self.selectedColor = undefined
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.get = function(k)
{
return self[k]
}
var build_palette = function()
{
colorctx = $(self.refs.palette).get(0).getContext('2d')
var gradient = colorctx.createLinearGradient(0,0,$(self.refs.palette).width(),0)
// fill color
gradient.addColorStop(0, "rgb(255, 0, 0)")
gradient.addColorStop(0.15, "rgb(255, 0, 255)")
gradient.addColorStop(0.33, "rgb(0, 0, 255)")
gradient.addColorStop(0.49, "rgb(0, 255, 255)")
gradient.addColorStop(0.67, "rgb(0, 255, 0)")
gradient.addColorStop(0.84, "rgb(255, 255, 0)")
gradient.addColorStop(1, "rgb(255, 0, 0)")
gradient.addColorStop(0, "rgb(0, 0, 0)")
// Apply gradient to canvas
colorctx.fillStyle = gradient;
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height)
// Create semi transparent gradient (white -> trans. -> black)
gradient = colorctx.createLinearGradient(0, 0, 0, $(self.refs.palette).width())
gradient.addColorStop(0, "rgba(255, 255, 255, 1)")
gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)")
gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)")
gradient.addColorStop(1, "rgba(0, 0, 0, 1)")
// Apply gradient to canvas
colorctx.fillStyle = gradient
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height)
//$(self.refs.palette).css("position", "absolute")
// now add mouse move event
var getHex = function(c)
{
s = c.toString(16)
if(s.length == 1) s = "0" + s
return s
}
var pick_color = function(e)
{
$(self.refs.palette).css("cursor","crosshair")
var offset = $(self.refs.palette).offset()
var x = e.pageX - offset.left
var y = e.pageY - offset.top
var color = colorctx.getImageData(x,y, 1, 1)
var data = {
r:color.data[0],
g:color.data[1],
b:color.data[2],
text:'rgb(' + color.data[0] + ', ' + color.data[1] + ', ' + color.data[2] + ')',
hex:'#' + getHex(color.data[0]) + getHex(color.data[1]) + getHex(color.data[2])
}
return data
}
var mouse_move_h = function(e)
{
var data = pick_color(e)
$(self.refs.colorval).css("background-color", data.text)
}
$(self.refs.palette).mouseenter(function(e){
$(self.refs.palette).on("mousemove",mouse_move_h)
})
$(self.refs.palette).mouseout(function(e){
$(self.refs.palette).unbind("mousemove",mouse_move_h)
if(self.selectedColor)
$(self.refs.colorval).css("background-color", self.selectedColor.text)
})
$(self.refs.palette).on("click", function(e){
data = pick_color(e)
$(self.refs.rgbtext).html(data.text)
$(self.refs.hextext).val(data.hex)
self.selectedColor = data
if(self.oncolorsetect)
self.oncolorsetect(data)
if(! self.root.observable) return
self.root.observable.trigger("colorselect",data)
})
}
this.on("mount", function(){
build_palette()
})
</script>
</afx-color-picker>

View File

@ -1,10 +0,0 @@
<afx-dummy>
<yield/>
<script>
var self = this
self.root.update = function()
{
self.update()
}
</script>
</afx-dummy>

View File

@ -1,226 +0,0 @@
<afx-file-view>
<afx-list-view ref="listview" observable = {root.observable}></afx-list-view>
<afx-grid-view ref = "gridview" header = {header} observable = {root.observable}></afx-grid-view>
<div class = "treecontainer" ref="treecontainer">
<afx-tree-view ref = "treeview" observable = {root.observable}></afx-tree-view>
</div>
<afx-label if = {status == true} class = "status" ref = "stbar"></afx-label>
<script>
var self = this
self.root.observable = opts.observable || riot.observable()
self.view = opts.view || 'list'
self.data = opts.data || []
self.path = opts.path || "home:///"
self.onfileselect
self.onfileopen
this.status = opts.status == undefined?true:opts.status
this.selectedFile = undefined
this.showhidden = opts.showhidden
this.preventUpdate = false
this.fetch = opts.fetch
this.chdir = opts.chdir
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
this.header = [{value:"__(File name)"},{value: "__(Type)", width:150}, {value: "__(Size)", width:70}]
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
if(k == 'view')
switchView()
if(k == "data")
self.selectedFile = undefined
if(k != "preventUpdate")
self.update()
}
self.root.get = function(k)
{
return self[k]
}
var sortByType = function(a,b)
{
return a.type < b.type ? -1 : ( a.type > b.type ? 1: 0 )
}
var calibre_size = function()
{
var h = $(self.root).outerHeight()
var w = $(self.root).width()
if(self.refs.stbar)
h -= ($(self.refs.stbar.root).height() + 10)
$(self.refs.listview.root).css("height", h + "px")
$(self.refs.gridview.root).css("height", h + "px")
$(self.refs.treecontainer).css("height", h + "px")
$(self.refs.listview.root).css("width", w + "px")
$(self.refs.gridview.root).css("width", w + "px")
$(self.refs.treecontainer).css("width", w + "px")
}
var refreshList = function(){
var items = []
$.each(self.data, function(i, v){
if(v.filename[0] == '.' && !self.showhidden) return
v.text = v.filename
if(v.text.length > 10)
v.text = v.text.substring(0,9) + "..."
v.iconclass = v.iconclass?v.iconclass:v.type
v.icon = v.icon
items.push(v)
})
self.refs.listview.root.set("items", items)
}
var refreshGrid = function(){
var rows = []
$.each(self.data, function(i,v){
if(v.filename[0] == '.' && !self.showhidden) return
var row = [{value:v.filename, iconclass: v.iconclass?v.iconclass:v.type, icon:v.icon},{value:v.mime},{value:v.size},{idx:i}]
rows.push(row)
})
self.refs.gridview.root.set("rows",rows)
}
var refreshTree = function(){
self.refs.treeview.root.set("selectedItem", null)
var tdata = {}
tdata.name = self.path
tdata.nodes = getTreeData(self.data)
self.refs.treeview.root.set("data", tdata)
}
var getTreeData = function(data)
{
nodes = []
$.each(data, function(i,v){
if(v.filename[0] == '.' && !self.showhidden) return
v.name = v.filename
if(v.type == 'dir')
{
v.nodes = []
v.open = false
}
v.iconclass = v.iconclass?v.iconclass:v.type
v.icon = v.icon
nodes.push(v)
})
return nodes
}
var refreshData = function(){
self.data.sort(sortByType)
if(self.view == "icon")
refreshList()
else if(self.view == "list")
refreshGrid()
else
refreshTree()
}
var switchView = function()
{
$(self.refs.listview.root).hide()
$(self.refs.gridview.root).hide()
$(self.refs.treecontainer).hide()
self.selectedFile = undefined
self.refs.listview.root.set("selected", -1)
self.refs.treeview.selectedItem = undefined
self.refs.treeview.root.set("fetch",function(e,f){
if(!self.fetch) return
self.fetch(e, function(d){
f(getTreeData(d))
})
})
if(self.refs.stbar)
self.refs.stbar.root.set("text", "")
switch (self.view) {
case 'icon':
$(self.refs.listview.root).show()
break;
case 'list':
$(self.refs.gridview.root).show()
break;
case 'tree':
$(self.refs.treecontainer).show()
break;
default:
break;
}
calibre_size()
}
self.on("updated", function(){
if(self.preventUpdate)
{
self.preventUpdate = false
}
else
refreshData()
//console.log("update")
//calibre_size()
})
self.on("mount", function(){
switchView()
self.refs.listview.onlistselect = function(data)
{
data.id = self.rid
self.root.observable.trigger("fileselect",data)
}
self.refs.listview.onlistdbclick = function(data)
{
data.id = self.rid
self.root.observable.trigger("filedbclick",data)
}
self.refs.gridview.root.observable = self.root.observable
self.refs.gridview.ongridselect = function(d)
{
var data = {id:self.rid, data:self.data[d.data.child[3].idx], idx:d.data.child[3].idx}
self.root.observable.trigger("fileselect",data)
}
self.refs.gridview.ongriddbclick = function(d)
{
var data = {id:self.rid, data:self.data[d.data.child[3].idx], idx:d.data.child[3].idx}
self.root.observable.trigger("filedbclick",data)
}
self.refs.treeview.ontreeselect = function(d)
{
if(!d) return;
var data;
var el = d;
if(d.treepath == 0)// select the root
{
el = self.path.asFileHandler()
el.size = 0
el.filename = el.path
}
var data = {id:self.rid, data:el}
self.root.observable.trigger("fileselect",data)
}
self.refs.treeview.ontreedbclick = function(d)
{
if(!d || d.treepath == 0) return;
var data = {id:self.rid, data:d}
self.root.observable.trigger("filedbclick",data)
}
self.root.observable.on("fileselect", function(e){
if(e.id != self.rid) return
self.selectedFile = e.data
if(self.onfileselect)
self.onfileselect(e.data)
if(self.refs.stbar)
self.refs.stbar.root.set("text", __("Selected: {0} ({1} bytes)", e.data.filename, e.data.size?e.data.size:"0"))//.html()
})
self.root.observable.on("filedbclick", function(e){
if(e.id != self.rid ) return
if(e.data.type != "dir" && self.onfileopen)
self.onfileopen(e.data)
else if(self.chdir && e.data.type == "dir")
self.chdir(e.data.path)
})
calibre_size()
self.root.observable.on("resize", function(e){
calibre_size()
})
self.root.observable.on("calibrate", function(e){
calibre_size()
})
/*self.root.observable.on("*", function(e){
console.log(e)
})*/
})
</script>
</afx-file-view>

View File

@ -1,183 +0,0 @@
<afx-float-list ref = "container">
<div ref = "list">
<div each={item,i in items } class={float_list_item:true, float_list_item_selected: parent._autoselect(item,i)} ondblclick = {parent._dbclick} onmousedown = {parent._select} oncontextmenu = {parent._select}>
<afx-label color = {item.color} iconclass = {item.iconclass} icon = {item.icon} text = {item.text}></afx-label>
</div>
</div>
<script>
this.items = opts.items || []
var self = this
self.selidx = -1
self.onlistselect = opts.onlistselect
self.onlistdbclick = opts.onlistdbclick
self.fetch = undefined
this.root.observable = opts.observable || riot.observable()
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
self.dir = opts.dir || "horizontal"
self.root.set = function(k,v)
{
if(k == "selected")
{
if(self.selidx != -1)
self.items[self.selidx].selected =false
if(v == -1)
self.selidx = -1
else
if(self.items[v]) self.items[v].selected = true
}
else if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.get = function(k)
{
if(k == "selected")
if(self.selidx == -1)
return undefined
else
return self.items[self.selidx]
return self[k]
}
self.root.push = function(e,u)
{
self.items.push(e)
if(u) self.update()
}
self.root.unshift = function(e,u)
{
self.items.unshift(e)
if(u) self.update()
}
self.root.remove = function(e,u)
{
var i = self.items.indexOf(e)
if(i >= 0)
{
if(self.selidx != -1)
{
self.items[self.selidx].selected =false
self.selidx = -1
}
self.items.splice(i, 1)
if(u)
self.update()
}
}
self.root.refresh = function()
{
_refresh()
}
this.on("mount", function(){
if(self.root.ready)
self.root.ready(self.root)
// now refresh the list
_refresh()
})
var _refresh = function()
{
var ctop = 20
var cleft = 20
var gw = $(self.refs.container).width()
var gh = $(self.refs.container).height()
$(self.refs.list)
.children()
.each(function(e)
{
$(this).unbind("mousedown")
_enable_drag($(this))
var w = $(this).width()
var h = $(this).height()
$(this).css("top", ctop + "px").css("left", cleft + "px")
if(self.dir == "horizontal")
{
ctop += h + 20
if(ctop > gh)
{
ctop = 20
cleft += w + 20
}
}
else
{
cleft += w + 20
if(cleft > gw )
{
cleft = 20
ctop += h + 20
}
}
})
}
var _enable_drag = function(el)
{
var globalof = $(self.refs.container).offset()
el
.css("user-select","none")
.css("cursor","default")
.css("position",'absolute')
.on("mousedown", function(e){
e.preventDefault()
offset = el.offset()
offset.top = e.clientY - offset.top
offset.left = e.clientX - offset.left
$(window).on("mousemove", function(e){
var top,left
top = e.clientY - offset.top - globalof.top
left = e.clientX - globalof.top - offset.left
left = left < 0?0:left
top = top < 0?0:top
el.css("top", top +"px").css("left",left + "px")
})
$(window).on("mouseup", function(e){
//console.log("unbind mouse up")
$(window).unbind("mousemove", null)
})
})
}
_autoselect(it,i)
{
if(self.selidx == i) return true
if(!it.selected || it.selected == false) return false
var data = {
id:self.rid,
data:it,
idx:i}
//if(self.selidx != -1)
// self.items[self.selidx].selected =false
self.selidx = i
if(self.onlistselect)
self.onlistselect(data)
this.root.observable.trigger('listselect',data)
return true
}
_select(event)
{
if(self.selidx != -1 && self.selidx < self.items.length)
self.items[self.selidx].selected =false
event.item.item.selected = true
//console.log(self.items)
self.update()
//event.preventUpdate = true
}
_dbclick(event)
{
data = {
id:self.rid,
data:event.item.item,
idx: event.item.i}
if(self.onlistdbclick)
self.onlistdbclick(data)
self.root.observable.trigger('listdbclick', data)
}
</script>
</afx-float-list>

View File

@ -1,218 +0,0 @@
<afx-grid-view>
<afx-grid-row ref="gridhead" rootid = {rid} observable = {root.observable} header="true" class = {grid_row_header:header} if = {header} cols = {header}> </afx-grid-row>
<div ref = "scroller" style="width:100%; overflow:auto;">
<div ref = "container" style ="padding-bottom:10px">
<afx-grid-row each={ child, i in rows } class = {selected: child.selected} rootid = {parent.rid} observable = {parent.root.observable} index = {i} cols = {child} ondblclick = {parent._dbclick} onclick = {parent._select} oncontextmenu = {parent._select} head = {parent.refs.gridhead} ></afx-grid-row>
</div>
</div>
<script>
this.header = opts.header
this.rows = opts.rows || []
var self = this
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
self.selidx = -1
self.nrow = 0
self.ongridselect = opts.ongridselect
self.ongriddbclick = opts.ongriddbclick
self.root.observable = opts.observable
self.root.set = function(k,v)
{
if(k == "selected")
self._select({item:self.rows[v], preventDefault:function(){}})
else if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
this.calibrate_size = function()
{
if(self.header && self.refs.gridhead)
{
$(self.refs.scroller).css("height",
$(self.root).height() - $(self.refs.gridhead.root).children().first().height()
+ "px")
}
else
$(self.refs.scroller).css("height",
$(self.root).height() + "px")
}
self.root.get = function(k)
{
if(k == "selected")
return (self.selidx == -1?null:self.rows[self.selidx])
return self[k]
}
this.on("mount", function(){
if(self.refs.gridhead)
self.refs.gridhead.observable = self.root.observable
$(self.refs.container)
.css("display","table")
//.css("flex-direction","column")
.css("width","100%")
self.calibrate_size()
self.root.observable.on("resize",function(){
if(self.root)
self.calibrate_size()
})
})
this.on("updated",function(){
if(self.selidx >= self.rows.length)
self.selidx = -1
if(self.nrow == self.rows.length) return
self.nrow = self.rows.length
self.calibrate_size()
})
_select(event)
{
var data = {
id:self.rid,
data:event.item}
if(self.ongridselect)
self.ongridselect(data)
if(self.selidx != -1)
self.rows[self.selidx].selected =false
self.selidx = event.item.i
self.rows[self.selidx].selected = true
self.root.observable.trigger('gridselect',data)
event.preventUpdate = true
self.update()
//event.preventDefault()
}
_dbclick(event)
{
data = {
id:self.rid,
data:event.item}
if(self.ongriddbclick)
self.ongriddbclick(data)
self.root.observable.trigger('griddbclick', data)
}
</script>
</afx-grid-view>
<afx-grid-row>
<div style = {!header? "display: table-cell;" :""} onclick = {parent._cell_select} each = { child,i in cols } class = {string:typeof child.value == "string", number: typeof child.value == "number", cellselected: parent._auto_cell_select(child,i)} >
<afx-label color={child.color} icon = {child.icon} iconclass = {child.iconclass} text = {child.value} ></afx-label>
</div>
<script>
this.cols = opts.cols || []
var self = this
this.rid = opts.rootid
this.index = opts.index
this.header = eval(opts.header)||false
this.head = opts.head
this.selidx = -1
self.observable = opts.observable
this.colssize = []
var update_header_size = function()
{
if(!self.cols || self.cols.length == 0) return
var totalw = $(self.root).parent().width()
if(totalw == 0) return
var ocw = 0
var nauto = 0
self.colssize = []
$.each(self.cols, function(i,e){
if(e.width)
{
self.colssize.push(e.width)
ocw += e.width
}
else
{
self.colssize.push(-1)
nauto++
}
})
if(nauto > 0)
{
var cellw = parseInt((totalw - ocw)/ nauto)
$.each(self.colssize,function(i,e){if(e == -1) self.colssize[i] = cellw})
}
calibrate_size()
}
var calibrate_size = function()
{
var i = 0
$(self.root)
.children()
.each(function(){
$(this).css("width", self.colssize[i]+"px")
i++
})
}
this.on("updated", function(){
if(self.header)
update_header_size()
else if(self.head && self.index == 0)
{
self.colssize = self.head.colssize
calibrate_size()
}
})
this.on("mount", function(){
if (self.header)
{
$(self.root)
.css("display", "flex")
.css("flex-direction", "row")
update_header_size()
}
else
{
$(self.root)
.css("display","table-row")
//.css("flex-direction","row")
.css("width","100%")
if(self.head && self.index == 0)
{
self.colssize = self.head.colssize
calibrate_size()
}
}
self.observable.on("gridcellselect", function(data){
if(data.id != self.rid || self.selidx == -1) return;
if(data.row != self.index)
{
self.cols[self.selidx].selected = false
self.selidx = -1
}
})
self.observable.on("resize",function(){
self.update()
})
})
_cell_select(event)
{
if(self.header) return;
if(self.selidx != -1)
{
self.cols[self.selidx].selected = false
self.selidx = -1
}
self.cols[event.item.i].selected = true
}
_auto_cell_select(child,i)
{
if(!child.selected || self.header) return false;
if(self.selidx == i) return true;
var data = {
id:self.rid,
data:child,
col:i,
row:self.index}
self.selidx = i
self.observable.trigger("gridcellselect",data)
return true;
}
</script>
</afx-grid-row>

View File

@ -1,79 +0,0 @@
<afx-hbox style = "display:block;">
<div ref = "container" class="afx-hbox-container">
<yield/>
</div>
<script>
var self = this
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
this.on('mount', function(){
self.root.observable = (self.parent && self.parent.root && self.parent.root.observable) || opts.observable || riot.observable()
$(self.refs.container)
.css("display","flex")
.css("flex-direction","row")
.css("width","100%")
calibrate_size()
if(self.root.observable)
{
self.root.observable.on("resize", function(w,h){
calibrate_size()
})
self.root.observable.on("calibrate", function(){
calibrate_size()
})
}
})
self.root.update = function()
{
self.update()
}
var calibrate_size = function()
{
var auto_width = []
var csize, ocwidth = 0, avaiheight;
avaiheight = $(self.root).height()
avaiWidth = $(self.root).width()
/*if(avaiheight == 0)
{
avaiheight = $(self.parent.root).height()
$(self.root).css("height", avaiheight+"px")
}
if(avaiWidth == 0)
{
avaiWidth = $(self.parent.root).width()
$(self.root).css("height", avaiWidth+"px")
}*/
$(self.refs.container).css("height",avaiheight + "px")
$(self.refs.container)
.children()
.each(function(e)
{
this.observable = self.root.observable
//.css("height",avaiheight + "px")
var dw = $(this).attr("data-width")
if(dw)
{
if(dw == "grow") return
if(dw[dw.length-1] === "%")
dw = Number(dw.slice(0,-1))*avaiWidth/100;
$(this).css("width",dw + "px")
ocwidth += Number(dw)
}
else
{
$(this).css("flex-grow","1")
auto_width.push(this)
}
})
csize = (avaiWidth - ocwidth)/ (auto_width.length)
if(csize > 0)
$.each(auto_width, function(i,v)
{
$(v).css("width", csize + "px")
})
self.root.observable.trigger("hboxchange",
{id:self.rid, w:csize, h:avaiheight})
}
</script>
</afx-hbox>

View File

@ -1,20 +0,0 @@
<afx-html ref = "container">
<script>
this.content = opts.content
this.root.innerHTML = this.content
this.updateContent = undefined
var self = this
this.root.set = function(k, v)
{
self[k] = v
if(k == "content")
self.root.innerHTML = v
else if(k == "updateContent")
self.update()
}
this.on("update", function(){
if(self.updateContent)
self.root.innerHTML = self.updateContent()
})
</script>
</afx-html>

View File

@ -1,33 +0,0 @@
<afx-label>
<span style = {color?"color:" + color:""} >
<i if={iconclass} class = {iconclass} ></i>
<i if={icon} class="icon-style" style = { "background: url("+window.OS.API.handler.get+"/"+icon+");background-size: 100% 100%;background-repeat: no-repeat;" }></i>
{ text?text.__():"" }
</span>
<script>
this.iconclass = opts.iconclass
this.icon = opts.icon
this.text = opts.text
this.color = opts.color
var self = this
this.on("update",function(){
self.iconclass = opts.iconclass
self.icon = opts.icon
self.text = opts.text
self.color = opts.color
})
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
opts[i] = v[i]
else
opts[k] = v
self.update()
}
self.root.get = function(k)
{
return self[k]
}
</script>
</afx-label>

View File

@ -1,227 +0,0 @@
<afx-list-view class = {dropdown: opts.dropdown == "true"} style = "display:flex; flex-direction:column">
<div class = "list-container" ref = "container" style="flex:1;">
<div if = {opts.dropdown == "true"} ref = "current" onclick = {show_list}>
<afx-label ref = "drlabel"></afx-label>
</div>
<ul ref = "mlist" >
<li each={item,i in items } class={selected: parent._autoselect(item,i)} ondblclick = {parent._dbclick} onclick = {parent._select} oncontextmenu = {parent._select}>
<afx-label class = {item.class} color = {item.color} iconclass = {item.iconclass} icon = {item.icon} text = {item.text}></afx-label>
<i if = {item.closable} class = "closable" click = {parent._remove}></i>
<ul if = {item.complex} class = "complex-content">
<li each = {ctn,j in item.detail} class = {ctn.class}>{ctn.text.toString()}</li>
</ul>
</li>
</ul>
</div>
<div if = {opts.dropdown != "true" && buttons} class = "button_container">
<afx-button each = {btn,i in buttons} text = {btn.text} icon = {btn.icon} iconclass = {btn.iconclass} onbtclick = {btn.onbtclick}></afx-button>
</div>
<script>
this.items = opts.items || []
var self = this
self.selidx = -1
self.onlistselect = opts.onlistselect
self.onlistdbclick = opts.onlistdbclick
self.onitemclose = opts.onitemclose
self.buttons = opts.buttons
var onclose = false
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
self.root.set = function(k,v)
{
if(k == "selected")
{
if(self.selidx != -1)
self.items[self.selidx].selected =false
if(v == -1)
self.selidx = -1
else
if(self.items[v]) self.items[v].selected = true
}
else if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.get = function(k)
{
if(k == "selected")
if(self.selidx != -1)
return self.items[self.selidx]
else
return undefined
else if(k == "count")
return self.items.length
return self[k]
}
self.root.selectNext = function()
{
var idx = self.selidx + 1
if(idx >= self.items.length) return;
if(self.selidx != -1)
self.items[self.selidx].selected =false
self.items[idx].selected =true
self.update()
}
self.root.selectPrev = function()
{
var idx = self.selidx - 1
if(idx < 0) return;
if(self.selidx != -1)
self.items[self.selidx].selected =false
self.items[idx].selected =true
self.update()
}
self.root.push = function(e,u)
{
self.items.push(e)
if(u) self.update()
}
self.root.unshift = function(e,u)
{
self.items.unshift(e)
if(u) self.update()
}
self.root.replaceItem = function(o, n, u)
{
var ix = self.items.indexOf(o)
if(ix >= 0)
{
self.items[ix] = n
if(u) self.update()
}
}
self.root.remove = function(e,u)
{
var i = self.items.indexOf(e)
if(i >= 0)
{
if(self.selidx != -1)
{
self.items[self.selidx].selected =false
self.selidx = -1
}
self.items.splice(i, 1)
if(u)
self.update()
onclose = true
}
}
if(opts.observable)
this.root.observable = opts.observable
else
{
this.root.observable = riot.observable()
}
this.on("mount", function(){
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
if(opts.dropdown == "true")
{
var cl = function()
{
$(self.refs.container).css("width", $(self.root).width() + "px" )
$(self.refs.current).css("width", $(self.root).width() + "px" )
$(self.refs.mlist).css("width", $(self.root).width() + "px" )
}
cl()
self.root.observable.on("calibrate", function(){
cl()
})
self.root.observable.on("resize", function(){
cl()
})
$(document).click(function(event) {
if(!$(event.target).closest(self.refs.container).length) {
$(self.refs.mlist).hide()
}
})
//$(self.root).css("position","relative")
$(self.refs.container)
.css("position","absolute")
.css("display","inline-block")
$(self.refs.mlist)
.css("position","absolute")
.css("display","none")
.css("top","100%")
.css("left","0")
self.root.observable.on("vboxchange", function(e){
if(e.id == self.parent.rid)
$(self.refs.container).css("width", $(self.root).parent().innerWidth() + "px" )
})
}
})
show_list(event)
{
var desktoph = $("#desktop").height()
var off = $(self.root).offset().top + $(self.refs.mlist).height()
if( off > desktoph )
$(self.refs.mlist)
.css("top","-" + $(self.refs.mlist).outerHeight() + "px")
else
$(self.refs.mlist).css("top","100%")
$(self.refs.mlist).show()
//event.preventDefault()
event.preventUpdate = true
}
_remove(event)
{
r = true
if(self.onitemclose)
r = self.onitemclose(event)
if(r)
self.root.remove(event.item.item, true)
}
_autoselect(it,i)
{
if(!it.selected || it.selected == false) return false
if(self.selidx == i) return true
var data = {
id:self.rid,
data:it,
idx:i}
//if(self.selidx != -1)
// self.items[self.selidx].selected =false
self.selidx = i
if(opts.dropdown == "true")
{
$(self.refs.mlist).hide()
self.refs.drlabel.root.set("*",it)
}
if(self.onlistselect)
self.onlistselect(data)
this.root.observable.trigger('listselect',data)
//console.log("list select")
return true
}
_select(event)
{
if(onclose)
{
onclose = false
event.preventUpdate = true
return
}
if(self.selidx != -1 && self.selidx < self.items.length)
self.items[self.selidx].selected =false
event.item.item.selected = true
}
_dbclick(event)
{
data = {
id:self.rid,
data:event.item.item,
idx: event.item.i}
if(self.onlistdbclick)
self.onlistdbclick(data)
self.root.observable.trigger('listdbclick', data)
}
</script>
</afx-list-view>

View File

@ -1,179 +0,0 @@
<afx-menu >
<ul class={context: opts.context == "true"}>
<li class="afx-corner-fix"></li>
<li ref = "container" each={ data,i in items } class = {afx_submenu:data.child != null && data.child.length > 0, fix_padding:data.icon} no-reorder>
<a href="#" onclick = {parent.onselect}>
<afx-switch if = {data.switch || data.radio} class = {checked:parent.checkItem(data)} enable = false swon = {data.checked} ></afx-switch>
<afx-label color = {data.color} iconclass = {data.iconclass} icon = {data.icon} text = {data.text} ></afx-label>
<span if={data.shortcut} class = "shortcut">{data.shortcut}</span>
</a>
<afx-menu ref = "submenus" index = {i} if={data.child != null && data.child.length > 0} child={data.child} onmenuselect = {data.onmenuselect} observable = {parent.root.observable} rootid = {parent.rid}></afx-menu>
</li>
<li class="afx-corner-fix"></li>
</ul>
<script>
this.items = opts.child || []
if(opts.index != undefined)
this.index = opts.index
else
this.index = -1
var isRoot
var lastChecked = undefined
if(opts.rootid)
{
this.rid = opts.rootid
isRoot = false
}
else
{
this.rid = $(this.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
isRoot = true
}
var self = this
this.onmenuselect = opts.onmenuselect
checkItem(d)
{
if(d.checked == true && d.radio)
{
if(lastChecked)
lastChecked.checked = false
lastChecked = d
lastChecked.checked = true
}
return false
}
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.push = function(e,u)
{
self.items.push(e)
if(u)
self.update()
}
self.root.unshift = function(e,u)
{
self.items.unshift(e)
if(u)
self.update()
}
self.root.remove = function(e,u)
{
var i = self.items.indexOf(e)
if(i >= 0)
self.items.splice(i, 1)
if(u)
self.update()
}
self.root.update = function()
{
self.update()
}
self.root.get = function(k)
{
return self[k]
}
self.root.show = function(e)
{
//only for menucontext
if(opts.context != "true") return
$(self.root)
.css("top", e.clientY - 15 + "px")
.css("left",e.clientX -5 + "px")
.show()
$(document).on("click",mnhide)
}
if(opts.observable)
{
this.root.observable = opts.observable
}
else
{
this.root.observable = riot.observable()
this.root.observable.on('menuselect',function(data){
if(self.onmenuselect)
self.onmenuselect(data)
if(opts.context == "true")
$(self.root).hide()
else if(!data.root && self.refs.container)
{
var arr = self.refs.container.length?self.refs.container:[self.refs.container]
for( var i in arr)
$("afx-menu",arr[i]).first().hide()
}
})
}
var mnhide = function(event)
{
if(opts.context == "true")
{
if(event.button == 2 || event.originalEvent.button == 2)
{
return
}
if(!$(event.target).closest(self.root).length) {
$(self.root).hide()
$(document).unbind("click",mnhide)
}
return
}
if(!$(event.target).closest(self.refs.container).length && self.refs.container) {
var arr = self.refs.container.length?self.refs.container:[self.refs.container]
for( var i in arr)
$("afx-menu",arr[i]).first().hide()
$(document).unbind("click",mnhide)
}
else
{
if(self.refs.container && self.refs.container.length)
for(var i in self.refs.container)
if(!$(event.target).closest(self.refs.container[i]).length) {
$("afx-menu",self.refs.container[i]).first().hide()
}
}
}
onselect(event)
{
var data = {id:self.rid, root:isRoot, e:event, item:event.item}
if(event.item.data.switch)
{
event.item.data.checked = !event.item.data.checked
} else if(event.item.data.radio)
{
if(lastChecked)
{
lastChecked.checked = false
}
event.item.data.checked = true
lastChecked = event.item.data
}
this.root.observable.trigger('menuselect',data)
if( this.onmenuselect && !isRoot) this.onmenuselect(data)
event.preventDefault()
$(document).unbind("click",mnhide)
if(opts.context == "true") return
if(isRoot && self.refs.container)
{
if(self.refs.container.length)
$("afx-menu",self.refs.container[event.item.i]).first().show()
else
$("afx-menu",self.refs.container).first().show()
$(document).on("click",mnhide)
return
}
}
</script>
</afx-menu>

View File

@ -1,100 +0,0 @@
<afx-nspinner>
<input ref = "holder" type="text" value = {value}></input>
<ul ref = "spinner">
<li class = "incr" ref= "incr" onclick="{ _incr }"> <i></i> </li>
<li class = "decr" ref = "decr" onclick="{ _decr }"> <i></i> </li>
</ul>
<script>
this.value = eval(opts.value) || 0
this.step = Number(opts.step) || 1
this.onchange = opts.onchange
var self = this
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.get = function(k)
{
return self[k]
}
self._incr = function(e)
{
self.value = self.value + self.step;
self.update();
if(self.onchange) self.onchange(self.value);
}
self.on("mount", function(){
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
$(self.refs.spinner).css("width", "20px" );
var cl = function()
{
$(self.refs.holder).css("width", $(self.root).width() - 20 + "px" )
$(self.refs.holder).css("height", $(self.root).height() + "px" )
$(self.refs.spinner)
.css("width","20px")
.css("height", $(self.root).height() + "px" )
$(self.refs.incr)
.css("height", $(self.root).height()/2 - 2 + "px")
.css("position", "relative")
$(self.refs.decr).css("height", $(self.root).height()/2 -2 + "px")
.css("position", "relative")
$(self.refs.spinner).find("li")
.css("display","block")
.css("text-align", "center")
.css("vertical-align", "middle")
$(self.refs.spinner).find("i")
.css("font-size", "16px")
.css("position", "absolute")
var fn = function(ie, pos)
{
var el = $(ie).find("i")
el
.css(pos,($(ie).height()-el.height()) /2 + "px" )
.css("left", ($(ie).width()-el.width())/2 + "px" )
}
fn(self.refs.decr, "bottom")
fn(self.refs.incr, "top")
}
cl()
self.root.observable.on("calibrate", function(){
cl()
})
self.root.observable.on("resize", function(){
cl()
});
$(self.refs.holder).on('keyup', function (e) {
if (e.keyCode == 13) {
var val = self.refs.holder.value;
if(!isNaN(val))
{
val = eval(val)
if(val < 0)
val = self.value;
self.value = val;
}
self.refs.holder.value = self.value;
if(self.onchange) self.onchange(self.value);
}
});
})
self._decr = function(e)
{
if(self.value == 0) return;
self.value = self.value - self.step;
self.update();
if(self.onchange) self.onchange(self.value);
}
</script>
</afx-nspinner>

View File

@ -1,45 +0,0 @@
<afx-overlay>
<yield/>
<script>
this.width = opts.width || 200
this.height = opts.height || 400
var self = this;
self.commander = null
this.root.observable = opts.observable || riot.observable()
var id = $(self.root).attr("data-id")
var calibre_size = function()
{
$(self.root)
.css("width", self.width + "px")
.css("height", self.height + "px")
self.root.observable.trigger("resize", {id:id,w:self.width,h:self.height})
}
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
if( k == "width" || k == "height")
calibre_size()
self.update()
}
self.root.get = function(k)
{
return self[k]
}
self.on("mount", function(){
$(self.root)
.css("position", "absolute")
//.css("z-index",1000000)
$(self.root).children().each(function(e){
this.observalbe = self.root.observalbe
})
calibre_size()
self.root.observable.trigger("rendered", self.root)
})
</script>
</afx-overlay>

View File

@ -1,85 +0,0 @@
<afx-resizer>
<script>
var self = this
self.dir = "hz"
self.resizable = undefined
self.parent = undefined
self.minsize = 0
self.on("mount", function(){
//self.parent = $(self.root).parent().parent()
var tagname = $(self.parent.root).prop("tagName")
self.resizable = $(self.root).prev().length == 1 ? $(self.root).prev()[0]: undefined
//self.nextel = $(self.root).next().length == 1 ? $(self.root).next()[0]: undefined
if(tagname == "AFX-HBOX")
{
self.dir = "hz"
$(self.root).css("cursor", "col-resize")
if(self.resizable)
{
self.minsize = parseInt($(self.resizable).attr("min-width"))
}
}
else if(tagname == "AFX-VBOX")
{
self.dir = "ve"
$(self.root).css("cursor", "row-resize")
if(self.resizable)
{
self.minsize = parseInt($(self.resizable).attr("min-height"))
}
}
else
{
//$(self.root).css("cursor", "normal")
self.dir = "none"
}
if(!self.minsize)
self.minsize = 10
enable_dragging()
})
var enable_dragging = function()
{
$(self.root)
.css("user-select","none")
$(self.root).on("mousedown", function(e){
e.preventDefault()
$(window).on("mousemove", function(evt){
if(!self.resizable) return
if(self.dir == "hz")
horizontalResize(evt)
else if (self.dir == "ve")
verticalResize(evt)
})
$(window).on("mouseup", function(evt){
//console.log("unbind mouse up")
$(window).unbind("mousemove", null)
})
})
}
var horizontalResize = function(e)
{
if(!self.resizable) return
var offset = $(self.resizable).offset()
w = Math.round(e.clientX - offset.left)
if(w < self.minsize) w = self.minsize
$(self.resizable).attr("data-width", w.toString())
self.parent.root.observable.trigger("calibrate", self.resizable)
}
var verticalResize = function(e)
{
//console.log("vboz")
if(!self.resizable) return
var offset = $(self.resizable).offset()
//console.log($(self.resizable).innerHeight())
//console.log(e.clientY, offset.top)
h = Math.round(e.clientY - offset.top)
if(h < self.minsize) h = minsize
$(self.resizable).attr("data-height", h.toString())
self.parent.root.observable.trigger("calibrate", self.resizable)
}
</script>
</afx-resizer>

View File

@ -1,107 +0,0 @@
<afx-slider>
<div class= "container" ref="container">
<div class = "progress" ref ="prg"></div>
<div if = {dragable} class = "dragpoint" ref = "point"></div>
</div>
<script>
this.value = Number(opts.value) || 0
this.max = Number(opts.max) || 100
if(opts.dragable != undefined)
this.dragable = eval(opts.dragable)
else
this.dragable = true
this.onchanging = opts.onchanging
this.onchange = opts.onchange
//this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
var self = this
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
/*if(k == "value")
{
if(self.onchange) self.onchange(self.value)
if(self.onchanging) self.onchanging(self.value)
}*/
self.update()
}
self.root.get = function(k)
{
return self[k]
}
var calibrate = function() {
if(self.value > self.max) self.value = self.max
$(self.refs.container).css("width", $(self.root).width() + "px")
var w = $(self.refs.container).width()*self.value/ self.max
$(self.refs.prg).css("width", w + "px").css("height", $(self.refs.container).height()+"px")
if(self.dragable)
{
var ow = w - $(self.refs.point).width()/2
var top = Math.floor(($(self.refs.prg).height() - $(self.refs.point).height())/2)
$(self.refs.point).css("left", ow + "px").css("top", top + "px")
}
}
self.on("update", function(){
calibrate()
})
var enable_dragging = function()
{
$(self.refs.point)
.css("user-select","none")
.css("cursor","default")
$(self.refs.point).on("mousedown", function(e){
e.preventDefault()
offset = $(self.refs.container).offset()
$(window).on("mousemove", function(e){
var left
left = e.clientX - offset.left
left = left < 0?0:left
var maxw = $(self.refs.container).width();
left = left > maxw?maxw : left
self.value = left*self.max/maxw
calibrate()
if(self.onchanging) self.onchanging(self.value)
})
$(window).on("mouseup", function(e){
if(self.onchange) self.onchange(self.value)
$(window).unbind("mousemove", null)
$(window).unbind("mouseup", null)
})
})
}
self.on("mount", function(){
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
if(self.dragable)
{
$(self.refs.point).css("position", "absolute")
$(self.refs.point).hide()
$(self.root).mouseover(function(){
$(self.refs.point).show()
}).mouseout(function(){
$(self.refs.point).hide()
})
enable_dragging()
}
$(self.refs.container).click( function(e){
var offset = $(self.refs.container).offset()
var left = e.clientX - offset.left
var maxw = $(self.refs.container).width()
self.value = left*self.max/maxw
calibrate()
if(self.onchange) self.onchange(self.value)
if(self.onchanging) self.onchanging(self.value)
})
self.root.observable.on("calibrate",function(){
calibrate()
})
self.root.observable.on("resize", function(){
calibrate()
})
calibrate()
})
</script>
</afx-slider>

View File

@ -1,56 +0,0 @@
<afx-switch>
<span class = {swon: swon} onclick = {toggle}></span>
<script>
if(opts.swon != undefined)
this.swon = opts.swon
else
this.swon = false
var self = this
//this.root.observable = opts.observable
if(opts.enable != undefined)
this.enable = opts.enable
else
this.enable = true
this.onchange = opts.onchange
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
opts[i] = v[i]
else
opts[k] = v
self.update()
}
self.root.get = function(k)
{
return self[k]
}
this.root.toggle = function()
{
opts.swon = !self.swon
self.update()
}
/*this.on("mount", function(){
})*/
this.on("update", function(e){
self.swon = typeof opts.swon == "function"?opts.swon():opts.swon
})
toggle(e)
{
if(!self.enable) return
opts.swon = !self.swon
self.swon = opts.swon
var data = {
id: self.rid,
data: opts.swon
}
if(opts.onchange)
opts.onchange(data)
if(self.root.observable)
self.root.observable.trigger("switch", data)
}
</script>
</afx-switch>

View File

@ -1,54 +0,0 @@
<afx-sys-panel>
<div>
<afx-menu data-id = "os_menu" ref = "aOsmenu" child={osmenu.child} onmenuselect = {osmenu.onmenuselect} class="afx-panel-os-menu"></afx-menu>
<afx-menu data-id = "appmenu" ref = "aAppmenu" child={appmenu.child} class = "afx-panel-os-app"></afx-menu>
<afx-menu data-id = "sys_tray" ref = "aTray" child={systray.child} onmenuselect = {systray.onmenuselect} class = "afx-panel-os-stray"></afx-menu>
</div>
<script>
this.osmenu = { child: [] }
this.appmenu = { child: [] }
this.systray = {
child: [],
onmenuselect: function(d){
if(d.root)
d.e.item.data.awake(d.e)
}
}
var self = this
self.root.attachservice = function(s)
{
self.refs.aTray.root.unshift(s,true)
s.attach(self.refs.aTray)
}
self.root.detachservice = function(s)
{
self.refs.aTray.root.remove(s, true)
}
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else
self[k] = v
self.update()
}
self.root.get = function(k)
{
return self[k]
}
self.root.update = function()
{
self.update()
}
this.on('mount', function() {
//console.log(self.refs.aOsmenu.root)
$(self.refs.aOsmenu.root).css("z-index",1000000)
$(self.refs.aAppmenu.root).css("z-index",1000000)
$(self.refs.aTray.root).css("z-index",1000000)
window.OS.courrier.trigger("syspanelloaded")
})
</script>
</afx-sys-panel>

View File

@ -1,71 +0,0 @@
<afx-tab-bar>
<afx-list-view ref = "list" />
<script>
var self = this
this.closable = opts.closable || false
self.ontabselect = opts.ontabselect
self.root.get = function (k) {
return self.refs.list.root.get(k)
}
self.root.update = function(){
self.update(true)
}
self.on("mount", function(){
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
self.refs.list.root.observable = self.root.observable
/*self.root.observable.on("listselect", function(){
console.log("list select")
})*/
self.refs.list.root.set ("onlistselect",function (e) {
//console.log("tab is seleced")
self.root.observable.trigger("tabselect", e)
if(self.ontabselect)
self.ontabselect(e)
})
})
self.root.set = function (k,v){
if( k == "*")
for(var i in v)
self.refs.list.root.set(i,v[i])
else if(k == "closable")
{
self.closable = v
}
else if(k == "ontabselect")
self.ontabselect = v
else
{
if(k == "items")
{
for(var i in v)
v[i].closable = self.closable
}
self.refs.list.root.set(k,v)
}
//self.update()
}
self.root.push = function(e,u)
{
e.closable = self.closable
self.refs.list.root.push(e,u)
}
self.root.replaceItem = function(o,n,u)
{
n.closable = self.closable
self.refs.list.root.replaceItem(o,n,u)
}
self.root.unshift = function(e,u)
{
self.refs.list.root.unshift(e,u)
}
self.root.remove = function(e,u)
{
self.refs.list.root.remove(e,u)
}
</script>
</afx-tab-bar>

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