452 Commits

Author SHA1 Message Date
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
80de4fcf5b Update TODO 2020-02-04 13:18:00 +01:00
2ef18dbb3d Update .travis.yml 2020-02-04 13:07:30 +01:00
be33f05d61 Update .travis.yml 2020-02-04 13:05:05 +01:00
8f1335bca9 Update .travis.yml 2020-02-04 13:00:09 +01:00
89d6b307ec fix Makefile 2019-12-19 22:39:48 +01:00
88e95e819a fix 2019-12-15 12:11:19 +01:00
e47a424a9f add server side api 2019-08-30 13:27:17 +02:00
9b75bc4ca2 enable sendmail in blogger 2019-06-13 11:06:38 +02:00
8a9941f6ed Merge branch 'master' of https://github.com/lxsang/antos 2019-06-11 18:33:30 +02:00
5db6901770 update Blogger 2019-06-11 18:33:14 +02:00
cd6d10910b extension for notepad 2019-05-03 13:18:51 +02:00
3e008c472b fix bug on the new firefox that fired menucontext and onclick at the sametime 2019-04-25 12:28:39 +02:00
10024d8613 Update README.md 2018-10-03 11:30:33 +02:00
028417b4f9 add type to PromptDialog 2018-10-01 18:40:45 +02:00
4552840440 GraphEditor move to antosdk-apps repository 2018-10-01 16:37:00 +02:00
eb4e52a31a Update README.md 2018-09-29 13:22:55 +02:00
fa15f3c3b4 fix notification application icon bug 2018-09-16 20:08:43 +02:00
4ce83df4bb minor fix 2018-09-14 19:29:13 +02:00
5b6e68116b fix bug 2018-09-13 21:41:14 +02:00
aec5457a31 fix bug and add afx-nspinner widget 2018-09-13 20:14:12 +02:00
7659438138 fix context menu bug on afx-apps-dock 2018-09-12 21:50:16 +02:00
205a3c0d5b Merge branch 'master' of https://github.com/lxsang/antos 2018-09-12 18:28:51 +02:00
b6100b2d07 change REST path on remote handle 2018-09-12 18:28:32 +02:00
e8cdeaedb6 Update README.md 2018-07-26 11:42:14 +02:00
8446f1874b Update README.md 2018-07-26 11:41:42 +02:00
4378723fff fix 2018-07-24 18:13:50 +02:00
46cec340e2 fix 2018-07-24 15:49:36 +02:00
b2db12e42f improvement & bug fix 2018-07-24 15:17:43 +02:00
3cad7be3bf fix 2018-04-14 17:31:42 +02:00
ab084d9e79 Blogger now support katex rendering 2018-04-12 23:16:22 +02:00
7a4ba008b0 generate locale files 2018-03-27 18:54:54 +02:00
59cde330da fix core api bugs 2018-03-27 18:47:27 +02:00
e72aaddf45 add tooltip support 2018-03-26 14:33:06 +02:00
5837fa742b add tooltip support 2018-03-26 14:26:34 +02:00
47bf528f89 add tooltip support 2018-03-26 14:22:23 +02:00
9f0986bc8d add tooltip support 2018-03-26 14:21:55 +02:00
d788f87ab9 fix setting save bug 2018-03-24 22:15:25 +01:00
57971e4338 fix core api bug 2018-03-24 00:21:53 +01:00
ca647d31c0 fix core api bug 2018-03-23 23:18:32 +01:00
c403a5b8bf merged Merge branch 'master' of https://github.com/lxsang/antos 2018-03-23 23:03:32 +01:00
035a7f3723 fix observable core 2018-03-23 23:03:17 +01:00
2438153c02 fix 2018-03-22 11:11:56 +01:00
af542f32c4 fix slider 2018-03-21 19:59:24 +01:00
3922f3764d fix stuff 2018-03-21 19:05:08 +01:00
278ee54b2e fix slider 2018-03-21 11:59:01 +01:00
da342ea574 Market now can update package to newer version 2018-03-21 11:21:33 +01:00
1b4f117060 add <afx-slider> 2018-03-21 00:21:04 +01:00
519209af0c add <afx-slider> 2018-03-21 00:18:18 +01:00
93622ec98a add <afx-slider> 2018-03-21 00:12:55 +01:00
0510da0701 fix 2018-03-20 15:19:32 +01:00
988993483f fix scheme load bug 2018-03-20 15:05:41 +01:00
857b2167c5 fix 2018-03-20 13:52:31 +01:00
f4f2e6f124 minor fix 2018-03-20 13:19:40 +01:00
f89c71e2b3 AntOSDK first working version, now application can be built from browser 2018-03-20 13:07:23 +01:00
09f72fd436 AntOSDK now can generate release zip of the application 2018-03-20 00:06:46 +01:00
0882a6a1f1 AntOSDK now can generate release zip of the application 2018-03-19 23:58:35 +01:00
1e4c134454 Build and run app directly from antOSDK 2018-03-19 19:21:45 +01:00
8643b819fd Build and run app directly from antOSDK 2018-03-19 19:13:28 +01:00
e8d4815e93 Build and run app directly from antOSDK 2018-03-19 19:11:38 +01:00
cd3bba7cf8 Update TODO 2018-03-19 12:07:12 +01:00
09ca30ad9a Update README.md 2018-03-19 11:26:53 +01:00
6fdf907868 Update README.md 2018-03-19 11:11:48 +01:00
b767e40db4 Update README.md 2018-03-19 11:11:36 +01:00
6be5672e43 Update .travis.yml 2018-03-19 11:08:25 +01:00
ceb704d862 Update .travis.yml 2018-03-19 11:04:05 +01:00
023b928bc7 Update .travis.yml 2018-03-19 11:02:09 +01:00
02a601b739 Update Makefile 2018-03-19 10:59:45 +01:00
8ae8251d0a Create .travis.yml 2018-03-19 10:55:09 +01:00
286fb256c8 add SDK application 2018-03-19 00:37:16 +01:00
129633844f add SDK application 2018-03-19 00:36:55 +01:00
b4428d0188 fix MarketPlace 2018-03-18 14:16:06 +01:00
4290ea602a minor fix 2018-03-17 23:46:37 +01:00
a111162e7b minor fix 2018-03-17 23:15:52 +01:00
5c5517e685 fix 2018-03-17 22:42:54 +01:00
6d8c17e8e7 minor fix 2018-03-17 21:54:03 +01:00
f548c241af add svg support to Preview 2018-03-17 21:49:50 +01:00
7133bdcbab fix graph editor 2018-03-17 20:50:51 +01:00
0ff98fee98 GraphEditor now support mermaid 2018-03-16 19:53:32 +01:00
2f57c05ef3 Blogger is no longer default application, it is move to app store 2018-03-16 12:57:44 +01:00
9deb575628 user uglifycss to compress css file 2018-03-15 22:36:38 +01:00
1450891943 merged
Merge branch 'master' of https://github.com/lxsang/antos
2018-03-15 11:00:47 +01:00
2221ea0bc4 add licence to source file 2018-03-15 11:00:24 +01:00
373f305848 Update README.md 2018-03-15 10:30:53 +01:00
43cbbf9e3d Update README.md 2018-03-15 10:30:33 +01:00
9dc4420ff2 Update README.md 2018-03-15 10:29:13 +01:00
bfedca3e3e Update LICENSE 2018-03-15 10:28:07 +01:00
7fa73561c4 Update README.md 2018-03-15 10:15:41 +01:00
df80cbd652 minor bug fix 2018-03-14 19:17:41 +01:00
f671966b96 fix appsByMime bug 2018-03-14 18:47:26 +01:00
1ff616786e fix marketplace uninstall bug 2018-03-13 19:41:46 +01:00
7878364f57 fix dot export 2018-03-13 19:23:12 +01:00
a0008663ab add DotEditor package 2018-03-13 18:05:33 +01:00
f1616d0240 fix list view rendering text 2018-03-12 23:59:14 +01:00
bb0a20b2ad fix setting error 2018-03-12 23:48:46 +01:00
377834e0ff fix setting error 2018-03-12 23:36:39 +01:00
7a609e3270 fix setting error 2018-03-12 23:20:06 +01:00
a06d3258f9 add Setting to the main build 2018-03-12 22:48:25 +01:00
56cf8ea770 fix File bug 2018-03-12 22:47:23 +01:00
d2677eb380 some know bug in File is not fixed yet 2018-03-12 19:48:22 +01:00
9d5f2250ba add buttons to Fime 2018-03-12 19:33:11 +01:00
d310d2c03b Finish package setting & add FR language 2018-03-12 18:55:40 +01:00
7fd6492a31 afx-list-view now can add action buttons 2018-03-12 11:33:33 +01:00
6c7aec69b7 fix MarketPlace ui bug when language change 2018-03-12 10:35:58 +01:00
e01d41c76b fix tab container update 2018-03-12 10:22:51 +01:00
02f8a256d3 add missing wp 2018-03-12 10:03:17 +01:00
07bb58b7eb add appearance setting 2018-03-12 00:42:14 +01:00
8fef0c5e96 add setting package 2018-03-11 21:31:40 +01:00
10015d99b0 font-awesome is used as default icon font for the entire system 2018-03-11 12:32:37 +01:00
800c95830d normal string format and locale support string format 2018-03-10 23:24:53 +01:00
7fefc17f04 fix menu bug 2018-03-10 23:16:48 +01:00
702a83df6a allow to overide locale method __() to all object 2018-03-10 22:46:28 +01:00
6367c321da add FormatedString class, automatically translate to new locale when locale is set 2018-03-10 20:42:09 +01:00
88cf90ae50 minor fix 2018-03-10 15:42:21 +01:00
a624d853ed add language make support to makefile 2018-03-10 15:40:25 +01:00
1b9e6e88fa Add global locale change event, application now automatically change language when the event is triggered 2018-03-10 12:22:01 +01:00
45c89be5be add vi_VN locale 2018-03-10 01:32:18 +01:00
d072566be8 all packages now support localisation 2018-03-10 01:05:34 +01:00
9e0cd29030 add localisation support, allow multi-languages 2018-03-09 19:54:33 +01:00
696114e2f7 add use strict mode to the core API 2018-03-09 10:11:15 +01:00
30da6fa8fc make MarketPlace singleton 2018-03-08 23:45:12 +01:00
8d3ae96f01 fix uninstall bug in market place 2018-03-08 23:33:32 +01:00
318f26d164 fix uninstall bug in market place 2018-03-08 23:29:37 +01:00
c82da84240 Fix CodeBlock 2018-03-08 22:58:14 +01:00
4afc368bdd code block 2018-03-08 19:33:50 +01:00
8877f46771 support screenshot in MarketPlace 2018-03-07 19:32:48 +01:00
657 changed files with 103663 additions and 29774 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

14
.travis.yml Normal file
View File

@ -0,0 +1,14 @@
language: javascript
node_js:
- 10.21.0
install:
- npm install terser
- npm install uglifycss
- npm install typescript@3.9.3
- npm install jest @types/jest ts-jest
- npm install @types/jquery
script:
- tsc -v
- make
- make test
- make release

62
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,62 @@
def remote = [:]
remote.name = 'workstation'
remote.host = 'workstation'
remote.user = 'dany'
remote.identityFile = '/var/jenkins_home/.ssh/id_rsa'
remote.allowAnyHosts = true
remote.agent = false
remote.logLevel = 'INFO'
pipeline{
agent { node{ label'master' }}
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('Prebuild build') {
steps {
sshCommand remote: remote, command: '''
set -e
export WORKSPACE=$(realpath "./jenkins/workspace/antos")
cd $WORKSPACE
npm install terser
npm install uglifycss
npm install typescript
npm install @types/jquery
'''
}
}
stage('Build release') {
steps {
sshCommand remote: remote, command: '''
set -e
export WORKSPACE=$(realpath "./jenkins/workspace/antos")
cd $WORKSPACE
[ -d build ] && rm -rf build
export BUILDDIR="$WORKSPACE/build/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
//}
}
}
}
}
}

687
LICENSE
View File

@ -1,21 +1,674 @@
MIT License
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (c) 2018 Xuan Sang LE
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Preamble
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
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 <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

250
Makefile
View File

@ -1,80 +1,169 @@
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
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
VERSION=1.2.1
BRANCH = b
BUILDID=$(shell git rev-parse --short HEAD)
GSED=sed
UNAME_S := $(shell uname -s)
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
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 Blogger Preview
main: build_coffees build_tags build_themes schemes libs build_packages
main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/
- cp README.md $(BUILDDIR)/
lite: build_coffee build_tag build_theme schemes build_packages
initd:
- mkdir -p $(BUILDDIR)
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
build_themes: antos_themes_build
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_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_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_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_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
@ -86,32 +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;\
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-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 -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
sed -i 's/resources\/antos_tags.js/scripts\/riot.min.js/g' $(BUILDDIR)/index.html
sed -i 's/scripts\/riot.compiler.min.js/resources\/antos_tags.js/g' $(BUILDDIR)/index.html
sed -i 's/type=\"riot\/tag\"//g' $(BUILDDIR)/index.html
# npm install minify -g
# npm install $(UGLIFYCSS) -g
# uglify the css
minify --output $(BUILDDIR)/resources/themes/antos/antos.css $(BUILDDIR)/resources/themes/antos/antos.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\
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 && minify --output $(BUILDDIR)/packages/$$d/main.css $(BUILDDIR)/packages/$$d/main.css;\
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;\
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

View File

@ -1,33 +1,77 @@
# antOS
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 provides a desktop like experience to remotely access resource on the server using web technologies. AntOS is based on jQuery and Riot, it is designed to be used along with our antd server and Lua based server side app, but can be adapted to be used with 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 v1.2.1
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 :)
[![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)
AntOS is a front-end system and API that implement the traditional desktop UI environment on the web browser. The front-end can connect to a remote server and acts as a virtual desktop environment (VDE). The original purpose of AntOS is to offer: (1) visual tools to access and control resource on remote server
and embedded linux environment; (2) front-end API for SaaS web-based applications. With its application API and the provided SDK, AntOS facilitates the
development and deployment of user specific applications inside de VDE environment.
![https://os.iohub.dev/VFS/shared/d4645d65b3e4bb348f1bde0d42598ad9b99367f5](https://os.iohub.dev/VFS/shared/d4645d65b3e4bb348f1bde0d42598ad9b99367f5)
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
## Change logs
* V.1.2.1 WIP - with Jenkinsfile support and webhooks
* 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)
## Demo
A demo of the webOS 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")
## Build
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)
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 :) ).
## AntOS applications (Available on the MarketPlace)
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
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
It you have any problem, please contact me or open an issue, i'll try to response ASAP.
- Documentation: [https://doc.iohub.dev/antos](https://doc.iohub.dev/antos)
- API: [https://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
## Licence
Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
Copyright 2017-2021 Xuan Sang LE <mrsang AT iohub DOT dev>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
AnTOS is is licensed under the GNU General Public License v3.0, see the LICENCE file for more information
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
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.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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/>.
**For comercial use, please contact author**

3
TODO
View File

@ -1,3 +0,0 @@
# add version number
# bug tracking app
# packages manager (restricted mode)

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,7 +0,0 @@
_GUI = self.OS.GUI
_API = self.OS.API
_PM = self.OS.PM
_OS = self.OS
_courrier = self.OS.courrier
this.onload = () ->
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,107 +0,0 @@
class BaseApplication extends this.OS.GUI.BaseModel
constructor: (name, args) ->
super name, args
_OS.setting.applications[@name] = {} if not _OS.setting.applications[@name]
@setting = _OS.setting.applications[@name]
@keycomb =
ALT: {}
CTRL: {}
SHIFT: {}
META: {}
init: ->
me = @
# first register some base event to the app
@subscribe "appregistry"
, ( m ) ->
me.applySetting m.data.m if (m.name is me.name)
@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"
#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) ->
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"
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
update:->
#implement by subclasses
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,311 +0,0 @@
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 = 'dia-window' apptitle='#{@title}' width='#{@conf.width}' height='#{@conf.height}'>
<afx-vbox>"
html += "<#{v.tag} #{v.att} style = 'margin-left:5px; margin-right:5px;' 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-right:5px;'></afx-button>" for k,v of @conf.buttons
html += "</div></afx-vbox></afx-app-window>"
#render the html
_GUI.htmlToScheme html, @, @host
main: () ->
@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", att: "data-height = '20'" },
{ tag: "input", att: "type = 'text'" }
],
width: 200,
height: 100,
resizable: false,
buttons: [
{
label: "0k",
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
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: 220,
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' }],
width: 313,
height: 220,
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", att: "style = 'padding-left:10px;'" }],
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: #{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 #{e.child.path}" if d.error
f d.result
location.set "onlistselect", (e) ->
return unless e and e.data.path
e.data.path.asFileHandler().read (d) ->
if(d.error)
return me.error "Resource not found #{e.data.path}"
fileview.set "path", e.data.path
fileview.set "data", d.result
location.set "items", ( i for i in @systemsetting.VFS.mountpoints when i.type isnt "app" )
location.set "selected", 0 unless location.get "selected"
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" unless f
if me.data and me.data.mimes
#verify the mime
m = false
for v in me.data.mimes
if f.mime.match (new RegExp v, "g")
m = true
break
return me.notify "Only #{me.data.mimes.join(",")} could be selected" unless m
d = f.path
d = f.path.asFileHandler().parent() if f.type is "file"
me.handler d, ($ filename).val(), f.path 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"
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,7 +0,0 @@
class BaseEvent
constructor: (@name) ->
@prevent = false
preventDefault:()->
@prevent = true
this.OS.GUI.BaseEvent = BaseEvent

View File

@ -1,79 +0,0 @@
class BaseModel
constructor: (@name, @args) ->
@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: () ->
evt = new _GUI.BaseEvent("exit")
@onexit(evt)
if not evt.prevent
delete @.observable
@dialog.quit() if @dialog
_PM.kill @
path: () ->
mt = @meta()
return mt.path if mt and mt.path
return 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
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 not _GUI.subwindows[d]
@error "Dialog #{d} not found"
return
@dialog = new _GUI.subwindows[d]()
#@dialog.observable = riot.observable() unless @dialog
@dialog.parent = @
@dialog.handler = f
@dialog.pid = @pid
@dialog.data = data
@dialog.title = title
@dialog.init()
publish: (t, m) ->
mt = @meta()
_courrier.trigger t, { id: @pid, name: @name, data: { m: m, icon: mt.icon, iconclass: mt.iconclass } }
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
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,41 +0,0 @@
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
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,238 +0,0 @@
String.prototype.hash = () ->
hash = 5381
i = this.length
hash = (hash * 33) ^ this.charCodeAt(--i) while i
hash >>> 0
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.asUnit8Array = () ->
bytes = []
for i in [0..(@length - 1)]
bytes.push @charCodeAt i
bytes = new Uint8Array(bytes)
return bytes
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:{}
#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: #{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"
_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
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]
throwe: (n) ->
err = undefined
try
throw new Error(n)
catch e
err = e
return "" if not err
return err

View File

@ -1,129 +0,0 @@
#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: new Object
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) ->
return unless _PM.processes[app]
tmp = []
tmp.push a for a in _PM.processes[app]
a.quit() for a in tmp
cleanup: ->
console.log "Clean up system"
($ "#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 () ->
_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,20 +0,0 @@
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 "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,412 +0,0 @@
self.OS.GUI =
subwindows: new Object()
dialog: undefined
fullscreen: false
shortcut:
ALT: {}
CTRL: {}
SHIFT: {}
META: {}
SYS_MENU: [
{
text: "Applications",
child: [],
dataid: "sys-apps"
iconclass: "fa fa-adn",
onmenuselect: (d) ->
_GUI.launch d.item.data.app
}
]
htmlToScheme: (html, app, parent) ->
scheme = $.parseHTML html
($ parent).append scheme
riot.mount ($ scheme), { observable: app.observable }
app.scheme = scheme[0]
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 #{d} not found", 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: #{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")
apps.push metas[idx]
return false
return false
catch e
_courrier.osfail "Find app by mimes #{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#{it.text} is not executable" 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 #{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 width", list
forceLaunch: (app, args) ->
console.log "This method is used for developing only, please use the launch method instead"
_PM.killAll app
($ _OS.APP[app].style).remove() if _OS.APP[app] and _OS.APP[app].style
_OS.APP[app] = undefined
_GUI.launch app, args
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) ->
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{_API.handler.get}/#{css}" }
.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 = "#{_API.handler.get}/#{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.one "rendered", () ->
dock.get(0).newapp data
app.sysdock = dock.get(0)
app.appmenu = ($ "[data-id = 'appmenu']", "#syspanel")[0]
app.init()
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()
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
initDM: ->
($ document).on 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', ()->
_GUI.fullscreen = not _GUI.fullscreen
# 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", $ "#wrapper"), { items: [] }
# context menu
riot.mount ($ "#contextmenu")
($ "#workspace").contextmenu (e) -> _GUI.bindContextMenu e
# desktop default file manager
desktop = $ "#desktop"
fp = _OS.setting.desktop.path.asFileHandler()
desktop[0].fetch = () ->
fn = () ->
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 fall: Cannot init desktop manager"
console.log s, e
refreshSystemMenu: () ->
_GUI.SYS_MENU.length = 1
_GUI.SYS_MENU[0].child.length = 0
_GUI.SYS_MENU[0].child.push v for k, v of _OS.setting.system.packages when (v and v.app)
_GUI.SYS_MENU.push v for k, v of _OS.setting.system.menu
_GUI.SYS_MENU.push
text: "Toggle Full screen",
dataid: "os-fullsize",
iconclass: "fa fa-tv"
_GUI.SYS_MENU.push
text: "Log out",
dataid: "sys-logout",
iconclass: "fa fa-user-times"
buildSystemMenu: () ->
menu =
text: ""
iconclass: "fa fa-eercast"
dataid: "sys-menu-root"
child: _GUI.SYS_MENU
menu.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
menu = [menu]
($ "[data-id = 'os_menu']", "#syspanel")[0].set "items", menu
#console.log menu
login: () ->
_OS.cleanup()
_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 fall: 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
# initDM
_GUI.initDM()
_courrier.observable.one "syspanelloaded", () ->
# TODO load packages list then build system menu
_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.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"

1127
src/core/gui.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,92 +0,0 @@
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}/lua-api/os"
_REST = self.OS.API.REST
self.OS.API.handler =
# get file, require authentification
get: "#{_REST}/fs/get"
# get shared file with publish
shared: "#{_REST}/fs/shared"
scandir: (p, c ) ->
path = "#{_REST}/fs/scandir"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail "Fail to scan directory: #{p}", e, s
mkdir: (p, c ) ->
path = "#{_REST}/fs/mkdir"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail "Fail to create directory: #{p}", e, s
sharefile: (p, pub , c) ->
path = "#{_REST}/fs/publish"
_API.post path, { path: p , publish: pub }, c, (e, s) ->
_courrier.osfail "Fail to publish file: #{p}", e, s
fileinfo: (p, c) ->
path = "#{_REST}/fs/fileinfo"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail "Fail to get file metadata: #{p}", e, s
readfile: (p, c, t) ->
path = "#{_REST}/fs/get/"
_API.get path + p, c, (e, s) ->
_courrier.osfail "Fail to read file: #{p}", e, s
, t
move: (s, d, c) ->
path = "#{_REST}/fs/move"
_API.post path, { src: s, dest: d }, c, (e, s) ->
_courrier.osfail "Fail to move file: #{s} -> #{d}", e, s
delete: (p , c) ->
path = "#{_REST}/fs/delete"
_API.post path, { path: p }, c, (e, s) ->
_courrier.osfail "Fail to delete: #{p}", e, s
fileblob: (p, c) ->
path = "#{_REST}/fs/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 #{d.command} package", e, s
upload: (d, c) ->
path = "#{_REST}/fs/upload"
_API.upload path, d, c, (e, s) ->
_courrier.osfail "Fail to upload file to: #{d}", e, s
write: (p, d , c) ->
path = "#{_REST}/fs/write"
_API.post path, { path: p, data: d }, c, (e, s) ->
_courrier.osfail "Fail to write to file: #{p}", e, s
scanapp: (p, c ) ->
path = "#{_REST}/system/application"
auth: (c) ->
p = "#{_REST}/system/auth"
_API.post p, {}, c, () ->
alert "Resource not found: #{p}"
login: (d, c) ->
p = "#{_REST}/system/login"
_API.post p, d, c, () ->
alert "Resource not found: #{p}"
logout: () ->
p = "#{_REST}/system/logout"
_API.post p, {}, (d) ->
_OS.boot()
, () ->
alert "Resource not found #{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() if f
, (e, s) ->
_courrier.osfail "Fail to make request: #{p}", e, s
f() if f
dbquery: (cmd, d, c) ->
path = "#{_REST}/db/#{cmd}"
_API.post path, d, c, (e, s) ->
_courrier.osfail "Fail to query data from database: #{path}", e, s

View File

@ -1,6 +0,0 @@
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

@ -0,0 +1,366 @@
{
"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"
,
"{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

@ -0,0 +1,419 @@
{
"About":"À propos de",
"About: {0}":"À propos de: {0}",
"Add category":"Ajouter une catégorie",
"Add repository":"Ajouter un dépôt",
"Address":"Adresse",
"Alive (ms)":"Actif (ms)",
"Application installed":"Application installée",
"Application {0} is not executable":"L'application {0} n'est pas exécutable",
"Application":"Application",
"Applications":"Applications",
"Authentication":"Authentification",
"Cancel":"Annuler",
"Cannot Edit category":"Impossible d'éditer la catégorie",
"Cannot add new category":"Impossible d'ajouter une nouvelle catégorie",
"Cannot create {0}":"Impossible de créer {0}",
"Cannot delete all content of: {0} [{1}]":"Impossible de supprimer tout le contenu de: {0} [{1}]",
"Cannot delete the category: {0} [{1}]":"Impossible de supprimer la catégorie: {0} [{1}]",
"Cannot delete the section: {0}":"Impossible de supprimer la section: {0}",
"Cannot delete: {0}":"Impossible de supprimer: {0}",
"Cannot down load the app {0}":"Impossible de télécharger l'application {0}",
"Cannot export file for embedding to text":"Impossible d'exporter le fichier pour l'intégrer au texte",
"Cannot fetch CV categories":"Impossible d'extraire les catégories de CV",
"Cannot fetch the entry content":"Impossible d'extraire le contenu",
"Cannot fetch user data":"Impossible d'extraire les données utilisateur",
"Cannot install {0}":"Impossible d'installer {0}",
"Cannot load 3rd library at: {0}":"Impossible de charger la bibliothèque à: {0}",
"Cannot move section":"Impossible de déplacer la section",
"Cannot read service script: {0}":"Impossible de lire le script de service: {0}",
"Cannot render the PDF file":"Impossible de rendre le fichier PDF",
"Cannot save blog: {0}":"Impossible d'enregistrer le blog: {0}",
"Cannot save section: {0}":"Impossible d'enregistrer la section: {0}",
"Cannot save system setting":"Impossible d'enregistrer les paramètres du système",
"Cannot save user data":"Impossible d'enregistrer les données utilisateur",
"Cannot share file: {0}":"Impossible de partager le fichier: {0}",
"Cannot uninstall package: {0}":"Impossible de désinstaller le package: {0}",
"Categories":"Catégories",
"Clear all":"Tout effacer",
"Close tab":"Fermer l'onglet",
"Close without saving ?":"Fermer sans enregistrer?",
"Close":"Fermer",
"Copy not yet implemented":"Copie non encore implémentée",
"Copy":"Copier",
"Created: {0}":"Créé: {0}",
"Cut":"Couper",
"Delete a post":"Supprimer le text",
"Delete category":"Supprimer la catégorie",
"Delete section":"Supprimer la section",
"Delete":"Supprimer",
"Desktop":"Bureau",
"Dialog {0} not found":"La boîte de dialogue {0} n'a pas été trouvée",
"Do you really want to delete this post ?":"Voulez-vous vraiment supprimer ce texte ?",
"Do you really want to delete: {0}?":"Voulez-vous vraiment supprimer: {0}?",
"Download":"Télécharger",
"Edit category":"Modifier la catégorie",
"Edit repository":"Modifier le dépôt",
"Edit":"Modifier",
"Email":"Email",
"Error find app by mimes {0}":"Erreur lors de la recherche d'application par mimes {0}",
"Error reading package meta data: {0}":"Erreur lors de la lecture des métadonnées du package: {0}",
"Error saving file {0}":"Erreur lors de l'enregistrement du fichier {0}",
"Exit":"Quitter",
"Fail to create directory: {0}":"Impossible de créer le répertoire: {0}",
"Fail to create {0}: {1}":"Échec de la création de {0}: {1}",
"Fail to delete {0}: {1}":"Échec de la suppression de {0}: {1}",
"Fail to delete: {0}":"Échec de la suppression: {0}",
"Fail to fetch packages list from: {0}":"Impossible d'extraire la liste des paquets de: {0}",
"Fail to get file meta data: {0}":"Échec d'obtention des métadonnées de fichier: {0}",
"Fail to make request: {0}":"Echec de la demande: {0}",
"Fail to move file: {0} -> {1}":"Échec du déplacement du fichier: {0} -> {1}",
"Fail to paste: {0}":"Impossible de coller: {0}",
"Fail to publish file: {0}":"Échec de la publication du fichier: {0}",
"Fail to query data from database: {0}":"Impossible d'interroger les données de la base de données: {0}",
"Fail to read file: {0}":"Impossible de lire le fichier: {0}",
"Fail to rename to {0}: {1}":"Impossible de renommer en {0}: {1}",
"Fail to scan directory: {0}":"Échec de l'analyse du répertoire: {0}",
"Fail to upload file to: {0}":"Impossible de télécharger le fichier vers: {0}",
"Fail to upload to {0}: {1}":"Impossible de télécharger vers {0}: {1}",
"Fail to write to file: {0}":"Échec de l'écriture dans le fichier: {0}",
"Fail to {0} package":"Échec du {0} paquet",
"File name":"Nom de fichier",
"File not found {0}":"Fichier introuvable {0}",
"File {0} copied":"Fichier {0} copié",
"File {0} cut":"Fichier {0} coupé",
"File":"Fichier",
"Folder name":"Nom de dossier",
"Format : [name] url":"Format: [nom] url",
"Found {0} sections":"{0} sections trouvées",
"From":"De",
"Full name must be entered":"Le nom complet doit être entré",
"Full name":"Nom complet",
"Google Drive":"Google Drive",
"Hidden files":"Fichiers cachés",
"Home":"Accueil",
"Icon view":"Vue d'icône",
"Ignore all {0} unsaved files ?":"Ignorer tous les {0} fichiers non enregistrés?",
"Install":"Installer",
"Invalid package: Meta data file not found":"Paquet invalide: Fichier de métadonnées non trouvé",
"Kill process":"Tuer le processus",
"Language file {0} not found":"Fichier de langue {0} introuvable",
"Launch":"Lancer",
"List view":"Vue de liste",
"Location":"Localisation",
"Log out":"Déconnecter",
"Logout":"Déconnecter",
"Mime type {0} is not supported":"Le type MIME {0} n'est pas supporté",
"Modify section entry":"Modifier le texte",
"Move to":"Déplacer vers",
"Name":"Nom",
"Navigation bar":"Barre de navigation",
"New file":"Nouveau fichier",
"New folder":"Nouveau dossier",
"New section entry for {0}":"Nouvelle entrée de section pour {0}",
"New":"Nouveau",
"No application available to open {0}":"Aucune application disponible pour ouvrir {0}",
"No post found: {0}":"Aucun texte trouvé: {0}",
"No":"Non",
"OS":"OS",
"Ok":"Oui",
"Only {0} could be selected":"Seul {0} peut être sélectionné",
"Open file":"Ouvrir un fichier",
"Open with":"Ouvrir avec",
"Open":"Ouvrir",
"Options":"Options",
"Package uninstalled":"Package désinstallé",
"Parent can not be the category itself":"Parent ne peut pas être la catégorie elle-même",
"Paste":"Coller",
"Phone":"Phone",
"Pid":"Pid",
"Please enter category name":"Veuillez entrer le nom de la catégorie",
"Please enter tags":"Veuillez entrer les tags",
"Please insert a title in the text: beginning with heading":"Veuillez insérer un titre dans le texte: en commençant par le heading",
"Please select a category":"Veuillez sélectionner une catégorie",
"Please select a date":"Veuillez sélectionner une date",
"Please select a file":"Veuillez sélectionner un fichier",
"Please select a parent category":"Veuillez sélectionner une catégorie parente",
"Please select a section to edit":"Veuillez sélectionner une section à modifier",
"Please select a section to move":"Veuillez sélectionner une section à déplacer",
"Preview":"Aperçu",
"Properties":"Propriétés",
"Quit without saving ?":"Quitter sans sauvegarder ?",
"Quit":"Quitter",
"Read more":"Lire la suite",
"Refresh":"Rafraîchir",
"Rename":"Renommer",
"Repositories":"Dépôts",
"Resource not found {0}":"Ressource non trouvée {0}",
"Resource not found: {0}":"Ressource non trouvée: {0}",
"Row {0}, col {1}, lines: {2}":"Range {0}, col {1}, lignes: {2}",
"Save as":"Enregistrer sous",
"Save":"sauvegarder",
"Section list is empty, please add one":"La liste des sections est vide, veuillez en ajouter une",
"Select image file":"Sélectionnez le fichier image",
"Service":"Service",
"Share file":"Partagez le fichier",
"Shared url: {0}":"URL partagée: {0}",
"Shared":"partagé",
"Short biblio":"Biblio court",
"Sidebar":"Barre latérale",
"Subtitle":"Sous-titre",
"System fail: Cannot init desktop manager":"Échec du système: impossible de démarrer le gestionnaire de bureau",
"System fail: Cannot init login screen":"Echec du système: Impossible de se connecter à l'écran de connexion",
"Tags":"Tags",
"This feature is not implemented yet":"Cette fonctionnalité n'est pas encore implémentée",
"Title or content must not be blank":"Le titre ou le contenu ne doit pas être vide",
"Title":"Titre",
"Toggle Full screen":"Basculer en plein écran",
"Tree view":"Vue de l'arbre",
"Type":"Type",
"Uninstall : {0}?":"Désinstaller: {0}?",
"Uninstall":"Désinstaller",
"Unknown API setting for {0}":"Paramètre d'API inconnu pour {0}",
"Updated: {0}":"Actualisé: {0}",
"Upload":"Télécharger",
"Url":"Url",
"User abort the authentication":"L'utilisateur annule l'authentification",
"User data updated":"Données utilisateur mises à jour",
"VDB Unknown condition for delete command":"VDB Condition inconnue pour la commande de suppression",
"VFS Cannot encode file: {0}":"VFS Impossible de coder le fichier: {0}",
"VFS cannot create : {0}":"VFS ne peut pas créer: {0}",
"VFS cannot delete : {0}":"VFS ne peut pas supprimer: {0}",
"VFS cannot download file : {0}":"VFS ne peut pas télécharger le fichier: {0}",
"VFS cannot get meta data for {0}":"VFS ne peut pas obtenir de métadonnées pour {0}",
"VFS cannot init {0}: {1}":"VFS ne peut pas init {0}: {1}",
"VFS cannot move : {0}":"VFS ne peut pas déplacer: {0}",
"VFS cannot read : {0}":"VFS ne peut pas lire: {0}",
"VFS cannot save : {0}":"VFS ne peut pas enregistrer: {0}",
"VFS cannot write : {0}":"VFS ne peut pas écrire: {0}",
"VFS unknown action: {0}":"Action inconnue VFS: {0}",
"VFS unknown handler: {0}":"Gestionnaire inconnu VFS: {0}",
"View":"Vue",
"Would you like to login to {0}?":"Voulez-vous vous connecter à {0}?",
"Wrong format: it should be [name] url":"Mauvais format: il devrait être [nom] url",
"Yes":"Oui",
"{0} is not a directory":"{0} n'est pas un répertoire"
,
"April":"Avril",
"August":"août",
"December":"Décembre",
"February":"Février",
"Fri":"Ven",
"January":"Janvier",
"July":"Juillet",
"June":"Juin",
"March":"Mars",
"May":"Mai",
"Mon":"Lun",
"November":"Novembre",
"October":"Octobre",
"Sat":"Sam",
"Selected: {0} ({1} bytes)":"Sélectionné: {0} ({1} octets)",
"September":"Septembre",
"Size":"Taille",
"Sun":"Dim",
"Thu":"Jeu",
"Tue":"Mar",
"Wed":"Mer"
,
"Add application":"Ajouter une application",
"Add mount point":"Ajouter un point de montage",
"Add service":"Ajouter un service",
"Appearance":"Apparence",
"Cannot fetch system locales: {0}":"Impossible d'extraire les paramètres régionaux du système: {0}",
"Cannot read wallpaper list from {0}":"Impossible de lire la liste des fonds d'écran à partir de {0}",
"Cannot save system setting: {0}":"Impossible d'enregistrer le paramètre système: {0}",
"Desktop path":"Chemin du bureau",
"Edit mount point":"Modifier le point de montage",
"Languages":"Langues",
"Local packages path":"Chemin d'accès aux packages locaux",
"Mount points":"Points de montage",
"Path":"Chemin",
"Please enter mount point name":"Veuillez entrer le nom du point de montage",
"Please select a directory":"Veuillez sélectionner un répertoire",
"Please select an entry":"Veuillez sélectionner une entrée",
"Remove: {0}?":"Supprimer: {0}?",
"Remove":"Supprimer",
"Select a directory":"Sélectionnez un répertoire",
"Startup applications":"Applications de démarrage",
"Startup services":"Services de démarrage",
"Startup":"Démarrage",
"System locale":"Paramètres régionaux du système",
"System setting saved":"Paramètre système enregistré",
"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"
,
"{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

@ -0,0 +1,307 @@
{
"About":"À propos de",
"About: {0}":"À propos de: {0}",
"Add category":"Ajouter une catégorie",
"Add repository":"Ajouter un dépôt",
"Address":"Adresse",
"Alive (ms)":"Actif (ms)",
"Application installed":"Application installée",
"Application {0} is not executable":"L'application {0} n'est pas exécutable",
"Application":"Application",
"Applications":"Applications",
"Authentication":"Authentification",
"Cancel":"Annuler",
"Cannot Edit category":"Impossible d'éditer la catégorie",
"Cannot add new category":"Impossible d'ajouter une nouvelle catégorie",
"Cannot create {0}":"Impossible de créer {0}",
"Cannot delete all content of: {0} [{1}]":"Impossible de supprimer tout le contenu de: {0} [{1}]",
"Cannot delete the category: {0} [{1}]":"Impossible de supprimer la catégorie: {0} [{1}]",
"Cannot delete the section: {0}":"Impossible de supprimer la section: {0}",
"Cannot delete: {0}":"Impossible de supprimer: {0}",
"Cannot down load the app {0}":"Impossible de télécharger l'application {0}",
"Cannot export file for embedding to text":"Impossible d'exporter le fichier pour l'intégrer au texte",
"Cannot fetch CV categories":"Impossible d'extraire les catégories de CV",
"Cannot fetch the entry content":"Impossible d'extraire le contenu",
"Cannot fetch user data":"Impossible d'extraire les données utilisateur",
"Cannot install {0}":"Impossible d'installer {0}",
"Cannot load 3rd library at: {0}":"Impossible de charger la bibliothèque à: {0}",
"Cannot move section":"Impossible de déplacer la section",
"Cannot read service script: {0}":"Impossible de lire le script de service: {0}",
"Cannot render the PDF file":"Impossible de rendre le fichier PDF",
"Cannot save blog: {0}":"Impossible d'enregistrer le blog: {0}",
"Cannot save section: {0}":"Impossible d'enregistrer la section: {0}",
"Cannot save system setting":"Impossible d'enregistrer les paramètres du système",
"Cannot save user data":"Impossible d'enregistrer les données utilisateur",
"Cannot share file: {0}":"Impossible de partager le fichier: {0}",
"Cannot uninstall package: {0}":"Impossible de désinstaller le package: {0}",
"Categories":"Catégories",
"Clear all":"Tout effacer",
"Close tab":"Fermer l'onglet",
"Close without saving ?":"Fermer sans enregistrer?",
"Close":"Fermer",
"Copy not yet implemented":"Copie non encore implémentée",
"Copy":"Copier",
"Created: {0}":"Créé: {0}",
"Cut":"Couper",
"Delete a post":"Supprimer le text",
"Delete category":"Supprimer la catégorie",
"Delete section":"Supprimer la section",
"Delete":"Supprimer",
"Desktop":"Bureau",
"Dialog {0} not found":"La boîte de dialogue {0} n'a pas été trouvée",
"Do you really want to delete this post ?":"Voulez-vous vraiment supprimer ce texte ?",
"Do you really want to delete: {0}?":"Voulez-vous vraiment supprimer: {0}?",
"Download":"Télécharger",
"Edit category":"Modifier la catégorie",
"Edit repository":"Modifier le dépôt",
"Edit":"Modifier",
"Email":"Email",
"Error find app by mimes {0}":"Erreur lors de la recherche d'application par mimes {0}",
"Error reading package meta data: {0}":"Erreur lors de la lecture des métadonnées du package: {0}",
"Error saving file {0}":"Erreur lors de l'enregistrement du fichier {0}",
"Exit":"Quitter",
"Fail to create directory: {0}":"Impossible de créer le répertoire: {0}",
"Fail to create {0}: {1}":"Échec de la création de {0}: {1}",
"Fail to delete {0}: {1}":"Échec de la suppression de {0}: {1}",
"Fail to delete: {0}":"Échec de la suppression: {0}",
"Fail to fetch packages list from: {0}":"Impossible d'extraire la liste des paquets de: {0}",
"Fail to get file meta data: {0}":"Échec d'obtention des métadonnées de fichier: {0}",
"Fail to make request: {0}":"Echec de la demande: {0}",
"Fail to move file: {0} -> {1}":"Échec du déplacement du fichier: {0} -> {1}",
"Fail to paste: {0}":"Impossible de coller: {0}",
"Fail to publish file: {0}":"Échec de la publication du fichier: {0}",
"Fail to query data from database: {0}":"Impossible d'interroger les données de la base de données: {0}",
"Fail to read file: {0}":"Impossible de lire le fichier: {0}",
"Fail to rename to {0}: {1}":"Impossible de renommer en {0}: {1}",
"Fail to scan directory: {0}":"Échec de l'analyse du répertoire: {0}",
"Fail to upload file to: {0}":"Impossible de télécharger le fichier vers: {0}",
"Fail to upload to {0}: {1}":"Impossible de télécharger vers {0}: {1}",
"Fail to write to file: {0}":"Échec de l'écriture dans le fichier: {0}",
"Fail to {0} package":"Échec du {0} paquet",
"File name":"Nom de fichier",
"File not found {0}":"Fichier introuvable {0}",
"File {0} copied":"Fichier {0} copié",
"File {0} cut":"Fichier {0} coupé",
"File":"Fichier",
"Folder name":"Nom de dossier",
"Format : [name] url":"Format: [nom] url",
"Found {0} sections":"{0} sections trouvées",
"From":"De",
"Full name must be entered":"Le nom complet doit être entré",
"Full name":"Nom complet",
"Google Drive":"Google Drive",
"Hidden files":"Fichiers cachés",
"Home":"Accueil",
"Icon view":"Vue d'icône",
"Ignore all {0} unsaved files ?":"Ignorer tous les {0} fichiers non enregistrés?",
"Install":"Installer",
"Invalid package: Meta data file not found":"Paquet invalide: Fichier de métadonnées non trouvé",
"Kill process":"Tuer le processus",
"Language file {0} not found":"Fichier de langue {0} introuvable",
"Launch":"Lancer",
"List view":"Vue de liste",
"Location":"Localisation",
"Log out":"Déconnecter",
"Logout":"Déconnecter",
"Mime type {0} is not supported":"Le type MIME {0} n'est pas supporté",
"Modify section entry":"Modifier le texte",
"Move to":"Déplacer vers",
"Name":"Nom",
"Navigation bar":"Barre de navigation",
"New file":"Nouveau fichier",
"New folder":"Nouveau dossier",
"New section entry for {0}":"Nouvelle entrée de section pour {0}",
"New":"Nouveau",
"No application available to open {0}":"Aucune application disponible pour ouvrir {0}",
"No post found: {0}":"Aucun texte trouvé: {0}",
"No":"Non",
"OS":"OS",
"Ok":"Oui",
"Only {0} could be selected":"Seul {0} peut être sélectionné",
"Open file":"Ouvrir un fichier",
"Open with":"Ouvrir avec",
"Open":"Ouvrir",
"Options":"Options",
"Package uninstalled":"Package désinstallé",
"Parent can not be the category itself":"Parent ne peut pas être la catégorie elle-même",
"Paste":"Coller",
"Phone":"Phone",
"Pid":"Pid",
"Please enter category name":"Veuillez entrer le nom de la catégorie",
"Please enter tags":"Veuillez entrer les tags",
"Please insert a title in the text: beginning with heading":"Veuillez insérer un titre dans le texte: en commençant par le heading",
"Please select a category":"Veuillez sélectionner une catégorie",
"Please select a date":"Veuillez sélectionner une date",
"Please select a file":"Veuillez sélectionner un fichier",
"Please select a parent category":"Veuillez sélectionner une catégorie parente",
"Please select a section to edit":"Veuillez sélectionner une section à modifier",
"Please select a section to move":"Veuillez sélectionner une section à déplacer",
"Preview":"Aperçu",
"Properties":"Propriétés",
"Quit without saving ?":"Quitter sans sauvegarder ?",
"Quit":"Quitter",
"Read more":"Lire la suite",
"Refresh":"Rafraîchir",
"Rename":"Renommer",
"Repositories":"Dépôts",
"Resource not found {0}":"Ressource non trouvée {0}",
"Resource not found: {0}":"Ressource non trouvée: {0}",
"Row {0}, col {1}, lines: {2}":"Range {0}, col {1}, lignes: {2}",
"Save as":"Enregistrer sous",
"Save":"sauvegarder",
"Section list is empty, please add one":"La liste des sections est vide, veuillez en ajouter une",
"Select image file":"Sélectionnez le fichier image",
"Service":"Service",
"Share file":"Partagez le fichier",
"Shared url: {0}":"URL partagée: {0}",
"Shared":"partagé",
"Short biblio":"Biblio court",
"Sidebar":"Barre latérale",
"Subtitle":"Sous-titre",
"System fail: Cannot init desktop manager":"Échec du système: impossible de démarrer le gestionnaire de bureau",
"System fail: Cannot init login screen":"Echec du système: Impossible de se connecter à l'écran de connexion",
"Tags":"Tags",
"This feature is not implemented yet":"Cette fonctionnalité n'est pas encore implémentée",
"Title or content must not be blank":"Le titre ou le contenu ne doit pas être vide",
"Title":"Titre",
"Toggle Full screen":"Basculer en plein écran",
"Tree view":"Vue de l'arbre",
"Type":"Type",
"Uninstall : {0}?":"Désinstaller: {0}?",
"Uninstall":"Désinstaller",
"Unknown API setting for {0}":"Paramètre d'API inconnu pour {0}",
"Updated: {0}":"Actualisé: {0}",
"Upload":"Télécharger",
"Url":"Url",
"User abort the authentication":"L'utilisateur annule l'authentification",
"User data updated":"Données utilisateur mises à jour",
"VDB Unknown condition for delete command":"VDB Condition inconnue pour la commande de suppression",
"VFS Cannot encode file: {0}":"VFS Impossible de coder le fichier: {0}",
"VFS cannot create : {0}":"VFS ne peut pas créer: {0}",
"VFS cannot delete : {0}":"VFS ne peut pas supprimer: {0}",
"VFS cannot download file : {0}":"VFS ne peut pas télécharger le fichier: {0}",
"VFS cannot get meta data for {0}":"VFS ne peut pas obtenir de métadonnées pour {0}",
"VFS cannot init {0}: {1}":"VFS ne peut pas init {0}: {1}",
"VFS cannot move : {0}":"VFS ne peut pas déplacer: {0}",
"VFS cannot read : {0}":"VFS ne peut pas lire: {0}",
"VFS cannot save : {0}":"VFS ne peut pas enregistrer: {0}",
"VFS cannot write : {0}":"VFS ne peut pas écrire: {0}",
"VFS unknown action: {0}":"Action inconnue VFS: {0}",
"VFS unknown handler: {0}":"Gestionnaire inconnu VFS: {0}",
"View":"Vue",
"Would you like to login to {0}?":"Voulez-vous vous connecter à {0}?",
"Wrong format: it should be [name] url":"Mauvais format: il devrait être [nom] url",
"Yes":"Oui",
"{0} is not a directory":"{0} n'est pas un répertoire"
,
"April":"Avril",
"August":"août",
"December":"Décembre",
"February":"Février",
"Fri":"Ven",
"January":"Janvier",
"July":"Juillet",
"June":"Juin",
"March":"Mars",
"May":"Mai",
"Mon":"Lun",
"November":"Novembre",
"October":"Octobre",
"Sat":"Sam",
"Selected: {0} ({1} bytes)":"Sélectionné: {0} ({1} octets)",
"September":"Septembre",
"Size":"Taille",
"Sun":"Dim",
"Thu":"Jeu",
"Tue":"Mar",
"Wed":"Mer"
,
"Add application":"Ajouter une application",
"Add mount point":"Ajouter un point de montage",
"Add service":"Ajouter un service",
"Appearance":"Apparence",
"Cannot fetch system locales: {0}":"Impossible d'extraire les paramètres régionaux du système: {0}",
"Cannot read wallpaper list from {0}":"Impossible de lire la liste des fonds d'écran à partir de {0}",
"Cannot save system setting: {0}":"Impossible d'enregistrer le paramètre système: {0}",
"Desktop path":"Chemin du bureau",
"Edit mount point":"Modifier le point de montage",
"Languages":"Langues",
"Local packages path":"Chemin d'accès aux packages locaux",
"Mount points":"Points de montage",
"Path":"Chemin",
"Please enter mount point name":"Veuillez entrer le nom du point de montage",
"Please select a directory":"Veuillez sélectionner un répertoire",
"Please select an entry":"Veuillez sélectionner une entrée",
"Remove: {0}?":"Supprimer: {0}?",
"Remove":"Supprimer",
"Select a directory":"Sélectionnez un répertoire",
"Startup applications":"Applications de démarrage",
"Startup services":"Services de démarrage",
"Startup":"Démarrage",
"System locale":"Paramètres régionaux du système",
"System setting saved":"Paramètre système enregistré",
"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"
}

67
src/core/languages/gen.sh Executable file
View File

@ -0,0 +1,67 @@
#!/bin/bash
# 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/.
ord() {
LC_CTYPE=C printf '%d' "'$1"
}
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
echo "Ignore: $SUBSTRING"
else
echo -e "\t\"$SUBSTRING\":\"$SUBSTRING\"," >> "tmp.json"
fi
done
grep --include=\*.{ts,html} -roh "$1" -e '\"__([^\"]*)\"' | while read -r line; do
len=$(( ${#line} - 6 ))
#echo $len
#echo $line
SUBSTRING=${line:4:len}
#echo $SUBSTRING
if test -f "$2" && [ ! -z "$(grep -F "\"$SUBSTRING\":" "$2")" ]
then
echo "Ignore: $SUBSTRING"
else
echo -e "\t\"$SUBSTRING\":\"$SUBSTRING\"," >> "tmp.json"
fi
done
if test -f tmp.json
then
sort tmp.json > tmp1.json
awk '!a[$0]++' "tmp1.json" > tmp.json
sed '$ s/.$//' tmp.json > tmp1.json
# remove duplicate entry
if test -f $2
then
cp $2 "$2.old"
sed '$ s/.$//' $2 > tmp.json
cat tmp.json > $2
echo "," >> $2
cat tmp1.json >> $2
echo "}" >> "$2"
else
echo "{"> "$2"
cat tmp1.json >> "$2"
echo "}" >> "$2"
fi
rm tmp.json tmp1.json
else
echo "Nothing change"
fi

View File

@ -0,0 +1,458 @@
{
"About":"Thông tin",
"About: {0}":"Thông tin: {0}",
"Add category":"Thêm danh mục",
"Add repository":"Thêm kho ứng dụng",
"Address":"Địa chỉ",
"Alive (ms)":"Hoạt động (ms)",
"Application installed":"Ứng dụng đã được cà đặt",
"Application {0} is not executable":"Ứng dụng {0} không thực thi được",
"Application":"Ứng dụng",
"Applications":"Các ứng dụng",
"Authentication":"Xác thực",
"Cancel":"Huỷ",
"Cannot Edit category":"Không thể sửa danh mục",
"Cannot add new category":"Không thể tạo mới danh mục",
"Cannot create {0}":"Không thể tạo {0}",
"Cannot delete all content of: {0} [{1}]":"Không thể xoá hết nội dung của: {0} [{1}]",
"Cannot delete the category: {0} [{1}]":"Không thể xoá danh mục: {0} [{1}]",
"Cannot delete the section: {0}":"Không thể xoá mục: {0}",
"Cannot delete: {0}":"Không thể xoá: {0}",
"Cannot down load the app {0}":"Không thể tải xuống ứng dụng {0}",
"Cannot export file for embedding to text":"Không thể xuất bản file để chèn vào text",
"Cannot fetch CV categories":"Không thể lấy danh mục CV",
"Cannot fetch the entry content":"Không thể truy vấn nội dung của mục",
"Cannot fetch user data":"Không thể truy vấn thông tin người dùng",
"Cannot install {0}":"Không thể cài đặt {0}",
"Cannot load 3rd library at: {0}":"Không thể tải về thư viện bên thứ 3: {0}",
"Cannot move section":"Không thể di chuyển mục",
"Cannot read service script: {0}":"Không thể tải về script của dịch vụ: {0}",
"Cannot render the PDF file":"Không thể hiển thị file PDF",
"Cannot save blog: {0}":"Không thể lưu blog: {0}",
"Cannot save section: {0}":"Không thể lưu mục: {0}",
"Cannot save system setting":"Không thể lưu cài đặt hệ thống",
"Cannot save user data":"Không thể lưu thông tin người dùng",
"Cannot share file: {0}":"Không thể chia sẻ file: {0}",
"Cannot uninstall package: {0}":"Không thể gỡ cài đặt ứng dụng: {0}",
"Categories":"Các danh mục",
"Clear all":"Xoá tất cả",
"Close tab":"Đóng tab",
"Close without saving ?":"Đóng không cần lưu ?",
"Close":"Đóng",
"Copy not yet implemented":"Chức năng copy chưa được cài đặt",
"Copy":"Copy",
"Created: {0}":"Tạo: {0}",
"Cut":"Cắt",
"Delete a post":"Xoá bài",
"Delete category":"Xoá danh mục",
"Delete section":"Xoá mục",
"Delete":"Xoá",
"Desktop":"Desktop",
"Dialog {0} not found":"Không tìm thấy hộp thoại {0}",
"Do you really want to delete this post ?":"Bạn muốn xoá bài này ?",
"Do you really want to delete: {0}?":"Bạn muốn xoá: {0}?",
"Download":"Tải về",
"Edit category":"Sửa danh mục",
"Edit repository":"Sửa kho ứng dụng",
"Edit":"Sửa",
"Email":"Email",
"Error find app by mimes {0}":"Lỗi khi tìm app bằng mime {0}",
"Error reading package meta data: {0}":"Lỗi khi đọc thông tin ứng dụng: {0}",
"Error saving file {0}":"Lỗi khi lưu file {0}",
"Exit":"Thoát",
"Fail to create directory: {0}":"Lỗi khi tạo folder: {0}",
"Fail to create {0}: {1}":"Lỗi khi tạo {0}: {1}",
"Fail to delete {0}: {1}":"Lỗi khi xoá {0}: {1}",
"Fail to delete: {0}":"Lỗi khi xoá: {0}",
"Fail to fetch packages list from: {0}":"Lỗi khi tải về danh sách ứng dụng từ: {0}",
"Fail to get file meta data: {0}":"Lỗi truy vấn thông tin file: {0}",
"Fail to make request: {0}":"Lỗi truy vấn: {0}",
"Fail to move file: {0} -> {1}":"Lỗi di chuyển file: {0} -> {1}",
"Fail to paste: {0}":"Lỗi khi dán: {0}",
"Fail to publish file: {0}":"Lỗi khi xuất bản file: {0}",
"Fail to query data from database: {0}":"Lỗi truy vấn cơ sở dữ liệu: {0}",
"Fail to read file: {0}":"Lỗi đọc file: {0}",
"Fail to rename to {0}: {1}":"Lỗi khi đổi tên file {0}: {1}",
"Fail to scan directory: {0}":"Lỗi khi quét thư mục: {0}",
"Fail to upload file to: {0}":"Lỗi khi tải file lên: {0}",
"Fail to upload to {0}: {1}":"Lỗi khi tải file lên {0}: {1}",
"Fail to write to file: {0}":"Lỗi khi ghi file: {0}",
"Fail to {0} package":"Lỗi {0} ứng dụng",
"File name":"Tên file",
"File not found {0}":"Không tìm thấy file {0}",
"File {0} copied":"Đã copy file {0}",
"File {0} cut":"Đã cắt file {0}",
"File":"File",
"Folder name":"Tên thư mục",
"Format : [name] url":"Định dạng : [name] url",
"Found {0} sections":"Tìm thấy {0} mục",
"From":"Từ",
"Full name must be entered":"Vui lòng điền họ và tên",
"Full name":"Họ và tên",
"Google Drive":"Google Drive",
"Hidden files":"File ẩn",
"Home":"Home",
"Icon view":"Xêm theo icon",
"Ignore all {0} unsaved files ?":"Bỏ qua {0} file chưa lưu ?",
"Install":"Cài đặt",
"Invalid package: Meta data file not found":"Ứng dụng không hợp lệ: không tìm thấy metadata",
"Kill process":"Đóng process",
"Language file {0} not found":"Không tìm thấy ngôn ngữ {0}",
"Launch":"Khởi chạy",
"List view":"Xem theo list",
"Location":"Vị trí",
"Log out":"Đăng xuất",
"Logout":"Đăng xuất",
"Mime type {0} is not supported":"Mime type {0} không được hổ trợ",
"Modify section entry":"Sửa mục",
"Move to":"Di chuyển vào",
"Name":"Tên",
"Navigation bar":"Thanh điều hướng",
"New file":"File mới",
"New folder":"Thư mục mới",
"New section entry for {0}":"Thêm mới mục trong {0}",
"New":"Tạo mới",
"No application available to open {0}":"Không tìm thấy ứng dụng dể đọc {0}",
"No post found: {0}":"Không tìm thấy bài: {0}",
"No":"Không",
"OS":"OS",
"Ok":"Có",
"Only {0} could be selected":"Chỉ file với mine {0} mới được chọn",
"Open file":"Mở file",
"Open with":"Mở với",
"Open":"Mở",
"Options":"Tuỳ chọn",
"Package uninstalled":"Đã gỡ bỏ ứng dụng",
"Parent can not be the category itself":"Danh mục cha không được trùng với danh mục hiện tại",
"Paste":"Dán",
"Phone":"Số điện thoại",
"Pid":"Pid",
"Please enter category name":"Vui lòng nhập tên danh mục",
"Please enter tags":"Vui lòng nhập tags",
"Please insert a title in the text: beginning with heading":"Vuil lòng nhập tiêu đề: bắt đầu bài bằng heading",
"Please select a category":"Vui lòng chọn danh mục",
"Please select a date":"Vui lòng chọn ngày",
"Please select a file":"Vui lòng chọn file",
"Please select a parent category":"Vui lòng chọn danh much cha",
"Please select a section to edit":"Vui lòng chọn mục để sửa",
"Please select a section to move":"Vuil lòng chọn mục để di chuyển",
"Preview":"Preview",
"Properties":"Thông tin",
"Quit without saving ?":"Thoát mà không lưu ?",
"Quit":"Thoát",
"Read more":"Đọc thêm",
"Refresh":"Làm mới",
"Rename":"Đổi tên",
"Repositories":"Các kho ứng dụng",
"Resource not found {0}":"Không tìm thấy tài nguyên {0}",
"Resource not found: {0}":"Không tìm thấy tài nguyên: {0}",
"Row {0}, col {1}, lines: {2}":"Hàng {0}, cột {1}, dòng: {2}",
"Save as":"Lưu như",
"Save":"Lưu",
"Section list is empty, please add one":"Danh sách mục rỗng, vui lòng tạo mới",
"Select image file":"Chọn file ảnh",
"Service":"Dịch vụ",
"Share file":"Chia sẻ",
"Shared url: {0}":"Liên kết chia sẻ: {0}",
"Shared":"Đã chia sẻ",
"Short biblio":"Tiểu sử ngắn",
"Sidebar":"Sidebar",
"Subtitle":"Tiêu đề con",
"System fail: Cannot init desktop manager":"Lỗi hệ thống: không thể khởi chạy trình quản lý desktop",
"System fail: Cannot init login screen":"Lỗi hệ thống: không thể khởi chạy màn hình đăng nhập",
"Tags":"Tags",
"This feature is not implemented yet":"Chức năng này chưa được cài đặt",
"Title or content must not be blank":"Tiêu đề hoặc nội dụng không được để trống",
"Title":"Tiêu đề",
"Toggle Full screen":"Bật/tắt toàn màn hình",
"Tree view":"Xem theo cây",
"Type":"Loại",
"Uninstall : {0}?":"Gỡ cài đặt : {0}?",
"Uninstall":"Gỡ cài đặt",
"Unknown API setting for {0}":"Không tìm thấy cài đặt API của {0}",
"Updated: {0}":"Đã cập nhật: {0}",
"Upload":"Tải lên",
"Url":"Url",
"User abort the authentication":"Người dùng huỷ xác thực",
"User data updated":"Thông tin người dùng đã được cập nhật",
"VDB Unknown condition for delete command":"VDB không rõ điều kiện cho lệnh xoá",
"VFS Cannot encode file: {0}":"VFS Không thể mã hoá file: {0}",
"VFS cannot create : {0}":"VFS không thể tạo : {0}",
"VFS cannot delete : {0}":"VFS không thể xoá : {0}",
"VFS cannot download file : {0}":"VFS không thể tải về : {0}",
"VFS cannot get meta data for {0}":"VFS không thể truy vấn thông tin của {0}",
"VFS cannot init {0}: {1}":"VFS không thể khởi động {0}: {1}",
"VFS cannot move : {0}":"VFS không thể di chuyển : {0}",
"VFS cannot read : {0}":"VFS không thể đọc : {0}",
"VFS cannot save : {0}":"VFS không thể lưu : {0}",
"VFS cannot write : {0}":"VFS không thể viết : {0}",
"VFS unknown action: {0}":"VFS không rõ hành động : {0}",
"VFS unknown handler: {0}":"VFS không rõ trình xữ lý: {0}",
"View":"Xem",
"Would you like to login to {0}?":"Bạn có muốn đăng nhập vào {0}?",
"Wrong format: it should be [name] url":"Sai định dạng: định dạng hợp lệ [name] url",
"Yes":"Có",
"{0} is not a directory":"{0} không phải là thư mục"
,
"April":"Tháng 4",
"August":"Tháng 8",
"December":"Tháng 12",
"February":"Tháng hai",
"Fri":"Sáu",
"January":"Tháng một",
"July":"Tháng 7",
"June":"Tháng 6",
"March":"Tháng 3",
"May":"Tháng 5",
"Mon":"Hai",
"November":"Tháng 11",
"October":"Tháng 10",
"Sat":"Bảy",
"Selected: {0} ({1} bytes)":"Đã chọn: {0} ({1} bytes)",
"September":"Tháng 9",
"Size":"Kích cỡ",
"Sun":"CN",
"Thu":"Năm",
"Tue":"Ba",
"Wed":"Tư"
,
"Add":"Thêm",
"Cannot read wallpaper list from {0}":"Không thể truy vấn danh sách wallpaper {0}",
"Theme":"Chủ đề",
"Wallpaper":"Hình nền"
,
"Appearance":"Giao diện",
"Cannot fetch system locales: {0}":"Không thể truy vấn danh sách ngôn ngữ: {0}",
"Cannot save system setting: {0}":"Không thể lưu cài đặt hệ thống: {0}",
"Desktop path":"Đường dẫn đến desktop",
"Languages":"Ngôn ngữ",
"Local packages path":"Đường dẫn đến ứng dụng riêng",
"Mount points":"Điểm truy cập nhanh",
"Path":"Đường dẫn",
"Please enter mount point name":"Vui lòng nhập điểm truy cập",
"Please select a directory":"Vui lòng chọn một thư mục",
"Remove: {0}?":"xoá: {0}?",
"Remove":"xoá",
"Select a directory":"Chọn một thư mục",
"System locale":"Ngôn ngữ",
"System setting saved":"Đã lưu cài đặt hệ thống",
"VFS":"VFS"
,
"Add mount point":"Thêm điểm truy cập",
"Edit mount point":"Sửa điểm truy cập"
,
"Services":"Dịch vụ",
"Startup":"Khởi chạy"
,
"Add application":"Thêm ứng dụng",
"Add service":"Thêm dịch vụ",
"Please select an entry":"Vui lòng chọn 1 mục từ danh sách",
"Startup applications":"Ứng dụng khởi chạy",
"Startup services":"Dịch vụ khởi chạy"
,
"Cannot export to PNG in this browser: {0}":"Cannot export to PNG in this browser: {0}",
"Cannot export to {0}: {1}":"Cannot export to {0}: {1}",
"Cannot export: {0}":"Cannot export: {0}",
"Export as":"Export as",
"File exported":"File exported",
"Syntax error: {0}":"Syntax error: {0}"
,
"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}"
,
"{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

@ -0,0 +1,396 @@
{
"About":"Thông tin",
"About: {0}":"Thông tin: {0}",
"Add category":"Thêm danh mục",
"Add repository":"Thêm kho ứng dụng",
"Address":"Địa chỉ",
"Alive (ms)":"Hoạt động (ms)",
"Application installed":"Ứng dụng đã được cà đặt",
"Application {0} is not executable":"Ứng dụng {0} không thực thi được",
"Application":"Ứng dụng",
"Applications":"Các ứng dụng",
"Authentication":"Xác thực",
"Cancel":"Huỷ",
"Cannot Edit category":"Không thể sửa danh mục",
"Cannot add new category":"Không thể tạo mới danh mục",
"Cannot create {0}":"Không thể tạo {0}",
"Cannot delete all content of: {0} [{1}]":"Không thể xoá hết nội dung của: {0} [{1}]",
"Cannot delete the category: {0} [{1}]":"Không thể xoá danh mục: {0} [{1}]",
"Cannot delete the section: {0}":"Không thể xoá mục: {0}",
"Cannot delete: {0}":"Không thể xoá: {0}",
"Cannot down load the app {0}":"Không thể tải xuống ứng dụng {0}",
"Cannot export file for embedding to text":"Không thể xuất bản file để chèn vào text",
"Cannot fetch CV categories":"Không thể lấy danh mục CV",
"Cannot fetch the entry content":"Không thể truy vấn nội dung của mục",
"Cannot fetch user data":"Không thể truy vấn thông tin người dùng",
"Cannot install {0}":"Không thể cài đặt {0}",
"Cannot load 3rd library at: {0}":"Không thể tải về thư viện bên thứ 3: {0}",
"Cannot move section":"Không thể di chuyển mục",
"Cannot read service script: {0}":"Không thể tải về script của dịch vụ: {0}",
"Cannot render the PDF file":"Không thể hiển thị file PDF",
"Cannot save blog: {0}":"Không thể lưu blog: {0}",
"Cannot save section: {0}":"Không thể lưu mục: {0}",
"Cannot save system setting":"Không thể lưu cài đặt hệ thống",
"Cannot save user data":"Không thể lưu thông tin người dùng",
"Cannot share file: {0}":"Không thể chia sẻ file: {0}",
"Cannot uninstall package: {0}":"Không thể gỡ cài đặt ứng dụng: {0}",
"Categories":"Các danh mục",
"Clear all":"Xoá tất cả",
"Close tab":"Đóng tab",
"Close without saving ?":"Đóng không cần lưu ?",
"Close":"Đóng",
"Copy not yet implemented":"Chức năng copy chưa được cài đặt",
"Copy":"Copy",
"Created: {0}":"Tạo: {0}",
"Cut":"Cắt",
"Delete a post":"Xoá bài",
"Delete category":"Xoá danh mục",
"Delete section":"Xoá mục",
"Delete":"Xoá",
"Desktop":"Desktop",
"Dialog {0} not found":"Không tìm thấy hộp thoại {0}",
"Do you really want to delete this post ?":"Bạn muốn xoá bài này ?",
"Do you really want to delete: {0}?":"Bạn muốn xoá: {0}?",
"Download":"Tải về",
"Edit category":"Sửa danh mục",
"Edit repository":"Sửa kho ứng dụng",
"Edit":"Sửa",
"Email":"Email",
"Error find app by mimes {0}":"Lỗi khi tìm app bằng mime {0}",
"Error reading package meta data: {0}":"Lỗi khi đọc thông tin ứng dụng: {0}",
"Error saving file {0}":"Lỗi khi lưu file {0}",
"Exit":"Thoát",
"Fail to create directory: {0}":"Lỗi khi tạo folder: {0}",
"Fail to create {0}: {1}":"Lỗi khi tạo {0}: {1}",
"Fail to delete {0}: {1}":"Lỗi khi xoá {0}: {1}",
"Fail to delete: {0}":"Lỗi khi xoá: {0}",
"Fail to fetch packages list from: {0}":"Lỗi khi tải về danh sách ứng dụng từ: {0}",
"Fail to get file meta data: {0}":"Lỗi truy vấn thông tin file: {0}",
"Fail to make request: {0}":"Lỗi truy vấn: {0}",
"Fail to move file: {0} -> {1}":"Lỗi di chuyển file: {0} -> {1}",
"Fail to paste: {0}":"Lỗi khi dán: {0}",
"Fail to publish file: {0}":"Lỗi khi xuất bản file: {0}",
"Fail to query data from database: {0}":"Lỗi truy vấn cơ sở dữ liệu: {0}",
"Fail to read file: {0}":"Lỗi đọc file: {0}",
"Fail to rename to {0}: {1}":"Lỗi khi đổi tên file {0}: {1}",
"Fail to scan directory: {0}":"Lỗi khi quét thư mục: {0}",
"Fail to upload file to: {0}":"Lỗi khi tải file lên: {0}",
"Fail to upload to {0}: {1}":"Lỗi khi tải file lên {0}: {1}",
"Fail to write to file: {0}":"Lỗi khi ghi file: {0}",
"Fail to {0} package":"Lỗi {0} ứng dụng",
"File name":"Tên file",
"File not found {0}":"Không tìm thấy file {0}",
"File {0} copied":"Đã copy file {0}",
"File {0} cut":"Đã cắt file {0}",
"File":"File",
"Folder name":"Tên thư mục",
"Format : [name] url":"Định dạng : [name] url",
"Found {0} sections":"Tìm thấy {0} mục",
"From":"Từ",
"Full name must be entered":"Vui lòng điền họ và tên",
"Full name":"Họ và tên",
"Google Drive":"Google Drive",
"Hidden files":"File ẩn",
"Home":"Home",
"Icon view":"Xêm theo icon",
"Ignore all {0} unsaved files ?":"Bỏ qua {0} file chưa lưu ?",
"Install":"Cài đặt",
"Invalid package: Meta data file not found":"Ứng dụng không hợp lệ: không tìm thấy metadata",
"Kill process":"Đóng process",
"Language file {0} not found":"Không tìm thấy ngôn ngữ {0}",
"Launch":"Khởi chạy",
"List view":"Xem theo list",
"Location":"Vị trí",
"Log out":"Đăng xuất",
"Logout":"Đăng xuất",
"Mime type {0} is not supported":"Mime type {0} không được hổ trợ",
"Modify section entry":"Sửa mục",
"Move to":"Di chuyển vào",
"Name":"Tên",
"Navigation bar":"Thanh điều hướng",
"New file":"File mới",
"New folder":"Thư mục mới",
"New section entry for {0}":"Thêm mới mục trong {0}",
"New":"Tạo mới",
"No application available to open {0}":"Không tìm thấy ứng dụng dể đọc {0}",
"No post found: {0}":"Không tìm thấy bài: {0}",
"No":"Không",
"OS":"OS",
"Ok":"Có",
"Only {0} could be selected":"Chỉ file với mine {0} mới được chọn",
"Open file":"Mở file",
"Open with":"Mở với",
"Open":"Mở",
"Options":"Tuỳ chọn",
"Package uninstalled":"Đã gỡ bỏ ứng dụng",
"Parent can not be the category itself":"Danh mục cha không được trùng với danh mục hiện tại",
"Paste":"Dán",
"Phone":"Số điện thoại",
"Pid":"Pid",
"Please enter category name":"Vui lòng nhập tên danh mục",
"Please enter tags":"Vui lòng nhập tags",
"Please insert a title in the text: beginning with heading":"Vuil lòng nhập tiêu đề: bắt đầu bài bằng heading",
"Please select a category":"Vui lòng chọn danh mục",
"Please select a date":"Vui lòng chọn ngày",
"Please select a file":"Vui lòng chọn file",
"Please select a parent category":"Vui lòng chọn danh much cha",
"Please select a section to edit":"Vui lòng chọn mục để sửa",
"Please select a section to move":"Vuil lòng chọn mục để di chuyển",
"Preview":"Preview",
"Properties":"Thông tin",
"Quit without saving ?":"Thoát mà không lưu ?",
"Quit":"Thoát",
"Read more":"Đọc thêm",
"Refresh":"Làm mới",
"Rename":"Đổi tên",
"Repositories":"Các kho ứng dụng",
"Resource not found {0}":"Không tìm thấy tài nguyên {0}",
"Resource not found: {0}":"Không tìm thấy tài nguyên: {0}",
"Row {0}, col {1}, lines: {2}":"Hàng {0}, cột {1}, dòng: {2}",
"Save as":"Lưu như",
"Save":"Lưu",
"Section list is empty, please add one":"Danh sách mục rỗng, vui lòng tạo mới",
"Select image file":"Chọn file ảnh",
"Service":"Dịch vụ",
"Share file":"Chia sẻ",
"Shared url: {0}":"Liên kết chia sẻ: {0}",
"Shared":"Đã chia sẻ",
"Short biblio":"Tiểu sử ngắn",
"Sidebar":"Sidebar",
"Subtitle":"Tiêu đề con",
"System fail: Cannot init desktop manager":"Lỗi hệ thống: không thể khởi chạy trình quản lý desktop",
"System fail: Cannot init login screen":"Lỗi hệ thống: không thể khởi chạy màn hình đăng nhập",
"Tags":"Tags",
"This feature is not implemented yet":"Chức năng này chưa được cài đặt",
"Title or content must not be blank":"Tiêu đề hoặc nội dụng không được để trống",
"Title":"Tiêu đề",
"Toggle Full screen":"Bật/tắt toàn màn hình",
"Tree view":"Xem theo cây",
"Type":"Loại",
"Uninstall : {0}?":"Gỡ cài đặt : {0}?",
"Uninstall":"Gỡ cài đặt",
"Unknown API setting for {0}":"Không tìm thấy cài đặt API của {0}",
"Updated: {0}":"Đã cập nhật: {0}",
"Upload":"Tải lên",
"Url":"Url",
"User abort the authentication":"Người dùng huỷ xác thực",
"User data updated":"Thông tin người dùng đã được cập nhật",
"VDB Unknown condition for delete command":"VDB không rõ điều kiện cho lệnh xoá",
"VFS Cannot encode file: {0}":"VFS Không thể mã hoá file: {0}",
"VFS cannot create : {0}":"VFS không thể tạo : {0}",
"VFS cannot delete : {0}":"VFS không thể xoá : {0}",
"VFS cannot download file : {0}":"VFS không thể tải về : {0}",
"VFS cannot get meta data for {0}":"VFS không thể truy vấn thông tin của {0}",
"VFS cannot init {0}: {1}":"VFS không thể khởi động {0}: {1}",
"VFS cannot move : {0}":"VFS không thể di chuyển : {0}",
"VFS cannot read : {0}":"VFS không thể đọc : {0}",
"VFS cannot save : {0}":"VFS không thể lưu : {0}",
"VFS cannot write : {0}":"VFS không thể viết : {0}",
"VFS unknown action: {0}":"VFS không rõ hành động : {0}",
"VFS unknown handler: {0}":"VFS không rõ trình xữ lý: {0}",
"View":"Xem",
"Would you like to login to {0}?":"Bạn có muốn đăng nhập vào {0}?",
"Wrong format: it should be [name] url":"Sai định dạng: định dạng hợp lệ [name] url",
"Yes":"Có",
"{0} is not a directory":"{0} không phải là thư mục"
,
"April":"Tháng 4",
"August":"Tháng 8",
"December":"Tháng 12",
"February":"Tháng hai",
"Fri":"Sáu",
"January":"Tháng một",
"July":"Tháng 7",
"June":"Tháng 6",
"March":"Tháng 3",
"May":"Tháng 5",
"Mon":"Hai",
"November":"Tháng 11",
"October":"Tháng 10",
"Sat":"Bảy",
"Selected: {0} ({1} bytes)":"Đã chọn: {0} ({1} bytes)",
"September":"Tháng 9",
"Size":"Kích cỡ",
"Sun":"CN",
"Thu":"Năm",
"Tue":"Ba",
"Wed":"Tư"
,
"Add":"Thêm",
"Cannot read wallpaper list from {0}":"Không thể truy vấn danh sách wallpaper {0}",
"Theme":"Chủ đề",
"Wallpaper":"Hình nền"
,
"Appearance":"Giao diện",
"Cannot fetch system locales: {0}":"Không thể truy vấn danh sách ngôn ngữ: {0}",
"Cannot save system setting: {0}":"Không thể lưu cài đặt hệ thống: {0}",
"Desktop path":"Đường dẫn đến desktop",
"Languages":"Ngôn ngữ",
"Local packages path":"Đường dẫn đến ứng dụng riêng",
"Mount points":"Điểm truy cập nhanh",
"Path":"Đường dẫn",
"Please enter mount point name":"Vui lòng nhập điểm truy cập",
"Please select a directory":"Vui lòng chọn một thư mục",
"Remove: {0}?":"xoá: {0}?",
"Remove":"xoá",
"Select a directory":"Chọn một thư mục",
"System locale":"Ngôn ngữ",
"System setting saved":"Đã lưu cài đặt hệ thống",
"VFS":"VFS"
,
"Add mount point":"Thêm điểm truy cập",
"Edit mount point":"Sửa điểm truy cập"
,
"Services":"Dịch vụ",
"Startup":"Khởi chạy"
,
"Add application":"Thêm ứng dụng",
"Add service":"Thêm dịch vụ",
"Please select an entry":"Vui lòng chọn 1 mục từ danh sách",
"Startup applications":"Ứng dụng khởi chạy",
"Startup services":"Dịch vụ khởi chạy"
,
"Cannot export to PNG in this browser: {0}":"Cannot export to PNG in this browser: {0}",
"Cannot export to {0}: {1}":"Cannot export to {0}: {1}",
"Cannot export: {0}":"Cannot export: {0}",
"Export as":"Export as",
"File exported":"File exported",
"Syntax error: {0}":"Syntax error: {0}"
,
"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,9 +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>

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-top: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,66 +0,0 @@
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.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.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,240 +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 }</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
self.update()
}
self.root.get = function(k)
{
return self[k]
}
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,60 +0,0 @@
<afx-apps-dock>
<afx-button class = {selected: parent.selectedApp && app.pid == parent.selectedApp.pid} each={ items } icon = {icon} text = {text} onbtclick = {onbtclick}>
</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.get = function(k)
{
return self[k]
}
this.on("mount", function(){
window.OS.courrier.trigger("sysdockloaded")
})
</script>
</afx-apps-dock>

View File

@ -1,42 +0,0 @@
<afx-button>
<button disabled={ enable == "false" } onclick="{ _onbtclick }" ref = "mybtn" >
<afx-label color = {color} icon={icon} iconclass = {iconclass} text = {text} ></afx-label>
</button>
<script>
this.enable = opts.enable
this.icon = opts.icon
this.iconclass = opts.iconclass
this.color = opts.color
this.text = opts.text || ""
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})
}
}
</script>
</afx-button>

View File

@ -1,104 +0,0 @@
<afx-calendar-view>
<div><i class ="prevmonth" onclick={prevmonth}></i>{text}<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.text = months[self.month] + ' ' + self.year
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,3 +0,0 @@
<afx-dummy>
<yield/>
</afx-dummy>

View File

@ -1,221 +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>
<div if = {status == true} class = "status" ref = "stbar"></div>
<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).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))
})
})
$(self.refs.stbar).html("")
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()
//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)
$(self.refs.stbar).empty()
$(self.refs.stbar).append($("<span>").append("Selected: " + e.data.filename + " (" + e.data.size + " bytes)"))//.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()
})
})
</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,65 +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.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()
})
}
})
var calibrate_size = function()
{
var auto_width = []
var csize, ocwidth = 0, avaiheight;
avaiheight = $(self.root).height()
avaiWidth = $(self.root).width()
$(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,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("+icon+");background-size: 100% 100%;background-repeat: no-repeat;" }></i>
{ 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,206 +0,0 @@
<afx-list-view class = {dropdown: opts.dropdown == "true"}>
<div class = "list-container" ref = "container">
<div if = {opts.dropdown == "true"} ref = "current" style = {opts.width?"min-width:" + opts.width + "px;":"min-width:150px;"} onclick = {show_list}>
</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}</li>
</ul>
</li>
</ul>
</div>
<script>
this.items = opts.items || []
var self = this
self.selidx = -1
self.onlistselect = opts.onlistselect
self.onlistdbclick = opts.onlistdbclick
self.onitemclose = opts.onitemclose
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(){
if(opts.dropdown == "true")
{
$(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.current).html(it.text)
}
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,175 +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.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,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,83 +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
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"
}
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,53 +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("update", function(e){
self.swon = opts.swon
self.onchange = opts.onchange
})
toggle(e)
{
if(!self.enable) return
opts.swon = !self.swon
var data = {
id: self.rid,
data: opts.swon
}
if(self.onchange)
self.onchange(data)
if(self.root.observable)
self.root.observable.trigger("switch", data)
}
</script>
</afx-switch>

View File

@ -1,50 +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]
}
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,74 +0,0 @@
<afx-tab-container>
<afx-list-view ref = "list" />
<script>
var self = this
this.closable = opts.closable || false
self.root.observable = opts.observable || riot.observable()
self.ontabselect = opts.ontabselect
get_observable(){
return self.root.observable
}
self.root.get = function (k) {
return self.refs.list.root.get(k)
}
self.root.update = function(){
self.update(true)
}
self.on("mount", function(){
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-container>

View File

@ -1,172 +0,0 @@
<afx-tree-view>
<div class={afx_tree_item_selected:treeroot.selectedItem && treeroot.selectedItem.treepath == data.treepath, afx_folder_item: isFolder(), afx_tree_item_odd: index%2 != 0 } onclick={select} ondblclick = {_dbclick} oncontextmenu = {select}>
<ul style = "padding:0;margin:0;white-space: nowrap;">
<li ref = "padding" ></li>
<li class = "itemname" style="display:inline-block;" >
<i if={ !isFolder() && data.iconclass} class = {data.iconclass} ></i>
<i if={!isFolder() && data.icon} class="icon-style" style = { "background: url("+data.icon+");background-size: 100% 100%;background-repeat: no-repeat;" }></i>
<span onclick={ toggle } if={ isFolder() } class={open ? 'afx-tree-view-folder-open' : 'afx-tree-view-folder-close'}></span>
{ data.name }
</li>
</ul>
</div>
<ul if={ isFolder() } show={ isFolder() && open }>
<li each={ child, i in data.nodes }>
<afx-tree-view ontreeselect = {parent.ontreeselect} index = {i} fetch = {parent.fetch} ontreedbclick = {parent.ontreedbclick} data={child} indent={indent+1} observable = {parent.root.observable} path = {parent.data.treepath + ">" + i} treeroot= {parent.treeroot}></afx-tree-view>
</li>
</ul>
<script>
var self = this
self.open = true
self.data = { name:"", nodes:null, treepath: opts.path, i:-1}
if(opts.data)
{
self.data = opts.data
//self.name = opts.data.name
//self.nodes = opts.data.nodes
//self.icon = opts.data.icon
self.open = opts.data.open == undefined?true:opts.data.open
//self.iconclass = opts.data.iconclass
}
self.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
self.data.rid = self.rid
self.data.i = opts.index
self.ontreeselect = opts.ontreeselect
self.ontreedbclick = opts.ontreedbclick
self.fetch = opts.fetch
self.indent = opts.indent || 0
var istoggle = false
if(opts.treeroot)
{
this.treeroot = opts.treeroot
this.treeroot.counter++
}
else
{
this.treeroot = self
this.treeroot.counter = 0
}
self.data.treepath = opts.path || 0
//self.selected = false
self.selectedItem = null
self.index = this.treeroot.counter
var _dfind = function(l,d, k, v)
{
if( d[k] == v ) return l.push(d)
if(d.nodes && d.nodes.length > 0)
for(var i in d.nodes)
_dfind(l, d.nodes[i],k,v)
}
self.root.find = function(k, v)
{
var l = []
_dfind(l,self.data,k,v)
return l
}
self.root.set = function(k,v)
{
if(k == "*")
for(var i in v)
self[i] = v[i]
else if (k == "data")
for(var i in v)
self.data[i] = v[i]
else if (k == "selectedItem")
{
if(self.ontreeselect)
self.ontreeselect(self.data)
self.treeroot.selectedItem = v
self.root.observable.trigger('treeselect',self.data)
}
else
self[k] = v
self.update()
}
self.root.get = function(k)
{
//if(k == "data")
// return {name:self.name, nodes: self.nodes, icon:self.icon, iconclass: self.iconclass, selectedItem:self.selectedItem}
return self[k]
}
if(opts.observable)
this.root.observable = opts.observable
else
{
this.root.observable = riot.observable()
}
this.on("mount", function(){
$(self.refs.padding)
.css("display", "inline-block")
.css("height","1px")
.css("padding",0)
.css("margin", 0)
.css("background-color","transparent")
.css("width", self.indent*15 + "px" )
})
isFolder() {
return self.data.nodes //&& self.nodes.length
}
toggle(e) {
self.open = !self.open
e.preventDefault()
istoggle = true
if(self.open && self.data.nodes.length == 0 && self.fetch)
{
self.fetch(e.item, function(d){
self.data.nodes = d
self.update()
})
}
}
select(event)
{
if(istoggle)
{
istoggle = false
return
}
/*var data = {
id:self.rid,
data:event.item,
path:self.data.path
} */
if(self.ontreeselect)
self.ontreeselect(self.data)
self.treeroot.selectedItem = self.data
this.root.observable.trigger('treeselect',self.data)
event.preventUpdate = true
self.treeroot.update()
event.preventDefault()
}
_dbclick(event)
{
if(istoggle)
{
istoggle = false
return
}
/*data = {
id:self.rid,
data:event.item,
path: self.data.path}*/
if(self.ontreedbclick)
{
self.ontreedbclick(self.data)
}
self.root.observable.trigger('treedbclick', self.data)
}
</script>
</afx-tree-view>

View File

@ -1,68 +0,0 @@
<afx-vbox style = "display:block;">
<div ref = "container" class="afx-vbox-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.refs.container)
.css("display","flex")
.css("flex-direction","column")
.css("width","100%")
//.css("background-color","red")
//.css("overflow", "hidden")
calibrate_size()
if(self.root.observable)
{
self.root.observable.on("resize", function(w,h){
calibrate_size()
})
self.root.observable.on("calibrate", function(){
calibrate_size()
})
}
})
var calibrate_size = function()
{
var auto_height = []
var csize, ocheight = 0, avaiheight;
avaiheight = $(self.root).height()
avaiwidth = $(self.root).width()
$(self.refs.container).css("height",avaiheight + "px")
$(self.refs.container)
.children()
.each(function(e)
{
this.observable = self.root.observable
//.css("border","1px solid black")
var dw = $(this).attr("data-height")
if(dw)
{
if(dw == "grow") return
if(dw[dw.length-1] === "%")
dw = Number(dw.slice(0,-1))*avaiheight/100;
$(this).css("height",dw + "px")
ocheight += Number(dw)
}
else
{
$(this)
.css("flex-grow","1")
auto_height.push(this)
}
})
csize = (avaiheight - ocheight)/ (auto_height.length)
if(csize > 0)
$.each(auto_height, function(i,v)
{
$(v).css("height", csize + "px")
})
self.root.observable.trigger("vboxchange",
{id:self.rid, w:avaiwidth, h:csize})
}
</script>
</afx-vbox>

661
src/core/tags/tag.ts Normal file
View File

@ -0,0 +1,661 @@
/**
*
* Extend the HTMLElement interface with some utility function need
* by AFX API
*
* @interface HTMLElement
*/
interface HTMLElement {
/**
* Recursively update a tag and all its children
*
* @param {*} [d] data to send to all element in the DOM subtree
* @memberof HTMLElement
*/
update(d?: any): void;
/**
*
* AFX will automatically bind the context menu on an HTMLElement
* if this function is defined on that element. The function should
* define the content of the context menu and its action
*
* Once the context menu is bound to the element, all context menu handle
* defined on any child of this element will be ignored.
*
* @param {JQuery.MouseEventBase} e a mouse event
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]]
* @memberof HTMLElement
*/
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void;
/**
* Mount the element and all the children on its DOM subtree. This action
* is performed in a top-down manner
*
* @memberof HTMLElement
*/
sync(): void;
/**
*
* This action allows to generated all the DOM nodes defined by all AFX tags
* in its hierarchy.
* It performs two operations, one top-down operation to generate all the
* necessary DOM nodes, another bottom-up operation to init all the AFX tag
* in the current element DOM hierarchy
*
* @param {OS.API.Announcer} o an AntOS observable object
* @memberof HTMLElement
*/
afxml(o: OS.API.Announcer): void;
/**
* Perform DOM generation ([[afxml]]) then mount ([[sync]]) all the
* elements.
*
* @param {OS.API.Announcer} o an AntOS observable object
* @param {boolean} [flag] indicates whether this is the top-most call of the operation
* @memberof HTMLElement
*/
uify(o: OS.API.Announcer, flag?: boolean): void;
/**
*
*
* @type {*}
* @memberof HTMLElement
*/
mozRequestFullScreen: any;
/**
*
*
* @type {*}
* @memberof HTMLElement
*/
webkitRequestFullscreen: any;
/**
*
*
* @type {*}
* @memberof HTMLElement
*/
msRequestFullscreen: any;
}
/**
*
*
* @interface Document
*/
interface Document {
mozCancelFullScreen: any;
webkitExitFullscreen: any;
cancelFullScreen: any;
}
namespace OS {
export namespace GUI {
/**
* [[TagLayoutType]] interface using by AFX tags to defined
* its internal DOM hierarchy
*
* @export
* @interface TagLayoutType
*/
export interface TagLayoutType {
/**
* Element tag name
*
* @type {string}
* @memberof TagLayoutType
*/
el: string;
/**
* Children layout of the current element
*
* @type {TagLayoutType[]}
* @memberof TagLayoutType
*/
children?: TagLayoutType[];
/**
* Reference name of the element used by AFX Tag
*
* @type {string}
* @memberof TagLayoutType
*/
ref?: string;
/**
* CSS class of the element
*
* @type {string}
* @memberof TagLayoutType
*/
class?: string;
/**
* this is the `data-id` attribute of the element,
* can be query by the [[aid]] Tag API function.
* Not to be confused with the DOM `id` attribute
*
* @type {(string | number)}
* @memberof TagLayoutType
*/
id?: string | number;
/**
* Tooltip text of the element
*
* @type {(string | FormattedString)}
* @memberof TagLayoutType
*/
tooltip?: string | FormattedString;
/**
* `data-width` of the element, not to be confused with
* the `width` attribute of the DOM element
*
* @type {number|string}
* @memberof TagLayoutType
*/
width?: number|string;
/**
** `data-height` of the element, not to be confused with
* the `height` attribute of the DOM element
*
* @type {number|string}
* @memberof TagLayoutType
*/
height?: number|string;
}
/**
* Data type for event issued by AFX tags
*
* @export
* @interface TagEventDataType
* @template T item template
*/
export interface TagEventDataType<T> {
/**
* Reference to the item involved in the event
*
* @type {T}
* @memberof TagEventDataType
*/
item?: T;
[propName: string]: any;
}
/**
* Format of the event issued by AFX tags
*
* @export
* @interface TagEventType
* @template T data type
*/
export interface TagEventType<T> {
/**
* `data-id` of the tag that trigger the
* event
*
* @type {(number | string)}
* @memberof TagEventType
*/
id: number | string;
/**
* Data object of the event
*
* @type {T}
* @memberof TagEventType
*/
data: T;
}
/**
* Drag and Drop data type sent between mouse events
*
* @export
* @interface DnDEventDataType
* @template T
*/
export interface DnDEventDataType<T> {
/**
* Reference to the source DOM element
*
* @type {T}
* @memberof DnDEventDataType
*/
from: T[];
/**
* Reference to the target DOM element
*
* @type {T}
* @memberof DnDEventDataType
*/
to: T;
}
/**
* Tag event callback type
*/
export type TagEventCallback<T> = (e: TagEventType<T>) => void;
/**
* Base abstract class for tag implementation, any AFX tag should be
* subclass of this class
*
* @export
* @abstract
* @class AFXTag
* @extends {HTMLElement}
*/
export abstract class AFXTag extends HTMLElement {
/**
* The announcer object of the tag
*
* @type {API.Announcer}
* @memberof AFXTag
*/
observable: API.Announcer;
/**
* Reference to some of the tag's children
* element. This reference object is built
* based on the `ref` property found in the
* tag layout [[TagLayoutType]]
*
* @protected
* @type {GenericObject<HTMLElement>}
* @memberof AFXTag
*/
protected refs: GenericObject<HTMLElement>;
/**
* boolean value indicated whether the tag
* is already mounted in the DOM tree
*
* @protected
* @type {boolean}
* @memberof AFXTag
*/
protected _mounted: boolean;
/**
*Creates an instance of AFXTag.
* @memberof AFXTag
*/
constructor() {
super();
if (!this.observable) {
this.observable = new Ant.OS.API.Announcer();
}
this._mounted = false;
this.refs = {};
}
/**
* This function verifies if a property name of the input object
* corresponds to a setter of the current tag. If this is the
* case, it sets the value of that property to the setter
*
* @param {GenericObject<any>} v input object
* @memberof AFXTag
*/
set(v: GenericObject<any>) {
for (let k in v) {
let descriptor = this.descriptor_of(k);
if (descriptor && descriptor.set) {
this[k] = v[k];
}
}
}
/**
* Setter to set the tooltip text to the current tag.
* The text should be in the following format:
* ```text
* cr|cl|ct|cb: tooltip text
* ```
*
* @memberof AFXTag
*/
set tooltip(v: string) {
if (!v) {
return;
}
$(this).attr("tooltip", v);
}
/**
*
* This function looking for a property name of the tag
* in its prototype chain. The descriptor of the property
* will be returned if it exists
*
* @private
* @param {string} k the property name to be queried
* @returns {PropertyDescriptor} the property descriptor or undefined
* @memberof AFXTag
*/
private descriptor_of(k: string): PropertyDescriptor {
let desc: PropertyDescriptor;
let obj = this;
do {
desc = Object.getOwnPropertyDescriptor(obj, k);
} while (!desc && (obj = Object.getPrototypeOf(obj)));
return desc;
}
/**
* Setter: set the id of the tag in string or number
*
* Getter: get the id of the current tag
*
* @memberof AFXTag
*/
set aid(v: string | number) {
$(this).attr("data-id", v);
}
get aid(): string | number {
return $(this).attr("data-id");
}
/**
* Implementation from HTMLElement interface,
* this function mount the current tag hierarchy
*
* @returns {void}
* @memberof AFXTag
*/
sync(): void {
if (this._mounted) {
return;
}
this._mounted = true;
this.mount();
super.sync();
}
/**
* Generate the DOM hierarchy of the current tag
*
* @param {API.Announcer} o observable object
* @memberof AFXTag
*/
afxml(o: API.Announcer): void {
if (o) this.observable = o;
if (!this.aid)
this.aid = (
Math.floor(Math.random() * 100000) + 1
).toString();
const children = $(this).children();
for (let obj of this.layout()) {
const dom = this.mkui(obj);
if (dom) {
$(dom).appendTo(this);
}
}
if (this.refs.yield) {
for (let v of children) {
$(v).detach().appendTo(this.refs.yield);
}
}
const attrs = {};
for (let i = 0; i < this.attributes.length; i++) {
const element = this.attributes[i];
let descriptor = this.descriptor_of(element.nodeName);
if (descriptor && descriptor.set) {
let value = "";
try {
value = JSON.parse(element.nodeValue);
} catch (e) {
value = element.nodeValue;
}
attrs[element.nodeName] = value;
}
}
super.afxml(this.observable);
this.init();
for (let k in attrs) {
this[k] = attrs[k];
}
}
/**
* Update the current tag hierarchy
*
* @param {*} d any data object
* @memberof AFXTag
*/
update(d: any): void {
this.reload(d);
super.update(d);
}
/**
* Init the current tag, this function
* is called before the [[mount]] function
*
* @protected
* @abstract
* @memberof AFXTag
*/
protected abstract init(): void;
/**
* Mount only the current tag
*
* @protected
* @abstract
* @memberof AFXTag
*/
protected abstract mount(): void;
/**
* Layout definition of a tag
*
* @protected
* @abstract
* @returns {TagLayoutType[]} tag layout object
* @memberof AFXTag
*/
protected abstract layout(): TagLayoutType[];
/**
* Update only the current tag, this function is
* called by [[update]] before chaining the
* update process to its children
*
* @protected
* @abstract
* @param {*} [d]
* @memberof AFXTag
*/
protected abstract reload(d?: any): void;
/**
* This function is used to re-render the current
* tag
*
* @protected
* @memberof AFXTag
*/
protected calibrate(): void {}
/**
* This function parses the input layout object
* and generates all the elements defined by
* the tag
*
* @private
* @param {TagLayoutType} tag tag layout object
* @returns {Element} the DOM element specified by the tag layout
* @memberof AFXTag
*/
private mkui(tag: TagLayoutType): Element {
if (!tag) {
return undefined;
}
const dom = $(`<${tag.el}>`);
if (tag.class) {
$(dom).addClass(tag.class);
}
if (tag.id) {
$(dom).attr("data-id", tag.id);
}
if (tag.height) {
$(dom).attr("data-height", tag.height);
}
if (tag.width) {
$(dom).attr("data-width", tag.width);
}
if (tag.tooltip) {
$(dom).attr("tooltip", tag.tooltip.__());
}
if (tag.children) {
for (let v of tag.children) {
$(this.mkui(v)).appendTo(dom);
}
}
if (tag.ref) {
this.refs[tag.ref] = dom[0];
}
// dom.mount @observable
return dom[0]; //.uify(@observable)
}
/**
* This function inserts or removes an attribute name
* to/from the target element based on the input `flag`.
*
* @protected
* @param {boolean} flag indicates whether the attribute name should be inserted o removed
* @param {string} v the attribute name
* @param {HTMLElement} [el] the target element
* @memberof AFXTag
*/
protected attsw(flag: boolean, v: string, el?: HTMLElement): void {
if (flag) this.atton(v, el);
else this.attoff(v, el);
}
/**
* Insert the attribute name to the target element
*
* @protected
* @param {string} v the attribute name
* @param {HTMLElement} [el] the target element
* @memberof AFXTag
*/
protected atton(v: string, el?: HTMLElement): void {
const element = el ? el : this;
$(element).attr(v, "");
}
/**
* Remove the attribute name from the target element
*
* @protected
* @param {string} v attribute name
* @param {HTMLElement} [el] the target element
* @memberof AFXTag
*/
protected attoff(v: string, el?: HTMLElement): void {
const element = el ? el : this;
element.removeAttribute(v);
}
/**
* Verify if the target element has an attribute name
*
* @protected
* @param {string} v attribute name
* @param {HTMLElement} [el] target element
* @returns {boolean}
* @memberof AFXTag
*/
protected hasattr(v: string, el?: HTMLElement): boolean {
const element = el ? el : this;
return element.hasAttribute(v);
}
}
HTMLElement.prototype.update = function (d): void {
$(this)
.children()
.each(function () {
if(this.update)
return this.update(d);
});
};
HTMLElement.prototype.sync = function (): void {
$(this)
.children()
.each(function () {
return this.sync();
});
};
HTMLElement.prototype.afxml = function (o: API.Announcer): void {
$(this)
.children()
.each(function () {
return this.afxml(o);
});
};
HTMLElement.prototype.uify = function (
o: API.Announcer,
toplevel?: boolean
): void {
this.afxml(o);
this.sync();
if (o && toplevel) o.trigger("mounted", this.aid);
};
/**
* All the AFX tags are defined in this namespace,
* these tags are defined as custom DOM elements and will be
* stored in the `customElements` registry of the browser
*/
export namespace tag {
/**
* Define an AFX tag as a custom element and add it to the
* global `customElements` registry. If the tag is redefined, i.e.
* the tag already exists, its behavior will be updated with the
* new definition
*
* @export
* @template T all classes that extends [[AFXTag]]
* @param {string} name name of the tag
* @param {{ new (): T }} cls the class that defines the tag
* @returns {void}
*/
export function define<T extends AFXTag>(
name: string,
cls: { new (): T }
): void {
try {
customElements.define(name, cls);
} catch (error) {
const proto = customElements.get(name);
if (cls) {
const props = Object.getOwnPropertyNames(cls.prototype);
// redefine the class
for (let prop of props) {
proto.prototype[prop] = cls.prototype[prop];
}
return;
}
throw error;
}
}
}
}
}

View File

@ -1,314 +0,0 @@
String.prototype.asFileHandler = () ->
list = @split ":///"
handlers = _API.VFS.findHandlers list[0]
if not handlers or handlers.length is 0
_courrier.osfail "VFS unknown handler: #{@}", (_API.throwe "OS.VFS"), @
return null
return new handlers[0](@)
this.OS.API.VFS =
handlers: { }
register: ( protos, cls ) ->
return self.OS.API.VFS.handlers[protos] = cls # if typeof protos is "string"
#_API.VFS.handlers[v] = cls for v in protos
findHandlers: (proto) ->
l = (v for k, v of _API.VFS.handlers when proto.trim().match (new RegExp k , "g"))
return l
class BaseFileHandler
constructor: (path) ->
@dirty = false
@cache = undefined
@setPath path
setPath: (p) ->
@ready = false
return unless p
@path = p.toString()
list = @path.split ":///"
@protocol = list[0]
return unless list.length > 1
re = list[1].replace(/^\/+|\/+$/g, '')
return if re is ""
@genealogy = re.split("/")
@basename = @genealogy[@genealogy.length - 1] unless @isRoot()
@ext = @basename.split( "." ).pop() unless @basename.lastIndexOf(".") is 0 or @basename.indexOf( "." ) is -1
isRoot: () -> (not @genealogy) or (@genealogy.size is 0)
child: (name) ->
if @isRoot()
return @path + name
else
return @path + "/" + name
isHidden: () ->
return false if not @basename
@basename[0] is "."
hash: () ->
return -1 unless @path
return @path.hash()
sendB64: (m, f) ->
me = @
return f "" unless @cache
if typeof @cache is "string"
b64 = @cache.asBase64()
b64 = "data:#{m};base64,#{b64}"
f(b64)
else
reader = new FileReader()
reader.readAsDataURL(@cache)
reader.onload = () ->
f reader.result
reader.onerror = (e) ->
return _courrier.osfail "Cannot ecode file: #{me.path}", (_API.throwe "OS.VFS"), e
parent: () ->
return @ if @isRoot()
return (@protocol + ":///" + (@genealogy.slice 0 , @genealogy.length - 1).join "/")
onready: (f, err) ->
# read meta data
return f() if @ready
me = @
me.meta (d) ->
if d.error
return if err then err d else _courrier.osfail "#{me.path}: #{d.error}", (_API.throwe "OS.VFS"), d.error
me.info = d.result
me.ready = true
f()
read: (f, t) ->
me = @
@onready (() -> me.action "read", t, f)
write: (d, f) ->
me = @
@action "write", d, (r) ->
_courrier.ostrigger "VFS", { m: "write", file: me } if r.result
f r
mk: (d, f) ->
me = @
@onready (() -> me.action "mk", d, (r) ->
_courrier.ostrigger "VFS", { m: "mk", file: me } if r.result
f r)
remove: (f) ->
me = @
@onready (() -> me.action "remove", null, (r) ->
_courrier.ostrigger "VFS", { m: "remove", file: me } if r.result
f r)
upload: (f) ->
me = @
@onready (() -> me.action "upload", null, (r) ->
_courrier.ostrigger "VFS", { m: "upload", file: me } if r.result
f r)
publish: (f) ->
me = @
@onready (() -> me.action "publish", null, (r) ->
_courrier.ostrigger "VFS", { m: "publish", file: me } if r.result
f r)
download: (f) ->
me = @
@onready (() -> me.action "download", null, f)
move: (d, f) ->
me = @
@onready (() -> me.action "move", d, (r) ->
_courrier.ostrigger "VFS", { m: "move", file: d.asFileHandler() } if r.result
f r)
execute: (f) ->
me = @
@onready (() -> me.action "execute", null, f)
#mk: (f) ->
meta: (f) ->
# for main action read, write, remove, execute
# must be implemented by subclasses
action: (n, p, f) ->
return _courrier.osfail "VFS unknown action: #{n}", (_API.throwe "OS.VFS"), n
# now export the class
self.OS.API.VFS.BaseFileHandler = BaseFileHandler
# Remote file handle
class RemoteFileHandler extends self.OS.API.VFS.BaseFileHandler
constructor: (path) ->
super path
meta: (f) ->
_API.handler.fileinfo @path, f
action: (n, p, f) ->
me = @
switch n
when "read"
return _API.handler.scandir @path, f if @info.type is "dir"
#read the file
return _API.handler.fileblob @path, f if p is "binary"
_API.handler.readfile @path, f, if p then p else "text"
when "mk"
return f { error: "#{@path} is not a directory" } if @info.type is "file"
_API.handler.mkdir "#{@path}/#{p}", f
when "write"
@sendB64 p, (data) ->
_API.handler.write me.path, data, f
when "upload"
return if @info.type is "file"
_API.handler.upload @path, f
when "remove"
_API.handler.delete @path, f
when "publish"
_API.handler.sharefile @path, true , f
when "download"
return if @info.type is "dir"
_API.handler.fileblob @path, (d) ->
blob = new Blob [d], { type: "octet/stream" }
_API.saveblob me.basename, blob
when "move"
_API.handler.move @path, p, f
else
return _courrier.osfail "VFS unknown action: #{n}", (_API.throwe "OS.VFS"), n
self.OS.API.VFS.register "^(home|desktop|os|Untitled)$", RemoteFileHandler
# Application Handler
class ApplicationHandler extends self.OS.API.VFS.BaseFileHandler
constructor: (path) ->
super path
@info = _OS.setting.system.packages[@basename] if @basename
@ready = true
meta: (f) ->
f()
action: (n, p, f) ->
me = @
switch n
when "read"
return f { result: @info } if @info
return unless @isRoot()
f { result: ( v for k, v of _OS.setting.system.packages ) }
when "mk"
return
when "write"
return
when "upload"
# install
return
when "remove"
#uninstall
return
when "publish"
return
when "download"
return
when "move"
return
else
return _courrier.osfail "VFS unknown action: #{n}", (_API.throwe "OS.VFS"), n
self.OS.API.VFS.register "^app$", ApplicationHandler
class BufferFileHandler extends self.OS.API.VFS.BaseFileHandler
constructor: (path, mime, data) ->
super path
@cache = data if data
@info =
mime: mime
path: path
size: if data then data.length else 0
name: @basename
type: "file"
meta: (f) ->
f()
onchange: (f) ->
@onchange = f
action: (n, p, f) ->
me = @
switch n
when "read"
return f { result: @cache }
when "mk"
return
when "write"
@cache = p
@onchange @ if @onchange
f { result: true }
when "upload"
# install
return
when "remove"
#uninstall
return
when "publish"
return
when "download"
blob = new Blob [@cache], { type: "octet/stream" }
_API.saveblob me.basename, blob
when "move"
return
else
return _courrier.osfail "VFS unknown action: #{n}", (_API.throwe "OS.VFS"), n
self.OS.API.VFS.register "^mem$", BufferFileHandler
class SharedFileHandler extends self.OS.API.VFS.BaseFileHandler
constructor: (path) ->
super path
@ready = true if @isRoot()
meta: (f) ->
_API.handler.fileinfo @path, f
action: (n, p, f) ->
me = @
switch n
when "read"
return _API.get "#{_API.handler.shared}/all", f, ((e, s)->) if @isRoot()
#read the file
return _API.handler.fileblob @path, f if p is "binary"
_API.handler.readfile @path, f, if p then p else "text"
when "mk"
return
when "write"
_API.handler.write @path, p, f
when "remove"
_API.handler.sharefile @basename, false, f
when "upload"
return
when "publish"
return f { result: @basename }
when "download"
return if @info.type is "dir"
_API.handler.fileblob @path, (d) ->
blob = new Blob [d], { type: "octet/stream" }
_API.saveblob me.basename, blob
when "move"
return
else
return _courrier.osfail "VFS unknown action: #{n}", (_API.throwe "OS.VFS"), n
self.OS.API.VFS.register "^shared$", SharedFileHandler

2323
src/core/vfs.ts Normal file

File diff suppressed because it is too large Load Diff

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