Compare commits

...

97 Commits

Author SHA1 Message Date
DanyLE
d72025741c doc: update README.md
All checks were successful
gitea-sync/antos-frontend/pipeline/head This commit looks good
2024-03-10 10:54:54 +01:00
DanyLE
cf61b434fa refactor: remove unused files 2024-03-10 10:34:07 +01:00
DanyLE
7a00c8293d fix: init npm before installing packages 2024-03-10 10:28:10 +01:00
DanyLE
6fa5f18159 feat: add install_dev to Makefile to install dependencies before build 2024-03-10 10:28:10 +01:00
DanyLE
bcf3370079 fix: simulate contextmenu on mobile device 2024-03-10 10:28:10 +01:00
DanyLE
21bf106b8b fix: list view item shall propagate click event to parent 2024-03-10 10:28:10 +01:00
DanyLE
ee4e451306 fix: dblclick event does not fire on mobile device (IOS) 2024-03-10 10:28:10 +01:00
DanyLE
647e6a1ade fix: page scale problem on mobile 2024-03-10 10:28:10 +01:00
DanyLE
a2cd2d6457 fix: invalid background settings 2024-03-10 10:28:10 +01:00
DanyLE
cc23bb7c2d style: update file UI style 2024-03-10 10:28:10 +01:00
DanyLE
0708683406 fix: minor UI bugs on File and Setting apps 2024-03-10 10:28:10 +01:00
DanyLE
14d32ac775 fix: remove debug message 2024-03-10 10:28:10 +01:00
DanyLE
b0b420e993 fix: use row/column as common directives for all UI horizontal/vertical direction 2024-03-10 10:28:10 +01:00
DanyLE
3ab5a6b5a8 update: antos API declaration 2024-03-10 10:28:10 +01:00
DanyLE
12c8439ef1 update: use latest UI API on system applications 2024-03-10 10:28:10 +01:00
DanyLE
ced141bcee feat: add APIs that support responsive UI on antos tags 2024-03-10 10:28:10 +01:00
DanyLE
89385950a0 fix: use CSS variable to define color palette for UI theme 2024-03-10 10:28:10 +01:00
DanyLE
50c6ad4504 fix: encode URI component when get file from VFS API 2024-03-10 10:28:10 +01:00
DanyLE
ac23a19909 fix: upload API only submit a task when files are selected 2024-03-10 10:28:10 +01:00
DanyLE
911104421c cleanup code 2024-03-10 10:28:10 +01:00
DanyLE
6ac1245002 feat: use a separated setting file for each application instead of a single system setting files 2024-03-10 10:28:10 +01:00
DanyLE
c1d0230070 feat: Introduce API.Task API that allow to track promise object via AntOS announcement system 2024-03-10 10:28:10 +01:00
Dany LE
224e9960d8 Update Jenkinsfile 2024-03-10 10:28:10 +01:00
Dany LE
b4c4cde08f Update Jenkinsfile 2024-03-10 10:28:10 +01:00
Dany LE
af58ca00f9 fix: clean up repo before build 2024-03-10 10:28:10 +01:00
Dany LE
8512d3c6ad fix(Jenkinsfile): use typescript 5.0 as typedoc 0.24 depends on it 2024-03-10 10:28:10 +01:00
DanyLE
aae645d665 fix: support passing arguments when pushing a service 2024-03-10 10:28:10 +01:00
DanyLE
fd229c5477 remove support for VDB, applications that used SQLite database can now use API provided by the libsqlite package (on MarketPlace) 2024-03-10 10:28:10 +01:00
DanyLE
d6063aafb3 fix: doc generation use latest typedoc version 2024-03-10 10:28:10 +01:00
DanyLE
100d5895c1 fix: operator not permit on newer version of typescript 2024-03-10 10:28:10 +01:00
DanyLE
a1390b4773 fix: jquery delaration file compatibility with current typescript sdk 2024-03-10 10:28:10 +01:00
DanyLE
cf3431c0a8 fix: extractZip bug introduced by last commit 2024-03-10 10:28:10 +01:00
DanyLE
b600515566 fix: creating missing directories from file paths when they are not specified in zip meta-data 2024-03-10 10:28:10 +01:00
DanyLE
a0c9ed6063 fix: calendar tag displays wrong date at the final week of month in some case 2024-03-10 10:28:10 +01:00
DanyLE
4fc8d1a1d8 gridview: allow to update row data 2024-03-10 10:28:10 +01:00
DanyLE
14116f4fdd safer way to attach element to data via getter 2024-03-10 10:28:10 +01:00
DanyLE
5cb13bbc47 Remove old menu element, use stackmenu instead 2024-03-10 10:28:10 +01:00
DanyLE
b133169ba2 ListView: add API to scroll the list to top/bottom 2024-03-10 10:28:10 +01:00
DanyLE
864d984b5a fix: label shall only allow to show text instead of html content 2024-03-10 10:28:10 +01:00
DanyLE
a654624c71 support icoclass_end in Label and Button HTML attribute 2024-03-10 10:28:10 +01:00
DanyLE
4a3184103c Update style for GridView, FileView and ListView 2024-03-10 10:28:10 +01:00
DanyLE
830dd2c23a Only break-word in notification tag 2024-03-10 10:28:10 +01:00
DanyLE
907e0b8d37 Update Label, Button and ListView
- Label and Button now can set icon on both left and right side of the text
- Fix ListView dropdown bug, and allow the dropdown list to positioned correctly based on its nearest anchored element
2024-03-10 10:28:10 +01:00
DanyLE
d58269f038 fix: Dialog scheme not found when it is defined outside of the dialogs namespace 2024-03-10 10:28:10 +01:00
DanyLE
7393acf1d5 allow to specify user data in some low level VFS interface API 2024-03-10 10:28:10 +01:00
DanyLE
8878bf6b9d add support for drop custom event when drag is enable on an HTMLElement 2024-03-10 10:28:10 +01:00
DanyLE
ceb412e592 Add custom dragging event support for all HTMLElement 2024-03-10 10:28:10 +01:00
DanyLE
fcf7267c27 sportlight only focus on searchbar when on desktop 2024-03-10 10:28:10 +01:00
DanyLE
84097f5e97 Clean up code 2024-03-10 10:28:10 +01:00
DanyLE
fd45b6ef53 Hide application when use click on active application dock item 2024-03-10 10:28:10 +01:00
DanyLE
5314d9f161 fix: prevent scroll on desktop
When focusing on a window which overflows the desktop,
the desktop scrolls automatically to bottom,
even when `overflow: hiddle` is set on CSS.

This tricky hack prevents this to happen
2024-03-10 10:28:10 +01:00
DanyLE
55296b2e18 fix: notification style 2024-03-10 10:28:10 +01:00
DanyLE
f465623a77 cleanup system services package 2024-03-10 10:28:10 +01:00
DanyLE
9df59ebbf2 Fix: style + typo 2024-03-10 10:28:10 +01:00
DanyLE
4fb60c46df Rework on Notification API + some sytem packages
- Rename Syslog to SystemReport
- All services previously on SystemReport now moved to the dedicated SystemServices Packages
- Rework on a more versatile notification GUI and API
- Applications now can display a local toast message instead of pushing a global notification message
2024-03-10 10:28:10 +01:00
DanyLE
994ac1c493 fix: window menu display bug 2024-03-10 10:28:10 +01:00
DanyLE
6ae84e31a7 Hide spotlight when an application is selected on appdock 2024-03-10 10:28:10 +01:00
DanyLE
86d9202b81 Re introduce the vboxchange, hboxchange events as many applications use it 2024-03-10 10:28:10 +01:00
DanyLE
62227742a5 Change color theme of the startup menu 2024-03-10 10:28:10 +01:00
DanyLE
cd45eef9d7 Update favicon to new color 2024-03-10 10:28:10 +01:00
DanyLE
0c6784b9a3 Update favicon 2024-03-10 10:28:10 +01:00
Dany LE
cc6c136a06 Update Makefile 2024-03-10 10:28:10 +01:00
Dany LE
c2d395138f Update Jenkinsfile 2024-03-10 10:28:10 +01:00
Dany LE
7e27fb123b Update README.md 2024-03-10 10:28:10 +01:00
DanyLE
9b1dc80638 Update Jenkinsfile 2024-03-10 10:28:10 +01:00
DanyLE
803aea229d Fix make file 2024-03-10 10:28:10 +01:00
DanyLE
09764109cb Fix make file 2024-03-10 10:28:10 +01:00
DanyLE
196b0a5679 Update icons + add documentation build in Jenkinsfile 2024-03-10 10:28:10 +01:00
DanyLE
d656f558f9 Add favicon to the page 2024-03-10 10:28:10 +01:00
DanyLE
3c6ab95fac Add official AntOS icon 2024-03-10 10:28:10 +01:00
Dany LE
a1ce4de1dd Update README.md 2024-03-10 10:28:10 +01:00
Dany LE
08fe4f53d0 Add files via upload 2024-03-10 10:28:10 +01:00
Dany LE
901d8df404 Update README.md 2024-03-10 10:28:10 +01:00
Dany LE
1f23caffa8 Add files via upload 2024-03-10 10:28:10 +01:00
Dany LE
a13250c5ff Add files via upload 2024-03-10 10:28:10 +01:00
DanyLE
579a31c9c7 update README 2024-03-10 10:28:10 +01:00
DanyLE
1610cbd76e update Syslog application + README 2024-03-10 10:28:10 +01:00
DanyLE
b94447eaec UI improvement + use lastest boostrap icon
- Update bootstrap icons to latest
- Redesign system tray for services monitoring
- Improve UI + bug fix on default packages
2024-03-10 10:28:10 +01:00
DanyLE
736c951c66 fix minor bug on appdock contextmenu handling 2024-03-10 10:23:50 +01:00
DanyLE
5e2212c0fc Update dark theme 2024-03-10 10:23:50 +01:00
DanyLE
ce6ad7537f Improve UI 2024-03-10 10:23:50 +01:00
DanyLE
1791b50480 Improve Firefox support + fix list view drag and drop bug 2024-03-10 10:23:50 +01:00
DanyLE
59b2e6e898 improve file icon view 2024-03-10 10:23:50 +01:00
DanyLE
737351362d Redesign the login form, preload all web font fonts on front page 2024-03-10 10:23:50 +01:00
DanyLE
58d7b75f13 Support pinned app in dock + remove old pinned apps UI 2024-03-10 10:23:50 +01:00
DanyLE
76996791d0 Improve application dock:
- Stack all instances of the same application to one single dock button
- Make the dock scrollable by mouse wheel or touch
2024-03-10 10:23:50 +01:00
DanyLE
778a4cf495 generate 2.0.0 release archive 2024-03-10 10:23:50 +01:00
DanyLE
aeb50cfca4 Add input tag and update all base dialogs to support mobile devices 2024-03-10 10:23:50 +01:00
DanyLE
0819fb664a Add stack panel component + redesign MarketPlace UI
- Continue improve UI elements
- Add stack panel UI tag
- Redesign MarketPlace UI to support mobile device
2024-03-10 10:23:50 +01:00
DanyLE
5763037425 Update version to 2.0.0-a 2024-03-10 10:23:50 +01:00
DanyLE
31b5000fa3 Rework on AntOS core to provide support to both mobile and desktop devices (experimental):
- Redesign the core UI API and tags to support Mobile devices
- Add new StackMenu tag
- Support touch events handling on touch devices
- Redesign File and Setting to work on mobile
- Improve Anouncement API
- Rework on default themes
2024-03-10 10:23:50 +01:00
Dany LE
13969511a5
Update README.md
Some checks failed
gitea-sync/antos/pipeline/head This commit looks good
gitea-sync/antos-frontend/pipeline/head There was a failure building this commit
2022-12-31 13:22:52 +01:00
Dany LE
dd56643e01
Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:16:40 +01:00
Dany LE
576051aca0
Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:15:20 +01:00
Dany LE
fec05d115f
Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:10:32 +01:00
Dany LE
5313f0b224
add screenshot
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 13:09:42 +01:00
Dany LE
25d1c5fd47
Update README.md
All checks were successful
gitea-sync/antos/pipeline/head This commit looks good
2022-12-31 12:18:14 +01:00
164 changed files with 7491 additions and 5499 deletions

View File

@ -1,20 +0,0 @@
---
kind: pipeline
type: exec
name: default
platform:
os: linux
arch: amd64
clone:
disable: true
steps:
- name: download
commands:
- cd /opt/www/htdocs/os && wget https://github.com/lxsang/antos/raw/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

14
Jenkinsfile vendored
View File

@ -22,20 +22,28 @@ pipeline{
steps { steps {
sh''' sh'''
cd $WORKSPACE cd $WORKSPACE
[ -d "$WORKSPACE/node_modules" ] && rm -rf "$WORKSPACE/node_modules" || true
[ -e "$WORKSPACE/package.json" ] && rm "$WORKSPACE/package.json" && rm "$WORKSPACE/package-lock.json" || true
npm install terser npm install terser
npm install uglifycss npm install uglifycss
npm install typescript npm install typescript@5.0
npm install @types/jquery npm install @types/jquery
npm i typedoc@0.24
npm i typedoc-plugin-merge-modules
buildir="build" buildir="build"
[ -d "$buildir" ] && rm -rf "$buildir" [ -d "$buildir" ] && rm -rf "$buildir"
export BUILDDIR="$WORKSPACE/$buildir/opt/www/htdocs/os" export BUILDDIR="$WORKSPACE/$buildir/opt/www/htdocs/os"
[ -d "doc" ] && rm -rf doc
mkdir doc
export DOCDIR="$WORKSPACE/doc"
make release make release
make doc
''' '''
script { script {
// only useful for any master branch // only useful for any master branch
//if (env.BRANCH_NAME =~ /^master/) { //if (env.BRANCH_NAME =~ /^master/) {
archiveArtifacts artifacts: 'd.ts/, build/', fingerprint: true archiveArtifacts artifacts: 'd.ts/, build/, doc/', fingerprint: true
//} //}
} }
} }

View File

@ -8,8 +8,8 @@ TSC=./node_modules/typescript/bin/tsc
UGLIFYJS=./node_modules/terser/bin/terser UGLIFYJS=./node_modules/terser/bin/terser
UGLIFYCSS=./node_modules/uglifycss/uglifycss UGLIFYCSS=./node_modules/uglifycss/uglifycss
VERSION=1.2.1 VERSION=2.0.0
BRANCH = b BRANCH = a
BUILDID=$(shell git rev-parse --short HEAD) BUILDID=$(shell git rev-parse --short HEAD)
GSED=sed GSED=sed
@ -27,7 +27,6 @@ tags = dist/core/tags/tag.js \
dist/core/tags/ListViewTag.js \ dist/core/tags/ListViewTag.js \
dist/core/tags/SwitchTag.js \ dist/core/tags/SwitchTag.js \
dist/core/tags/NSpinnerTag.js \ dist/core/tags/NSpinnerTag.js \
dist/core/tags/MenuTag.js \
dist/core/tags/GridViewTag.js \ dist/core/tags/GridViewTag.js \
dist/core/tags/TabBarTag.js \ dist/core/tags/TabBarTag.js \
dist/core/tags/TabContainerTag.js \ dist/core/tags/TabContainerTag.js \
@ -40,14 +39,17 @@ tags = dist/core/tags/tag.js \
dist/core/tags/OverlayTag.js \ dist/core/tags/OverlayTag.js \
dist/core/tags/AppDockTag.js \ dist/core/tags/AppDockTag.js \
dist/core/tags/SystemPanelTag.js \ dist/core/tags/SystemPanelTag.js \
dist/core/tags/DesktopTag.js dist/core/tags/DesktopTag.js \
dist/core/tags/StackMenuTag.js \
dist/core/tags/StackPanelTag.js \
dist/core/tags/InputTag.js \
dist/core/tags/NotificationTag.js
javascripts= dist/core/core.js \ javascripts= dist/core/core.js \
dist/core/settings.js \ dist/core/settings.js \
dist/core/handles/RemoteHandle.js \ dist/core/handles/RemoteHandle.js \
dist/core/Announcerment.js \ dist/core/Announcerment.js \
dist/core/vfs.js \ dist/core/vfs.js \
dist/core/db.js \
dist/core/BaseModel.js \ dist/core/BaseModel.js \
dist/core/BaseApplication.js \ dist/core/BaseApplication.js \
dist/core/BaseService.js \ dist/core/BaseService.js \
@ -60,7 +62,7 @@ javascripts= dist/core/core.js \
antfx = $(tags) \ antfx = $(tags) \
dist/core/Announcerment.js dist/core/Announcerment.js
packages = Syslog Files MarketPlace Setting NotePad packages = SystemServices SystemReport Files MarketPlace Setting NotePad
main: initd build_javascripts build_themes libs build_packages languages main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/ - cp src/index.html $(BUILDDIR)/
@ -145,8 +147,10 @@ build_themes: antos_light antos_dark
-rm -rf $(BUILDDIR)/resources/themes/system/* -rm -rf $(BUILDDIR)/resources/themes/system/*
-mkdir -p $(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/fonts $(BUILDDIR)/resources/themes/system
cp -r src/themes/system/icons $(BUILDDIR)/resources/themes/system
cp -r src/themes/system/wp $(BUILDDIR)/resources/themes/system cp -r src/themes/system/wp $(BUILDDIR)/resources/themes/system
for f in src/themes/system/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/system.css;done for f in src/themes/system/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/system.css;done
for f in src/themes/default/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/antos.css;done
antos_light: antos_light:
@echo "$(BLUE)Building themes name: antos-light$(NC)" @echo "$(BLUE)Building themes name: antos-light$(NC)"
@ -221,13 +225,25 @@ ar:
echo -n $(VERSION) > release/latest echo -n $(VERSION) > release/latest
release: main uglify release: main uglify
.PHONY: doc release clean
doc: doc:
./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS API" --out $(DOCDIR) # npm install typedoc --save-dev
# npm install typedoc-plugin-merge-modules --save-dev
# ./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS $(VERSION)-$(BRANCH)-$(BUILDID) API" --out $(DOCDIR)
./node_modules/.bin/typedoc --hideGenerator --plugin typedoc-plugin-merge-modules --entryPointStrategy expand --name "AntOS $(VERSION)-$(BRANCH)-$(BUILDID) API" --out $(DOCDIR)
test: build_javascripts test: build_javascripts
jest jest
install_dev:
npm init -y
npm install terser
npm install uglifycss
npm install typescript@5.0
npm install @types/jquery
npm i typedoc@0.24
npm i typedoc-plugin-merge-modules
clean: clean:
rm -rf $(BUILDDIR)/resources rm -rf $(BUILDDIR)/resources
rm -rf $(BUILDDIR)/scripts rm -rf $(BUILDDIR)/scripts

View File

@ -1,17 +1,39 @@
# antOS v1.2.1 # AntOS frontend
[![Build Status](https://ci.iohub.dev/buildStatus/icon?job=gitea-sync%2Fantos%2Fmaster)](https://ci.iohub.dev/job/gitea-sync/job/antos/job/master/)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flxsang%2Fantos.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
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 Frontend implementation of AntOS remote desktop environment: [https://github.com/antos-rde](https://github.com/antos-rde).
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) The frontend is developed in typescript/javascript + CSS, it provides the
Core API, web-based window manager, application APIs, a GUI toolkit, and file system abstractions. It also includes an application store and an SDK for in-browser application development, deployment, and packaging. The frontend is designed to work across devices, including desktop computers and mobile devices.
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos) ## Build
`Nodejs` and `npm` is necessary to build the project:
```sh
# install dependencies packages
make install_dev
# build release
BUILDDIR=/path/to/output make release
# see more in Makefile for more build target
```
## demo
A demo of the VDE is available at [https://app.iohub.dev/antos/](https://app.iohub.dev/antos/) using username: demo and password: demo.
If one want to run AntOS VDE locally in their system, a docker image is available at:
[https://github.com/lxsang/antosaio](https://github.com/lxsang/antosaio)
## AntOS applications (Available on the MarketPlace)
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
## Frontend Documentation
- API: [https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/](https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/)
## Change logs ## Change logs
* V1.2.1 ### v.2.0.0
- Work In Progress: The UI is redesigned to support mobile device
### V1.2.1
- 9b5da17 - App name now can differ from pkgname - 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 - 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 - b6c90e5 - update image path in readme
@ -53,7 +75,7 @@ Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
- 52709d5 - improve Window GUI API - 52709d5 - improve Window GUI API
- 9c06d88 - AntOS load automatically custom VFS handles if available - 9c06d88 - AntOS load automatically custom VFS handles if available
- c23cb1b - Improve core API: - improve OS exit API - improve VFS API - c23cb1b - Improve core API: - improve OS exit API - improve VFS API
* V.1.2.0 Improvement GUI API ### V.1.2.0 Improvement GUI API
- [x] File dialog should remember last opened folder - [x] File dialog should remember last opened folder
- [x] Add dynamic key-value dialog that work on any object - [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] Window list panel should show window title in tooltip when mouse hovering on application icon
@ -82,23 +104,9 @@ Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
- Introduce new JSON based syntax for SDK task/target definition - 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) * From this version, docker image of All-in-one AntOS system is available at: [https://hub.docker.com/r/xsangle/antosaio](https://hub.docker.com/r/xsangle/antosaio)
## Demo
A demo of the VDE is available at [https://app.iohub.dev/antos/](https://app.iohub.dev/antos/) using username: demo and password: demo.
If one want to run AntOS VDE locally in their system, a docker image is available at:
[https://github.com/lxsang/antosaio](https://github.com/lxsang/antosaio)
## AntOS applications (Available on the MarketPlace)
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
## Documentation
- Documentation: [https://doc.iohub.dev/antos](https://doc.iohub.dev/antos)
- API: [https://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
## Licence ## Licence
Copyright 2017-2021 Xuan Sang LE <mrsang AT iohub DOT dev> Copyright 2017-2022 Xuan Sang LE <mrsang AT iohub DOT dev>
AnTOS is is licensed under the GNU General Public License v3.0, see the LICENCE file for more information AnTOS is is licensed under the GNU General Public License v3.0, see the LICENCE file for more information

2171
d.ts/antos.d.ts vendored

File diff suppressed because it is too large Load Diff

3
d.ts/jquery.d.ts vendored
View File

@ -1,7 +1,4 @@
export as namespace Sizzle;
declare const Sizzle: SizzleStatic; declare const Sizzle: SizzleStatic;
export = Sizzle;
interface SizzleStatic { interface SizzleStatic {
selectors: Sizzle.Selectors; selectors: Sizzle.Selectors;

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

Binary file not shown.

View File

@ -1 +1 @@
1.2.1 2.0.0

View File

@ -21,5 +21,15 @@ Ant.onload = function () {
"webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange", "webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",
() => (Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen) () => (Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen)
); );
const agent = navigator.userAgent||navigator.vendor||(window as any).opera;
Ant.OS.mobile = false;
if(
/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(agent)
||
/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(agent.substr(0,4))
)
{
Ant.OS.mobile = true;
}
return Ant.OS.boot(); return Ant.OS.boot();
}; };

View File

@ -107,7 +107,6 @@ namespace OS {
* @interface AnnouncerListenerType * @interface AnnouncerListenerType
*/ */
export interface AnnouncerListenerType { export interface AnnouncerListenerType {
[index: number]: {
/** /**
* The event name * The event name
* *
@ -120,8 +119,7 @@ namespace OS {
* *
*/ */
f: (d: any) => void; f: (d: any) => void;
}[]; };
}
/** /**
* This class is the based class used in AntOS event * This class is the based class used in AntOS event
@ -134,7 +132,7 @@ namespace OS {
export class Announcer { export class Announcer {
/** /**
* The observable object that stores event name * The observable object that stores event name
* and its corresponding callback in [[ObservableEntryType]] * and its corresponding callback in {@link ObservableEntryType}
* *
* @type {GenericObject<ObservableEntryType>} * @type {GenericObject<ObservableEntryType>}
* @memberof Announcer * @memberof Announcer
@ -292,16 +290,10 @@ namespace OS {
* and callbacks * and callbacks
*/ */
export var observable: API.Announcer = new API.Announcer(); 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 * Placeholder of all global events listeners
*/ */
export var listeners: API.AnnouncerListenerType = {}; export var listeners: Map<BaseModel | 0, API.AnnouncerListenerType[]> = new Map();
/** /**
* Subscribe to a global event * Subscribe to a global event
@ -311,14 +303,29 @@ namespace OS {
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback * @param {(d: API.AnnouncementDataType<any>) => void} f event callback
* @param {GUI.BaseModel} a the process (Application/service) related to the callback * @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 { export function on(e: string, f: (d: API.AnnouncementDataType<any>) => void, a?: BaseModel): void {
if (!announcer.listeners[a.pid]) { let key: BaseModel | 0 = 0;
announcer.listeners[a.pid] = []; if(a)
key = a;
if (!announcer.listeners.has(key)) {
announcer.listeners.set(key, []);
} }
announcer.listeners[a.pid].push({ e, f }); const collection = announcer.listeners.get(key);
collection.push({ e, f });
announcer.observable.on(e, f); announcer.observable.on(e, f);
} }
/**
* Subscribe to a global event once
*
* @export
* @param {string} e event name
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
*/
export function one(e: string, f: (d: API.AnnouncementDataType<any>) => void): void {
announcer.observable.one(e, f);
}
/** /**
* Trigger a global event * Trigger a global event
* *
@ -390,27 +397,14 @@ namespace OS {
* @returns {void} * @returns {void}
*/ */
export function unregister(app: BaseModel): void { export function unregister(app: BaseModel): void {
if ( if (!announcer.listeners.has(app)) {
!announcer.listeners[app.pid] ||
!(announcer.listeners[app.pid].length > 0)
) {
return; return;
} }
for (let i of announcer.listeners[app.pid]) { const collection = announcer.listeners.get(app);
for (let i of collection) {
announcer.observable.off(i.e, i.f); announcer.observable.off(i.e, i.f);
} }
delete announcer.listeners[app.pid]; announcer.listeners.delete(app);
}
/**
* Allocate message id
*
* @export
* @returns {number}
*/
export function getMID(): number {
quota += 1;
return quota;
} }
} }
} }

View File

@ -35,9 +35,21 @@ namespace OS {
*/ */
export abstract class BaseApplication extends BaseModel { export abstract class BaseApplication extends BaseModel {
/** /**
* Placeholder of all settings specific to the application. * Watcher of all settings specific to the application.
* The settings stored in this object will be saved to system * The settings stored in this object will be saved to application folder
* setting when logout and can be reused in the next login session * in JSON format as .settings.json and will be loaded automatically
* when application is initialized.
*
* This object is globally acessible to all processes of the same application
*
* @type {GenericObject<any>}
* @memberof BaseApplication
*/
static setting_wdg: GenericObject<any>;
/**
* Reference to per application setting i.e. setting_wdg
* *
* @type {GenericObject<any>} * @type {GenericObject<any>}
* @memberof BaseApplication * @memberof BaseApplication
@ -61,31 +73,6 @@ namespace OS {
*/ */
sysdock: GUI.tag.AppDockTag; 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. *Creates an instance of BaseApplication.
* @param {string} name application name * @param {string} name application name
@ -94,13 +81,8 @@ namespace OS {
*/ */
constructor(name: string, args: AppArgumentsType[]) { constructor(name: string, args: AppArgumentsType[]) {
super(name, args); super(name, args);
if (!setting.applications[this.name]) { this.setting = (this.constructor as any).setting_wdg;
setting.applications[this.name] = {};
}
this.setting = setting.applications[this.name];
this.keycomb = {}; this.keycomb = {};
this._loading_toh = undefined;
this._pending_task = [];
} }
/** /**
@ -114,20 +96,17 @@ namespace OS {
* @returns {void} * @returns {void}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
init(): void { init(): Promise<any> {
return new Promise(async (ok, nok) =>{
try {
this.off("*"); this.off("*");
this.on("exit", () => this.quit(false)); this.on("exit", () => this.quit(false));
// first register some base event to the app // first register some base event to the app
this.on("focus", () => { this.on("focus", () => {
//if(this.sysdock.selectedApp != this)
this.sysdock.selectedApp = this; this.sysdock.selectedApp = this;
this.appmenu.pid = this.pid; (this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
this.appmenu.items = this.baseMenu() || [];
OS.PM.pidactive = this.pid; OS.PM.pidactive = this.pid;
this.appmenu.onmenuselect = (
d: GUI.tag.MenuEventData
): void => {
return this.trigger("menuselect", d);
};
this.trigger("focused", undefined); this.trigger("focused", undefined);
if (this.dialog) { if (this.dialog) {
return this.dialog.show(); return this.dialog.show();
@ -135,8 +114,6 @@ namespace OS {
}); });
this.on("hide", () => { this.on("hide", () => {
this.sysdock.selectedApp = null; this.sysdock.selectedApp = null;
this.appmenu.items = [];
this.appmenu.pid = -1;
if (this.dialog) { if (this.dialog) {
return this.dialog.hide(); return this.dialog.hide();
} }
@ -155,39 +132,54 @@ namespace OS {
this.applySetting(m.message as string); 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); this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme(); await this.loadScheme();
this.applyAllSetting();
}
catch(e)
{
nok(__e(e));
}
});
} }
/**
* API function to register responsive UI event to the current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @param {GUI.TagResponsiveCallback} responsive callback
* @returns {void}
* @memberof BaseApplication
*/
protected morphon(validator: GUI.TagResponsiveValidator, callback: GUI.TagResponsiveCallback)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphon(validator, callback);
}
/**
* API function to unregister responsive UI event from current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @returns {void}
* @memberof BaseApplication
*/
protected morphoff(validator: GUI.TagResponsiveValidator)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphoff(validator);
}
/** /**
* Render the application UI by first loading its scheme * Render the application UI by first loading its scheme
* and then mount this scheme to the DOM tree * and then mount this scheme to the DOM tree
* *
* @protected * @protected
* @returns {void} * @returns {Promise<any>}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected loadScheme(): void { protected loadScheme(): Promise<any> {
//now load the scheme //now load the scheme
const path = `${this.meta().path}/scheme.html`; const path = `${this.meta().path}/scheme.html`;
return this.render(path); return this.render(path);
@ -195,9 +187,8 @@ namespace OS {
/** /**
* API function to perform an heavy task. * API function to perform an heavy task.
* This function will trigger the global `loading` * This function will create a Task that is tracked by any
* event at the beginning of the task, and the `loaded` * task manager implementation
* event after finishing the task
* *
* @protected * @protected
* @param {Promise<any>} promise the promise on a task to be performed * @param {Promise<any>} promise the promise on a task to be performed
@ -205,15 +196,11 @@ namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected load(promise: Promise<any>): Promise<void> { protected load(promise: Promise<any>): Promise<void> {
const q = this._api.mid(); return this._api.Task(async (resolve, reject) => {
return new Promise(async (resolve, reject) => {
this._api.loading(q, this.name);
try { try {
await promise; await promise;
this._api.loaded(q, this.name, "OK"); return resolve(undefined);
return resolve();
} catch (e) { } catch (e) {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e)); return reject(__e(e));
} }
}); });
@ -328,21 +315,6 @@ namespace OS {
} }
} }
/**
* Set a setting value to the application setting
* registry
*
* @protected
* @param {string} k setting name
* @param {*} v setting value
* @returns {void}
* @memberof BaseApplication
*/
protected registry(k: string, v: any): void {
this.setting[k] = v;
return this.publish("appregistry", k);
}
/** /**
* Show the appliation * Show the appliation
* *
@ -360,9 +332,6 @@ namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
blur(): void { blur(): void {
if (this.appmenu && this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
this.trigger("blur", undefined); this.trigger("blur", undefined);
if(this.dialog) if(this.dialog)
{ {
@ -401,6 +370,16 @@ namespace OS {
return (this.scheme as GUI.tag.WindowTag).apptitle; return (this.scheme as GUI.tag.WindowTag).apptitle;
} }
/**
* Getter to access the application window instance
*
* @memberof BaseApplication
*/
get window(): GUI.tag.WindowTag
{
return this.scheme as GUI.tag.WindowTag;
}
/** /**
* Function called when the application exit. * Function called when the application exit.
* If the input exit event is prevented, the application * If the input exit event is prevented, the application
@ -414,9 +393,6 @@ namespace OS {
protected onexit(evt: BaseEvent): void { protected onexit(evt: BaseEvent): void {
this.cleanup(evt); this.cleanup(evt);
if (!evt.prevent) { if (!evt.prevent) {
if (this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
$(this.scheme).remove(); $(this.scheme).remove();
} }
} }
@ -435,7 +411,7 @@ namespace OS {
* Base menu definition. This function * Base menu definition. This function
* returns the based menu definition of all applications. * returns the based menu definition of all applications.
* Other application specific menu entries * Other application specific menu entries
* should be defined in [[menu]] function * should be defined in {@link menu} function
* *
* @protected * @protected
* @returns {GUI.BasicItemType[]} * @returns {GUI.BasicItemType[]}
@ -479,7 +455,36 @@ namespace OS {
} }
/** /**
* The cleanup function that is called by [[onexit]] function. * Show local toast notification
*
* @param {any} data to send
* @param {GUI.ToastOptions} notification options see {@link GUI.ToastOptions}
* @returns {void}
* @memberof BaseApplication
*/
toast(data: any, opts?: GUI.ToastOptions): void {
let options: GUI.ToastOptions = {
location: GUI.ANCHOR.SOUTH_EST,
timeout: 3,
tag: "afx-label"
};
if(opts)
{
for(const k in opts)
{
options[k] = opts[k];
}
}
let d = data;
if(typeof data == "string" || data instanceof FormattedString)
{
d = {text: data};
}
this._gui.toast(d,options, this);
}
/**
* The cleanup function that is called by {@link onexit} function.
* Application need to override this function to perform some * Application need to override this function to perform some
* specific task before exiting or to prevent the application * specific task before exiting or to prevent the application
* to be exited * to be exited
@ -489,23 +494,6 @@ namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected cleanup(e: BaseEvent): void {} 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; BaseApplication.type = ModelType.Application;

View File

@ -58,23 +58,16 @@ namespace OS {
} }
/** /**
* Exit the sub-window * Purge the model from the system
* *
* @returns {void} * @protected
* @memberof SubWindow * @memberof BaseModel
*/ */
quit(): void { protected destroy(): void
const evt = new BaseEvent("exit", false); {
this.onexit(evt);
if (!evt.prevent) {
delete this._observable;
if (this.scheme) { if (this.scheme) {
$(this.scheme).remove(); $(this.scheme).remove();
} }
if (this.dialog) {
return this.dialog.quit();
}
}
} }
/** /**
@ -218,6 +211,19 @@ namespace OS {
} }
} }
/**
* Show the dialog
*
* @memberof BaseDialog
*/
show(): void {
this.trigger("focus", undefined);
this.trigger("focused", undefined);
if (this.dialog) {
this.dialog.show();
}
}
} }
/** /**
@ -230,6 +236,7 @@ namespace OS {
* @extends {BaseDialog} * @extends {BaseDialog}
*/ */
export class BasicDialog extends BaseDialog { export class BasicDialog extends BaseDialog {
['constructor']: typeof BasicDialog
/** /**
* Placeholder for the UI scheme to be rendered. This can * Placeholder for the UI scheme to be rendered. This can
* be either the string definition of the scheme or * be either the string definition of the scheme or
@ -243,7 +250,7 @@ namespace OS {
/** /**
* If the `markup` variable is not provided, then * If the `markup` variable is not provided, then
* the [[init]] function will find the scheme definition * the {@link init} function will find the scheme definition
* in this class variable * in this class variable
* *
* @static * @static
@ -281,13 +288,12 @@ namespace OS {
return GUI.htmlToScheme(this.markup, this, this.host); return GUI.htmlToScheme(this.markup, this, this.host);
} else { } else {
// a file handle // a file handle
return this.render(this.markup.path); this.render(this.markup.path);
} }
} else if ( } else if (
GUI.dialogs[this.name] && this.constructor.scheme
GUI.dialogs[this.name].scheme
) { ) {
const html: string = GUI.dialogs[this.name].scheme; const html: string = this.constructor.scheme;
return GUI.htmlToScheme(html.trim(), this, this.host); return GUI.htmlToScheme(html.trim(), this, this.host);
} else { } else {
this.error(__("Unable to find dialog scheme")); this.error(__("Unable to find dialog scheme"));
@ -320,6 +326,7 @@ namespace OS {
} }
win.resizable = false; win.resizable = false;
win.minimizable = false; win.minimizable = false;
win.menu = undefined;
$(win).trigger("focus"); $(win).trigger("focus");
} }
} }
@ -365,23 +372,15 @@ namespace OS {
*/ */
main(): void { main(): void {
super.main(); super.main();
const $input = $(this.find("txtInput")); const input = this.find("txtInput") as GUI.tag.InputTag;
if (this.data && this.data.label) { if(this.data)
(this.find( {
"lbl" input.set(this.data);
) as tag.LabelTag).text = this.data.label;
}
if (this.data && this.data.value) {
$input.val(this.data.value);
}
if (this.data && this.data.type) {
($input[0] as HTMLInputElement).type = this.data.type
} }
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => { (this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
if (this.handle) { if (this.handle) {
this.handle($input.val()); this.handle(input.value);
} }
return this.quit(); return this.quit();
}; };
@ -392,49 +391,39 @@ namespace OS {
return this.quit(); return this.quit();
}; };
$input.on("keyup", (e) => { input.on("keyup", (e) => {
if (e.which !== 13) { if (e.which !== 13) {
return; return;
} }
if (this.handle) { if (this.handle) {
this.handle($input.val()); this.handle(input.value);
} }
return this.quit(); return this.quit();
}); });
$input.trigger("focus"); input.trigger("focus");
} }
} }
/** /**
* Scheme definition of the Prompt dialog * Scheme definition of the Prompt dialog
*/ */
PromptDialog.scheme = `\ PromptDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt"> <afx-app-window width='250' height='200' apptitle = "Prompt">
<afx-vbox> <afx-vbox padding = "10">
<afx-hbox> <afx-input data-id= "txtInput"></afx-input>
<div data-width = "10" ></div> <div data-height="35" style="text-align: right;">
<afx-vbox> <afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<div data-height="10" ></div> <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
<afx-label data-id = "lbl" ></afx-label> </div>
<input type = "text" data-id= "txtInput" ></input>
<div data-height="10" ></div>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
/** /**
* A text dialog is similar to a [[PromptDialog]] nut allows * A text dialog is similar to a {@link PromptDialog} nut allows
* user to input multi-line text. * user to input multi-line text.
* *
* Refer to [[PromptDialog]] for the definition of input and callback data * Refer to {@link PromptDialog} for the definition of input and callback data
* of the dialog * of the dialog
* *
* @export * @export
@ -457,15 +446,11 @@ namespace OS {
*/ */
main(): void { main(): void {
super.main(); super.main();
const $input = $(this.find("txtInput")); const input = this.find("txtInput") as tag.InputTag;
if (this.data && this.data.value) { if(this.data)
$input.val(this.data.value); input.set(this.data);
}
if (this.data && this.data.disable) {
$input.prop('disabled', true);
}
(this.find("btn-Ok") as tag.ButtonTag).onbtclick = (_e) => { (this.find("btn-Ok") as tag.ButtonTag).onbtclick = (_e) => {
const value = $input.val(); const value = input.value;
if (!value || value === "") { if (!value || value === "") {
return; return;
} }
@ -481,7 +466,7 @@ namespace OS {
return this.quit(); return this.quit();
}; };
$input.focus(); input.trigger("focus");
} }
} }
/** /**
@ -489,21 +474,12 @@ namespace OS {
*/ */
TextDialog.scheme = `\ TextDialog.scheme = `\
<afx-app-window data-id = "TextDialog" width='400' height='300'> <afx-app-window data-id = "TextDialog" width='400' height='300'>
<afx-vbox> <afx-vbox padding="10">
<afx-hbox> <afx-input data-id= "txtInput" verbose="true"></afx-input>
<div data-width = "10" ></div> <div data-height="40" style="text-align:right;padding-top:5px;">
<afx-vbox> <afx-button data-id = "btn-Ok" text = "__(Ok)" ></afx-button>
<div data-height="10" ></div> <afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
<textarea data-id= "txtInput" ></textarea> </div>
<div data-height="10" ></div>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btn-Ok" text = "__(Ok)" data-width = "40" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -568,23 +544,13 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
CalendarDialog.scheme = `\ CalendarDialog.scheme = `\
<afx-app-window width='300' height='250' apptitle = "Calendar" > <afx-app-window width='350' height='380' apptitle = "Calendar" >
<afx-vbox> <afx-vbox padding="10">
<afx-hbox>
<div data-width = "10" ></div>
<afx-vbox>
<div data-height="10" ></div>
<afx-calendar-view data-id = "cal" ></afx-calendar-view> <afx-calendar-view data-id = "cal" ></afx-calendar-view>
<div data-height="10" ></div> <div data-height="35" style = 'text-align: right;'>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button> <afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button> <afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox> </div>
<div data-height="10" ></div>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -599,7 +565,7 @@ namespace OS {
* title: string // window title * title: string // window title
* } * }
* ``` * ```
* Callback data: [[ColorType]] object * Callback data: {@link ColorType} object
* *
* @export * @export
* @class ColorPickerDialog * @class ColorPickerDialog
@ -647,23 +613,13 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
ColorPickerDialog.scheme = `\ ColorPickerDialog.scheme = `\
<afx-app-window width='320' height='250' apptitle = "Color picker" > <afx-app-window width='320' height='300' apptitle = "Color picker" >
<afx-vbox> <afx-vbox padding = "10">
<afx-hbox>
<div data-width = "10" ></div>
<afx-vbox>
<div data-height="10" ></div>
<afx-color-picker data-id = "cpicker" ></afx-color-picker> <afx-color-picker data-id = "cpicker" ></afx-color-picker>
<div data-height="10" ></div> <div data-height="35" style = "text-align: right;">
<afx-hbox data-height="30"> <afx-button data-id = "btnOk" text = "__(Ok)" ></afx-button>
<div ></div> <afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button> </div>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
<div data-height="10" ></div>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -727,22 +683,12 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
InfoDialog.scheme = `\ InfoDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Info" > <afx-app-window width='300' height='350' apptitle = "Info" >
<afx-vbox> <afx-vbox padding = "10">
<afx-hbox>
<div data-width = "10" ></div>
<afx-vbox>
<div data-height="10" ></div>
<afx-grid-view data-id = "grid" ></afx-grid-view> <afx-grid-view data-id = "grid" ></afx-grid-view>
<div data-height="10" ></div> <div data-height="35" style="text-align: right;">
<afx-hbox data-height="30"> <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
<div ></div> </div>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
<div data-height="10" ></div>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -808,22 +754,13 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
YesNoDialog.scheme = `\ YesNoDialog.scheme = `\
<afx-app-window width='200' height='150' apptitle = "Prompt"> <afx-app-window width='250' height='200' apptitle = "Warning">
<afx-vbox> <afx-vbox padding = "10">
<afx-hbox> <afx-label data-id = "lbl" valign="top" ></afx-label>
<div data-width = "10" ></div> <div data-height="35" style = "text-align: right;">
<afx-vbox> <afx-button data-id = "btnYes" text = "__(Yes)" ></afx-button>
<div data-height="10" ></div> <afx-button data-id = "btnNo" text = "__(No)"></afx-button>
<afx-label data-id = "lbl" ></afx-label> </div>
<div data-height="10" ></div>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btnYes" text = "__(Yes)" data-width = "40" ></afx-button>
<afx-button data-id = "btnNo" text = "__(No)" data-width = "40" ></afx-button>
</afx-hbox>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -894,22 +831,13 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
SelectionDialog.scheme = `\ SelectionDialog.scheme = `\
<afx-app-window width='250' height='300' apptitle = "Selection"> <afx-app-window width='350' height='300' apptitle = "Selection">
<afx-vbox> <afx-vbox padding = "10">
<afx-hbox>
<div data-width = "10" ></div>
<afx-vbox>
<div data-height="10" ></div>
<afx-list-view data-id = "list" ></afx-list-view> <afx-list-view data-id = "list" ></afx-list-view>
<div data-height="10" ></div> <div data-height="35" style = "text-align: right;">
<afx-hbox data-height="30"> <afx-button data-id = "btnOk" text = "__(Ok)" ></afx-button>
<div ></div> <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button> </div>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -953,18 +881,11 @@ namespace OS {
iconclass: mt.iconclass, iconclass: mt.iconclass,
text: `${mt.name}(v${mt.version})`, text: `${mt.name}(v${mt.version})`,
}); });
$(this.find("mydesc")).html(mt.description); $(this.find("mydesc")).html(`${mt.description} <br/> ${mt.info.author} (${mt.info.email})`);
// grid data for author info // grid data for author info
if (!mt.info) { if (!mt.info) {
return; return;
} }
const rows = [
[{ text: __("Author") }, { text: mt.info.author }],
[{ text: __("Email") }, { text: mt.info.email }]
];
const grid = this.find("mygrid") as tag.GridViewTag;
grid.header = [{ text: "", width: 100 }, { text: "" }];
grid.rows = rows;
`pkg://${mt.pkgname?mt.pkgname:mt.app}/README.md` `pkg://${mt.pkgname?mt.pkgname:mt.app}/README.md`
.asFileHandle() .asFileHandle()
.read() .read()
@ -988,25 +909,19 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
AboutDialog.scheme = `\ AboutDialog.scheme = `\
<afx-app-window data-id = 'about-window' width='450' height='400'> <afx-app-window data-id = 'about-window' width='550' height='450'>
<afx-vbox> <afx-vbox padding = "5">
<div style="text-align:center; margin-top:10px;" data-height="50"> <div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;"> <h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel' style="display: inline-block;"></afx-label> <afx-label data-id = 'mylabel' style="display: inline-block;"></afx-label>
</h3> </h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i> <i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div> </div>
<afx-hbox data-height="60">
<div data-width="10"></div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-hbox>
<div data-id="read-me" style="overflow-x: hidden; overflow-y: auto;"></div> <div data-id="read-me" style="overflow-x: hidden; overflow-y: auto;"></div>
<div data-height="10"></div> <div data-height="10"></div>
<afx-hbox data-height="30"> <div data-height="35" style = "text-align: right;">
<div ></div> <afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" ></afx-button> </div>
</afx-hbox>
<div data-height = "10"></div>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -1069,7 +984,7 @@ namespace OS {
super.main(); super.main();
const fileview = this.find("fileview") as tag.FileViewTag; const fileview = this.find("fileview") as tag.FileViewTag;
const location = this.find("location") as tag.ListViewTag; const location = this.find("location") as tag.ListViewTag;
const filename = this.find("filename") as HTMLInputElement; const filename = this.find("filename") as tag.InputTag;
fileview.fetch = (path: string) => fileview.fetch = (path: string) =>
new Promise(function (resolve, reject) { new Promise(function (resolve, reject) {
if (!path) { if (!path) {
@ -1126,7 +1041,7 @@ namespace OS {
} }
fileview.onfileselect = function (e) { fileview.onfileselect = function (e) {
if (e.data.type === "file") { if (e.data.type === "file") {
return $(filename).val(e.data.filename); return filename.value = e.data.filename;
} }
}; };
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => { (this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
@ -1173,7 +1088,7 @@ namespace OS {
} }
} }
const name = $(filename).val(); const name = filename.value;
if (this.handle) { if (this.handle) {
this.handle({ file: f, name }); this.handle({ file: f, name });
} }
@ -1187,9 +1102,13 @@ namespace OS {
}; };
if (this.data && this.data.file) { if (this.data && this.data.file) {
$(filename) $(filename).show();
.css("display", "block") filename.value = (this.data.file.basename || "Untitled");
.val(this.data.file.basename || "Untitled"); this.trigger("resize");
}
else
{
$(filename).hide();
this.trigger("resize"); this.trigger("resize");
} }
if (this.data && this.data.hidden) { if (this.data && this.data.hidden) {
@ -1215,21 +1134,16 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
FileDialog.scheme = `\ FileDialog.scheme = `\
<afx-app-window width='400' height='300'> <afx-app-window width='400' height='450'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox> <afx-vbox>
<afx-list-view data-id = "location" dropdown = "true" data-height = "35"></afx-list-view>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view> <afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" ></input> <afx-input data-height = '52' label = "__(Target file/folder)" type = "text" data-id = "filename" ></afx-input>
<afx-hbox data-height = '30'> <div style=' text-align:right;' data-height="35">
<div style=' text-align:right;'>
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button> <afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button> <afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div> </div>
<div data-width="5"></div>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-hbox>
</afx-app-window>\ </afx-app-window>\
`; `;
@ -1311,17 +1225,15 @@ namespace OS {
* @memberof MultiInputDialog * @memberof MultiInputDialog
*/ */
init(): void { init(): void {
let height = 60; let height = 85;
let html = ""; let html = "";
if (this.data && this.data.model) { if (this.data && this.data.model) {
const model = this.data.model; const model = this.data.model;
for (const key in model) { for (const key in model) {
html += `\ html += `\
<afx-label data-height="25" text="{0}" ></afx-label> <afx-input data-height="52" text="{0}" type="text" name = {1} ></afx-input>
<input data-height="25" type="text" name="{1}" ></input>
<div data-height="10" ></div>
`.format(model[key], key); `.format(model[key], key);
height += 60; height += 52;
} }
} }
this.markup = MultiInputDialog.scheme.format(height, html); this.markup = MultiInputDialog.scheme.format(height, html);
@ -1364,20 +1276,13 @@ namespace OS {
*/ */
MultiInputDialog.scheme = `\ MultiInputDialog.scheme = `\
<afx-app-window width='350' height='{0}'> <afx-app-window width='350' height='{0}'>
<afx-hbox> <afx-vbox padding = "5">
<div data-width="10" ></div>
<afx-vbox>
<div data-height="5" ></div>
{1} {1}
<afx-hbox data-height="30"> <div data-height="35" style = "text-align: right;">
<div ></div> <afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button> <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button> </div>
</afx-hbox>
<div data-height="5" ></div>
</afx-vbox> </afx-vbox>
<div data-width="10" ></div>
</afx-hbox>
</afx-app-window>`; </afx-app-window>`;
@ -1463,28 +1368,22 @@ namespace OS {
*/ */
private addField(key: string, value: string, removable: boolean): void { private addField(key: string, value: string, removable: boolean): void {
const div = $("<div>") const div = $("<div>")
.css("width", "100%")
.css("display", "flex") .css("display", "flex")
.css("flex-direction", "row") .css("flex-direction", "row")
.appendTo(this.container); .appendTo(this.container);
$("<input>") $("<input>")
.attr("type", "text") .attr("type", "text")
.css("width", "120px") .css("flex", "1")
.css("height", "23px")
.val(key) .val(key)
.appendTo(div); .appendTo(div);
$("<input>") $("<input>")
.attr("type", "text") .attr("type", "text")
.css("width", "200px") .css("flex", "1")
.css("height", "23px")
.val(value) .val(value)
.appendTo(div); .appendTo(div);
if (removable) { if (removable) {
const btn = $("<afx-button>"); const btn = $("<afx-button>");
btn[0].uify(undefined); btn[0].uify(undefined);
$("button", btn)
.css("width", "23px")
.css("height", "23px");
(btn[0] as tag.ButtonTag).iconclass = "fa fa-minus"; (btn[0] as tag.ButtonTag).iconclass = "fa fa-minus";
btn btn
.on("click", () => { .on("click", () => {
@ -1492,12 +1391,13 @@ namespace OS {
}) })
.appendTo(div); .appendTo(div);
} }
else { else
{
$("<div>") $("<div>")
.css("width", "23px") .css("width", "40px")
.css("height", "35px")
.appendTo(div); .appendTo(div);
} }
} }
} }
@ -1506,23 +1406,18 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
KeyValueDialog.scheme = `\ KeyValueDialog.scheme = `\
<afx-app-window width='350' height='300'> <afx-app-window width='400' height='350'>
<afx-hbox> <afx-vbox padding = "10">
<div data-width="10" ></div>
<afx-vbox>
<div data-height="5" ></div>
<afx-label text="__(Enter key-value data)" data-height="30"></afx-label> <afx-label text="__(Enter key-value data)" data-height="30"></afx-label>
<div data-id="container"></div> <div data-id="container"></div>
<afx-hbox data-height="30"> <afx-hbox data-height="35">
<afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "30" ></afx-button> <afx-button data-id = "btnAdd" iconclass="fa fa-plus" data-width = "35" ></afx-button>
<div ></div> <div style = "text-align: right;">
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button> <afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button> <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
</div>
</afx-hbox> </afx-hbox>
<div data-height="5" ></div>
</afx-vbox> </afx-vbox>
<div data-width="10" ></div>
</afx-hbox>
</afx-app-window>`; </afx-app-window>`;
} }
} }

View File

@ -155,7 +155,7 @@ namespace OS {
* to handle all local events inside that model. * to handle all local events inside that model.
* *
* This observable object is propagate to all the * This observable object is propagate to all the
* UI elements ([[AFXTag]]) inside the model * UI elements ({@link OS.GUI.AFXTag}) inside the model
* *
* @protected * @protected
* @type {API.Announcer} * @type {API.Announcer}
@ -296,6 +296,8 @@ namespace OS {
this.on("exit", () => this.quit(false)); this.on("exit", () => this.quit(false));
this.host = this._gui.desktop(); this.host = this._gui.desktop();
this.dialog = undefined; this.dialog = undefined;
// relay global events to local events
this.subscribe("desktopresize", (e) => this.observable.trigger("desktopresize", e));
} }
/** /**
@ -320,10 +322,10 @@ namespace OS {
* *
* @protected * @protected
* @param {string} p VFS path to the UI scheme definition * @param {string} p VFS path to the UI scheme definition
* @returns {void} * @returns {Promise<any>}
* @memberof BaseModel * @memberof BaseModel
*/ */
protected render(p: string): void { protected render(p: string): Promise<any> {
return GUI.loadScheme(p, this, this.host); return GUI.loadScheme(p, this, this.host);
} }
@ -334,7 +336,7 @@ namespace OS {
* @returns {void} * @returns {void}
* @memberof BaseModel * @memberof BaseModel
*/ */
quit(force: boolean): void { quit(force: boolean = false): void {
const evt = new BaseEvent("exit", force); const evt = new BaseEvent("exit", force);
this.onexit(evt); this.onexit(evt);
if (!evt.prevent) { if (!evt.prevent) {
@ -346,10 +348,22 @@ namespace OS {
if (this.dialog) { if (this.dialog) {
this.dialog.quit(); this.dialog.quit();
} }
return PM.kill(this); announcer.unregister(this);
this.destroy();
} }
} }
/**
* Purge the model from the system
*
* @protected
* @memberof BaseModel
*/
protected destroy(): void
{
return PM.kill(this);
}
/** /**
* Model meta data, need to be implemented by * Model meta data, need to be implemented by
* subclasses * subclasses
@ -565,10 +579,10 @@ namespace OS {
} }
/** /**
* Open a [[YesNoDialog]] to confirm a task * Open a {@link OS.GUI.dialogs.YesNoDialog} to confirm a task
* *
* @protected * @protected
* @param {GenericObject<any>} data [[YesNoDialog]] input data * @param {GenericObject<any>} data {@link OS.GUI.dialogs.YesNoDialog} input data
* @returns {Promise<boolean>} * @returns {Promise<boolean>}
* @memberof BaseModel * @memberof BaseModel
*/ */
@ -687,12 +701,16 @@ namespace OS {
* @returns {HTMLElement} * @returns {HTMLElement}
* @memberof BaseModel * @memberof BaseModel
*/ */
protected find(id: string): HTMLElement { protected find<T extends HTMLElement>(id: string): T {
if (this.scheme) { if (this.scheme) {
return $(`[data-id='${id}']`, this.scheme)[0]; return $(`[data-id='${id}']`, this.scheme)[0] as T;
} }
} }
/*protected $(id: string) : T {
return this.find(id) as T;
}*/
/** /**
* Select all DOM Element inside the UI of the model * Select all DOM Element inside the UI of the model
* using JQuery selector * using JQuery selector

View File

@ -55,10 +55,10 @@ namespace OS {
/** /**
* Text of the service shown in the system tray * Text of the service shown in the system tray
* *
* @type {string} * @type {string | FormattedString}
* @memberof BaseService * @memberof BaseService
*/ */
text: string; text: string | FormattedString;
/** /**
* Reference to the menu entry DOM element attached * Reference to the menu entry DOM element attached
@ -71,7 +71,7 @@ namespace OS {
/** /**
* Reference to the timer that periodically executes the callback * Reference to the timer that periodically executes the callback
* defined in [[watch]]. * defined in {@link watch}.
* *
* @private * @private
* @type {number} * @type {number}
@ -79,13 +79,6 @@ namespace OS {
*/ */
private timer: number; private timer: number;
/**
* Reference to the system tray menu
*
* @type {HTMLElement}
* @memberof BaseService
*/
holder: HTMLElement;
/** /**
* Placeholder for service select callback * Placeholder for service select callback
@ -93,7 +86,7 @@ namespace OS {
* @memberof BaseService * @memberof BaseService
*/ */
onmenuselect: ( onmenuselect: (
d: OS.GUI.TagEventType<GUI.tag.MenuEventData> d: OS.GUI.TagEventType<GUI.tag.StackMenuEventData>
) => void; ) => void;
/** /**
@ -108,7 +101,6 @@ namespace OS {
this.iconclass = "fa fa-paper-plane-o"; this.iconclass = "fa fa-paper-plane-o";
this.text = ""; this.text = "";
this.timer = undefined; this.timer = undefined;
this.holder = undefined;
this.onmenuselect = (d) => { this.onmenuselect = (d) => {
return this.awake(d); return this.awake(d);
}; };
@ -140,7 +132,9 @@ namespace OS {
* @memberof BaseService * @memberof BaseService
*/ */
update(): void { update(): void {
(this.domel as GUI.tag.MenuEntryTag).data = this; if(!this.domel)
return;
(this.domel as GUI.tag.ListViewItemTag).data = this;
} }
/** /**
@ -153,17 +147,6 @@ namespace OS {
return application[this.name].meta; 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 * Set the callback that will be called periodically
* after a period of time. * after a period of time.
@ -234,7 +217,7 @@ namespace OS {
* @param {GUI.TagEventType} e * @param {GUI.TagEventType} e
* @memberof BaseService * @memberof BaseService
*/ */
abstract awake(e: GUI.TagEventType<GUI.tag.MenuEventData>): void; abstract awake(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void;
/** /**
* Do nothing * Do nothing

View File

@ -39,8 +39,8 @@ interface String {
/** /**
* Parse the current string and convert it * Parse the current string and convert it
* to an object of type [[Version]] if the string * to an object of type {@link OS.Version} if the string
* is in the format recognized by [[Version]], * is in the format recognized by {@link OS.Version},
* e.g.: `1.0.1-a` * e.g.: `1.0.1-a`
* *
* @returns {OS.Version} * @returns {OS.Version}
@ -98,7 +98,7 @@ interface String {
format(...args: any[]): string; format(...args: any[]): string;
/** /**
* Create a [[FormattedString]] object using the current * Create a {@link OS.FormattedString} object using the current
* string and the input parameters * string and the input parameters
* *
* @param {...any[]} args * @param {...any[]} args
@ -157,9 +157,28 @@ interface String {
trimBy(arg: string): string; trimBy(arg: string): string;
} }
/**
* Extend the Array prototype with some API
* functions used by AntOS API
*
* @interface Array
* @template T
*/
interface Array<T> {
/**
* Check if the array includes an element
*
* @param {T} element to check
* @returns {boolean}
* @memberof Array
*/
includes(element: T): boolean;
}
/** /**
* Extend the Data prototype with the * Extend the Data prototype with the
* [[timestamp]] function * {@link timestamp} function
* *
* @interface Date * @interface Date
*/ */
@ -191,7 +210,7 @@ interface GenericObject<T> {
} }
/** /**
* Global function to create a [[FormattedString]] from * Global function to create a {@link OS.FormattedString} from
* a formatted string and a list of parameters. Example * a formatted string and a list of parameters. Example
* *
* ```typescript * ```typescript
@ -214,6 +233,71 @@ declare function __(...args: any[]): OS.FormattedString | string;
*/ */
declare function __e(e: Error): Error; declare function __e(e: Error): Error;
/**
* JQuery event-extensions to support doubletap on
* mobile device
*/
jQuery.event.special.dbltap = {
bindType: 'touchend',
delegateType: 'touchend',
handle: function (event: any) {
var handleObj = event.handleObj,
targetData = jQuery.data(event.target),
now = new Date().getTime(),
delta = targetData.lastTouchEnd ? now - targetData.lastTouchEnd : 0,
delay = delay == null ? 300 : delay;
if (delta < delay && delta > 30) {
targetData.lastTouchEnd = null;
event.type = handleObj.origType;
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function (property) {
event[property] = event.originalEvent.changedTouches[0][property];
})
// let jQuery handle the triggering of "dbltap" event handlers
handleObj.handler.apply(this, arguments);
} else {
targetData.lastTouchEnd = now;
}
}
};
/**
* JQuery event-extensions to support long touch event on
* mobile device
*/
jQuery.event.special.longtouch = {
bindType: 'touchstart',
//delegateType: 'touchstart',
handle: function (evt: any) {
let targetData = jQuery.data(evt.target);
let handleObj = evt.handleObj;
targetData.lastTouchStart = new Date().getTime();
$(evt.target).on("touchend", (event) => {
let now = new Date().getTime();
let end_targetData = jQuery.data(event.target);
let delta = end_targetData.lastTouchStart ? now - end_targetData.lastTouchStart : 0;
$(event.target).off("touchend");
const offset_top = Math.abs(event.originalEvent.changedTouches[0].clientY - evt.originalEvent.changedTouches[0].clientY);
const offset_left = Math.abs(event.originalEvent.changedTouches[0].clientX - evt.originalEvent.changedTouches[0].clientX);
console.log(offset_left, offset_top);
if(delta > 1000 && offset_top < 10 && offset_left < 10)
{
['clientX', 'clientY', 'pageX', 'pageY'].forEach(function (property) {
evt[property] = event.originalEvent.changedTouches[0][property];
})
event.preventDefault();
evt.type = handleObj.origType;
handleObj.handler.apply(this, arguments);
}
});
}
};
/** /**
* This namespace is the main entry point of AntOS * This namespace is the main entry point of AntOS
* API * API
@ -286,7 +370,7 @@ namespace OS {
/** /**
* The value of the format pattern represented * The value of the format pattern represented
* in [[fs]] * in {@link fs}
* *
* @type {any[]} * @type {any[]}
* @memberof FormattedString * @memberof FormattedString
@ -505,10 +589,8 @@ namespace OS {
* *
* @memberof Version * @memberof Version
*/ */
set version_string(v: string) set version_string(v: string) {
{ if (!v) {
if(!v)
{
this.string = undefined; this.string = undefined;
this.major = undefined; this.major = undefined;
this.minor = undefined; this.minor = undefined;
@ -526,8 +608,7 @@ namespace OS {
this.branch = 3; this.branch = 3;
if (arr.length >= 2 && br[arr[1]]) { if (arr.length >= 2 && br[arr[1]]) {
this.branch = br[arr[1]]; this.branch = br[arr[1]];
if(arr[2]) if (arr[2]) {
{
this.build_id = arr[2]; this.build_id = arr[2];
} }
} }
@ -551,8 +632,7 @@ namespace OS {
this.patch = Number(mt[2]); this.patch = Number(mt[2]);
} }
} }
get version_string(): string get version_string(): string {
{
return this.string; return this.string;
} }
@ -797,23 +877,27 @@ namespace OS {
/** /**
* Variable represents the current AntOS version, it * Variable represents the current AntOS version, it
* is an instance of [[Version]] * is an instance of {@link OS.Version}
*/ */
export const VERSION: Version = new Version(undefined); export const VERSION: Version = new Version(undefined);
/** /**
* Variable represents the current AntOS source code repository * Variable represents the current AntOS source code repository
* is an instance of [[string]] * is an instance of string
*/ */
export const REPOSITORY: string = "https://github.com/lxsang/antos"; export const REPOSITORY: string = "https://github.com/lxsang/antos";
/**
* Indicate whether the current de
*/
export var mobile: boolean = false;
/** /**
* Register a model prototype to the system namespace. * Register a model prototype to the system namespace.
* There are two types of model to be registered, if the model * There are two types of model to be registered, if the model
* is of type [[SubWindow]], its prototype will be registered * is of type {@link OS.GUI.SubWindow}, its prototype will be registered
* in the [[dialogs]] namespace, otherwise, if the model type * in the {@link OS.GUI.dialogs} namespace, otherwise, if the model type
* is [[Application]] or [[Service]], its prototype will be * is {@link OS.application.BaseApplication} or {@link OS.application.BaseService}, its prototype will be
* registered in the [[application]] namespace. * registered in the {@link application} namespace.
* *
* When a model is loaded in the system, its prototype is registered * When a model is loaded in the system, its prototype is registered
* for later uses * for later uses
@ -859,7 +943,6 @@ namespace OS {
$("#wrapper").empty(); $("#wrapper").empty();
GUI.clearTheme(); GUI.clearTheme();
announcer.observable = new API.Announcer(); announcer.observable = new API.Announcer();
announcer.quota = 0;
resetSetting(); resetSetting();
PM.processes = {}; PM.processes = {};
PM.pidalloc = 0; PM.pidalloc = 0;
@ -867,7 +950,7 @@ namespace OS {
/** /**
* Booting up AntOS. This function checks whether the user * Booting up AntOS. This function checks whether the user
* is successfully logged in, then call [[startAntOS]], otherwise * is successfully logged in, then call {@link OS.GUI.startAntOS}, otherwise
* it shows the login screen * it shows the login screen
* *
* @export * @export
@ -875,6 +958,7 @@ namespace OS {
export function boot(): void { export function boot(): void {
//first login //first login
console.log("Booting system"); console.log("Booting system");
// check whether we are on mobile device
API.handle API.handle
.auth() .auth()
.then(function (d: API.RequestResult) { .then(function (d: API.RequestResult) {
@ -899,7 +983,7 @@ namespace OS {
/** /**
* Perform the system shutdown operation. This function calls all * Perform the system shutdown operation. This function calls all
* clean up handles in [[cleanupHandles]], then save the system setting * clean up handles in {@link cleanupHandles}, then save the system setting
* before exiting * before exiting
* *
* @export * @export
@ -921,7 +1005,7 @@ namespace OS {
} }
/** /**
* Register a callback to the system [[cleanupHandles]] * Register a callback to the system {@link cleanupHandles}
* *
* @export * @export
* @param {string} n callback string name * @param {string} n callback string name
@ -959,7 +1043,7 @@ namespace OS {
export interface PackageMetaType { export interface PackageMetaType {
/** /**
* The application class name, if the package has only services * The application class name, if the package has only services
* this property is ignored and [[pkgname]] should be specified * this property is ignored and {@link pkgname} should be specified
* *
* @type {string} * @type {string}
* @memberof PackageMetaType * @memberof PackageMetaType
@ -967,7 +1051,7 @@ namespace OS {
app?: string; app?: string;
/** /**
* Package name, in case of [[app]] being undefined, this property * Package name, in case of {@link app} being undefined, this property
* need to be specified * need to be specified
* *
* @type {string} * @type {string}
@ -1106,7 +1190,7 @@ namespace OS {
/** /**
* Package version, should be in a format conforming * Package version, should be in a format conforming
* to the version definition in [[Version]] class * to the version definition in {@link Version} class
* *
* @type {string} * @type {string}
* @memberof PackageMetaType * @memberof PackageMetaType
@ -1163,14 +1247,41 @@ namespace OS {
export var lang: GenericObject<string> = {}; export var lang: GenericObject<string> = {};
/** /**
* Re-export the system announcement [[getMID]] function to the * A task is a Promise object that is tracked by AntOS via the
* core API * Announcerment system
*
* Task manager implementation can subscribe to the following global events
* - ANTOS-TASK-PENDING : a new task/promise is created and executing
* - ANTOS-TASK-FULFILLED: a fullfilled task is a resolved promise
* - ANTOS-TASK-REJECTED: a rejected task is a rejected or error promise
*
* Whenever a task is created by this API, it states will be automatically announced
* to any subscribers of these events
* *
* @export * @export
* @returns {number} * @param {Promise} a Promise object
*/ */
export function mid(): number { export function Task(fn: (resolve: (any) => void, reject: (any) => void) => void): Promise<any> {
return announcer.getMID(); return new Promise(async (ok, nok) => {
const promise = new Promise(fn);
const ann: API.AnnouncementDataType<Promise<any>> = {} as API.AnnouncementDataType<Promise<any>>;
ann.name = "OS";
ann.u_data = promise;
ann.id = Math.floor(Math.random() * 1e6);
try {
ann.message = "ANTOS-TASK-PENDING";
announcer.trigger(ann.message, ann);
const data = await promise;
ok(data);
ann.message = "ANTOS-TASK-FULFILLED";
announcer.trigger(ann.message, ann);
}
catch (e) {
ann.message = "ANTOS-TASK-REJECTED";
announcer.trigger(ann.message, ann);
nok(__e(e));
}
});
} }
/** /**
@ -1185,10 +1296,9 @@ namespace OS {
* @returns {Promise<any>} a promise on the result data * @returns {Promise<any>} a promise on the result data
*/ */
export function post(p: string, d: any): Promise<any> { export function post(p: string, d: any): Promise<any> {
return new Promise(function (resolve, reject) { return API.Task(async (resolve, reject) => {
const q = announcer.getMID(); try {
API.loading(q, p); $.ajax({
return $.ajax({
type: "POST", type: "POST",
url: p, url: p,
contentType: "application/json", contentType: "application/json",
@ -1206,13 +1316,15 @@ namespace OS {
success: null, success: null,
}) })
.done(function (data) { .done(function (data) {
API.loaded(q, p, "OK");
return resolve(data); return resolve(data);
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
API.loaded(q, p, "FAIL"); reject(e);
return reject(API.throwe(s));
}); });
}
catch (e) {
reject(__e(e));
}
}); });
} }
@ -1228,21 +1340,17 @@ namespace OS {
* @returns {Promise<ArrayBuffer>} a promise on the returned binary data * @returns {Promise<ArrayBuffer>} a promise on the returned binary data
*/ */
export function blob(p: string): Promise<ArrayBuffer> { export function blob(p: string): Promise<ArrayBuffer> {
return new Promise(function (resolve, reject) { return API.Task(function (resolve, reject) {
const q = announcer.getMID();
const r = new XMLHttpRequest(); const r = new XMLHttpRequest();
r.open("GET", p, true); r.open("GET", p, true);
r.responseType = "arraybuffer"; r.responseType = "arraybuffer";
r.onload = function (e) { r.onload = function (e) {
if (this.status === 200 && this.readyState === 4) { if (this.status === 200 && this.readyState === 4) {
API.loaded(q, p, "OK");
resolve(this.response); resolve(this.response);
} else { } else {
API.loaded(q, p, "FAIL");
reject(API.throwe(__("Unable to get blob: {0}", p))); reject(API.throwe(__("Unable to get blob: {0}", p)));
} }
}; };
API.loading(q, p);
r.send(); r.send();
}); });
} }
@ -1258,24 +1366,22 @@ namespace OS {
* @returns {Promise<any>} * @returns {Promise<any>}
*/ */
export function upload(p: string, d: string): Promise<any> { export function upload(p: string, d: string): Promise<any> {
return new Promise(function (resolve, reject) { return new Promise((resolve, reject) => {
const q = announcer.getMID();
//insert a temporal file selector //insert a temporal file selector
const o = const o =
$("<input>") $("<input>")
.attr("type", "file") .attr("type", "file")
.attr("multiple", "true"); .attr("multiple", "true");
o.on("change", function () { o.on("change", async () => {
try {
const files = (o[0] as HTMLInputElement).files; const files = (o[0] as HTMLInputElement).files;
const n_files = files.length;
if (n_files > 0)
API.loading(q, p);
const formd = new FormData(); const formd = new FormData();
formd.append("path", d); formd.append("path", d);
jQuery.each(files, (i, file) => { jQuery.each(files, (i, file) => {
formd.append(`upload-${i}`, file); formd.append(`upload-${i}`, file);
}); });
return $.ajax({ const ret = await API.Task((ok, nok) => {
$.ajax({
url: p, url: p,
data: formd, data: formd,
type: "POST", type: "POST",
@ -1283,15 +1389,19 @@ namespace OS {
processData: false, processData: false,
}) })
.done(function (data) { .done(function (data) {
API.loaded(q, p, "OK"); ok(data);
resolve(data);
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
API.loaded(q, p, "FAIL"); //o.remove();
o.remove(); nok(API.throwe(s));
reject(API.throwe(s));
}); });
}); });
resolve(ret);
}
catch (e) {
reject(__e(e));
}
});
return o.trigger("click"); return o.trigger("click");
}); });
} }
@ -1317,44 +1427,6 @@ namespace OS {
o.remove(); o.remove();
} }
/**
* Helper function to trigger the global `loading`
* event. This event should be triggered in the
* beginning of a heavy task
*
* @export
* @param {number} q message id, see [[mid]]
* @param {string} p message string
*/
export function loading(q: number, p: string): void {
const data:API.AnnouncementDataType<number> = {} as API.AnnouncementDataType<number>;
data.id = q;
data.message = p;
data.name = p;
data.u_data = PM.pidactive;
announcer.trigger("loading", data);
}
/**
* Helper function to trigger the global `loaded`
* event: This event should be triggered in the
* end of a heavy task that has previously triggered
* the `loading` event
*
* @export
* @param {number} q the message id of the corresponding `loading` event
* @param {string} p the message string
* @param {string} m message status (`OK` of `FAIL`)
*/
export function loaded(q: number, p: string, m: string): void {
const data:API.AnnouncementDataType<boolean> = {} as API.AnnouncementDataType<boolean>;
data.id = q;
data.message = p;
data.name = "OS";
data.u_data = false;
announcer.trigger("loaded", data);
}
/** /**
* Perform an REST GET request * Perform an REST GET request
* *
@ -1368,7 +1440,7 @@ namespace OS {
* @returns {Promise<any>} a Promise on the requested data * @returns {Promise<any>} a Promise on the requested data
*/ */
export function get(p: string, t: string = undefined): Promise<any> { export function get(p: string, t: string = undefined): Promise<any> {
return new Promise(function (resolve, reject) { return API.Task(function (resolve, reject) {
const conf: any = { const conf: any = {
type: "GET", type: "GET",
url: p, url: p,
@ -1376,15 +1448,11 @@ namespace OS {
if (t) { if (t) {
conf.dataType = t; conf.dataType = t;
} }
const q = announcer.getMID();
API.loading(q, p);
return $.ajax(conf) return $.ajax(conf)
.done(function (data) { .done(function (data) {
API.loaded(q, p, "OK");
return resolve(data); return resolve(data);
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
API.loaded(q, p, "FAIL");
return reject(API.throwe(s)); return reject(API.throwe(s));
}); });
}); });
@ -1506,8 +1574,8 @@ namespace OS {
* Fetch the package meta-data from the server * Fetch the package meta-data from the server
* *
* @export * @export
* @returns {Promise<RequestResult>} Promise on a [[RequestResult]]. * @returns {Promise<RequestResult>} Promise on a {@link RequestResult}.
* A success request result should contain a list of [[PackageMetaType]] * A success request result should contain a list of {@link PackageMetaType}
*/ */
export function fetch(): Promise<RequestResult> { export function fetch(): Promise<RequestResult> {
return API.handle.packages({ return API.handle.packages({
@ -1553,7 +1621,7 @@ namespace OS {
* Save the current user setting * Save the current user setting
* *
* @export * @export
* @returns {Promise<RequestResult>} promise on a [[RequestResult]] * @returns {Promise<RequestResult>} promise on a {@link RequestResult}
*/ */
export function setting(): Promise<RequestResult> { export function setting(): Promise<RequestResult> {
return API.handle.setting(); return API.handle.setting();
@ -1599,7 +1667,7 @@ namespace OS {
* text in spotlight. * text in spotlight.
* *
* This function will call all the search handles stored * This function will call all the search handles stored
* in [[searchHandle]] and build the search result based * in {@link searchHandle} and build the search result based
* on output of these handle * on output of these handle
* *
* @export * @export
@ -1612,11 +1680,11 @@ namespace OS {
for (let k in searchHandle) { for (let k in searchHandle) {
const ret = searchHandle[k](text); const ret = searchHandle[k](text);
if (ret.length > 0) { if (ret.length > 0) {
ret.unshift({ /*ret.unshift({
text: k, text: k,
class: "search-header", class: "search-header",
dataid: "header", dataid: "header",
}); });*/
r = r.concat(ret); r = r.concat(ret);
} }
} }
@ -1624,7 +1692,7 @@ namespace OS {
} }
/** /**
* Register a search handle to the global [[searchHandle]] * Register a search handle to the global {@link searchHandle}
* *
* @export * @export
* @param {string} name handle name string * @param {string} name handle name string
@ -1783,5 +1851,37 @@ namespace OS {
}); });
return o; return o;
} }
/**
* A watcher is a Proxy wrapper to an object
*
* It is used to automatically detect changes in the
* target object and notify the change to a callback
* handler
*
* @export
* @param {Object} target object
* @param {(obj: Object, key: string, value: any, path: any[]) => void} callback function
* @returns {Proxy} the wrapper object
*/
export function watcher(target: GenericObject<any>, callback: (obj: Object, key: any, value: any, path: any[]) => void): Object {
const create_handle_for = (path: any[]) => {
return {
get: (obj: Object, key: any) => {
if (typeof obj[key] === "object" && obj[key] !== null) {
return new Proxy(obj[key], create_handle_for(path.concat(key)));
}
return obj[key];
},
set: (obj: Object, prop: any, value: any) => {
obj[prop] = value;
callback(obj, prop, value, path);
return true;
}
}
};
return new Proxy(target, create_handle_for([]));
}
} }
} }

View File

@ -1,202 +0,0 @@
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see https://www.gnu.org/licenses/.
namespace OS {
export namespace API {
/**
* Simple Virtual Database (VDB) application API.
*
* This API abstracts and provides a standard way to
* connect to a server-side relational database (e.g. sqlite).
*
* Each user when connected has their own database previously
* created. All VDB operations related to that user will be
* performed on this database.
*
* The creation of user database need to be managed by the server-side API.
* The VDB API assumes that the database already exist. All operations
* is performed in tables level
*
* @export
* @class DB
*/
export class DB {
/**
* A table name on the user's database
*
* @private
* @type {string}
* @memberof DB
*/
private table: string;
/**
*Creates an instance of DB.
* @param {string} table table name
* @memberof DB
*/
constructor(table: string) {
this.table = table;
}
/**
* Save data to the current table. The input
* data must conform to the table record format.
*
* On the server side, if the table doest not
* exist yet, it should be created automatically
* by inferring the data structure of the input
* object
*
* @param {GenericObject<any>} d data object represents a current table record
* @returns {Promise<API.RequestResult>}
* @memberof DB
*/
save(d: GenericObject<any>): Promise<API.RequestResult> {
return new Promise(async (resolve, reject) => {
try {
const r = await API.handle.dbquery("save", {
table: this.table,
data: d,
});
if (r.error) {
return reject(API.throwe(r.error.toString()));
}
return resolve(r);
} catch (e) {
return reject(__e(e));
}
});
}
/**
* delete record(s) from the current table by
* a conditional object
*
* @param {*} c conditional object, c can be:
*
* * a `number`: the operation will delete the record with `id = c`
* * a `string`: The SQL string condition that selects record to delete
* * a conditional object represents a SQL condition statement as an object,
* example: `pid = 10 AND cid = 2` is represented by:
*
* ```typescript
* {
* exp: {
* "and": {
* pid: 10,
* cid: 2
* }
* }
* ```
*
* @returns {Promise<API.RequestResult>}
* @memberof DB
*/
delete(
c: GenericObject<any> | number | string
): Promise<API.RequestResult> {
return new Promise(async (resolve, reject) => {
const rq: any = { table: this.table };
if (!c || c === "") {
reject(API.throwe("OS.DB: unknown condition"));
}
if (isNaN(c as number)) {
rq.cond = c;
} else {
rq.id = c;
}
try {
const r = await API.handle.dbquery("delete", rq);
if (r.error) {
return reject(API.throwe(r.error.toString()));
}
return resolve(r);
} catch (e) {
return reject(__e(e));
}
});
}
/**
* Get a record in the table by its primary key
*
* @param {number} id the primary key value
* @returns {Promise<GenericObject<any>>} Promise on returned record data
* @memberof DB
*/
get(id: number): Promise<GenericObject<any>> {
return new Promise(async (resolve, reject) => {
try {
const r = await API.handle.dbquery("get", {
table: this.table,
id: id,
});
if (r.error) {
return reject(API.throwe(r.error.toString()));
}
return resolve(r.result as GenericObject<any>);
} catch (e) {
return reject(__e(e));
}
});
}
/**
* Find records by a condition
*
* @param {GenericObject<any>} cond conditional object
*
* a conditional object represents a SQL condition statement as an object,
* example: `pid = 10 AND cid = 2 ORDER BY date DESC` is represented by:
*
* ```typescript
* {
* exp: {
* "and": {
* pid: 10,
* cid: 2
* }
* },
* order: {
* date: "DESC"
* }
* }
* ```
* @returns {Promise<GenericObject<any>[]>}
* @memberof DB
*/
find(cond: GenericObject<any>): Promise<GenericObject<any>[]> {
return new Promise(async (resolve, reject) => {
try {
const r = await API.handle.dbquery("select", {
table: this.table,
cond,
});
if (r.error) {
return reject(API.throwe(r.error.toString()));
}
return resolve(r.result as GenericObject<any>[]);
} catch (e) {
return reject(__e(e));
}
});
}
}
}
}

View File

@ -26,6 +26,48 @@ namespace OS {
* - System dialogs definition * - System dialogs definition
*/ */
export namespace GUI { export namespace GUI {
/**
* Enum definition of different UI locattion
*
* @export
* @enum {string }
*/
export enum ANCHOR {
/**
* Center top
*/
NORTH = "NORTH",
/**
* Center bottom
*/
SOUTH = "SOUTH",
/**
* Center left
*/
WEST = "WEST",
/**
* Center right
*/
EST = "EST",
/**
* Top left
*/
NORTH_WEST = "NORTH_WEST",
/**
* Bottom left
*/
SOUTH_WEST = "SOUTH_WEST",
/**
* Top right
*/
NORTH_EST = "NORTH_EST",
/**
* Bottom right
*/
SOUTH_EST = "SOUTH_EST",
}
/** /**
* AntOS keyboard shortcut type definition * AntOS keyboard shortcut type definition
* *
@ -66,7 +108,7 @@ namespace OS {
/** /**
* Item children, usually used by tree view or menu item * Item children, usually used by tree view or menu item
* This property is keep for compatibility purposes only. * This property is keep for compatibility purposes only.
* Otherwise, the [[nodes]] property should be used * Otherwise, the {@link nodes} property should be used
* *
* @type {BasicItemType[]} * @type {BasicItemType[]}
* @memberof BasicItemType * @memberof BasicItemType
@ -94,7 +136,6 @@ namespace OS {
* is allowed at a time. A dialog may have sub dialog * is allowed at a time. A dialog may have sub dialog
*/ */
export var dialog: BaseDialog; export var dialog: BaseDialog;
/** /**
* Placeholder for system shortcuts * Placeholder for system shortcuts
*/ */
@ -105,7 +146,7 @@ namespace OS {
* UI elements, then insert this UI scheme to the DOM tree. * UI elements, then insert this UI scheme to the DOM tree.
* *
* This function renders the UI of the application before calling the * This function renders the UI of the application before calling the
* application's [[main]] function * application's `main` function
* *
* @export * @export
* @param {string} html html scheme string * @param {string} html html scheme string
@ -133,28 +174,29 @@ namespace OS {
/** /**
* Load an application scheme file then render * Load an application scheme file then render
* it with [[htmlToScheme]] * it with {@link htmlToScheme}
* *
* @export * @export
* @param {string} path VFS path to the scheme file * @param {string} path VFS path to the scheme file
* @param {BaseModel} app the target application * @param {BaseModel} app the target application
* @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered. * @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered.
* @return {Promise<any>} a promise object
*/ */
export function loadScheme( export function loadScheme(
path: string, path: string,
app: BaseModel, app: BaseModel,
parent: HTMLElement | string parent: HTMLElement | string
): void { ): Promise<any> {
path.asFileHandle() return new Promise(async (ok,nok) =>{
.read() try {
.then(function (x) { const x = await path.asFileHandle().read();
if (!x) {
return;
}
htmlToScheme(x, app, parent); htmlToScheme(x, app, parent);
}) ok(true);
.catch((e) => { }
announcer.oserror(__("Cannot load scheme: {0}", path), e); catch(e)
{
nok(__e(e));
}
}); });
} }
@ -165,6 +207,7 @@ namespace OS {
*/ */
export function clearTheme(): void { export function clearTheme(): void {
$("head link#ostheme").attr("href", ""); $("head link#ostheme").attr("href", "");
$("body").attr("theme", "");
} }
/** /**
@ -181,6 +224,7 @@ namespace OS {
} }
const path = `resources/themes/${name}/${name}.css`; const path = `resources/themes/${name}/${name}.css`;
$("head link#ostheme").attr("href", path); $("head link#ostheme").attr("href", path);
$("body").attr("theme", name);
} }
@ -192,7 +236,7 @@ namespace OS {
*/ */
export function systemDock(): GUI.tag.AppDockTag export function systemDock(): GUI.tag.AppDockTag
{ {
return $("#sysdock")[0] as tag.AppDockTag; return $("[data-id='sysdock']")[0] as tag.AppDockTag;
} }
/** /**
@ -242,6 +286,87 @@ namespace OS {
}); });
} }
/**
* Toast notification configuration options
*
*
* @export
* @interface ToastOptions
*/
export interface ToastOptions {
/**
* Where the Toast is displayed? see {@link ANCHOR}
*
* @type {ANCHOR}
* @memberof ToastOptions
*/
location?: ANCHOR;
/**
* Timeout (in seconds) before the Toast disappears
* Set this value to 0 to prevent the Toast to disappear,
* in this case, user need to explicitly close the notification
*
* @type {number}
* @memberof ToastOptions
*/
timeout?: number;
/**
* AFXTag that is used to render the data
*
* @type {number}
* @memberof ToastOptions
*/
tag?: string;
}
/**
* Toast notification API
* Show a toast message on different posisition on screen, see {@link ToastOptions}
*
* @export
* @param
* @returns
*/
export function toast(data: any, opts?: ToastOptions, app: application.BaseApplication = undefined): void
{
let notification_el = undefined;
if(app)
{
notification_el = app.window.notification;
}
else
{
notification_el = $("#sys_notification")[0] as tag.NotificationTag;
}
let options: ToastOptions = {
location: ANCHOR.NORTH,
timeout: 3,
tag: "afx-label"
};
if(opts)
{
for(const k in opts)
{
options[k] = opts[k];
}
}
const toast_el = $(`<afx-toast-notification>`)[0] as tag.ToastNotificationTag;
const content_el = $(`<${options.tag}>`)[0] as AFXTag;
$(toast_el).append(content_el);
toast_el.uify(undefined);
notification_el.push(toast_el, options.location);
content_el.set(data);
if(options.timeout && options.timeout != 0)
{
setTimeout(function(){
$(toast_el).remove();
clearTimeout(this);
}, options.timeout*1000);
}
}
/** /**
* Find a list of applications that support a specific mime * Find a list of applications that support a specific mime
* type in the system packages meta-data * type in the system packages meta-data
@ -392,7 +517,7 @@ namespace OS {
/** /**
* Kill an running processes of an application, then * Kill an running processes of an application, then
* unregister the application prototype definition * unregister the application prototype definition
* from the [[application]] namespace. * from the {@link application} namespace.
* *
* This process is similar to uninstall the application * This process is similar to uninstall the application
* from the current system state * from the current system state
@ -445,7 +570,7 @@ namespace OS {
* in the system. * in the system.
* *
* This function fist loads and registers the application prototype * This function fist loads and registers the application prototype
* definition in the [[application]] namespace, then update * definition in the {@link application} namespace, then update
* the system packages meta-data * the system packages meta-data
* *
* First it tries to load the package with the app name is also the * First it tries to load the package with the app name is also the
@ -520,9 +645,10 @@ namespace OS {
* *
* @export * @export
* @param {string} ph * @param {string} ph
* @param {AppArgumentsType[]} [params] service arguments
* @returns {Promise<PM.ProcessType>} * @returns {Promise<PM.ProcessType>}
*/ */
export function pushService(ph: string): Promise<PM.ProcessType> { export function pushService(ph: string, params?: AppArgumentsType[]): Promise<PM.ProcessType> {
return new Promise(async function (resolve, reject) { return new Promise(async function (resolve, reject) {
const arr = ph.split("/"); const arr = ph.split("/");
const srv = arr[1]; const srv = arr[1];
@ -538,7 +664,8 @@ namespace OS {
} }
const d = await PM.createProcess( const d = await PM.createProcess(
srv, srv,
application[srv] application[srv],
params
); );
return resolve(d); return resolve(d);
} }
@ -598,6 +725,42 @@ namespace OS {
e); e);
return reject(e); return reject(e);
} }
const mt = application[app].meta;
// load application setting if any
let settings = {};
try
{
if(mt.path.asFileHandle().protocol === "home")
{
settings = await `${mt.path}/.settings.json`.asFileHandle().read("json");
}
else
{
// system package
settings = await `home:///.antos/settings/${app}.json`.asFileHandle().read("json");
}
}
catch(_)
{
}
application[app].setting_wdg = API.watcher(settings, (o,k,v,p) => {
let key = k;
if(p.length > 0)
{
key = p[0];
}
const data: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
data.icon = undefined;
if (mt && mt.icon) {
data.icon = `${mt.path}/${mt.icon}`;
}
data.id = 0;
data.name = app;
data.message = key;
data.iconclass = mt?mt.iconclass:undefined;
data.u_data = undefined;
return announcer.trigger("appregistry", data);
});
const p = await PM.createProcess( const p = await PM.createProcess(
app, app,
application[app], application[app],
@ -643,10 +806,7 @@ namespace OS {
const data = { const data = {
icon: null, icon: null,
iconclass: meta.iconclass || "", iconclass: meta.iconclass || "",
app, app
onbtclick() {
return app.toggle();
},
}; };
// TODO: this path is not good, need to create a blob of it // TODO: this path is not good, need to create a blob of it
if (meta.icon) { if (meta.icon) {
@ -657,15 +817,11 @@ namespace OS {
if (!meta.icon && !meta.iconclass) { if (!meta.icon && !meta.iconclass) {
data.iconclass = "fa fa-cogs"; data.iconclass = "fa fa-cogs";
} }
const dock = $("#sysdock")[0] as tag.AppDockTag; const dock = systemDock();
app.sysdock = dock; app.sysdock = dock;
app.init(); app.init();
app.observable.one("rendered", function () { app.observable.one("rendered", function () {
app.appmenu = $( dock.addapp(data);
"[data-id = 'appmenu']",
"#syspanel"
)[0] as tag.MenuTag;
dock.newapp(data);
}); });
} }
@ -714,7 +870,7 @@ namespace OS {
* @returns * @returns
*/ */
export function undock(app: application.BaseApplication) { export function undock(app: application.BaseApplication) {
return ($("#sysdock")[0] as tag.AppDockTag).removeapp(app); return systemDock().removeapp(app);
} }
/** /**
@ -744,7 +900,7 @@ namespace OS {
* Bind a context menu event to an AntOS element. * Bind a context menu event to an AntOS element.
* *
* This will find the fist element which defines a handle * This will find the fist element which defines a handle
* named [[contextMenuHandle]] and bind the context menu * named {@link contextMenuHandle} and bind the context menu
* event to it. * event to it.
* *
* @param {JQuery.MouseEventBase} event mouse event * @param {JQuery.MouseEventBase} event mouse event
@ -753,7 +909,7 @@ namespace OS {
function bindContextMenu(event: JQuery.MouseEventBase): void { function bindContextMenu(event: JQuery.MouseEventBase): void {
var handle = function (e: HTMLElement) { var handle = function (e: HTMLElement) {
if (e.contextmenuHandle) { if (e.contextmenuHandle) {
const m = $("#contextmenu")[0] as tag.MenuTag; const m = $("#contextmenu")[0] as tag.StackMenuTag;
m.onmenuselect = () => { }; m.onmenuselect = () => { };
return e.contextmenuHandle(event, m); return e.contextmenuHandle(event, m);
} else { } else {
@ -896,9 +1052,9 @@ namespace OS {
const scheme = $.parseHTML(schemes.ws); const scheme = $.parseHTML(schemes.ws);
$("#wrapper").append(scheme); $("#wrapper").append(scheme);
announcer.observable.one("sysdockloaded", () => { announcer.one("sysdockloaded", () => {
$(window).on("keydown", function (event) { $(window).on("keydown", function (event) {
const dock = $("#sysdock")[0] as tag.AppDockTag; const dock = systemDock();
if (!dock) { if (!dock) {
return; return;
} }
@ -939,11 +1095,11 @@ namespace OS {
}); });
// system menu and dock // system menu and dock
$("#syspanel")[0].uify(undefined); $("#syspanel")[0].uify(undefined);
$("#sysdock")[0].uify(undefined);
$("#systooltip")[0].uify(undefined); $("#systooltip")[0].uify(undefined);
$("#contextmenu")[0].uify(undefined);
$("#wrapper").on("contextmenu", (e) => bindContextMenu(e)); const ctxmenu = $("#contextmenu")[0];
ctxmenu.uify(undefined);
$("#wrapper").on(OS.mobile?"longtouch":"contextmenu", (e) => bindContextMenu(e as JQuery.MouseEventBase));
// tooltip // tooltip
$(document).on("mouseover", function (e) { $(document).on("mouseover", function (e) {
const el: any = $(e.target).closest("[tooltip]"); const el: any = $(e.target).closest("[tooltip]");
@ -957,6 +1113,8 @@ namespace OS {
); );
}); });
// mount it // mount it
const nottification = $("#sys_notification")[0] as tag.NotificationTag;
nottification.uify(undefined);
desktop().uify(undefined); desktop().uify(undefined);
} }
@ -972,7 +1130,7 @@ namespace OS {
/** /**
* Show the login screen and perform the login operation. * Show the login screen and perform the login operation.
* *
* Once login successfully, the [[startAntOS]] will be called * Once login successfully, the {@link startAntOS} will be called
* *
* @export * @export
*/ */
@ -983,29 +1141,33 @@ namespace OS {
.replace("[ANTOS_VERSION]", OS.VERSION.version_string) .replace("[ANTOS_VERSION]", OS.VERSION.version_string)
); );
$("#wrapper").append(scheme); $("#wrapper").append(scheme);
$("#btlogin").on("click", async function () { $("#login_form")[0].uify(undefined);
($("#btlogin")[0] as tag.ButtonTag).onbtclick = async function () {
const data: API.UserLoginType = { const data: API.UserLoginType = {
username: $("#txtuser").val() as string, username: $("#txtuser").val() as string,
password: $("#txtpass").val() as string, password: $("#txtpass").val() as string,
}; };
const err_label = $("#login_error")[0] as tag.LabelTag;
try { try {
const d = await API.handle.login(data); const d = await API.handle.login(data);
if (d.error) { if (d.error) {
return $("#login_error").html(d.error as string); err_label.iconclass = "bi bi-exclamation-diamond-fill";
return err_label.text = d.error as string;
} }
return startAntOS(d.result); return startAntOS(d.result);
} catch (e) { } catch (e) {
return $("#login_error").html("Login: server error"); err_label.iconclass = "bi bi-bug-fill";
return err_label.text = __("Login: server error");
}
};
($("#txtpass")[0] as tag.InputTag).on("keyup", function (e) {
if (e.which === 13) {
return $("#btlogin button").trigger("click");
} }
}); });
$("#txtpass").on("keyup", function (e) { ($("#txtuser")[0] as tag.InputTag).on("keyup",function (e) {
if (e.which === 13) { if (e.which === 13) {
return $("#btlogin").trigger("click"); return $("#btlogin button").trigger("click");
}
});
$("#txtuser").keyup(function (e) {
if (e.which === 13) {
return $("#btlogin").trigger("click");
} }
}); });
} }
@ -1033,8 +1195,8 @@ namespace OS {
// load theme // load theme
loadTheme(setting.appearance.theme, true); loadTheme(setting.appearance.theme, true);
wallpaper(undefined); wallpaper(undefined);
OS.announcer.observable.one("syspanelloaded", async function () { OS.announcer.one("syspanelloaded", async function () {
OS.announcer.observable.on("systemlocalechange", (_) => OS.announcer.on("systemlocalechange", (_) =>
$("#syspanel")[0].update() $("#syspanel")[0].update()
); );
@ -1085,10 +1247,10 @@ namespace OS {
}); });
// initDM // initDM
API.setLocale(setting.system.locale).then(() => initDM()); API.setLocale(setting.system.locale).then(() => initDM());
Ant.OS.announcer.observable.on("error", function (d) { Ant.OS.announcer.on("error", function (d) {
console.log(d.u_data); console.log(d.u_data);
}); });
Ant.OS.announcer.observable.on("fail", function (d) { Ant.OS.announcer.on("fail", function (d) {
console.log(d.u_data); console.log(d.u_data);
}); });
} }
@ -1105,22 +1267,28 @@ namespace OS {
schemes.ws = `\ schemes.ws = `\
<afx-sys-panel id = "syspanel"></afx-sys-panel> <afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace"> <div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock> <afx-desktop id = "desktop" dir="column" ></afx-desktop>
<afx-desktop id = "desktop" dir="vertical" ></afx-desktop> <afx-notification id="sys_notification" ></afx-notification>
</div> </div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu> <afx-stack-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-stack-menu>
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label> <afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
<textarea id="clipboard"></textarea>\ <textarea id="clipboard"></textarea>\
`; `;
schemes.login = `\ schemes.login = `\
<div id = "login_form"> <afx-vbox id = "login_form">
<p>Welcome to AntOS, please login</p> <afx-label data-height="35" text="Welcome to AntOS, please login"></afx-label>
<input id = "txtuser" type = "text" value = "demo" ></input> <afx-vbox padding = "10">
<input id = "txtpass" type = "password" value = "demo" ></input> <afx-input data-height="52" id = "txtuser" type = "text" value = "demo" label="User name"></afx-input>
<button id = "btlogin">Login</button> <div data-height="10"></div>
<div id = "login_error"></div> <afx-input data-height="52" id = "txtpass" type = "password" value = "demo" label="Password"></afx-input>
</div> <div data-height="10"></div>
<afx-hbox>
<afx-label id = "login_error"></afx-label>
<afx-button id = "btlogin" text="Login" iconclass = "bi bi-box-arrow-in-right" data-width="content"></afx-button>
</afx-hbox>
</afx-vbox>
</afx-vbox>
<div id = "antos_build_id"><a href="${OS.REPOSITORY}/tree/[ANTOS_BUILD_ID]">AntOS v[ANTOS_VERSION]</div>\ <div id = "antos_build_id"><a href="${OS.REPOSITORY}/tree/[ANTOS_BUILD_ID]">AntOS v[ANTOS_VERSION]</div>\
`; `;
} }

View File

@ -158,7 +158,7 @@ namespace OS {
* *
* @export * @export
* @param {string} p a VFS file path e.g. home://test/ * @param {string} p a VFS file path e.g. home://test/
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]] * @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or a list of FileInfoType * which contains an error or a list of FileInfoType
*/ */
export function scandir(p: string): Promise<RequestResult> { export function scandir(p: string): Promise<RequestResult> {
@ -203,7 +203,7 @@ namespace OS {
* *
* @export * @export
* @param {string} p VFS file path * @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]] * @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or an object of FileInfoType * which contains an error or an object of FileInfoType
*/ */
export function fileinfo(p: string): Promise<RequestResult> { export function fileinfo(p: string): Promise<RequestResult> {
@ -226,12 +226,12 @@ namespace OS {
* - xml, html: the response is a XML/HTML object * - xml, html: the response is a XML/HTML object
* - text: plain text * - text: plain text
* *
* @returns {Promise<any>} A promise on a [[RequestResult]] * @returns {Promise<any>} A promise on a {@link RequestResult}
* which contains an error or an object of [[FileInfoType]] * which contains an error or an object of {@link FileInfoType}
*/ */
export function readfile(p: string, t: string): Promise<any> { export function readfile(p: string, t: string): Promise<any> {
const path = `${API.REST}/VFS/get/`; const path = `${API.REST}/VFS/get/`;
return API.get(path + p, t); return API.get(path + encodeURIComponent(p), t);
} }
/** /**
@ -240,7 +240,7 @@ namespace OS {
* @export * @export
* @param {string} s VFS source file path * @param {string} s VFS source file path
* @param {string} d VFS destination file path * @param {string} d VFS destination file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]] * @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or a success response * which contains an error or a success response
*/ */
export function move(s: string, d: string): Promise<RequestResult> { export function move(s: string, d: string): Promise<RequestResult> {
@ -253,7 +253,7 @@ namespace OS {
* *
* @export * @export
* @param {string} p VFS file path * @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]] * @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or a success response * which contains an error or a success response
*/ */
export function remove(p: string): Promise<RequestResult> { export function remove(p: string): Promise<RequestResult> {
@ -278,7 +278,7 @@ namespace OS {
* *
* @export * @export
* @param {PackageCommandType} d a package command of type PackageCommandType * @param {PackageCommandType} d a package command of type PackageCommandType
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] * @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/ */
export function packages( export function packages(
d: PackageCommandType d: PackageCommandType
@ -292,7 +292,7 @@ namespace OS {
* *
* @export * @export
* @param {string} d VFS destination directory path * @param {string} d VFS destination directory path
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] * @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/ */
export function upload(d: string): Promise<RequestResult> { export function upload(d: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/upload`; const path = `${API.REST}/VFS/upload`;
@ -305,7 +305,7 @@ namespace OS {
* @export * @export
* @param {string} p path to the VFS file * @param {string} p path to the VFS file
* @param {string} d file data encoded in Base 64 * @param {string} d file data encoded in Base 64
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] * @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/ */
export function write( export function write(
p: string, p: string,
@ -371,8 +371,8 @@ namespace OS {
* Check if a user is logged in * Check if a user is logged in
* *
* @export * @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that * @returns {Promise<RequestResult>} a promise on a {@link RequestResult} that
* contains an error or a [[UserSettingType]] object * contains an error or a {@link OS.setting.UserSettingType} object
*/ */
export function auth(): Promise<RequestResult> { export function auth(): Promise<RequestResult> {
const p = `${API.REST}/user/auth`; const p = `${API.REST}/user/auth`;
@ -383,9 +383,9 @@ namespace OS {
* Perform a login operation * Perform a login operation
* *
* @export * @export
* @param {UserLoginType} d user data [[UserLoginType]] * @param {UserLoginType} d user data {@link UserLoginType}
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that * @returns {Promise<RequestResult>} a promise on a {@link RequestResult} that
* contains an error or a [[UserSettingType]] object * contains an error or a {@link OS.setting.UserSettingType} object
*/ */
export function login(d: UserLoginType): Promise<RequestResult> { export function login(d: UserLoginType): Promise<RequestResult> {
const p = `${API.REST}/user/login`; const p = `${API.REST}/user/login`;
@ -396,7 +396,7 @@ namespace OS {
* Perform a logout operation * Perform a logout operation
* *
* @export * @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] * @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/ */
export function logout(): Promise<RequestResult> { export function logout(): Promise<RequestResult> {
const p = `${API.REST}/user/logout`; const p = `${API.REST}/user/logout`;
@ -407,65 +407,12 @@ namespace OS {
* Save the current user settings * Save the current user settings
* *
* @export * @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] * @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/ */
export function setting(): Promise<RequestResult> { export function setting(): Promise<RequestResult> {
const p = `${API.REST}/system/settings`; const p = `${API.REST}/system/settings`;
return API.post(p, OS.setting); 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

@ -11,7 +11,7 @@ namespace OS {
| application.BaseApplication | application.BaseApplication
| application.BaseService; | application.BaseService;
/** /**
* Alias to all classes that extends [[BaseModel]] * Alias to all classes that extends {@link BaseModel}
*/ */
export type ModelTypeClass = { export type ModelTypeClass = {
new <T extends BaseModel>(args: AppArgumentsType[]): T; new <T extends BaseModel>(args: AppArgumentsType[]): T;
@ -35,7 +35,7 @@ namespace OS {
* @export * @export
* @param {string} app class name string * @param {string} app class name string
* @param {ProcessTypeClass} cls prototype class * @param {ProcessTypeClass} cls prototype class
* @param {GUI.AppArgumentsType[]} [args] process arguments * @param {AppArgumentsType[]} [args] process arguments
* @returns {Promise<ProcessType>} a promise on the created process * @returns {Promise<ProcessType>} a promise on the created process
*/ */
export function createProcess( export function createProcess(
@ -136,10 +136,28 @@ namespace OS {
if (i >= 0) { if (i >= 0) {
if (application[app.name].type === ModelType.Application) { if (application[app.name].type === ModelType.Application) {
GUI.undock(app as application.BaseApplication); GUI.undock(app as application.BaseApplication);
// save setting file if any
if(PM.processes[app.name].length == 1)
{
const app_class = application[app.name] as typeof OS.application.BaseApplication;
let file = `${app_class.meta.path}/.settings.json`.asFileHandle();
if(file.protocol !== "home")
{
file = `home:///.antos/settings/${app.name}.json`.asFileHandle();
}
file.cache = app_class.setting_wdg;
//file.cache = JSON.stringify(app_class.setting_wdg, undefined, 4);
file
.write("object")
.catch((e) =>{
return announcer.osinfo(
__("Unable to save settings for application {0}: {1}", app.name, e.toString()));
});
}
} else { } else {
GUI.detachservice(app as application.BaseService); GUI.detachservice(app as application.BaseService);
} }
announcer.unregister(app); // announcer.unregister(app);
delete PM.processes[app.name][i]; delete PM.processes[app.name][i];
PM.processes[app.name].splice(i, 1); PM.processes[app.name].splice(i, 1);
} }

View File

@ -308,7 +308,7 @@ namespace OS {
/** /**
* Package repositories setting. * Package repositories setting.
* This configuration is used by [[MarketPlace]] * This configuration is used by {@link OS.application.MarketPlace}
* for package management * for package management
* *
* @type {{ * @type {{
@ -398,7 +398,7 @@ namespace OS {
*/ */
export function resetSetting(): void { export function resetSetting(): void {
setting.desktop = { setting.desktop = {
path: "home://.desktop", path: "home://.antos/desktop",
menu: [], menu: [],
showhidden: false, showhidden: false,
}; };
@ -476,13 +476,13 @@ namespace OS {
menu: [], menu: [],
packages: {}, packages: {},
pkgpaths: { pkgpaths: {
user: "home://.packages", user: "home://.antos/packages",
system: "os://packages", system: "os://packages",
}, },
repositories: [], repositories: [],
startup: { startup: {
apps: [], apps: [],
services: ["Syslog/PushNotification", "Syslog/Calendar"], services: ["SystemServices/PushNotification", "SystemServices/Calendar"],
pinned: [], pinned: [],
}, },
}; };
@ -535,7 +535,7 @@ namespace OS {
setting.system.repositories = [ setting.system.repositories = [
{ {
text: "Github", text: "Github",
url: "https://raw.githubusercontent.com/lxsang/antosdk-apps/master/packages.json" url: "https://raw.githubusercontent.com/lxsang/antosdk-apps/2.0.x/packages.json"
} }
] ]
} }

View File

@ -15,7 +15,7 @@ namespace OS {
* @type {application.BaseApplication} * @type {application.BaseApplication}
* @memberof AppDockItemType * @memberof AppDockItemType
*/ */
app: application.BaseApplication; app?: application.BaseApplication;
/** /**
* Reference to the DOM element of * Reference to the DOM element of
@ -38,15 +38,12 @@ namespace OS {
*/ */
export class AppDockTag extends AFXTag { export class AppDockTag extends AFXTag {
/** /**
* variable holds the application select event * Cache of touch event
* callback handle
* *
* @private * @private
* @type {TagEventCallback<any>} * @meberof AppDockTag
* @memberof AppDockTag
*/ */
private _onappselect: TagEventCallback<any>; private _previous_touch: {x: number, y: number};
/** /**
* Items data of the dock * Items data of the dock
* *
@ -72,7 +69,6 @@ namespace OS {
*/ */
constructor() { constructor() {
super(); super();
this._onappselect = (e) => {};
} }
/** /**
@ -98,9 +94,6 @@ namespace OS {
break; break;
} }
} }
if (i !== -1) {
$(this.items[i].domel).attr("tooltip", `cr:${app.title()}`);
}
} }
/** /**
@ -111,6 +104,8 @@ namespace OS {
*/ */
protected init(): void { protected init(): void {
this._items = []; this._items = [];
this._previous_touch = {x: 0, y:0};
} }
/** /**
@ -155,7 +150,7 @@ namespace OS {
let el = undefined; let el = undefined;
for (let it of this.items) { for (let it of this.items) {
it.app.blur(); it.app.blur();
$(it.domel).removeClass(); $(it.domel).removeClass("selected");
if (v && v === it.app) { if (v && v === it.app) {
el = it; el = it;
} }
@ -167,6 +162,11 @@ namespace OS {
} }
$(el.domel).addClass("selected"); $(el.domel).addClass("selected");
($(Ant.OS.GUI.workspace)[0] as FloatListTag).unselect(); ($(Ant.OS.GUI.workspace)[0] as FloatListTag).unselect();
const evt = {
id: this.aid,
data: v
};
announcer.trigger("appselect", evt);
} }
get selectedApp(): application.BaseApplication { get selectedApp(): application.BaseApplication {
@ -188,6 +188,63 @@ namespace OS {
return this._selectedItem; return this._selectedItem;
} }
/**
* Add a button to the dock
*
* @private
* @param {string} [name] associated application name
* @param {AppDockItemType} [item] dock item
* @param {boolean} [pinned] the button is pinned to the dock ?
* @memberof AppDockTag
*/
private add_button(name:string, item: AppDockItemType, pinned: boolean = false): void
{
const collection = $(this).children().filter((i, e) => {
return (e as ButtonTag).data.name == name;
});
if(collection.length > 0)
{
(collection[0] as ButtonTag).data.pinned = true;
item.domel = collection[0] as ButtonTag;
return;
}
const el = $("<afx-button>");
const bt = el[0] as ButtonTag;
el.appendTo(this);
el[0].uify(this.observable);
bt.set(item);
bt.data = {
name: name,
pinned: pinned
};
item.domel = bt;
bt.onbtclick = (e) => {
e.data.stopPropagation();
this
.handleAppSelect(bt);
};
}
private update_button(el: ButtonTag): void
{
const collection = this.items.filter(it => it.app.name == el.data.name);
if(collection.length == 1)
{
$(el).removeClass("plural");
}
if(collection.length == 0)
{
if(el.data.pinned)
{
$(el).removeClass();
}
else
{
$(el).remove();
}
}
}
/** /**
* When a new application process is created, this function * When a new application process is created, this function
* will be called to add new application entry to the dock. * will be called to add new application entry to the dock.
@ -197,25 +254,84 @@ namespace OS {
* @param {AppDockItemType} item an application dock item entry * @param {AppDockItemType} item an application dock item entry
* @memberof AppDockTag * @memberof AppDockTag
*/ */
newapp(item: AppDockItemType): void { addapp(item: AppDockItemType): void {
this.items.push(item); const collection = this.items.filter(it => it.app.name == item.app.name);
const el = $("<afx-button>"); let bt = undefined;
const bt = el[0] as ButtonTag; if(collection.length == 0)
el.appendTo(this); {
el[0].uify(this.observable); this.add_button(item.app.name, item);
bt.set(item); }
bt.data = item.app; else
{
bt = collection[0].domel;
item.domel = bt; item.domel = bt;
$(bt).attr("tooltip", `cr:${item.app.title()}`); $(bt).addClass("plural");
bt.onbtclick = (e) => { }
e.id = this.aid; this.items.push(item);
//e.data.item = item;
this._onappselect(e);
item.app.show();
};
this.selectedApp = item.app; this.selectedApp = item.app;
} }
/**
* Handle the application selection action
*
* @private
* @returns {Promise<application.BaseApplication>}
* @memberof AppDockTag
*/
private handleAppSelect(bt: ButtonTag): Promise<application.BaseApplication>
{
return new Promise(async (resolve, reject) => {
try {
const name = bt.data.name as string;
const collection = this.items.filter(it => it.app.name == name);
const ctxmenu = $("#contextmenu")[0] as tag.StackMenuTag;
ctxmenu.hide();
if(collection.length == 0)
{
resolve(await GUI.launch(name, []) as application.BaseApplication);
return;
}
if(collection.length == 1)
{
if(PM.getActiveApp() == collection[0].app)
{
collection[0].app.hide();
}
else
{
collection[0].app.show();
}
resolve(collection[0].app);
return;
}
// show the context menu containning a list of application to select
const menu_data = collection.map(e => {
return {
text: (e.app.scheme as WindowTag).apptitle,
icon: e.icon,
iconclass: e.iconclass,
app: e.app
};
});
const offset = $(bt).offset();
ctxmenu.nodes = menu_data;
$(ctxmenu)
.css("left", offset.left)
.css("bottom", $(this).height());
ctxmenu.onmenuselect = (e) =>
{
e.data.item.data.app.show();
resolve(e.data.item.data.app);
}
ctxmenu.show();
}
catch(e)
{
reject(__e(e));
}
});
}
/** /**
* Delete and application entry from the dock. * Delete and application entry from the dock.
* This function will be called when an application * This function will be called when an application
@ -236,9 +352,11 @@ namespace OS {
} }
if (i !== -1) { if (i !== -1) {
const appName = this.items[i].app.name;
const el = this.items[i].domel as ButtonTag;
delete this.items[i].app; delete this.items[i].app;
this.items.splice(i, 1); this.items.splice(i, 1);
$($(this).children()[i]).remove(); this.update_button(el);
} }
} }
@ -253,34 +371,37 @@ namespace OS {
if (e.target === this) { if (e.target === this) {
return; return;
} }
m.hide();
const bt = ($(e.target).closest( const bt = ($(e.target).closest(
"afx-button" "afx-button"
)[0] as any) as ButtonTag; )[0] as any) as ButtonTag;
const app = bt.data as application.BaseApplication; const name = bt.data.name as string;
m.items = [ const collection = this.items.filter(it => it.app.name == name);
m.nodes = [
{ text: "__(New window)", dataid: "new" }, { text: "__(New window)", dataid: "new" },
{ text: "__(Show)", dataid: "show" }, { text: "__(Hide all)", dataid: "hide" },
{ text: "__(Hide)", dataid: "hide" }, { text: "__(Close all)", dataid: "quit" },
{ text: "__(Close)", dataid: "quit" },
]; ];
m.onmenuselect = function (evt) { m.onmenuselect = function (evt) {
const item = evt.data.item.data; switch (evt.data.item.data.dataid) {
if (app[item.dataid]) {
return app[item.dataid]();
}
else
{
switch (item.dataid) {
case "new": case "new":
GUI.launch(app.name, []); GUI.launch(bt.data.name as string, []);
break;
case "hide":
collection.forEach((el,_) => el.app.hide());
break;
case "quit":
collection.forEach((el,_) => el.app.quit());
break; break;
default: default:
break; break;
} }
}
}; };
return m.show(e); const offset = $(bt).offset();
$(m)
.css("left", offset.left)
.css("bottom", $(this).height());
return m.show();
}; };
announcer.trigger("sysdockloaded", undefined); announcer.trigger("sysdockloaded", undefined);
GUI.bindKey("CTRL-ALT-2", (e) =>{ GUI.bindKey("CTRL-ALT-2", (e) =>{
@ -318,6 +439,60 @@ namespace OS {
} }
this.items[index].app.trigger("focus"); this.items[index].app.trigger("focus");
}); });
this.addEventListener("touchstart", e => {
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, { passive: true});
this.addEventListener("touchmove", e => {
const offset = {x:0, y:0};
offset.x = this._previous_touch.x - e.touches[0].pageX ;
offset.y = this._previous_touch.y - e.touches[0].pageY;
(this as any).scrollLeft += offset.x;
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, { passive: true});
this.addEventListener("wheel", (evt)=>{
(this as any).scrollLeft += (evt as WheelEvent).deltaY;
},{ passive: true});
announcer.on("app-pinned", (_) => {
this.refresh_pinned_app();
});
this.refresh_pinned_app();
}
/**
* refresh the pinned application list
*
* @private
* @memberof AppDockTag
*/
private refresh_pinned_app(): void
{
if(!setting.system.startup.pinned)
return;
// unpin all application on the dock
$(this).children().each((i,e) => {
(e as ButtonTag).data.pinned = false;
});
// pin all setting application on the dock
setting.system.startup.pinned
.filter((el) =>{
const app = setting.system.packages[el];
return app && app.app
})
.forEach((name) => {
const app = setting.system.packages[name];
const item = {
icon: app.icon,
iconclass: app.iconclass,
app: undefined
};
this.add_button(name, item, true);
});
// update to remove the button
$(this).children().each((i,e) => {
this.update_button(e as ButtonTag);
});
} }
} }
define("afx-apps-dock", AppDockTag); define("afx-apps-dock", AppDockTag);

View File

@ -21,10 +21,25 @@ namespace OS {
/** /**
* Custom user data * Custom user data
* *
* @type {GenericObject<any>} * @type {any}
* @memberof ButtonTag * @memberof ButtonTag
*/ */
data: GenericObject<any>; private _data: any;
/**
* Custom user data setter/gettter
*
* @memberof ButtonTag
*/
set data(v: any)
{
this._data = v;
this.set(v);
}
get data(): any
{
return this._data;
}
/** /**
*Creates an instance of ButtonTag. *Creates an instance of ButtonTag.
@ -65,6 +80,27 @@ namespace OS {
(this.refs.label as LabelTag).iconclass = v; (this.refs.label as LabelTag).iconclass = v;
} }
/**
* Set the icon class on the right side of the button, this property
* allows to style the button icon using CSS
*
* @memberof ButtonTag
*/
set iconclass$(v: string) {
$(this).attr("iconclass_end", v);
(this.refs.label as LabelTag).iconclass$ = v;
}
/**
* Set the CSS class of the label icon on the right side
*
* @memberof ButtonTag
*/
set iconclass_end(v: string) {
this.iconclass$ = v;
}
/** /**
* Setter: Set the text of the button * Setter: Set the text of the button
* *

View File

@ -113,8 +113,10 @@ namespace OS {
* @memberof CalendarTag * @memberof CalendarTag
*/ */
protected mount(): void { protected mount(): void {
$(this.refs.prev).on("click",(e) => this.prevmonth()); (this.refs.prev as ButtonTag).iconclass = "fa fa-angle-left";
$(this.refs.next).on("click",(e) => this.nextmonth()); (this.refs.next as ButtonTag).iconclass = "fa fa-angle-right";
(this.refs.prev as ButtonTag).onbtclick = (e) => this.prevmonth();
(this.refs.next as ButtonTag).onbtclick = (e) => this.nextmonth();
const grid = this.refs.grid as GridViewTag; const grid = this.refs.grid as GridViewTag;
grid.header = [ grid.header = [
{ text: "__(Sun)" }, { text: "__(Sun)" },
@ -138,7 +140,7 @@ namespace OS {
* This function triggers the date select event * This function triggers the date select event
* *
* @private * @private
* @param {TagEventType} e AFX tag event data [[TagEventType]] * @param {TagEventType} e AFX tag event data {@link TagEventType}
* @returns {void} * @returns {void}
* @memberof CalendarTag * @memberof CalendarTag
*/ */
@ -249,6 +251,8 @@ namespace OS {
]; ];
const this_month = new Date(this._year, this._month, 1); const this_month = new Date(this._year, this._month, 1);
const next_month = new Date(this._year, this._month + 1, 1); const next_month = new Date(this._year, this._month + 1, 1);
const grid = this.refs.grid as GridViewTag;
grid.rows = [];
// Find out when this month starts and ends. // Find out when this month starts and ends.
const first_week_day = this_month.getDay(); const first_week_day = this_month.getDay();
const days_in_this_month = Math.round( const days_in_this_month = Math.round(
@ -292,14 +296,11 @@ namespace OS {
week_day++; week_day++;
} }
for ( for (
let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2; let i = 0; i < 7 - row.length; i++
asc2 ? i <= end2 : i >= end2;
asc2 ? i++ : i--
) { ) {
row.push({ text: "" }); row.push({ text: "" });
} }
rows.push(row); rows.push(row);
const grid = this.refs.grid as GridViewTag;
grid.rows = rows; grid.rows = rows;
(this.refs.mlbl as LabelTag).text = `${ (this.refs.mlbl as LabelTag).text = `${
months[this._month] months[this._month]
@ -319,9 +320,9 @@ namespace OS {
el: "div", el: "div",
ref: "ctrl", ref: "ctrl",
children: [ children: [
{ el: "i", class: "prevmonth", ref: "prev" }, { el: "afx-button", class: "prevmonth", ref: "prev" },
{ el: "afx-label", ref: "mlbl" }, { el: "afx-label", ref: "mlbl" },
{ el: "i", class: "nextmonth", ref: "next" }, { el: "afx-button", class: "nextmonth", ref: "next" },
], ],
}, },
{ el: "afx-grid-view", ref: "grid" }, { el: "afx-grid-view", ref: "grid" },

View File

@ -61,6 +61,19 @@ namespace OS {
* @memberof DesktopTag * @memberof DesktopTag
*/ */
protected mount(): void { protected mount(): void {
/**
* TRICKY HACK
* When focusing on a window which overflows the desktop,
* the desktop scrolls automatically to bottom,
* even when `overflow: hiddle` is set on CSS.
*
* The following event listener prevents
* the desktop to scroll down in this case
*/
$(this).on("scroll", (e) =>{
if(this.scrollTop != 0)
this.scrollTop = 0;
});
if(this.observer) if(this.observer)
{ {
this.observer.disconnect(); this.observer.disconnect();
@ -87,7 +100,14 @@ namespace OS {
this.onready = (_) => { this.onready = (_) => {
this.observable = OS.announcer.observable; this.observable = OS.announcer.observable;
window.onresize = () => { window.onresize = () => {
announcer.trigger("desktopresize", undefined); const evt = {
id: this.aid,
data: {
width: $(this).width(),
height: $(this).height()
}
}
announcer.trigger("desktopresize", evt);
this.calibrate(); this.calibrate();
}; };
@ -134,8 +154,8 @@ namespace OS {
{ text: __("Refresh"), dataid: "desktop-refresh" } as GUI.BasicItemType, { text: __("Refresh"), dataid: "desktop-refresh" } as GUI.BasicItemType,
]; ];
menu = menu.concat(setting.desktop.menu.map(e => e)); menu = menu.concat(setting.desktop.menu.map(e => e));
m.items = menu; m.nodes = menu;
m.onmenuselect = (evt: TagEventType<tag.MenuEventData>) => { m.onmenuselect = (evt) => {
if (!evt.data || !evt.data.item) return; if (!evt.data || !evt.data.item) return;
const item = evt.data.item.data; const item = evt.data.item.data;
switch (item.dataid) { switch (item.dataid) {
@ -162,7 +182,7 @@ namespace OS {
}; };
this.refresh(); this.refresh();
announcer.observable.on("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => { announcer.on("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
if (["read", "publish", "download"].includes(d.message as string)) { if (["read", "publish", "download"].includes(d.message as string)) {
return; return;
} }

View File

@ -27,6 +27,15 @@ namespace OS {
*/ */
private _onfileopen: TagEventCallback<API.FileInfoType>; private _onfileopen: TagEventCallback<API.FileInfoType>;
/**
* placeholder for directory changed event callback
*
* @private
* @type {TagEventCallback<API.VFS.BaseFileHandle>}
* @memberof FileViewTag
*/
private _ondirchanged: TagEventCallback<API.VFS.BaseFileHandle>;
/** /**
* Reference to the all selected files meta-datas * Reference to the all selected files meta-datas
* *
@ -92,6 +101,7 @@ namespace OS {
this.chdir = true; this.chdir = true;
this.view = "list"; this.view = "list";
this._onfileopen = this._onfileselect = (e) => { }; this._onfileopen = this._onfileselect = (e) => { };
this._ondirchanged = (e) => { };
this._selectedFiles = []; this._selectedFiles = [];
const fn = function(r1, r2, i) { const fn = function(r1, r2, i) {
let t1 = r1[i].text; let t1 = r1[i].text;
@ -115,17 +125,17 @@ namespace OS {
t1 = t1.toString().toLowerCase(); t1 = t1.toString().toLowerCase();
t2 = t2.toString().toLowerCase(); t2 = t2.toString().toLowerCase();
} }
if(this.__f) if(this.desc)
{ {
this.desc = ! this.desc;
if(t1 < t2) { return -1; } if(t1 < t2) { return -1; }
if(t1 > t2) { return 1; } if(t1 > t2) { return 1; }
} }
else else
{ {
this.desc = ! this.desc;
if(t1 > t2) { return -1; } if(t1 > t2) { return -1; }
if(t1 < t2) { return 1; } if(t1 < t2) { return 1; };
} }
return 0; return 0;
}; };
@ -157,7 +167,7 @@ namespace OS {
/** /**
* set the function that allows to fetch file entries. * set the function that allows to fetch file entries.
* This handle function should return a promise on * This handle function should return a promise on
* an arry of [[API.FileInfoType]] * an arry of {@link API.FileInfoType}
* *
* @memberof FileViewTag * @memberof FileViewTag
*/ */
@ -168,7 +178,7 @@ namespace OS {
/** /**
* set the callback handle for the file select event. * set the callback handle for the file select event.
* The parameter of the callback should be an object * The parameter of the callback should be an object
* of type [[TagEventType]]<T> with the data type `T` is [[API.FileInfoType]] * of type {@link TagEventType}<T> with the data type `T` is {@link API.FileInfoType}
* *
* @memberof FileViewTag * @memberof FileViewTag
*/ */
@ -176,10 +186,21 @@ namespace OS {
this._onfileselect = e; this._onfileselect = e;
} }
/**
* set the callback handle for the directory changed event.
* The parameter of the callback should be an object
* of type {@link TagEventType}<T> with the data type `T` is {@link API.VFS.BaseFileHandle}
*
* @memberof FileViewTag
*/
set onchdir(e: TagEventCallback<API.VFS.BaseFileHandle>) {
this._ondirchanged = e;
}
/** /**
set the callback handle for the file open event. set the callback handle for the file open event.
* The parameter of the callback should be an object * The parameter of the callback should be an object
* of type [[TagEventType]]<T> with the data type `T` is [[API.FileInfoType]] * of type {@link TagEventType}<T> with the data type `T` is {@link API.FileInfoType}
* *
* @memberof FileViewTag * @memberof FileViewTag
*/ */
@ -214,7 +235,7 @@ namespace OS {
* *
* Turn on/off the changing current working directory feature * Turn on/off the changing current working directory feature
* of the widget when a directory is double clicked. If enabled, * of the widget when a directory is double clicked. If enabled,
* the widget will use the configured [[fetch]] function to query * the widget will use the configured {@link fetch} function to query
* the content of the selected directory * the content of the selected directory
* *
* Getter: * Getter:
@ -325,7 +346,7 @@ namespace OS {
* *
* Set the path of the current working directory. * Set the path of the current working directory.
* When called the widget will refresh the current * When called the widget will refresh the current
* working directory using the configured [[fetch]] * working directory using the configured {@link fetch}
* function * function
* *
* Getter: * Getter:
@ -352,6 +373,9 @@ namespace OS {
if (this.status) { if (this.status) {
(this.refs.status as LabelTag).text = " "; (this.refs.status as LabelTag).text = " ";
} }
const evt = { id: this.aid, data: v.asFileHandle() };
this._ondirchanged(evt);
this.observable.trigger("chdir", evt);
}) })
.catch((e: Error) => .catch((e: Error) =>
announcer.oserror(e.toString(), e) announcer.oserror(e.toString(), e)
@ -457,7 +481,7 @@ namespace OS {
let h = $(this).outerHeight(); let h = $(this).outerHeight();
const w = $(this).width(); const w = $(this).width();
if (this.status) { if (this.status) {
h -= $(this.refs.status).height() + 10; h -= $(this.refs.status).height();
} }
$(this.refs.listview).css("height", h + "px"); $(this.refs.listview).css("height", h + "px");
$(this.refs.gridview).css("height", h + "px"); $(this.refs.gridview).css("height", h + "px");
@ -647,7 +671,7 @@ namespace OS {
} }
if (this.status) { if (this.status) {
(this.refs.status as LabelTag).text = __( (this.refs.status as LabelTag).text = __(
"Selected: {0} ({1} bytes)", "{0} ({1} bytes)",
e.filename, e.filename,
e.size ? e.size : "0" e.size ? e.size : "0"
); );
@ -669,10 +693,11 @@ namespace OS {
e.type = "dir"; e.type = "dir";
e.mime = "dir"; e.mime = "dir";
} }
const evt = { id: this.aid, data: e };
if (e.type === "dir" && this.chdir) { if (e.type === "dir" && this.chdir) {
this.path = e.path; this.path = e.path;
} else { } else {
const evt = { id: this.aid, data: e };
this._onfileopen(evt); this._onfileopen(evt);
this.observable.trigger("fileopen", evt); this.observable.trigger("fileopen", evt);
} }

View File

@ -48,30 +48,6 @@ namespace OS {
this._onready = v; 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 * Disable the dropdown option in this list
@ -125,6 +101,7 @@ namespace OS {
* @memberof FloatListTag * @memberof FloatListTag
*/ */
protected mount(): void { protected mount(): void {
$(this.refs.current).hide();
$(this.refs.container) $(this.refs.container)
.css("width", "100%") .css("width", "100%")
.css("height", "100%"); .css("height", "100%");
@ -147,49 +124,26 @@ namespace OS {
*/ */
push(v: GenericObject<any>) { push(v: GenericObject<any>) {
const el = super.push(v); 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) $(el)
.css("user-select", "none") .css("user-select", "none")
.css("cursor", "default") .css("cursor", "default")
.css("display", "block") .css("display", "block")
.css("position", "absolute") .css("position", "absolute");
.on("mousedown", (evt) => { el.enable_drag();
$(el).on("dragging", (evt) => {
const e = evt.originalEvent as CustomEvent;
const globalof = $(this.refs.mlist).offset(); const globalof = $(this.refs.mlist).offset();
evt.preventDefault(); const offset = e.detail.offset;
const offset = $(el).offset(); let top = e.detail.current.clientY - offset.top - globalof.top;
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 = let left =
e.clientX - globalof.left - offset.left; e.detail.current.clientX - globalof.left - offset.left;
left = left < 0 ? 0 : left; left = left < 0 ? 0 : left;
top = top < 0 ? 0 : top; top = top < 0 ? 0 : top;
return $(el) $(el)
.css("top", `${top}px`) .css("top", `${top}px`)
.css("left", `${left}px`); .css("left", `${left}px`);
}; })
return el;
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);
});
} }
/** /**
@ -215,7 +169,7 @@ namespace OS {
.css("left", `${cleft}px`); .css("left", `${cleft}px`);
const w = $(e).width(); const w = $(e).width();
const h = $(e).height(); const h = $(e).height();
if (this.dir === "vertical") { if (this.dir === "column") {
ctop += h + 20; ctop += h + 20;
if (ctop + h > gh) { if (ctop + h > gh) {
ctop = 20; ctop = 20;

View File

@ -35,10 +35,11 @@ namespace OS {
/** /**
* Data placeholder for a collection of cell data * Data placeholder for a collection of cell data
* *
* @private
* @type {GenericObject<any>[]} * @type {GenericObject<any>[]}
* @memberof GridRowTag * @memberof GridRowTag
*/ */
data: GenericObject<any>[]; private _data: GenericObject<any>[];
/** /**
* placeholder for the row select event callback * placeholder for the row select event callback
@ -89,6 +90,25 @@ namespace OS {
return this.hasattr("selected"); return this.hasattr("selected");
} }
/**
* setter: set row data
*
* getter: get row data
*/
set data(v: GenericObject<any>[]) {
this._data = v;
if(v)
{
this.attach(v);
}
for (let celi = 0; celi < this.children.length; celi++) {
const cel = (this.children[celi] as GridCellPrototype);
cel.data = v[celi];
}
}
get data(): GenericObject<any>[] {
return this._data;
}
/** /**
* Mount the tag, do nothing * Mount the tag, do nothing
* *
@ -209,7 +229,7 @@ namespace OS {
* Setter: * Setter:
* *
* Set the data of the cell, this will trigger * Set the data of the cell, this will trigger
* the [[ondatachange]] function * the {@link ondatachange} function
* *
* Getter: * Getter:
* *
@ -220,6 +240,7 @@ namespace OS {
set data(v: GenericObject<any>) { set data(v: GenericObject<any>) {
if (!v) return; if (!v) return;
this._data = v; this._data = v;
this.attach(v);
this.ondatachange(); this.ondatachange();
if (!v.selected) { if (!v.selected) {
return; return;
@ -234,7 +255,7 @@ namespace OS {
* Setter: * Setter:
* *
* Set/unset the current cell as selected. * Set/unset the current cell as selected.
* This will trigger the [[cellselect]] * This will trigger the {@link cellselect}
* event * event
* *
* Getter: * Getter:
@ -279,12 +300,12 @@ namespace OS {
e: TagEventType<GridCellPrototype> e: TagEventType<GridCellPrototype>
): void => { }; ): void => { };
this.selected = false; this.selected = false;
$(this).css("display", "block"); //$(this).css("display", "block");
$(this).on("click", (e) => { $(this).on("click", (e) => {
let evt = { id: this.aid, data: this }; let evt = { id: this.aid, data: this };
return this.cellselect(evt, false); return this.cellselect(evt, false);
}); });
$(this).on("dblclick", (e) => { $(this).on(OS.mobile?"dbltap":"dblclick", (e) => {
let evt = { id: this.aid, data: this }; let evt = { id: this.aid, data: this };
return this.cellselect(evt, true); return this.cellselect(evt, true);
}); });
@ -324,7 +345,7 @@ namespace OS {
/** /**
* Simple grid cell defines a grid cell with * Simple grid cell defines a grid cell with
* an [[LabelTag]] as it cell layout * an {@link LabelTag} as it cell layout
* *
* @export * @export
* @class SimpleGridCellTag * @class SimpleGridCellTag
@ -369,7 +390,7 @@ namespace OS {
protected calibrate(): void { } protected calibrate(): void { }
/** /**
* The layout of the cell with a simple [[LabelTag]] * The layout of the cell with a simple {@link LabelTag}
* *
* @returns * @returns
* @memberof SimpleGridCellTag * @memberof SimpleGridCellTag
@ -516,12 +537,10 @@ namespace OS {
*/ */
set dragndrop(v: boolean) { set dragndrop(v: boolean) {
this.attsw(v, "dragndrop"); this.attsw(v, "dragndrop");
if(!v) if (!v) {
{
$(this.refs.container).off("mousedown", this._onmousedown); $(this.refs.container).off("mousedown", this._onmousedown);
} }
else else {
{
$(this.refs.container).on( $(this.refs.container).on(
"mousedown", "mousedown",
this._onmousedown this._onmousedown
@ -642,8 +661,7 @@ namespace OS {
set cellitem(v: string) { set cellitem(v: string) {
const currci = this.cellitem; const currci = this.cellitem;
$(this).attr("cellitem", v); $(this).attr("cellitem", v);
if(v != currci) if (v != currci) {
{
// force render data // force render data
$(this.refs.grid).empty(); $(this.refs.grid).empty();
this.rows = this.rows; this.rows = this.rows;
@ -678,11 +696,15 @@ namespace OS {
)[0] as GridCellPrototype; )[0] as GridCellPrototype;
element.uify(this.observable); element.uify(this.observable);
element.data = item; element.data = item;
item.domel = element;
element.oncellselect = (e) => { element.oncellselect = (e) => {
if(element.data.sort) if (element.data.sort) {
{
this.sort(element.data, element.data.sort); this.sort(element.data, element.data.sort);
if (element.data.desc) {
$(element).attr("sort", "desc");
}
else {
$(element).attr("sort", "asc");
}
} }
}; };
i++; i++;
@ -691,7 +713,7 @@ namespace OS {
const rz = $(`<afx-resizer>`).appendTo( const rz = $(`<afx-resizer>`).appendTo(
this.refs.header this.refs.header
)[0] as ResizerTag; )[0] as ResizerTag;
$(rz).css("width", "3px"); $(rz).css("width", "1px");
let next_item = undefined; let next_item = undefined;
if (i < v.length) { if (i < v.length) {
next_item = v[i]; next_item = v[i];
@ -752,43 +774,33 @@ namespace OS {
set rows(rows: GenericObject<any>[][]) { set rows(rows: GenericObject<any>[][]) {
this._rows = rows; this._rows = rows;
if (!rows) return; if (!rows) return;
for (const el of this._header) {
$(el.domel).attr("sort", "none");
}
// update existing row with new data // update existing row with new data
const ndrows = rows.length; const ndrows = rows.length;
const ncrows = this.refs.grid.children.length; const ncrows = this.refs.grid.children.length;
const nmin = ndrows < ncrows ? ndrows : ncrows; const nmin = ndrows < ncrows ? ndrows : ncrows;
if(this.selectedRow) if (this.selectedRow) {
{
this.selectedRow.selected = false; this.selectedRow.selected = false;
this._selectedRow = undefined; this._selectedRow = undefined;
this._selectedRows = []; this._selectedRows = [];
} }
for(let i = 0; i < nmin; i++) for (let i = 0; i < nmin; i++) {
{
const rowel = (this.refs.grid.children[i] as GridRowTag); const rowel = (this.refs.grid.children[i] as GridRowTag);
rowel.data = rows[i]; rowel.data = rows[i];
rowel.data.domel = rowel;
for(let celi = 0; celi < rowel.children.length; celi++)
{
const cel = (rowel.children[celi] as GridCellPrototype);
cel.data = rows[i][celi];
cel.data.domel = cel;
}
} }
// remove existing remaining rows // remove existing remaining rows
if(ndrows < ncrows) if (ndrows < ncrows) {
{
const arr = Array.prototype.slice.call(this.refs.grid.children); const arr = Array.prototype.slice.call(this.refs.grid.children);
const blacklist = arr.slice(nmin, ncrows); const blacklist = arr.slice(nmin, ncrows);
for(const r of blacklist) for (const r of blacklist) {
{
this.delete(r); this.delete(r);
} }
} }
// or add more rows // or add more rows
else if(ndrows > ncrows) else if (ndrows > ncrows) {
{ for (let i = nmin; i < ndrows; i++) {
for(let i = nmin; i < ndrows; i++)
{
this.push(rows[i], false); this.push(rows[i], false);
} }
} }
@ -839,7 +851,7 @@ namespace OS {
return fn.call(context, a, b, index); return fn.call(context, a, b, index);
} }
this._rows.sort(__fn); this._rows.sort(__fn);
context.__f = ! context.__f; context.desc = !context.desc;
this.rows = this._rows; this.rows = this._rows;
} }
/** /**
@ -871,6 +883,25 @@ namespace OS {
} }
$(row).remove(); $(row).remove();
} }
/**
* Scroll the grid view to bottom
*
* @memberof GridViewTag
*/
scroll_to_bottom()
{
this.refs.container.scrollTo({ top: this.refs.container.scrollHeight, behavior: 'smooth' })
}
/**
* Scroll the grid view to top
*
* @memberof GridViewTag
*/
scroll_to_top()
{
this.refs.container.scrollTo({ top: 0, behavior: 'smooth' });
}
/** /**
* Push a row to the grid * Push a row to the grid
@ -878,7 +909,7 @@ namespace OS {
* @param {GenericObject<any>[]} row list of cell data * @param {GenericObject<any>[]} row list of cell data
* @param {boolean} flag indicates where the row is add to beginning or end * @param {boolean} flag indicates where the row is add to beginning or end
* of the row * of the row
* @memberof GridViewTags * @memberof GridViewTag
*/ */
push(row: GenericObject<any>[], flag: boolean): void { push(row: GenericObject<any>[], flag: boolean): void {
const rowel = $("<afx-grid-row>").css( const rowel = $("<afx-grid-row>").css(
@ -899,22 +930,19 @@ namespace OS {
const el = rowel[0] as GridRowTag; const el = rowel[0] as GridRowTag;
rowel[0].uify(this.observable); rowel[0].uify(this.observable);
el.data = row;
row.domel = rowel[0];
for (let cell of row) { for (let cell of row) {
let tag = this.cellitem; let tag = this.cellitem;
if (cell.tag) { if (cell.tag) {
({ tag } = cell); tag = cell.tag;
} }
const el = $(`<${tag}>`).appendTo(rowel); const el = $(`<${tag}>`).appendTo(rowel);
cell.domel = el[0];
const element = el[0] as GridCellPrototype; const element = el[0] as GridCellPrototype;
element.uify(this.observable); element.uify(this.observable);
element.oncellselect = (e) => this.cellselect(e, false); element.oncellselect = (e) => this.cellselect(e, false);
element.oncelldbclick = (e) => this.cellselect(e, true); element.oncelldbclick = (e) => this.cellselect(e, true);
element.data = cell;
} }
el.data = row;
el.onrowselect = (e) => this.rowselect({ el.onrowselect = (e) => this.rowselect({
id: el.aid, id: el.aid,
data: { item: el } data: { item: el }
@ -997,12 +1025,9 @@ namespace OS {
} }
evt.data.items = this.selectedRows; evt.data.items = this.selectedRows;
} else { } else {
if(this.selectedRows.length > 0) if (this.selectedRows.length > 0) {
{ for (const item of this.selectedRows) {
for(const item of this.selectedRows) if (item != row) {
{
if(item != row)
{
item.selected = false; item.selected = false;
} }
} }
@ -1110,8 +1135,8 @@ namespace OS {
let i = 0; let i = 0;
for (let v of colssize) { for (let v of colssize) {
template += `${v}px `; template += `${v}px `;
i++;
template_header += `${v}px `; template_header += `${v}px `;
i++;
if (i < colssize.length && this.resizable) { if (i < colssize.length && this.resizable) {
template_header += "3px "; template_header += "3px ";
} }
@ -1121,6 +1146,10 @@ namespace OS {
"grid-template-columns", "grid-template-columns",
template_header template_header
); );
if(this.resizable)
{
$(this.refs.grid).css("column-gap","3px");
}
} }
/** /**
@ -1145,8 +1174,7 @@ namespace OS {
to: undefined, to: undefined,
}; };
this._onmousedown = (e) => { this._onmousedown = (e) => {
if(this.multiselect || this.selectedRows == undefined || this.selectedRows.length == 0) if (this.multiselect || this.selectedRows == undefined || this.selectedRows.length == 0) {
{
return; return;
} }
let el: any = $(e.target).closest("afx-grid-row"); let el: any = $(e.target).closest("afx-grid-row");
@ -1154,8 +1182,7 @@ namespace OS {
return; return;
} }
el = el[0]; el = el[0];
if(!this.selectedRows.includes(el)) if (!this.selectedRows.includes(el)) {
{
return; return;
} }
this._dnd.from = this.selectedRows; this._dnd.from = this.selectedRows;
@ -1205,7 +1232,6 @@ namespace OS {
.css("top", top + "px") .css("top", top + "px")
.css("left", left + "px"); .css("left", left + "px");
}; };
return this.calibrate(); return this.calibrate();
} }
@ -1222,6 +1248,7 @@ namespace OS {
{ {
el: "div", el: "div",
ref: "container", ref: "container",
class: "grid_content_container",
children: [{ el: "div", ref: "grid" }], children: [{ el: "div", ref: "grid" }],
}, },
]; ];

265
src/core/tags/InputTag.ts Normal file
View File

@ -0,0 +1,265 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* This tag define a basic text input and its behavior
*
* @export
* @class InputTag
* @extends {AFXTag}
*/
export class InputTag extends AFXTag {
/**
*Creates an instance of InputTag.
* @memberof InputTag
*/
constructor() {
super();
}
/**
* Set the path to the header icon, the path should be
* a VFS file path
*
* @memberof InputTag
*/
set icon(v: string) {
$(this).attr("icon", v);
(this.refs.label as LabelTag).icon = v;
}
/**
* Set the icon class to the header
*
* @memberof InputTag
*/
set iconclass(v: string) {
$(this).attr("iconclass", v);
(this.refs.label as LabelTag).iconclass = v;
}
/**
* Alias to header setter/getter
*
* @memberof InputTag
*/
set text(v: string | FormattedString) {
this.label = v;
}
get text(): string | FormattedString {
return this.label;
}
/**
* Setter: Set the text of the label
*
* Getter: Get the current label test
*
* @memberof InputTag
*/
set label(v: string | FormattedString) {
(this.refs.label as LabelTag).text = v;
}
get label(): string | FormattedString {
return (this.refs.label as LabelTag).text;
}
/**
* Setter: Enable or disable the input
*
* Getter: Get the `enable` property of the input
*
* @memberof InputTag
*/
set disable(v: boolean) {
$(this.refs.area).prop("disabled", v);
$(this.refs.input).prop("disabled", v);
}
get disable(): boolean {
return !$(this.input).prop("disabled");
}
/**
* Setter: set verbosity of the input
*
* Getter: Get the current input verbosity
*
* @memberof InputTag
*/
set verbose(v: boolean) {
this.attsw(v, "verbose");
this.calibrate();
}
get verbose(): boolean {
return this.hasattr("verbose");
}
/**
* JQuery style generic event handling on the input element
*
* @param {string} enname: JQuery event name
* @param {JQuery.TypeEventHandler<HTMLInputElement | HTMLTextAreaElement, unknown, any, any, string>} handle: JQuery handle
* @memberof InputTag
*/
on(ename: string, handle:JQuery.TypeEventHandler<HTMLInputElement | HTMLTextAreaElement, unknown, any, any, string> ): void
{
$(this.input).on(ename, handle);
}
/**
* Manually trigger an event
*
* @param {string} evt: JQuery event name
* @memberof InputTag
*/
trigger(evt: string)
{
$(this.input).trigger(evt);
}
/**
* Mount the tag
*
* @protected
* @memberof InputTag
*/
protected mount() {
// Do nothing
}
/**
* Get the current active input element
*
* @memberof InputTag
*/
get input(): HTMLInputElement | HTMLTextAreaElement
{
if(this.verbose)
{
return this.refs.area as HTMLTextAreaElement;
}
return this.refs.input as HTMLInputElement;
}
/**
* Get/set the current active input value
*
* @memberof InputTag
*/
get value(): string{
return this.input.value;
}
set value(v: string)
{
this.input.value = v;
}
/**
* Get/set input type
* This only affects the inline input element
*
* @memberof InputTag
*/
get type(): string{
if(this.verbose) return undefined;
return (this.input as HTMLInputElement).type;
}
set type(v: string)
{
if(!this.verbose)
{
(this.input as HTMLInputElement).type = v;
}
}
/**
* Get/set input name
*
* @memberof InputTag
*/
get name(): string{
return (this.input as HTMLInputElement).name;
}
set name(v: string)
{
(this.input as HTMLInputElement).name = v;
}
/**
* Init the tag before mounting
*
* @protected
* @memberof InputTag
*/
protected init(): void {
this.disable = false;
this.verbose = false;
this.type = "text";
}
/**
* Re-calibrate, do nothing in this tag
*
* @protected
* @memberof InputTag
*/
protected calibrate(): void
{
/*$(this.refs.area)
.css("width", "100%");
$(this.refs.input)
.css("width", "100%");*/
if(this.verbose)
{
$(this.refs.area).show();
$(this.refs.input).hide();
(this.refs.input as HTMLInputElement).value = "";
}
else
{
$(this.refs.area).hide();
$(this.refs.input).show();
(this.refs.area as HTMLTextAreaElement).value = "";
}
}
/**
* Update the current tag, do nothing in this tag
*
* @param {*} [d]
* @memberof InputTag
*/
reload(d?: any): void {}
/**
* Input layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof InputTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "afx-label",
ref: "label"
},
{
el: "input",
ref:"input"
},
{
el: "textarea",
ref: "area"
},
{
el: "div"
}
];
}
}
define("afx-input", InputTag);
}
}
}

View File

@ -38,6 +38,8 @@ namespace OS {
.css("display", "flex"); .css("display", "flex");
$(this.refs.iclass) $(this.refs.iclass)
.css("flex-shrink",0); .css("flex-shrink",0);
$(this.refs.iclass_end)
.css("flex-shrink",0);
$(this.refs.i) $(this.refs.i)
.css("flex-shrink",0); .css("flex-shrink",0);
$(this.refs.text) $(this.refs.text)
@ -66,6 +68,7 @@ namespace OS {
this.iconclass = undefined; this.iconclass = undefined;
this.text = undefined; this.text = undefined;
this.selectable = false; this.selectable = false;
this.iconclass$ = undefined;
} }
/** /**
@ -94,6 +97,49 @@ namespace OS {
$(this.refs.i).hide(); $(this.refs.i).hide();
} }
} }
/**
* set horizontal aligment of the label content
*
* @param {string} v shall be "left, right, or center"
*/
set halign(v: string)
{
let align = "center";
switch(v)
{
case "left":
align = "flex-start";
break;
case "right":
align = "flex-end";
break;
default:
break;
}
$(this.refs.container).css("justify-content", align);
}
/**
* set horizontal aligment of the label content
*
* @param {string} v shall be "top, bottom, or center"
*/
set valign(v: string)
{
let align = "center";
switch(v)
{
case "top":
align = "flex-start";
break;
case "bottom":
align = "flex-end";
break;
default:
break;
}
$(this.refs.container).css("align-items", align);
}
/** /**
* Set the CSS class of the label icon * Set the CSS class of the label icon
@ -111,6 +157,31 @@ namespace OS {
} }
} }
/**
* Set the CSS class of the label icon on the right side
*
* @memberof LabelTag
*/
set iconclass_end(v: string) {
this.iconclass$ = v;
}
/**
* Set the CSS class of the label icon on the right side
*
* @memberof LabelTag
*/
set iconclass$(v: string) {
$(this).attr("iconclass_end", v);
$(this.refs.iclass_end).removeClass();
if (v) {
$(this.refs.iclass_end).addClass(v);
$(this.refs.iclass_end).show();
} else {
$(this.refs.iclass_end).hide();
}
}
/** /**
* Setter: Set the text of the label * Setter: Set the text of the label
* *
@ -120,9 +191,9 @@ namespace OS {
*/ */
set text(v: string | FormattedString) { set text(v: string | FormattedString) {
this._text = v; this._text = v;
if (v && v !== "") { if (v) {
$(this.refs.text).show(); $(this.refs.text).show();
$(this.refs.text).html(v.__()); $(this.refs.text).text(v.__());
} else { } else {
$(this.refs.text).hide(); $(this.refs.text).hide();
} }
@ -174,6 +245,7 @@ namespace OS {
{ el: "i", ref: "iclass" }, { el: "i", ref: "iclass" },
{ el: "i", ref: "i", class: "icon-style" }, { el: "i", ref: "i", class: "icon-style" },
{ el: "i", ref: "text", class: "label-text" }, { el: "i", ref: "text", class: "label-text" },
{ el: "i", ref: "iclass_end" },
], ],
}, },
]; ];

View File

@ -6,7 +6,7 @@ namespace OS {
*/ */
export type ListItemEventData = TagEventDataType<ListViewItemTag>; export type ListItemEventData = TagEventDataType<ListViewItemTag>;
/** /**
* A list item represent the individual view of an item in the [[ListView]]. * A list item represent the individual view of an item in the {@link OS.GUI.tag.ListViewTag}.
* This class is an abstract prototype class, implementation of any * This class is an abstract prototype class, implementation of any
* list view item should extend it * list view item should extend it
* *
@ -42,7 +42,7 @@ namespace OS {
* @type {TagEventCallback<ListItemEventData>} * @type {TagEventCallback<ListItemEventData>}
* @memberof ListViewItemTag * @memberof ListViewItemTag
*/ */
private _onctxmenu: TagEventCallback<ListItemEventData>; //private _onctxmenu: TagEventCallback<ListItemEventData>;
/** /**
* Click event callback placeholder * Click event callback placeholder
@ -77,7 +77,7 @@ namespace OS {
*/ */
constructor() { constructor() {
super(); super();
this._onselect = this._onctxmenu = this._onclick = this._ondbclick = this._onclose = ( this._onselect /*= this._onctxmenu*/ = this._onclick = this._ondbclick = this._onclose = (
e e
) => {}; ) => {};
} }
@ -134,9 +134,11 @@ namespace OS {
* *
* @memberof ListViewItemTag * @memberof ListViewItemTag
*/ */
/*
set onctxmenu(v: TagEventCallback<ListViewItemTag>) { set onctxmenu(v: TagEventCallback<ListViewItemTag>) {
this._onctxmenu = v; this._onctxmenu = v;
} }
*/
/** /**
* Set the item click event handle * Set the item click event handle
@ -172,20 +174,23 @@ namespace OS {
* @memberof ListViewItemTag * @memberof ListViewItemTag
*/ */
protected mount(): void { protected mount(): void {
$(this.refs.item).attr("dataref", "afx-list-item"); $(this).addClass("afx-list-item");
$(this.refs.item).on("contextmenu", (e) => { /*
$(this.refs.item).on(OS.mobile?"longtouch":"contextmenu", (e) => {
this._onctxmenu({ id: this.aid, data: this }); this._onctxmenu({ id: this.aid, data: this });
}); });
*/
$(this.refs.item).on("click",(e) => { $(this.refs.item).on("click",(e) => {
this._onclick({ id: this.aid, data: this }); this._onclick({ id: this.aid, data: this, originalEvent: e });
//e.stopPropagation();
}); });
$(this.refs.item).on("dblclick",(e) => { $(this.refs.item).on(OS.mobile?"dbltap":"dblclick",(e) => {
this._ondbclick({ id: this.aid, data: this }); this._ondbclick({ id: this.aid, data: this, originalEvent: e });
e.stopPropagation();
}); });
$(this.refs.btcl).on("click",(e) => { $(this.refs.btcl).on("click",(e) => {
this._onclose({ id: this.aid, data: this }); this._onclose({ id: this.aid, data: this, originalEvent: e });
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
}); });
@ -195,21 +200,29 @@ namespace OS {
* Layout definition of the item tag. * Layout definition of the item tag.
* This function define the outer layout of the item. * This function define the outer layout of the item.
* Custom inner layout of each item implementation should * Custom inner layout of each item implementation should
* be defined in [[itemlayout]] * be defined in {@link itemlayout}
* *
* @protected * @protected
* @returns {TagLayoutType[]} * @returns {TagLayoutType[]}
* @memberof ListViewItemTag * @memberof ListViewItemTag
*/ */
protected layout(): TagLayoutType[] { protected layout(): TagLayoutType[] {
let children = [{el: "i", class: "closable", ref: "btcl"}] as TagLayoutType[];
const itemlayout = this.itemlayout();
if(Array.isArray(itemlayout))
{
children = children.concat(itemlayout);
}
else
{
children.unshift(itemlayout);
}
return [ return [
{ {
el: "li", el: "li",
ref: "item", ref: "item",
children: [ children:children,
this.itemlayout(),
{ el: "i", class: "closable", ref: "btcl" },
],
}, },
]; ];
} }
@ -218,7 +231,7 @@ namespace OS {
* Setter: * Setter:
* *
* Set the data of the list item. This will * Set the data of the list item. This will
* trigger the [[ondatachange]] function * trigger the {@link ondatachange} function
* *
* Getter: * Getter:
* *
@ -228,6 +241,10 @@ namespace OS {
*/ */
set data(v: GenericObject<any>) { set data(v: GenericObject<any>) {
this._data = v; this._data = v;
if(v)
{
this.attach(v);
}
this.ondatachange(); this.ondatachange();
} }
get data(): GenericObject<any> { get data(): GenericObject<any> {
@ -240,10 +257,10 @@ namespace OS {
* *
* @protected * @protected
* @abstract * @abstract
* @returns {TagLayoutType} * @returns {TagLayoutType | TagLayoutType[]}
* @memberof ListViewItemTag * @memberof ListViewItemTag
*/ */
protected abstract itemlayout(): TagLayoutType; protected abstract itemlayout(): TagLayoutType | TagLayoutType[];
/** /**
* This function is called when the item data is changed. * This function is called when the item data is changed.
@ -329,14 +346,101 @@ namespace OS {
* List item custom layout definition * List item custom layout definition
* *
* @protected * @protected
* @returns {TagLayoutType} * @returns {TagLayoutType | TagLayoutType[]}
* @memberof SimpleListItemTag * @memberof SimpleListItemTag
*/ */
protected itemlayout(): TagLayoutType { protected itemlayout(): TagLayoutType | TagLayoutType[] {
return { el: "afx-label", ref: "label" }; return { el: "afx-label", ref: "label" };
} }
} }
/**
* The layout of a double line list item contains two
* AFX labels
*
* @export
* @class DoubleLineListItemTag
* @extends {ListViewItemTag}
*/
export class DoubleLineListItemTag extends ListViewItemTag {
/**
*Creates an instance of DoubleLineListItemTag.
* @memberof DoubleLineListItemTag
*/
constructor() {
super();
}
/**
* Reset some property to default
*
* @protected
* @memberof DoubleLineListItemTag
*/
protected init(): void {
this.closable = false;
this.data = {};
}
/**
* Do nothing
*
* @protected
* @memberof DoubleLineListItemTag
*/
protected calibrate(): void {}
/**
* Refresh the inner label when the item data
* is changed
*
* @protected
* @returns {void}
* @memberof DoubleLineListItemTag
*/
protected ondatachange(): void {
const v = this.data;
if (!v) {
return;
}
const line1 = this.refs.line1 as LabelTag;
const line2 = this.refs.line2 as LabelTag;
line1.set(v);
if(v.description)
{
line2.set(v.description);
}
if (v.selected) {
this.selected = v.selected;
}
if (v.closable) {
this.closable = v.closable;
}
}
/**
* Re-render the list item
*
* @protected
* @memberof DoubleLineListItemTag
*/
protected reload(): void {
this.data = this.data;
}
/**
* List item custom layout definition
*
* @protected
* @returns {TagLayoutType | TagLayoutType[]}
* @memberof DoubleLineListItemTag
*/
protected itemlayout(): TagLayoutType | TagLayoutType[] {
return [{ el: "afx-label", ref: "line1", class:"title" }, { el: "afx-label", ref: "line2", class:"description" }];
}
}
/** /**
* This tag defines a traditional or a dropdown list widget. * This tag defines a traditional or a dropdown list widget.
* It contains a collection of list items in which layout * It contains a collection of list items in which layout
@ -422,7 +526,7 @@ namespace OS {
/** /**
* A collection of selected items in the list. * A collection of selected items in the list.
* The maximum size of this collection is 1 if * The maximum size of this collection is 1 if
* the [[multiselect]] feature is disabled * the {@link multiselect} feature is disabled
* *
* @private * @private
* @type {ListViewItemTag[]} * @type {ListViewItemTag[]}
@ -430,6 +534,14 @@ namespace OS {
*/ */
private _selectedItems: ListViewItemTag[]; private _selectedItems: ListViewItemTag[];
/**
* The anchor element that the list view positioned on
* This is helpful when rendering dropdown list
* @private
* @type{HTMLElement}
* @memberof ListViewTag
*/
private _anchor: HTMLElement;
/** /**
* Data placeholder of the list * Data placeholder of the list
* *
@ -439,6 +551,9 @@ namespace OS {
*/ */
private _data: GenericObject<any>[]; private _data: GenericObject<any>[];
private _drop: (any) => void;
private _show: (any) => void;
/** /**
* Event data passing between mouse event when performing * Event data passing between mouse event when performing
* drag and drop on the list * drag and drop on the list
@ -468,6 +583,8 @@ namespace OS {
) => {}; ) => {};
this._selectedItems = []; this._selectedItems = [];
this._selectedItem = undefined; this._selectedItem = undefined;
this._drop = (e) => {this.dropoff(e)};
this._show = (e) => {this.showlist(e)};
} }
/** /**
@ -482,10 +599,9 @@ namespace OS {
this.dropdown = false; this.dropdown = false;
this.selected = -1; this.selected = -1;
this.dragndrop = false; this.dragndrop = false;
$(this) this._anchor = undefined;
.css("display", "flex")
.css("flex-direction", "column");
this.itemtag = "afx-list-item"; this.itemtag = "afx-list-item";
$(this).addClass("afx-list-view");
} }
/** /**
@ -508,32 +624,19 @@ namespace OS {
this.attsw(v, "dropdown"); this.attsw(v, "dropdown");
$(this.refs.container).removeAttr("style"); $(this.refs.container).removeAttr("style");
$(this.refs.mlist).removeAttr("style"); $(this.refs.mlist).removeAttr("style");
$(this.refs.container).css("flex", 1);
$(this).removeClass("dropdown"); $(this).removeClass("dropdown");
const drop = (e: any) => {
return this.dropoff(e);
};
const show = (e: any) => {
return this.showlist(e);
};
if (v) { if (v) {
$(this).addClass("dropdown"); $(this).addClass("dropdown");
$(this.refs.current).show(); $(this.refs.current).show();
$(document).on("click", drop); $(document).on("click", this._drop);
$(this.refs.current).on("click", show); $(this.refs.current).on("click", this._show);
$(this.refs.container) $(this.refs.mlist).hide();
.css("position", "absolute")
.css("display", "inline-block");
$(this.refs.mlist)
.css("position", "absolute")
.css("display", "none")
.css("top", "100%")
.css("left", "0");
this.calibrate(); this.calibrate();
} else { } else {
$(document).off("click", this._drop);
$(this.refs.current).off("click", this._show);
$(this.refs.current).hide(); $(this.refs.current).hide();
$(document).off("click", drop); $(this.refs.mlist).show();
$(this.refs.current).off("click", show);
} }
} }
@ -642,7 +745,7 @@ namespace OS {
* Button layout allows to add some custom * Button layout allows to add some custom
* behaviors to the list. * behaviors to the list.
* *
* Each button data should define the [[onbtclick]] * Each button data should define the {@link OS.GUI.tag.ButtonTag.onbtclick}
* event handle to specify the custom behavior * event handle to specify the custom behavior
* *
* When the list is configured as dropdown. The buttons * When the list is configured as dropdown. The buttons
@ -676,7 +779,28 @@ namespace OS {
(bt[0] as ButtonTag).set(item); (bt[0] as ButtonTag).set(item);
} }
} }
/**
* Getter: Get list direction: row or column (default)
*
* Setter: Get list direction: row or column
*
* @type {string}
* @memberof ListViewTag
*/
set dir(v: string) {
if(this.dropdown)
{
$(this).attr("dir", "column");
}
else
{
$(this).attr("dir", v);
}
this.calibrate();
}
get dir(): string {
return $(this).attr("dir");
}
/** /**
* Getter: Get data of the list * Getter: Get data of the list
* *
@ -769,7 +893,13 @@ namespace OS {
get selectedItems(): ListViewItemTag[] { get selectedItems(): ListViewItemTag[] {
return this._selectedItems; return this._selectedItems;
} }
/**
* get the selected item index
*
* @readonly
* @type {number}
* @memberof ListViewTag
*/
get selected(): number | number[] { get selected(): number | number[] {
if (this.multiselect) { if (this.multiselect) {
return this.selectedItems.map(function ( return this.selectedItems.map(function (
@ -808,7 +938,7 @@ namespace OS {
* Add an item to the beginning or end of the list * Add an item to the beginning or end of the list
* *
* @param {GenericObject<any>} item list item data * @param {GenericObject<any>} item list item data
* @param {boolean} [flag] indicates whether to add the item in the beginning of the list * @param {boolean} flag indicates whether to add the item in the beginning of the list
* @returns {ListViewItemTag} the added list item element * @returns {ListViewItemTag} the added list item element
* @memberof ListViewTag * @memberof ListViewTag
*/ */
@ -832,9 +962,11 @@ namespace OS {
} }
el[0].uify(this.observable); el[0].uify(this.observable);
const element = el[0] as ListViewItemTag; const element = el[0] as ListViewItemTag;
$(element).attr("list-id",this.aid);
/*
element.onctxmenu = (e) => { element.onctxmenu = (e) => {
return this.iclick(e, true); return this.iclick(e, true);
}; };*/
element.onitemdbclick = (e) => { element.onitemdbclick = (e) => {
this.idbclick(e); this.idbclick(e);
this.iclick(e, false); this.iclick(e, false);
@ -849,7 +981,6 @@ namespace OS {
return this.iclose(e); return this.iclose(e);
}; };
element.data = item; element.data = item;
item.domel = el[0];
return element; return element;
} }
@ -955,12 +1086,12 @@ namespace OS {
/** /**
* This function triggers the double click event on an item * This function triggers the double click event on an item
* *
* @private * @protected
* @param {TagEventType} e tag event object * @param {TagEventType} e tag event object
* @returns * @returns
* @memberof ListViewTag * @memberof ListViewTag
*/ */
private idbclick(e: TagEventType<ListViewItemTag>) { protected idbclick(e: TagEventType<ListViewItemTag>) {
const evt: TagEventType<ListItemEventData> = { const evt: TagEventType<ListItemEventData> = {
id: this.aid, id: this.aid,
data: { item: e.data }, data: { item: e.data },
@ -972,12 +1103,12 @@ namespace OS {
/** /**
* This function triggers the list item select event * This function triggers the list item select event
* *
* @private * @protected
* @param {TagEventType} e tag event object * @param {TagEventType} e tag event object
* @returns * @returns
* @memberof ListViewTag * @memberof ListViewTag
*/ */
private iselect(e: TagEventType<ListViewItemTag>) { protected iselect(e: TagEventType<ListViewItemTag>) {
if (!e.data) { if (!e.data) {
return; return;
} }
@ -1031,9 +1162,10 @@ namespace OS {
} }
} }
if (this.dropdown) { // set the label content event it is hidden
const label = this.refs.drlabel as LabelTag; const label = this.refs.drlabel as LabelTag;
label.set(e.data.data); label.set(e.data.data);
if (this.dropdown) {
$(this.refs.mlist).hide(); $(this.refs.mlist).hide();
} }
const evt = { id: this.aid, data: edata }; const evt = { id: this.aid, data: edata };
@ -1059,12 +1191,12 @@ namespace OS {
return; return;
} }
let el: any = $(e.target).closest( let el: any = $(e.target).closest(
"li[dataref='afx-list-item']" `[list-id='${this.aid}']`
); );
if (el.length === 0) { if (el.length === 0) {
return; return;
} }
el = el.parent()[0]; el = el[0];
if(!this.selectedItems.includes(el)) if(!this.selectedItems.includes(el))
{ {
return; return;
@ -1080,12 +1212,12 @@ namespace OS {
$(window).off("mousemove", this._onmousemove); $(window).off("mousemove", this._onmousemove);
$("#systooltip").hide(); $("#systooltip").hide();
let el: any = $(e.target).closest( let el: any = $(e.target).closest(
"li[dataref='afx-list-item']" `[list-id='${this.aid}']`
); );
if (el.length === 0) { if (el.length === 0) {
return; return;
} }
el = el.parent()[0]; el = el[0];
if (this._dnd.from.includes(el)) { if (this._dnd.from.includes(el)) {
return; return;
} }
@ -1126,9 +1258,20 @@ namespace OS {
.css("top", top + "px") .css("top", top + "px")
.css("left", left + "px"); .css("left", left + "px");
}; };
const label = (this.refs.drlabel as LabelTag);
label.iconclass$ = "bi bi-chevron-down";
label.text = "";
$(this.refs.drlabel).css("display", "inline-block"); $(this.refs.drlabel).css("display", "inline-block");
$(this.refs.btlist).hide(); $(this.refs.btlist).hide();
this.observable.on("resize", (e) => this.calibrate()); this.observable.on("resize", (e) => this.calibrate());
let anchor = $(this).parent();
while (anchor && anchor.css('position') === 'static') {
anchor = anchor.parent();
}
if(anchor && anchor[0])
{
this._anchor = anchor[0];
}
return this.calibrate(); return this.calibrate();
} }
@ -1167,16 +1310,31 @@ namespace OS {
if (!this.dropdown) { if (!this.dropdown) {
return; return;
} }
const desktoph = $(Ant.OS.GUI.workspace).height(); if(! $(this.refs.mlist).is(":hidden"))
const offset = {
$(this).offset().top + $(this.refs.mlist).height(); $(this.refs.mlist).hide();
if (offset > desktoph) { return;
$(this.refs.mlist).css( }
"top",
`-${$(this.refs.mlist).outerHeight()}px` const desktoph = $(Ant.OS.GUI.workspace).outerHeight();
); const wheight = $(this).offset().top + $(this.refs.mlist).outerHeight()*1.5;
const position = $(this).position();
let offset = 0;
if(this._anchor)
{
offset = $(this._anchor).scrollTop();
}
if (wheight > desktoph) {
const ypos = offset + position.top - $(this.refs.mlist).outerHeight();
$(this.refs.mlist)
.css("top",`${ypos}px`)
.css("left", `${position.left}px`);
} else { } else {
$(this.refs.mlist).css("top", "100%"); const ypos = offset + $(this).position().top + $(this.refs.container).outerHeight();
$(this.refs.mlist)
.css("top", `${ypos}px`)
.css("left", `${position.left}px`);
} }
$(this.refs.mlist).show(); $(this.refs.mlist).show();
} }
@ -1196,6 +1354,40 @@ namespace OS {
} }
} }
/**
* Scroll the list view to end
*
* @memberof ListViewTag
*/
scroll_to_end()
{
if(this.dir == "column")
{
this.refs.mlist.scrollTo({ top: this.refs.mlist.scrollHeight, behavior: 'smooth' });
}
else
{
this.refs.mlist.scrollTo({ left: this.refs.mlist.scrollWidth, behavior: 'smooth' });
}
}
/**
* Scroll the list view to beginning
*
* @memberof ListViewTag
*/
scroll_to_start()
{
if(this.dir == "column")
{
this.refs.mlist.scrollTo({ top: 0, behavior: 'smooth' });
}
else
{
this.refs.mlist.scrollTo({ left: 0, behavior: 'smooth' });
}
}
/** /**
* calibrate the list layout * calibrate the list layout
* *
@ -1207,9 +1399,12 @@ namespace OS {
if (!this.dropdown) { if (!this.dropdown) {
return; return;
} }
const w = `${$(this).width()}px`; const w = `${$(this).innerWidth()}px`;
$(this.refs.container).css("width", w); const h = `${$(this).outerHeight()}px`;
$(this.refs.current).css("width", w); $(this.refs.container).css("width", "100%");
$(this.refs.container).css("height", h);
$(this.refs.current).css("width", "100%");
$(this.refs.mlist).css("width", w); $(this.refs.mlist).css("width", w);
} }
@ -1244,6 +1439,7 @@ namespace OS {
define("afx-list-view", ListViewTag); define("afx-list-view", ListViewTag);
define("afx-list-item", SimpleListItemTag); define("afx-list-item", SimpleListItemTag);
define("afx-dbline-list-item", DoubleLineListItemTag);
} }
} }
} }

View File

@ -1,826 +0,0 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Menu event data interface definition
*/
export type MenuEventData = TagEventDataType<MenuEntryTag>;
/**
* This class defines the abstract prototype of an menu entry.
* Any implementation of menu entry tag should extend this class
*
* @export
* @abstract
* @class MenuEntryTag
* @extends {AFXTag}
*/
export abstract class MenuEntryTag extends AFXTag {
/**
* Data placeholder of the menu entry
*
* @private
* @type {GenericObject<any>}
* @memberof MenuEntryTag
*/
private _data: GenericObject<any>;
/**
* placeholder of `menu entry select` event handle
*
* @private
* @type {TagEventCallback<MenuEventData>}
* @memberof MenuEntryTag
*/
private _onmenuselect: TagEventCallback<MenuEventData>;
/**
* placeholder of `sub-menu entry select event` handle
*
* @private
* @type {TagEventCallback<MenuEventData>}
* @memberof MenuEntryTag
*/
private _onchildselect: TagEventCallback<MenuEventData>;
/**
* Reference to the parent menu entry of current one
*
* @type {MenuEntryTag}
* @memberof MenuEntryTag
*/
parent: MenuEntryTag;
/**
* Reference to the root menu entry
*
* @type {MenuTag}
* @memberof MenuEntryTag
*/
root: MenuTag;
/**
*Creates an instance of MenuEntryTag.
* @memberof MenuEntryTag
*/
constructor() {
super();
this._onmenuselect = this._onchildselect = (
e: TagEventType<MenuEventData>
): void => {};
}
/**
* Init the tag before mounting
*
* @protected
* @memberof MenuEntryTag
*/
protected init(): void {
this.nodes = undefined;
}
/**
* Set the `menu entry select` event handle
*
* @memberof MenuEntryTag
*/
set onmenuselect(v: TagEventCallback<MenuEventData>) {
this._onmenuselect = v;
}
/**
* Setter: Set the `sub menu entry select` event handle
*
* Getter: get the current `sub menu entry select` event handle
*
* @memberof MenuEntryTag
*/
set onchildselect(v: TagEventCallback<MenuEventData>) {
this._onchildselect = v;
}
get onchildselect(): TagEventCallback<MenuEventData> {
return this._onchildselect;
}
/**
* Setter: Set data to the entry
*
* Getter: Get data of the current menu entry
*
* @memberof MenuEntryTag
*/
set data(data: GenericObject<any>) {
this._data = data;
this.set(data);
}
get data(): GenericObject<any> {
return this._data;
}
/**
* Check whether the current menu entry has sub-menu
*
* @protected
* @returns {boolean}
* @memberof MenuEntryTag
*/
protected has_nodes(): boolean {
const ch = this.nodes;
return ch && ch.length > 0;
}
/**
* Check whether the current menu entry is the root entry
*
* @protected
* @returns
* @memberof MenuEntryTag
*/
protected is_root() {
if (this.parent) {
return false;
} else {
return true;
}
}
/**
* Layout definition of the menu entry
* This function define the outer layout of the menu entry.
* Custom inner layout of each item implementation should
* be defined in [[itemlayout]]
* @protected
* @returns {TagLayoutType[]}
* @memberof MenuEntryTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "li",
ref: "container",
children: [
{
el: "a",
ref: "entry",
children: this.itemlayout(),
},
{ el: "afx-menu", ref: "submenu" },
],
},
];
}
/**
* Setter: Set the sub-menu data
*
* Getter: Get the sub-menu data
*
* @memberof MenuEntryTag
*/
set nodes(v: GenericObject<any>[]) {
$(this.refs.container).removeClass("afx_submenu");
if (!v || !(v.length > 0)) {
$(this.refs.submenu).hide();
return;
}
$(this.refs.container).addClass("afx_submenu");
$(this.refs.submenu).show().attr("style", "");
const element = this.refs.submenu as MenuTag;
element.parent = this;
element.root = this.root;
element.items = v;
// ensure that the data is in sync
this._data.nodes = v;
if (this.is_root()) {
$(this.refs.container).on("mouseleave",(e) => {
return $(this.refs.submenu).attr("style", "");
});
}
}
get nodes(): GenericObject<any>[] {
if (this.data && this.data.nodes) {
return this.data.nodes;
}
return undefined;
}
/**
* Bind some base event to the menu entry
*
* @protected
* @memberof MenuEntryTag
*/
protected mount(): void {
$(this.refs.entry).on("click",(e) => this.select(e));
}
/**
* Hide the sub-menu of the current menu entry
*
* @private
* @returns {void}
* @memberof MenuEntryTag
*/
private submenuoff(): void {
const p = this.parent;
if (!p) {
$(this.refs.submenu).attr("style", "");
return;
}
return p.submenuoff();
}
/**
* This function trigger two event:
* - the `onmenuselect` event on the current entry
* - the `onchildselect` event on the parent of the current entry
*
* @protected
* @param {JQuery.ClickEvent} e
* @memberof MenuEntryTag
*/
protected select(e: JQuery.ClickEvent): void {
const evt = {
id: this.aid,
data: { item: this, event: e },
};
e.preventDefault();
if (this.is_root() && this.has_nodes()) {
$(this.refs.submenu).show();
} else {
this.submenuoff();
}
this._onmenuselect(evt);
if (this.parent) {
this.parent.onchildselect(evt);
}
if (this.root) {
this.root.onmenuitemselect(evt);
}
}
/**
* custom inner layout of a menu entry
*
* @protected
* @abstract
* @returns {TagLayoutType[]}
* @memberof MenuEntryTag
*/
protected abstract itemlayout(): TagLayoutType[];
}
/**
* This class extends the [[MenuEntryTag]] prototype. It inner layout is
* defined with the following elements:
* - a [[SwitchTag]] acts as checker or radio
* - a [[LabelTag]] to display the content of the menu entry
* - a `span` element that display the keyboard shortcut of the entry
*
* @class SimpleMenuEntryTag
* @extends {MenuEntryTag}
*/
export class SimpleMenuEntryTag extends MenuEntryTag {
/**
*Creates an instance of SimpleMenuEntryTag.
* @memberof SimpleMenuEntryTag
*/
constructor() {
super();
}
/**
* Reset some properties to default value
*
* @protected
* @memberof SimpleMenuEntryTag
*/
protected init(): void {
super.init();
this.switch = false;
this.radio = false;
this.checked = false;
}
/**
* Do nothing
*
* @protected
* @memberof SimpleMenuEntryTag
*/
protected calibrate(): void {}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof SimpleMenuEntryTag
*/
protected reload(d?: any): void {}
/**
* Setter: Turn on/off the checker feature of the menu entry
*
* Getter: Check whether the checker feature is enabled on this menu entry
*
* @memberof SimpleMenuEntryTag
*/
set switch(v: boolean) {
this.attsw(v, "switch");
if (this.radio || v) {
$(this.refs.switch).show();
} else {
$(this.refs.switch).hide();
}
}
get switch(): boolean {
return this.hasattr("switch");
}
/**
* Setter: Turn on/off the radio feature of the menu entry
*
* Getter: Check whether the radio feature is enabled
*
* @memberof SimpleMenuEntryTag
*/
set radio(v: boolean) {
this.attsw(v, "radio");
if (this.switch || v) {
$(this.refs.switch).show();
} else {
$(this.refs.switch).hide();
}
}
get radio(): boolean {
return this.hasattr("radio");
}
/**
* Setter:
*
* Toggle the switch on the menu entry, this setter
* only works when the `checker` or `radio` feature is
* enabled
*
* Getter:
*
* Check whether the switch is turned on
*
* @memberof SimpleMenuEntryTag
*/
set checked(v: boolean) {
this.attsw(v, "checked");
if (this.data) this.data.checked = v;
if (!this.radio && !this.switch) {
return;
}
(this.refs.switch as SwitchTag).swon = v;
}
get checked(): boolean {
return this.hasattr("checked");
}
/**
* Set the label icon using a VFS path
*
* @memberof SimpleMenuEntryTag
*/
set icon(v: string) {
//$(this.refs.container).removeClass("fix_padding");
if (!v) {
return;
}
//$(this).attr("icon", v);
const label = this.refs.label as LabelTag;
label.icon = v;
//$(this.refs.container).addClass("fix_padding");
}
/**
* Set the label CSS icon class
*
* @memberof SimpleMenuEntryTag
*/
set iconclass(v: string) {
if (!v) {
return;
}
const label = this.refs.label as LabelTag;
label.iconclass = v;
}
/**
* Set the label text
*
* @memberof SimpleMenuEntryTag
*/
set text(v: string) {
if (v === undefined) {
return;
}
const label = this.refs.label as LabelTag;
label.text = v;
}
/**
* Set the keyboard shortcut text
*
* @memberof SimpleMenuEntryTag
*/
set shortcut(v: string) {
$(this.refs.shortcut).hide();
if (!v) {
return;
}
$(this.refs.shortcut).show();
$(this.refs.shortcut).text(v);
}
/**
* Uncheck all sub-menu items of the current menu entry
* that have the radio feature enabled
*
* @returns {void}
* @memberof SimpleMenuEntryTag
*/
protected reset_radio(): void {
if (!this.has_nodes()) {
return;
}
for (let v of this.nodes) {
if (!v.domel.radio) {
continue;
}
v.domel.checked = false;
}
}
/**
* Mount the current tag
*
* @protected
* @memberof SimpleMenuEntryTag
*/
protected mount(): void {
super.mount();
(this.refs.switch as SwitchTag).enable = false;
}
/**
* Trigger the onmenuselect and onchildselect events
*
* @protected
* @param {JQuery.ClickEvent} e Mouse click event
* @returns {void}
* @memberof SimpleMenuEntryTag
*/
protected select(e: JQuery.ClickEvent): void {
if (this.switch) {
this.checked = !this.checked;
} else if (this.radio) {
const p = this.parent as SimpleMenuEntryTag;
if (p) {
p.reset_radio();
}
this.checked = !this.checked;
}
return super.select(e);
}
/**
* Inner item layout of the menu entry
*
* @returns
* @memberof SimpleMenuEntryTag
*/
itemlayout() {
return [
{ el: "afx-switch", ref: "switch" },
{ el: "afx-label", ref: "label" },
{ el: "span", class: "shortcut", ref: "shortcut" },
];
}
}
/**
* A menu tag contains a collection of menu entries in which each
* entry maybe a leaf entry or may contain a submenu
*
* @export
* @class MenuTag
* @extends {AFXTag}
*/
export class MenuTag extends AFXTag {
/**
* Reference to the parent menu entry of the current value.
* This value is `undefined` in case of the current menu is
* the root menu
*
* @type {MenuEntryTag}
* @memberof MenuTag
*/
parent: MenuEntryTag;
/**
* Reference to the root menu
*
* @type {MenuTag}
* @memberof MenuTag
*/
root: MenuTag;
/**
* The `pid` of the application that attached to this menu.
* This value is optional
*
* @type {number}
* @memberof MenuTag
*/
pid?: number;
/**
* placeholder for menu select event handle
*
* @private
* @type {TagEventCallback<MenuEventData>}
* @memberof MenuTag
*/
private _onmenuselect: TagEventCallback<MenuEventData>;
/**
* Menu data placeholder
*
* @private
* @type {GenericObject<any>[]}
* @memberof MenuTag
*/
private _items: GenericObject<any>[];
/**
*Creates an instance of MenuTag.
* @memberof MenuTag
*/
constructor() {
super();
}
/**
* Reset some properties to default value
*
* @protected
* @memberof MenuTag
*/
protected init(): void {
this.contentag = "afx-menu-entry";
this.context = false;
this._items = [];
this._onmenuselect = (
e: TagEventType<MenuEventData>
): void => {};
}
/**
* Do nothing
*
* @protected
* @memberof MenuTag
*/
protected calibrate(): void {}
/**
* Do nothing
*
* @protected
* @param {*} [d]
* @memberof MenuTag
*/
protected reload(d?: any): void {}
/**
* Setter: Set the menu items data
*
* Getter: Get menu items data
*
* @memberof MenuTag
*/
set items(data: GenericObject<any>[]) {
this._items = data;
$(this.refs.container).empty();
data.map((item) => this.push(item, false));
}
get items(): GenericObject<any>[] {
return this._items;
}
/**
* Setter: Set whether the current menu is a context menu
*
* Getter: Check whether the current menu is a context menu
*
* @memberof MenuTag
*/
set context(v: boolean) {
this.attsw(v, "context");
$(this.refs.wrapper).removeClass("context");
if (!v) {
return;
}
$(this.refs.wrapper).addClass("context");
$(this).hide();
}
get context(): boolean {
return this.hasattr("context");
}
/**
* Set menu select event handle
*
* @memberof MenuTag
*/
set onmenuselect(v: TagEventCallback<MenuEventData>) {
this._onmenuselect = v;
}
/**
* Setter:
*
* Set the default tag name of the menu item.
* If the tag is not specified in an item data,
* this value will be used
*
* Getter:
*
* Get the default menu entry tag name
*
* @memberof MenuTag
*/
set contentag(v: string) {
$(this).attr("contentag", v);
}
get contentag(): string {
return $(this).attr("contentag");
}
/**
* Get the reference to the function that triggers
* the menu select event
*
* @readonly
* @type {TagEventCallback}
* @memberof MenuTag
*/
get onmenuitemselect(): TagEventCallback<MenuEventData> {
return this.handleselect;
}
/**
* This function triggers the menu select event
*
* @private
* @param {TagEventType} e
* @memberof MenuTag
*/
private handleselect(e: TagEventType<MenuEventData>): void {
if (this.context) {
$(this).hide();
}
e.id = this.aid;
this._onmenuselect(e);
this.observable.trigger("menuselect", e);
}
/**
* Show the current menu. This function is called
* only if the current menu is a context menu
*
* @param {JQuery.MouseEventBase} e JQuery mouse event
* @returns {void}
* @memberof MenuTag
*/
show(e: JQuery.MouseEventBase): void {
if (!this.context) {
return;
}
$(this)
.css("top", e.clientY - 15 + "px")
.css("left", e.clientX - 5 + "px")
.show();
}
/**
* Test whether the current menu is the root menu
*
* @private
* @returns {boolean}
* @memberof MenuTag
*/
private is_root(): boolean {
return this.root === undefined;
}
/**
* Mount the menu tag and bind some basic events
*
* @protected
* @returns {void}
* @memberof MenuTag
*/
protected mount(): void {
$(this.refs.container).css("display", "contents");
if (!this.context) {
return;
}
$(this.refs.wrapper).on("mouseleave",(e) => {
if (!this.is_root()) {
return;
}
return $(this).hide();
});
}
/**
* Add a menu entry to the beginning of the current
* menu
*
* @param {GenericObject<any>} item menu entry data
* @memberof MenuTag
*/
unshift(item: GenericObject<any>): void {
this.push(item, true);
}
/**
* Delete a menu entry
*
* @param {MenuEntryTag} item reference to the DOM element of an menu entry
* @memberof MenuTag
*/
delete(item: MenuEntryTag): void {
const el = item.data;
const data = this.items;
if (data.includes(el)) {
data.splice(data.indexOf(el), 1);
}
$(item).remove();
}
/**
* Add an menu entry to the beginning or end of the menu
*
* @param {GenericObject<any>} item menu entry data
* @param {boolean} flag indicates whether the entry should be added to the beginning of the menu
* @returns {MenuEntryTag}
* @memberof MenuTag
*/
push(item: GenericObject<any>, flag: boolean): MenuEntryTag {
let tag = this.contentag;
if (item.tag) {
tag = item.tag;
}
const el = $(`<${tag}>`);
if (flag) {
$(this.refs.container).prepend(el[0]);
if (!this.items.includes(item)) {
this.items.unshift(item);
}
} else {
el.appendTo(this.refs.container);
if (!this.items.includes(item)) {
this.items.push(item);
}
}
const entry = el[0] as MenuEntryTag;
entry.uify(this.observable);
entry.parent = this.parent;
entry.root = this.parent ? this.parent.root : this;
entry.data = item;
item.domel = entry;
return entry;
}
/**
* Menu tag layout definition
*
* @returns
* @memberof MenuTag
*/
layout() {
return [
{
el: "ul",
ref: "wrapper",
children: [
{ el: "li", class: "afx-corner-fix" },
{ el: "div", ref: "container" },
{ el: "li", class: "afx-corner-fix" },
],
},
];
}
}
define("afx-menu", MenuTag);
define("afx-menu-entry", SimpleMenuEntryTag);
}
}
}

View File

@ -114,38 +114,6 @@ namespace OS {
* @memberof NSpinnerTag * @memberof NSpinnerTag
*/ */
calibrate(): void { 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");
} }
/** /**

View File

@ -0,0 +1,189 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Toast notification tag
*
* @export
* @class ToastNotificationTag
* @extends {AFXTag}
*/
export class ToastNotificationTag extends AFXTag {
/**
*Creates an instance of ToastNotificationTag.
* @memberof ToastNotificationTag
*/
constructor() {
super();
}
/**
* Mount the tag
*
* @protected
* @memberof ToastNotificationTag
*/
protected mount() {
$(this.refs.header).on('click',(e) => {
$(this).remove();
})
}
/**
* Init the tag before mounting
*
* @protected
* @memberof ToastNotificationTag
*/
protected init(): void {
};
/**
* Re-calibrate tag
*
* @protected
* @memberof ToastNotificationTag
*/
protected calibrate(): void {}
/**
* Update the current tag, do nothing in this tag
*
* @param {*} [d]
* @memberof ToastNotificationTag
*/
reload(d?: any): void {}
/**
* Tag layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof ToastNotificationTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "div", id: "toast_container", ref: "container",
children:[
{
el: "div",
ref: "header",
id: "toast_header",
},
{
el: "div",
ref: "yield",
id:"toast_content",
}
]
}
];
}
}
/**
* This tag manage all notification UI on the desktop
*
* @export
* @class NotificationTag
* @extends {AFXTag}
*/
export class NotificationTag extends AFXTag {
/**
*Creates an instance of NotificationTag.
* @memberof NotificationTag
*/
constructor() {
super();
}
/**
* Mount the tag
*
* @protected
* @memberof NotificationTag
*/
protected mount() {
}
/**
* Init the tag before mounting
*
* @protected
* @memberof NotificationTag
*/
protected init(): void {
};
/**
* Push anotification to a specific location
*
* @memberof NotificationTag
*/
push(tag: AFXTag, loc: ANCHOR = ANCHOR.NORTH): void
{
if(!this.refs[loc])
{
return;
}
switch(loc)
{
case ANCHOR.NORTH:
case ANCHOR.NORTH_EST:
case ANCHOR.NORTH_WEST:
$(this.refs[loc]).prepend(tag);
break;
case ANCHOR.SOUTH:
case ANCHOR.SOUTH_EST:
case ANCHOR.SOUTH_WEST:
$(this.refs[loc]).append(tag);
break;
default: break;
}
this.calibrate();
}
/**
* Re-calibrate tag
*
* @protected
* @memberof NotificationTag
*/
protected calibrate(): void {}
/**
* Update the current tag, do nothing in this tag
*
* @param {*} [d]
* @memberof NotificationTag
*/
reload(d?: any): void {}
/**
* Tag layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof NotificationTag
*/
protected layout(): TagLayoutType[] {
return [
{ el: "div", id: "north", ref: "NORTH" },
{ el: "div", id: "south", ref: "SOUTH" },
{ el: "div", id: "north_west", ref: "NORTH_WEST" },
{ el: "div", id: "south_west", ref: "SOUTH_WEST" },
{ el: "div", id: "north_est", ref: "NORTH_EST" },
{ el: "div", id: "south_est", ref: "SOUTH_EST" }
];
}
}
define("afx-notification", NotificationTag);
define("afx-toast-notification", ToastNotificationTag);
}
}
}

View File

@ -3,7 +3,7 @@ namespace OS {
export namespace tag { export namespace tag {
/** /**
* An overlay tag is a layout tag that alway stay on top of * An overlay tag is a layout tag that alway stay on top of
* the virtual desktop environment. Tile layout elements ([[VBoxTag]], [[HboxTag]]) * the virtual desktop environment. Tile layout elements ({@link OS.GUI.tag.VBoxTag}, {@link OS.GUI.tag.HBoxTag})
* can be used inside this tag to compose elements * can be used inside this tag to compose elements
* *
* @export * @export
@ -127,13 +127,16 @@ namespace OS {
* @memberof OverlayTag * @memberof OverlayTag
*/ */
calibrate(): void { calibrate(): void {
$(this).css("width", this.width).css("height", this.height); $(this)
.css("width", this.width)
.css("height", this.height);
return this.observable.trigger("resize", { return this.observable.trigger("resize", {
id: this.aid, id: this.aid,
data: { data: {
w: this.width, w: this.width,
h: this.height, h: this.height,
}, },
root: true
}); });
} }

View File

@ -3,7 +3,7 @@ namespace OS {
export namespace tag { export namespace tag {
/** /**
* A `resizer` tag is basically used to dynamically resize an element using mouse. * A `resizer` tag is basically used to dynamically resize an element using mouse.
* It is usually put inside a [[TileLayoutTag]] an can be attached to any element. Example: * It is usually put inside a {@link TileLayoutTag} an can be attached to any element. Example:
* *
* The resizer tag in the following example will be attached to the first `afx-vbox`, * The resizer tag in the following example will be attached to the first `afx-vbox`,
* and allows to resize this element using mouse * and allows to resize this element using mouse
@ -41,8 +41,8 @@ namespace OS {
/** /**
* Reference to the parent tag of the current tag. * Reference to the parent tag of the current tag.
* The parent tag should be an instance of a [[TileLayoutTag]] * The parent tag should be an instance of a {@link TileLayoutTag}
* such as [[VBoxTag]] or [[HBoxTag]] * such as {@link VBoxTag} or {@link HBoxTag}
* *
* @private * @private
* @type {*} * @type {*}
@ -92,8 +92,8 @@ namespace OS {
* Setter: * Setter:
* *
* Set resize direction, two possible values: * Set resize direction, two possible values:
* - `hz` - horizontal direction, resize by width * - `row` - horizontal direction, resize by width
* - `ve` - vertical direction, resize by height * - `column` - vertical direction, resize by height
* *
* Getter: * Getter:
* *
@ -104,19 +104,15 @@ namespace OS {
set dir(v: string) { set dir(v: string) {
let att: string; let att: string;
$(this).attr("dir", v); $(this).attr("dir", v);
$(this).off("mousedown", null); $(this).off("pointerdown", null);
if (v === "hz") { if (v === "row") {
$(this).css("cursor", "col-resize");
$(this).addClass("horizontal");
if (this._resizable_el) { if (this._resizable_el) {
att = $(this._resizable_el).attr("min-width"); att = $(this._resizable_el).attr("min-width");
if (att) { if (att) {
this._minsize = parseInt(att); this._minsize = parseInt(att);
} }
} }
} else if (v === "ve") { } else if (v === "column") {
$(this).css("cursor", "row-resize");
$(this).addClass("vertical");
if (this._resizable_el) { if (this._resizable_el) {
att = $(this._resizable_el).attr("min-height"); att = $(this._resizable_el).attr("min-height");
if (att) { if (att) {
@ -187,14 +183,31 @@ namespace OS {
? $(this).prev()[0] ? $(this).prev()[0]
: undefined; : undefined;
} }
if(this.dir)
if (tagname === "AFX-HBOX") { {
this.dir = "hz"; return;
} else if (tagname === "AFX-VBOX") {
this.dir = "ve";
} else {
this.dir = "hz";
} }
if (tagname === "AFX-HBOX") {
this.dir = "row";
} else if (tagname === "AFX-VBOX") {
this.dir = "column";
} else {
this.dir = "row";
}
}
/**
* Setter Disable or enable the resize event
*
* @memberof ResizerTag
*/
set disable(v: boolean)
{
this.attsw(v, "disable");
}
get disable(): boolean
{
return this.hasattr("disable");
} }
/** /**
@ -208,24 +221,28 @@ namespace OS {
if (!this.dir || this.dir == "none") { if (!this.dir || this.dir == "none") {
return; return;
} }
$(this).on("mousedown", (e) => { $(this).on("pointerdown", (e) => {
e.preventDefault(); e.preventDefault();
$(window).on("mousemove", (evt) => { if(this.disable)
{
return;
}
$(window).on("pointermove", (evt) => {
if (!this._resizable_el) { if (!this._resizable_el) {
return; return;
} }
if (this.dir === "hz") { if (this.dir === "row") {
return this.horizontalResize(evt); return this.horizontalResize(evt as JQuery.MouseEventBase);
} else if (this.dir === "ve") { } else if (this.dir === "column") {
return this.verticalResize(evt); return this.verticalResize(evt as JQuery.MouseEventBase);
} }
}); });
return $(window).on("mouseup", function (evt) { return $(window).on("pointerup", function (evt) {
$(window).off("mousemove", null); $(window).off("pointermove", null);
$(window).off("mouseup", null); $(window).off("pointerup", null);
return $(window).off("mouseup", null); return $(window).off("pointerup", null);
}); });
}); });
} }

View File

@ -65,6 +65,7 @@ namespace OS {
this.enable = true; this.enable = true;
this._max = 100; this._max = 100;
this._value = 0; this._value = 0;
this.precision = false;
this._onchange = this._onchanging = () => {}; this._onchange = this._onchanging = () => {};
} }
@ -98,7 +99,17 @@ namespace OS {
set onvaluechanging(f: TagEventCallback<number>) { set onvaluechanging(f: TagEventCallback<number>) {
this._onchanging = f; this._onchanging = f;
} }
/**
* Setter/Getter: set and get precision reading
*
* @memberof SliderTag
*/
set precision(v: boolean) {
this.attsw(v, "precision");
}
get precision(): boolean {
return this.hasattr("precision");
}
/** /**
* Setter: Enable/disable the slider * Setter: Enable/disable the slider
* *
@ -110,15 +121,15 @@ namespace OS {
this.attsw(v, "enable"); this.attsw(v, "enable");
if (v) { if (v) {
$(this) $(this)
.on("mouseover",() => { .on("pointerover",() => {
return $(this.refs.point).show(); return $(this.refs.point).show();
}) })
.on("mouseout",() => { .on("pointerout",() => {
return $(this.refs.point).hide(); return $(this.refs.point).hide();
}); });
} else { } else {
$(this.refs.point).hide(); $(this.refs.point).hide();
$(this).off("mouseover").off("mouseout"); $(this).off("pointerover").off("pointerout");
} }
} }
get enable(): boolean { get enable(): boolean {
@ -188,7 +199,16 @@ namespace OS {
*/ */
calibrate(): void { calibrate(): void {
if (this.value > this.max) { if (this.value > this.max) {
this.value = this.max; this._value = this.max;
}
if(! this.precision)
{
this._value = Math.round(this.value);
$(this.refs.point).text(this.value);
}
else
{
$(this.refs.point).text((Math.round(this.value * 100) / 100).toFixed(2));
} }
$(this.refs.container).css("width", $(this).width() + "px"); $(this.refs.container).css("width", $(this).width() + "px");
const w = const w =
@ -197,17 +217,17 @@ namespace OS {
$(this.refs.prg) $(this.refs.prg)
.css("width", w + "px") .css("width", w + "px")
.css("height", $(this.refs.container).height() + "px"); .css("height", $(this.refs.container).height() + "px");
if (this.enable) { //if (this.enable) {
const ow = w - $(this.refs.point).width() / 2; const ow = w - ($(this.refs.point).outerWidth() / 2.0);
const top = Math.floor( const top = Math.floor(
($(this.refs.prg).height() - ($(this.refs.prg).height() +
$(this.refs.point).height()) / $(this.refs.point).height()) /
2 2 + 3
); );
$(this.refs.point) $(this.refs.point)
.css("left", ow + "px") .css("left", ow + "px")
.css("top", top + "px"); .css("bottom", top + "px");
} //}
} }
/** /**
@ -220,10 +240,10 @@ namespace OS {
$(this.refs.point) $(this.refs.point)
.css("user-select", "none") .css("user-select", "none")
.css("cursor", "default"); .css("cursor", "default");
$(this.refs.point).on("mousedown", (e) => { $(this).on("pointerdown", (e) => {
e.preventDefault(); e.preventDefault();
const offset = $(this.refs.container).offset(); const offset = $(this.refs.container).offset();
$(window).on("mousemove", (e) => { $(window).on("pointermove", (e) => {
let left = e.clientX - offset.left; let left = e.clientX - offset.left;
left = left < 0 ? 0 : left; left = left < 0 ? 0 : left;
const maxw = $(this.refs.container).width(); const maxw = $(this.refs.container).width();
@ -236,13 +256,13 @@ namespace OS {
}); });
}); });
$(window).on("mouseup", (e) => { $(window).on("pointerup", (e) => {
this._onchange({ this._onchange({
id: this.aid, id: this.aid,
data: this.value, data: this.value,
}); });
$(window).off("mousemove", null); $(window).off("pointermove", null);
return $(window).off("mouseup", null); return $(window).off("pointerup", null);
}); });
}); });
} }

View File

@ -0,0 +1,573 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* menu event data type definition
*/
export type StackMenuEventData = TagEventDataType<ListViewItemTag>;
/**
* The layout of a simple stack menu item
*
* @export
* @class SimpleStackMenuItemTag
* @extends {ListViewItemTag}
*/
export class SimpleStackMenuItemTag extends ListViewItemTag {
/**
*Creates an instance of SimpleStackMenuItemTag.
* @memberof SimpleStackMenuItemTag
*/
constructor() {
super();
}
/**
* Reset some property to default
*
* @protected
* @memberof SimpleStackMenuItemTag
*/
protected init(): void {
this.closable = false;
this.data = {};
this.switch = false;
this.radio = false;
this.checked = false;
}
/**
* Mount the current tag
*
* @protected
* @memberof SimpleStackMenuItemTag
*/
protected mount(): void {
super.mount();
(this.refs.switch as SwitchTag).enable = false;
}
/**
* Setter: Turn on/off the checker feature of the menu entry
*
* Getter: Check whether the checker feature is enabled on this menu entry
*
* @memberof SimpleStackMenuItemTag
*/
set switch(v: boolean) {
this.attsw(v, "switch");
if (this.radio || v) {
$(this.refs.switch).show();
} else {
$(this.refs.switch).hide();
}
}
get switch(): boolean {
return this.hasattr("switch");
}
/**
* Setter: select/unselect the current item
*
* Getter: Check whether the current item is selected
*
* @memberof SimpleStackMenuItemTag
*/
set selected(v: boolean) {
if(v)
{
if (this.switch) {
this.checked = !this.checked;
} else if (this.radio) {
// reset radio
const p = this.parentElement;
if (p) {
for(let item of Array.from(p.children))
{
const el = item as SimpleStackMenuItemTag;
if(el.radio)
{
el.checked = false;
}
}
}
this.checked = !this.checked;
}
}
super.selected = v;
}
get selected(): boolean {
return this.hasattr("selected");
}
/**
* Setter: Turn on/off the radio feature of the menu entry
*
* Getter: Check whether the radio feature is enabled
*
* @memberof SimpleStackMenuItemTag
*/
set radio(v: boolean) {
this.attsw(v, "radio");
if (this.switch || v) {
$(this.refs.switch).show();
} else {
$(this.refs.switch).hide();
}
}
get radio(): boolean {
return this.hasattr("radio");
}
/**
* Setter:
*
* Toggle the switch on the menu entry, this setter
* only works when the `checker` or `radio` feature is
* enabled
*
* Getter:
*
* Check whether the switch is turned on
*
* @memberof SimpleStackMenuItemTag
*/
set checked(v: boolean) {
this.attsw(v, "checked");
if (this.data) this.data.checked = v;
if (!this.radio && !this.switch) {
return;
}
(this.refs.switch as SwitchTag).swon = v;
}
get checked(): boolean {
return this.hasattr("checked");
}
/**
* Set the keyboard shortcut text
*
* @memberof SimpleStackMenuItemTag
*/
set shortcut(v: string) {
$(this.refs.shortcut).hide();
if (!v) {
return;
}
$(this.refs.shortcut).show();
$(this.refs.shortcut).text(v);
}
/**
* Do nothing
*
* @protected
* @memberof SimpleStackMenuItemTag
*/
protected calibrate(): void {
}
/**
* Refresh the inner label when the item data
* is changed
*
* @protected
* @returns {void}
* @memberof SimpleStackMenuItemTag
*/
protected ondatachange(): void {
const v = this.data;
if (!v) {
return;
}
if(v.nodes && v.nodes.length > 0)
{
$(this.refs.submenu).show();
}
else
{
$(this.refs.submenu).hide();
}
const label = this.refs.label as LabelTag;
this.set(v);
label.set(v);
if (v.selected) {
this.selected = v.selected;
}
}
/**
* Re-render the list item
*
* @protected
* @memberof SimpleStackMenuItemTag
*/
protected reload(): void {
this.data = this.data;
}
/**
* List item custom layout definition
*
* @protected
* @returns {TagLayoutType}
* @memberof SimpleStackMenuItemTag
*/
protected itemlayout(): TagLayoutType {
return {
el:"div",
children: [
{ el: "afx-switch", ref: "switch" },
{ el: "afx-label", ref: "label" },
{ el: "span", class: "shortcut", ref: "shortcut" },
{ el: "span", class: "afx-submenu", ref: "submenu" },
]
};
}
}
/**
* A stack menu is a multilevel menu that
* uses a single list view to navigate all menu levels
* instead of using a traditional cascade style menu
*
* @export
* @class StackMenuTag
* @extends {AFXTag}
*/
export class StackMenuTag extends AFXTag {
/**
* Data stack, the list always displays the
* element on the top of the stack
*
* @type {GenericObject<any>[][]}
* @memberof StackMenuTag
*/
private stack: GenericObject<any>[][];
/**
* Update the current tag, do nothing
*
* @protected
* @param {*} [d]
* @memberof StackMenuTag
*/
protected reload(d?: any): void {}
/**
* Placeholder of tab select event handle
*
* @private
* @type {TagEventCallback<TabEventData>}
* @memberof StackMenuTag
*/
private _onmenuselect: TagEventCallback<StackMenuEventData>;
/**
* Stack menu constructor
*
* @memberof StackMenuTag
*/
constructor() {
super();
}
/**
* Reset to default some property value
*
* @protected
* @memberof StackMenuTag
*/
protected init(): void {
this.stack = [];
this._onmenuselect = (_) => {};
this.context = false;
}
/**
* Recalcutate the menu coordinate in case of
* context menu
*
* @protected
* @memberof StackMenuTag
*/
protected calibrate(): void {
if(this.context)
{
const offset = $(this).position();
let left = offset.left;
let top = offset.top;
const ph = $(this).parent().height();
const pw = $(this).parent().width();
const dy = top + $(this).height() - ph;
const dx = left + $(this).width() - pw;
if(dx < 0 && dy < 0)
{
return;
}
top -= dy > 0?dy:0;
left -= dx > 0?dx:0;
$(this)
.css("top", top + "px")
.css("left", left + "px");
}
}
/**
* Reset the menu to its initial state
*
* @memberof StackMenuTag
*/
reset(): void {
const btn = this.refs.title as ButtonTag;
const list = this.refs.list as ListViewTag;
list.selected = -1;
btn.data = undefined;
if(this.stack.length > 0)
{
let arr = this.stack[0];
this.stack = [];
list.data = arr[1] as any;
$(btn).hide();
}
}
/**
* Mount the menu and bind some basic events
*
* @protected
* @memberof StackMenuTag
*/
protected mount(): void {
const btn = this.refs.title as ButtonTag;
const list = this.refs.list as ListViewTag;
list.itemtag = "afx-stack-menu-item";
btn.onbtclick = (_) => {
let arr = this.stack.pop();
if(this.stack.length == 0)
{
$(btn).hide();
btn.data = undefined;
}
else
{
btn.data = arr[0];
btn.iconclass = "bi bi-backspace";
}
list.data = arr[1] as any;
};
list.onlistselect = (e) => {
let data = e.data.item.data;
e.id = this.aid;
if(btn.data && btn.data.onchildselect)
{
btn.data.onchildselect(e);
}
if(data.onmenuselect)
{
data.onmenuselect(e);
}
this._onmenuselect(e);
this.observable.trigger("menuselect", e);
if(data.nodes && data.nodes.length > 0)
{
this.stack.push([btn.data, list.data]);
btn.data = data;
btn.iconclass = "bi bi-backspace";
$(btn).show();
list.selected = -1;
list.data = data.nodes;
if(this.context)
{
this.calibrate();
}
} else if (this.context) {
$(this).hide();
}
};
}
/**
* Setter: set current selected item index
*
* Getter: Get current selected item index
*
* @memberof StackMenuTag
*/
set selected(i: number | number[])
{
const list = this.refs.list as ListViewTag;
list.selected = i;
}
get selected(): number | number[]
{
const list = this.refs.list as ListViewTag;
return list.selected;
}
/**
* Setter: Set whether the current menu is a context menu
*
* Getter: Check whether the current menu is a context menu
*
* @memberof StackMenuTag
*/
set context(v: boolean) {
this.attsw(v, "context");
$(this).removeClass("context");
if (!v) {
return;
}
$(this).addClass("context");
$(this).hide();
}
get context(): boolean {
return this.hasattr("context");
}
/**
* Get the latest selected item
*
* @readonly
* @type {ListViewItemTag}
* @memberof StackMenuTag
*/
get selectedItem(): ListViewItemTag {
const list = this.refs.list as ListViewTag;
return list.selectedItem;
}
/**
* Get all the selected items
*
* @readonly
* @type {ListViewItemTag[]}
* @memberof StackMenuTag
*/
get selectedItems(): ListViewItemTag[] {
const list = this.refs.list as ListViewTag;
return list.selectedItems;
}
/**
* The following setter/getter are keep for backward compatible
* with the MenuTag interface
*
* Setter: Set the menu data
*
* Getter: Get the menu data
*
* @deprecated
* @memberof StackMenuTag
*/
set items(v: GenericObject<any>[]) {
this.nodes = v;
}
get items(): GenericObject<any>[] {
return this.nodes;
}
/**
* Setter: Set the menu data
*
* Getter: Get the menu data
*
* @memberof StackMenuTag
*/
set nodes(v: GenericObject<any>[]) {
this.stack = [];
this.reset();
(this.refs.list as ListViewTag).data = v;
$(this.refs.title).hide();
}
get nodes(): GenericObject<any>[] {
return (this.refs.list as ListViewTag).data;
}
/**
* Set the `menu entry select` event handle
*
* @memberof StackMenuTag
*/
set onmenuselect(v: TagEventCallback<StackMenuEventData>) {
this._onmenuselect = v;
}
/**
* Hide the current menu. This function is called
* only if the current menu is context menu
*
* @memberof StackMenuTag
*/
hide(): void
{
if (!this.context) {
return;
}
$(this)
.css("bottom", "unset")
.css("top", "unset")
.css("left", "unset")
.css("right", "unset")
.hide();
}
/**
* Show the current menu. This function is called
* only if the current menu is a context menu
*
* @param {JQuery.MouseEventBase} e JQuery mouse event
* @returns {void}
* @memberof StackMenuTag
*/
show(e?: JQuery.MouseEventBase): void {
const list = this.refs.list as ListViewTag;
const btn = this.refs.title as ButtonTag;
if (!this.context) {
return;
}
if(e)
{
const offset = $(this).parent().offset();
let top = e.clientY - offset.top - 15;
let left = e.clientX - offset.left - 5;
$(this)
.css("top", top + "px")
.css("left", left + "px")
.css("bottom", "unset")
.css("right", "unset");
}
const dropoff = (e) => {
if($(e.target).closest(`[list-id="${list.aid}"]`).length > 0)
{
return;
}
if($(e.target).closest(btn).length > 0)
{
return;
}
this.hide();
$(document).off("click", dropoff);
};
$(document).on("click", dropoff);
$(this).show();
this.calibrate();
}
/**
* Tag layout definition
*
* @protected
* @returns {TagLayoutType[]}
* @memberof StackMenuTag
*/
protected layout(): TagLayoutType[] {
return [
{
el: "afx-button",
ref: "title"
},
{
el: "afx-list-view",
ref: "list",
}
];
}
}
define("afx-stack-menu", StackMenuTag);
define("afx-stack-menu-item", SimpleStackMenuItemTag);
}
}
}

View File

@ -0,0 +1,76 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* A stack pannel allows to navigate back and forth between pannels
* (container widget). Each container widget in the stack should be
* composed inside a {@link HBoxTag}
*
*
* @export
* @class StackPanelTag
* @extends {AFXTag}
*/
export class StackPanelTag extends TabContainerTag {
private _current_pannel_index: number;
/**
* Mount the tag and bind basic events
*
* @protected
* @memberof StackPanelTag
*/
protected mount(): void {
this._current_pannel_index = -1;
super.mount();
this.observable.one("mounted", (id) => {
this.tabbarheight = 0;
$(this.refs.bar).hide();
this.navigateNext();
});
}
/**
* Set the tab select event handle
*
* @memberof StackPanelTag
*/
set ontabselect(f: TagEventCallback<TabContainerTabType>) {
}
/**
* Navigate to the next panel
*
* @memberof StackPanelTag
*/
navigateNext(): void
{
if(this._current_pannel_index >= this.tabs.length)
return;
this._current_pannel_index++;
this.navigate();
}
/**
* Navigate back to the previous panel
*
* @memberof StackPanelTag
*/
navigateBack(): void
{
if(this._current_pannel_index <= 0)
return;
this._current_pannel_index--;
this.navigate()
}
/**
* Navigate to a custom panel
*
* @memberof StackPanelTag
*/
private navigate()
{
this.selectedIndex = this._current_pannel_index;
}
}
define("afx-stack-panel", StackPanelTag);
}
}
}

View File

@ -34,10 +34,27 @@ namespace OS {
* Store pending loading task * Store pending loading task
* *
* @private * @private
* @type {Promise<any>[]}
* @memberof SystemPanelTag
*/
private _pending_task: Promise<any>[];
/**
* Flag indicate where the selected application shall be openned
*
* @private
* @type {boolean}
* @memberof SystemPanelTag
*/
private _prevent_open: boolean;
/**
* Store the current attached service
*
* @private
* @type {number[]} * @type {number[]}
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
private _pending_task: number[]; private _services: application.BaseService[];
/** /**
* Loading animation check timeout * Loading animation check timeout
@ -70,13 +87,16 @@ namespace OS {
constructor() { constructor() {
super(); super();
this._osmenu = { this._osmenu = {
text: __("Start"), text: "",
iconclass: "fa fa-circle", //iconclass: "fa fa-circle",
icon: "os://resources/themes/system/icons/antos-32x32.png"
}; };
this._view = false; this._view = false;
this._pending_task = []; this._pending_task = [];
this._loading_toh = undefined; this._loading_toh = undefined;
this.app_list= []; this.app_list= [];
this._services = [];
this._prevent_open = false;
} }
/** /**
@ -105,8 +125,7 @@ namespace OS {
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
attachservice(s: application.BaseService) { attachservice(s: application.BaseService) {
(this.refs.systray as MenuTag).unshift(s); this._services.unshift(s);
return s.attach(this.refs.systray);
} }
/** /**
@ -118,14 +137,16 @@ namespace OS {
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
private open(): void { private open(): void {
if(this._prevent_open)
{
this._prevent_open = false;
return;
}
const applist = this.refs.applist as ListViewTag; const applist = this.refs.applist as ListViewTag;
const el = applist.selectedItem; const el = applist.selectedItem;
if (!el) { if (!el) {
return; return;
} }
if (!el.data || el.data.dataid === "header") {
return;
}
this.toggle(false); this.toggle(false);
// launch the app or open the file // launch the app or open the file
Ant.OS.GUI.openWith(el.data as AppArgumentsType); Ant.OS.GUI.openWith(el.data as AppArgumentsType);
@ -142,21 +163,23 @@ namespace OS {
*/ */
private search(e: JQuery.KeyboardEventBase): void { private search(e: JQuery.KeyboardEventBase): void {
const applist = this.refs.applist as ListViewTag; const applist = this.refs.applist as ListViewTag;
const catlist = this.refs.catlist as ListViewTag; const catlist = this.refs.catlist as TabBarTag;
switch (e.which) { switch (e.which) {
case 27: case 27:
// escape key // escape key
return this.toggle(false); return this.toggle(false);
case 37: case 37:
return e.preventDefault(); this._prevent_open = true;
case 38:
applist.selectPrev(); applist.selectPrev();
return e.preventDefault(); return e.preventDefault();
case 38:
return e.preventDefault();
case 39: case 39:
this._prevent_open = true;
applist.selectNext();
return e.preventDefault(); return e.preventDefault();
case 40: case 40:
applist.selectNext();
return e.preventDefault(); return e.preventDefault();
case 13: case 13:
e.preventDefault(); e.preventDefault();
@ -187,9 +210,8 @@ namespace OS {
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
detachservice(s: application.BaseService): void { detachservice(s: application.BaseService): void {
(this.refs.systray as MenuTag).delete( const index = this._services.indexOf(s);
s.domel as MenuEntryTag this._services.splice(index, 1);
);
} }
/** /**
@ -206,23 +228,17 @@ namespace OS {
ref: "panel", ref: "panel",
children: [ children: [
{ {
el: "afx-menu", el: "afx-button",
ref: "osmenu", ref: "osmenu",
class: "afx-panel-os-menu", class: "afx-panel-os-menu",
}, },
{ {
el: "afx-menu", el: "afx-apps-dock",
ref: "pinned", ref: "sysdock",
class: "afx-panel-os-pinned-app", id: "sysdock"
}, },
{ {
el: "afx-menu", el: "afx-button",
id: "appmenu",
ref: "appmenu",
class: "afx-panel-os-app",
},
{
el: "afx-menu",
id: "systray", id: "systray",
ref: "systray", ref: "systray",
class: "afx-panel-os-stray", class: "afx-panel-os-stray",
@ -247,17 +263,13 @@ namespace OS {
], ],
}, },
{ {
el: "afx-hbox", el: "afx-vbox",
children: [ children: [
{ {
el: "afx-list-view", el: "afx-tab-bar",
id: "catlist", id: "catlist",
ref: "catlist", ref: "catlist",
width:"40%" height:45
},
{
el: "afx-resizer",
width: 3,
}, },
{ {
el: "afx-list-view", el: "afx-list-view",
@ -269,7 +281,7 @@ namespace OS {
{ {
el: "afx-hbox", el: "afx-hbox",
id: "btlist", id: "btlist",
height: 30, height: 40,
children: [ children: [
{ {
el: "afx-button", el: "afx-button",
@ -293,6 +305,7 @@ namespace OS {
}, },
], ],
}, },
]; ];
} }
@ -304,7 +317,7 @@ namespace OS {
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
private refreshAppList(): void { private refreshAppList(): void {
let catlist_el = (this.refs.catlist as tag.ListViewTag); let catlist_el = (this.refs.catlist as tag.TabBarTag);
let k: string, v: API.PackageMetaType; let k: string, v: API.PackageMetaType;
const catlist = new Set(); const catlist = new Set();
this.app_list = []; this.app_list = [];
@ -349,7 +362,7 @@ namespace OS {
iconclass: "bi bi-gear-wide" iconclass: "bi bi-gear-wide"
}); });
}); });
catlist_el.data = cat_list_data; catlist_el.items = cat_list_data;
catlist_el.selected = 0; catlist_el.selected = 0;
} }
@ -369,7 +382,11 @@ namespace OS {
this.calibrate(); this.calibrate();
$(document).on("click", this._cb); $(document).on("click", this._cb);
(this.refs.search as HTMLInputElement).value = ""; (this.refs.search as HTMLInputElement).value = "";
$(this.refs.search).trigger("focus"); if(!OS.mobile)
{
$(this.refs.search).focus();
}
} else { } else {
$(this.refs.overlay).hide(); $(this.refs.overlay).hide();
$(document).off("click", this._cb); $(document).off("click", this._cb);
@ -386,34 +403,6 @@ namespace OS {
}px`; }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, * Check if the loading tasks ended,
* if it the case, stop the animation * if it the case, stop the animation
@ -431,6 +420,18 @@ namespace OS {
clearTimeout(this._loading_toh); clearTimeout(this._loading_toh);
this._loading_toh = undefined; this._loading_toh = undefined;
} }
private show_systray(): void
{
const ctxmenu = $("#contextmenu")[0] as tag.StackMenuTag;
ctxmenu.hide();
ctxmenu.nodes = this._services;
$(ctxmenu)
.css("right", 0)
.css("bottom", $(this).height());
ctxmenu.show();
}
/** /**
* Mount the tag bind some basic event * Mount the tag bind some basic event
* *
@ -438,19 +439,16 @@ namespace OS {
* @memberof SystemPanelTag * @memberof SystemPanelTag
*/ */
protected mount(): void { protected mount(): void {
(this.refs.osmenu as MenuTag).items = [this._osmenu]; const systray = this.refs.systray as GUI.tag.ButtonTag;
(this.refs.osmenu as ButtonTag).set(this._osmenu);
this._cb = (e) => { this._cb = (e) => {
if ( if (
!$(e.target).closest($(this.refs.overlay)).length && !$(e.target).closest($(this.refs.overlay)).length &&
!$(e.target).closest(this.refs.osmenu).length !$(e.target).closest(this.refs.osmenu).length
) { ) {
return this.toggle(false); 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({ (this.refs.btscreen as ButtonTag).set({
iconclass: "fa fa-tv", iconclass: "fa fa-tv",
onbtclick: (e) => { onbtclick: (e) => {
@ -475,17 +473,24 @@ namespace OS {
return Ant.OS.exit(); return Ant.OS.exit();
}, },
}); });
(this.refs.osmenu as MenuTag).onmenuselect = (e) => { (this.refs.osmenu as ButtonTag).onbtclick = (e) => {
return this.toggle(true); if($(this.refs.overlay).is(":hidden"))
{
this.toggle(true);
}
else
{
this.toggle(false);
}
}; };
$(this.refs.search).on("keyup", (e) => { $(this.refs.search).on("keyup", (e) => {
return this.search(e); return this.search(e);
}); });
$(this.refs.applist).on("click", (e) => { (this.refs.applist as ListViewTag).onlistselect = (_) => {
return this.open(); return this.open();
}); };
Ant.OS.GUI.bindKey("CTRL- ", (e) => { Ant.OS.GUI.bindKey("CTRL- ", (e) => {
if (this._view === false) { if (this._view === false) {
return this.toggle(true); return this.toggle(true);
@ -493,8 +498,8 @@ namespace OS {
return this.toggle(false); return this.toggle(false);
} }
}); });
const catlist = (this.refs.catlist as tag.ListViewTag); const catlist = (this.refs.catlist as tag.TabBarTag);
catlist.onlistselect = (e) => { catlist.ontabselect = (e) => {
const applist = (this.refs.applist as ListViewTag); const applist = (this.refs.applist as ListViewTag);
if(catlist.selected === 0) if(catlist.selected === 0)
{ {
@ -510,58 +515,52 @@ namespace OS {
applist.selected = -1; applist.selected = -1;
}; };
$(this.refs.overlay) $(this.refs.overlay)
.css("left", 0)
.css("top", `${$(this.refs.panel).height()}px`)
.css("bottom", "0")
.hide(); .hide();
(this.refs.pinned as GUI.tag.MenuTag).onmenuselect = (e) => { this.refs.osmenu.contextmenuHandle = (e, m) => { };
const app = e.data.item.data.app; systray.contextmenuHandle = (e, m) => { };
if(!app) this.refs.panel.contextmenuHandle = (e, m) => { };
return; announcer.on("ANTOS-TASK-PENDING", (o: API.AnnouncementDataType<Promise<any>>) => {
GUI.launch(app, []); if(this._pending_task.length == 0)
};
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"); $(this.refs.panel).addClass("loading");
systray.iconclass = "fa-spin fa fa-cog";
}
this._pending_task.push(o.u_data);
$(GUI.workspace).css("cursor", "wait"); $(GUI.workspace).css("cursor", "wait");
}); });
systray.iconclass = "bi bi-sliders";
announcer.observable.on("loaded", (o: API.AnnouncementDataType<number>) => { systray.onbtclick = (e) => {
const i = this._pending_task.indexOf(o.id); e.data.stopPropagation();
this.show_systray();
};
const remove_task = (o: Promise<any>) => {
const i = this._pending_task.indexOf(o);
if (i >= 0) { if (i >= 0) {
this._pending_task.splice(i, 1); this._pending_task.splice(i, 1);
} }
if (this._pending_task.length === 0) { if (this._pending_task.length === 0) {
// set time out // set time out
systray.iconclass = "bi bi-sliders";
if(!this._loading_toh) if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000); this._loading_toh = setTimeout(() => this.animation_check(),1000);
} }
};
announcer.on("ANTOS-TASK-FULFILLED", (o: API.AnnouncementDataType<Promise<any>>) => {
remove_task(o.u_data);
});
announcer.on("ANTOS-TASK-REJECTED", (o: API.AnnouncementDataType<Promise<any>>) => {
remove_task(o.u_data);
});
announcer.on("desktopresize", (e) => {
this.calibrate();
});
announcer.on("appselect", (e) => {
if(this._view)
this.toggle(false);
}); });
this.RefreshPinnedApp();
Ant.OS.announcer.trigger("syspanelloaded", undefined); Ant.OS.announcer.trigger("syspanelloaded", undefined);
} }
} }

View File

@ -39,6 +39,13 @@ namespace OS {
*/ */
private _ontabselect: TagEventCallback<TabEventData>; private _ontabselect: TagEventCallback<TabEventData>;
/**
* Cache of touch event
*
* @private
* @meberof TabBarTag
*/
private _previous_touch: {x: number, y: number};
/** /**
*Creates an instance of TabBarTag. *Creates an instance of TabBarTag.
* @memberof TabBarTag * @memberof TabBarTag
@ -57,6 +64,8 @@ namespace OS {
*/ */
protected init(): void { protected init(): void {
this.selected = -1; this.selected = -1;
this.dir = "row";
this._previous_touch = {x: 0, y:0};
} }
/** /**
@ -82,6 +91,30 @@ namespace OS {
return this.hasattr("closable"); return this.hasattr("closable");
} }
/**
* Setter:
*
* Set the tab bar direction:
* - `row`: horizontal direction
* - `column`: vertical direction
*
* Getter:
*
* Get the tab bar direction
*
* @memberof TabBarTag
*/
set dir(v: string) {
$(this).attr("dir", v);
if (!v) {
return;
}
(this.refs.list as ListViewTag).dir = v;
}
get dir(): string {
return $(this).attr("dir") as any;
}
/** /**
* Add a tab in the end of the tab bar * Add a tab in the end of the tab bar
* *
@ -144,6 +177,16 @@ namespace OS {
get selected(): number | number[] { get selected(): number | number[] {
return (this.refs.list as ListViewTag).selected; return (this.refs.list as ListViewTag).selected;
} }
/**
* Get the latest selected item
*
* @readonly
* @type {ListViewItemTag}
* @memberof TabBarTag
*/
get selectedItem(): ListViewItemTag {
return (this.refs.list as ListViewTag).selectedItem;
}
/** /**
* Set the tab close event handle * Set the tab close event handle
@ -179,6 +222,75 @@ namespace OS {
this._ontabselect(e); this._ontabselect(e);
return this.observable.trigger("tabselect", e); return this.observable.trigger("tabselect", e);
}; };
const list_container = $(".list-container", this.refs.list);
list_container.each((i,el) => {
el.addEventListener("touchstart", e => {
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, {passive: true});
el.addEventListener("touchmove", e => {
const offset = {x:0, y:0};
offset.x = this._previous_touch.x - e.touches[0].pageX ;
offset.y = this._previous_touch.y - e.touches[0].pageY;
if(this.dir == "row")
{
el.scrollLeft += offset.x;
}
else
{
el.scrollTop += offset.y;
}
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, {passive: true});
el.addEventListener("wheel", (evt)=>{
if(this.dir == "row")
{
el.scrollLeft += (evt as WheelEvent).deltaY;
}
else
{
el.scrollTop += (evt as WheelEvent).deltaY;
}
}, {passive: true});
});
}
/**
* Scroll the tabbar to end
*
* @memberof TabBarTag
*/
scroll_to_end()
{
const list_container = $(".list-container", this.refs.list)[0];
if(this.dir == "column")
{
list_container.scrollTo({ top: list_container.scrollHeight, behavior: 'smooth' });
}
else
{
list_container.scrollTo({ left: list_container.scrollWidth, behavior: 'smooth' });
}
}
/**
* Scroll the tabbar to begin
*
* @memberof TabBarTag
*/
scroll_to_start()
{
const list_container = $(".list-container", this.refs.list)[0];
if(this.dir == "column")
{
list_container.scrollTo({ top: 0, behavior: 'smooth' });
}
else
{
list_container.scrollTo({ left: 0, behavior: 'smooth' });
}
} }
/** /**

View File

@ -19,9 +19,9 @@ namespace OS {
} }
export namespace tag { export namespace tag {
/** /**
* A tab container allows to attach each tab on a [[TabBarTag]] * A tab container allows to attach each tab on a {@link TabBarTag}
* with a container widget. The attached container widget should be * with a container widget. The attached container widget should be
* composed inside a [[HBoxTag]] * composed inside a {@link HBoxTag}
* *
* The tab bar in a tab container can be configured to display tabs * The tab bar in a tab container can be configured to display tabs
* in horizontal (row) or vertical (column) order. Default to vertical order * in horizontal (row) or vertical (column) order. Default to vertical order
@ -45,11 +45,11 @@ namespace OS {
/** /**
* Placeholder of the tab select event handle * Placeholder of the tab select event handle
* *
* @private * @protected
* @type {TagEventCallback<TabContainerTabType>} * @type {TagEventCallback<TabContainerTabType>}
* @memberof TabContainerTag * @memberof TabContainerTag
*/ */
private _ontabselect: TagEventCallback<TabContainerTabType>; protected _ontabselect: TagEventCallback<TabContainerTabType>;
/** /**
*Creates an instance of TabContainerTag. *Creates an instance of TabContainerTag.
@ -102,13 +102,17 @@ namespace OS {
} }
/** /**
* Select a tab by its index * Setter: Select a tab by its index
* Getter: Get the current selected index
* *
* @memberof TabContainerTag * @memberof TabContainerTag
*/ */
set selectedIndex(i: number) { set selectedIndex(i: number) {
(this.refs.bar as TabBarTag).selected = i; (this.refs.bar as TabBarTag).selected = i;
} }
get selectedIndex(): number {
return (this.refs.bar as TabBarTag).selected as number;
}
/** /**
* Setter: * Setter:
@ -129,6 +133,14 @@ namespace OS {
return; return;
} }
(this.refs.wrapper as TileLayoutTag).dir = v; (this.refs.wrapper as TileLayoutTag).dir = v;
if(v == "row")
{
(this.refs.bar as TabBarTag).dir = "column";
}
else
{
(this.refs.bar as TabBarTag).dir = "row";
}
} }
get dir(): "row" | "column" { get dir(): "row" | "column" {
return $(this).attr("dir") as any; return $(this).attr("dir") as any;
@ -175,7 +187,7 @@ namespace OS {
return; return;
} }
$(this.refs.bar).attr("data-width", `${v}`); $(this.refs.bar).attr("data-width", `${v}`);
(this.refs.wrapper as TileLayoutTag).calibrate(); this.observable.trigger("resize", undefined);
} }
/** /**
@ -185,8 +197,11 @@ namespace OS {
* @memberof TabContainerTag * @memberof TabContainerTag
*/ */
set tabbarheight(v: number) { set tabbarheight(v: number) {
if (!v) {
return;
}
$(this.refs.bar).attr("data-height", `${v}`); $(this.refs.bar).attr("data-height", `${v}`);
(this.refs.wrapper as TileLayoutTag).calibrate(); this.observable.trigger("resize", undefined);
} }
/** /**

View File

@ -1,6 +1,9 @@
namespace OS { namespace OS {
export namespace GUI { export namespace GUI {
export namespace tag { export namespace tag {
type TileItemDirection = "row" | "column" | "row-reverse" | "column-reverse";
/** /**
* A tile layout organize it child elements * A tile layout organize it child elements
* in a fixed horizontal or vertical direction. * in a fixed horizontal or vertical direction.
@ -23,13 +26,17 @@ namespace OS {
super(); super();
} }
private _padding: number;
/** /**
* Do nothing * Do nothing
* *
* @protected * @protected
* @memberof TileLayoutTag * @memberof TileLayoutTag
*/ */
protected init(): void {} protected init(): void {
this.padding = 0;
}
/** /**
* Do nothing * Do nothing
* *
@ -73,17 +80,66 @@ namespace OS {
* *
* @memberof TileLayoutTag * @memberof TileLayoutTag
*/ */
set dir(v: "row" | "column") { set dir(v: TileItemDirection) {
if (!v) { if (!v) {
return; return;
} }
$(this).attr("dir", v); $(this).attr("dir", v);
$(this.refs.yield).css("flex-direction", v); this.reversed = this.reversed;
this.calibrate(); this.calibrate();
} }
get dir(): "row" | "column" { get dir(): TileItemDirection {
return $(this).attr("dir") as any; return $(this).attr("dir") as any;
} }
/**
* Setter:
*
* SET content padding
*
* Getter:
*
* Get content padding
*
* @memberof TileLayoutTag
*/
set padding(v: number)
{
$(this).attr("padding", v);
this._padding = v;
}
get padding(): number
{
return this._padding;
}
/**
* setter: Reverse order of the content in the tile
*
* getter: return if the tile's content is in reversed order
*
* @meberof TileLayoutTags
*/
set reversed(v: boolean)
{
this.attsw(v, "reversed");
if(!this.dir)
{
return;
}
let newdir = "row";
if(this.dir.startsWith("column"))
{
newdir = "column"
}
if(v)
{
newdir += "-reverse";
}
$(this.refs.yield).css("flex-direction", newdir);
}
get reversed(): boolean
{
return this.hasattr("reversed");
}
/** /**
* Mount the element * Mount the element
@ -95,9 +151,7 @@ namespace OS {
protected mount(): void { protected mount(): void {
$(this).css("display", "block"); $(this).css("display", "block");
$(this.refs.yield) $(this.refs.yield)
.css("display", "flex") .css("display", "flex");
.css("width", "100%")
.css("height", "100%");
this.observable.on("resize", (e) => this.calibrate()); this.observable.on("resize", (e) => this.calibrate());
return this.calibrate(); return this.calibrate();
} }
@ -109,6 +163,10 @@ namespace OS {
* @memberof TileLayoutTag * @memberof TileLayoutTag
*/ */
calibrate(): void { calibrate(): void {
$(this.refs.yield)
.css("padding", this.padding)
.css("width", `${$(this).width() - this.padding*2}px`)
.css("height", `${$(this).height() - this.padding*2}px`);
if (this.dir === "row") { if (this.dir === "row") {
return this.hcalibrate(); return this.hcalibrate();
} }
@ -128,16 +186,24 @@ namespace OS {
private hcalibrate(): void { private hcalibrate(): void {
const auto_width = []; const auto_width = [];
let ocwidth = 0; let ocwidth = 0;
const avaiheight = $(this).height(); const avaiWidth = $(this).width() - this.padding * 2;
const avaiWidth = $(this).width(); const avaiheight = $(this).innerHeight() - this.padding * 2;
//$(this.refs.yield).css("height", `${avaiheight}px`);
$(this.refs.yield) $(this.refs.yield)
.children() .children()
.each(function (e) { .each(function (e) {
$(this).css("height", "100%"); $(this).css("height", "100%");
let attv = $(this).attr("data-width"); let attv = $(this).attr("data-width");
let dw = 0; let dw = 0;
if (attv && attv !== "grow") { if (!attv || attv == "grow") {
$(this).css("flex-grow", "1");
auto_width.push(this);
return;
}
if(attv == "content")
{
ocwidth += $(this).width();
return;
}
if (attv[attv.length - 1] === "%") { if (attv[attv.length - 1] === "%") {
dw = dw =
(parseInt(attv.slice(0, -1)) * (parseInt(attv.slice(0, -1)) *
@ -148,10 +214,6 @@ namespace OS {
} }
$(this).css("width", `${dw}px`); $(this).css("width", `${dw}px`);
ocwidth += dw; ocwidth += dw;
} else {
$(this).css("flex-grow", "1");
auto_width.push(this);
}
}); });
const csize = (avaiWidth - ocwidth) / auto_width.length; const csize = (avaiWidth - ocwidth) / auto_width.length;
@ -160,7 +222,7 @@ namespace OS {
$(v).css("width", `${csize}px`) $(v).css("width", `${csize}px`)
); );
} }
return this.observable.trigger("hboxchange", { this.observable.trigger("hboxchange", {
id: this.aid, id: this.aid,
data: { w: avaiWidth, h: avaiheight }, data: { w: avaiWidth, h: avaiheight },
}); });
@ -177,16 +239,24 @@ namespace OS {
private vcalibrate(): void { private vcalibrate(): void {
const auto_height = []; const auto_height = [];
let ocheight = 0; let ocheight = 0;
const avaiheight = $(this).height(); const avaiheight = $(this).innerHeight() - this.padding * 2;
const avaiwidth = $(this).width(); const avaiwidth = $(this).width() - this.padding * 2;
//$(this.refs.yield).css("height", `${avaiheight}px`);
$(this.refs.yield) $(this.refs.yield)
.children() .children()
.each(function (e) { .each(function (e) {
let dh = 0; let dh = 0;
$(this).css("width", "100%"); $(this).css("width", "100%");
let attv = $(this).attr("data-height"); let attv = $(this).attr("data-height");
if (attv && attv !== "grow") { if (!attv || attv == "grow") {
$(this).css("flex-grow", "1");
auto_height.push(this);
return;
}
if(attv == "content")
{
ocheight += $(this).height();
return;
}
if (attv[attv.length - 1] === "%") { if (attv[attv.length - 1] === "%") {
dh = dh =
(parseInt(attv.slice(0, -1)) * (parseInt(attv.slice(0, -1)) *
@ -197,10 +267,6 @@ namespace OS {
} }
$(this).css("height", `${dh}px`); $(this).css("height", `${dh}px`);
ocheight += dh; ocheight += dh;
} else {
$(this).css("flex-grow", "1");
auto_height.push(this);
}
}); });
const csize = (avaiheight - ocheight) / auto_height.length; const csize = (avaiheight - ocheight) / auto_height.length;
@ -210,7 +276,7 @@ namespace OS {
); );
} }
return this.observable.trigger("vboxchange", { this.observable.trigger("vboxchange", {
id: this.aid, id: this.aid,
data: { w: avaiwidth, h: avaiheight }, data: { w: avaiwidth, h: avaiheight },
}); });

View File

@ -111,7 +111,7 @@ namespace OS {
* Placeholder for the `fetch` function of the node. * Placeholder for the `fetch` function of the node.
* This function is used to fetch the child nodes of the * This function is used to fetch the child nodes of the
* current nodes. This function should return a promise on * current nodes. This function should return a promise on
* a list of [[TreeViewDataType]] * a list of {@link TreeViewDataType}
* *
* @memberof TreeViewItemPrototype * @memberof TreeViewItemPrototype
*/ */
@ -164,7 +164,7 @@ namespace OS {
* Setter: * Setter:
* *
* Set the data of the current node. This will trigger the * Set the data of the current node. This will trigger the
* [[ondatachange]] function * {@link ondatachange} function
* *
* Getter: * Getter:
* *
@ -182,7 +182,7 @@ namespace OS {
this.treepath = v.path; this.treepath = v.path;
} }
this.selected = v.selected; this.selected = v.selected;
v.domel = this; this.attach(v);
this.ondatachange(); this.ondatachange();
} }
get data(): TreeViewDataType { get data(): TreeViewDataType {
@ -380,17 +380,17 @@ namespace OS {
.css("flex-direction", "row") .css("flex-direction", "row")
.css("align-items", "center") .css("align-items", "center")
.css("white-space", "nowrap"); .css("white-space", "nowrap");
$(this.refs.itemholder).css("display", "inline-block"); //$(this.refs.itemholder).css("display", "inline-block");
$(this.refs.wrapper).on("click",(e) => { $(this.refs.wrapper).on("click",(e) => {
this.selected = true; this.selected = true;
}); });
$(this.refs.wrapper).on("dblclick", (e) => { $(this.refs.wrapper).on(OS.mobile?"dbltap":"dblclick", (e) => {
this._evt.data.dblclick = true; this._evt.data.dblclick = true;
this.selected = true; this.selected = true;
}); });
$(this.refs.toggle) $(this.refs.toggle)
.css("display", "inline-block") //.css("display", "inline-block")
.css("width", "15px") .css("width", "15px")
.css("flex-shrink", 0) .css("flex-shrink", 0)
.addClass("afx-tree-view-item") .addClass("afx-tree-view-item")
@ -405,7 +405,7 @@ namespace OS {
* Layout definition of a node. This function * Layout definition of a node. This function
* returns the definition of the base outer layout * returns the definition of the base outer layout
* of a node. Custom inner layout of the node should * of a node. Custom inner layout of the node should
* be defined in the [[itemlayout]] function * be defined in the {@link itemlayout} function
* *
* @protected * @protected
* @returns {TagLayoutType[]} * @returns {TagLayoutType[]}
@ -464,8 +464,8 @@ namespace OS {
} }
/** /**
* SimpleTreeViewItem extends [[TreeViewItemPrototype]] and * SimpleTreeViewItem extends {@link TreeViewItemPrototype} and
* define it inner layout using a [[LabelTag]] * define it inner layout using a {@link LabelTag}
* *
* @export * @export
* @class SimpleTreeViewItem * @class SimpleTreeViewItem
@ -641,7 +641,7 @@ namespace OS {
* Placeholder for the `fetch` function of the tree. * Placeholder for the `fetch` function of the tree.
* This function is used to fetch the child nodes of the * This function is used to fetch the child nodes of the
* current tree. This function should return a promise on * current tree. This function should return a promise on
* a list of [[TreeViewDataType]] * a list of {@link TreeViewDataType}
* *
* @memberof TreeViewTag * @memberof TreeViewTag
*/ */

View File

@ -63,6 +63,14 @@ namespace OS {
*/ */
private _history: GenericObject<any>; private _history: GenericObject<any>;
/**
* This placeholder store the callback for the menu open event
*
* @private
* @type {(el: StackMenuTag) => void}
* @memberof WindowTag
*/
private _onmenuopen: (el: StackMenuTag) => void;
/** /**
* This placeholder stores the offset of the virtual desktop element * This placeholder stores the offset of the virtual desktop element
* *
@ -97,6 +105,15 @@ namespace OS {
return this.hasattr("blur-overlay"); return this.hasattr("blur-overlay");
} }
/**
* Setter: set menu open event handler
*
* @memberof WindowTag
*/
set onmenuopen(f: (el: StackMenuTag) => void)
{
this._onmenuopen = f;
}
/** /**
* Init window tag * Init window tag
* - `shown`: false * - `shown`: false
@ -117,6 +134,7 @@ namespace OS {
this.minimizable = true; this.minimizable = true;
this.resizable = true; this.resizable = true;
this.apptitle = "Untitled"; this.apptitle = "Untitled";
this._onmenuopen = undefined;
} }
/** /**
@ -175,6 +193,24 @@ namespace OS {
return this._height; return this._height;
} }
/**
* Set the application menu content
*
* @memberof WindowTag
*/
set menu(v: GenericObject<any>[])
{
if(!v || v.length == 0)
{
$(this.refs.btnMenu).hide();
}
else
{
(this.refs.stackmenu as StackMenuTag).nodes = v;
$(this.refs.btnMenu).show();
}
}
/** /**
* Setter: enable/disable window minimizable * Setter: enable/disable window minimizable
* *
@ -237,6 +273,15 @@ namespace OS {
return $(this).attr("apptitle"); return $(this).attr("apptitle");
} }
/**
* Get the notification tag
*
* @memberof WindowTag
*/
get notification(): NotificationTag
{
return this.refs.notification as NotificationTag;
}
/** /**
* Resize all the children of the window based on its width and height * Resize all the children of the window based on its width and height
* *
@ -262,22 +307,56 @@ namespace OS {
* @memberof WindowTag * @memberof WindowTag
*/ */
protected mount(): void { protected mount(): void {
const btn_menu = (this.refs.btnMenu as ButtonTag);
const min_btn = (this.refs["minbt"] as ButtonTag);
const max_btn = (this.refs["maxbt"] as ButtonTag);
const close_btn = (this.refs["closebt"] as ButtonTag);
const stackmenu = (this.refs.stackmenu as StackMenuTag);
stackmenu.context = true;
btn_menu.iconclass = "bi bi-list";
min_btn.iconclass = "bi bi-dash";
max_btn.iconclass = "bi bi-stop";
close_btn.iconclass = "bi bi-x";
this.contextmenuHandle = function (e) { }; this.contextmenuHandle = function (e) { };
$(this.refs["minbt"]).on("click", (e) => { min_btn.onbtclick =(_) => {
return this.observable.trigger("hide", { return this.observable.trigger("hide", {
id: this.aid, id: this.aid,
}); });
}); };
btn_menu.onbtclick = (e) => {
$(this.refs["maxbt"]).on("click", (e) => { e.data.stopPropagation();
if($(stackmenu).is(":hidden"))
{
if(this._onmenuopen)
{
this._onmenuopen(stackmenu);
}
else
{
stackmenu.reset();
}
stackmenu.show();
}
else
{
$(stackmenu).hide();
}
};
max_btn.onbtclick = (_) => {
return this.toggle_window(); return this.toggle_window();
}); };
$(this.refs["closebt"]).on("click", (e) => { close_btn.onbtclick = (_) => {
return this.observable.trigger("exit", { return this.observable.trigger("exit", {
id: this.aid, id: this.aid,
}); });
}); };
stackmenu.onmenuselect = (e) => {
if(!e.data.item.data.nodes)
{
stackmenu.selected = -1;
}
}
const left = ($(this.desktop).width() - this.width) / 2; const left = ($(this.desktop).width() - this.width) / 2;
const top = ($(this.desktop).height() - this.height) / 2; const top = ($(this.desktop).height() - this.height) / 2;
$(this) $(this)
@ -285,7 +364,7 @@ namespace OS {
.css("left", `${left}px`) .css("left", `${left}px`)
.css("top", `${top}px`) .css("top", `${top}px`)
.css("z-index", 10); .css("z-index", 10);
$(this).on("mousedown", (e) => { $(this).on("pointerdown", (e) => {
if (this._shown) { if (this._shown) {
return; return;
} }
@ -293,7 +372,6 @@ namespace OS {
id: this.aid, id: this.aid,
}); });
}); });
//$(this.refs.win_overlay).css("background-color", "red");
$(this.refs["dragger"]).on("dblclick", (e) => { $(this.refs["dragger"]).on("dblclick", (e) => {
return this.toggle_window(); return this.toggle_window();
}); });
@ -347,7 +425,26 @@ namespace OS {
h: this.height, h: this.height,
}); });
$(this).attr("tabindex", 0).css("outline", "none"); $(this).attr("tabindex", 0).css("outline", "none");
return this.observable.trigger("rendered", { if(OS.mobile)
{
this.toggle_window();
//this.minimizable = false;
this.resizable = false;
}
this.observable.on("desktopresize", (e) => {
if(this._isMaxi)
{
this._isMaxi = false;
this.toggle_window(true);
}
/*else
{
const w = this.width > e.data.width ? e.data.width: this.width;
const h = this.height > e.data.height ? e.data.height: this.height;
this.setsize({ w: w, h: h });
}*/
});
this.observable.trigger("rendered", {
id: this.aid, id: this.aid,
}); });
} }
@ -371,6 +468,7 @@ namespace OS {
this.observable.trigger("resize", { this.observable.trigger("resize", {
id: this.aid, id: this.aid,
data: o, data: o,
root: true
}); });
} }
@ -384,12 +482,12 @@ namespace OS {
$(this.refs["dragger"]) $(this.refs["dragger"])
.css("user-select", "none") .css("user-select", "none")
.css("cursor", "default"); .css("cursor", "default");
$(this.refs["dragger"]).on("mousedown", (e) => { $(this.refs.dragger).on("pointerdown", (e) => {
e.preventDefault(); e.originalEvent.preventDefault();
const offset = $(this).offset(); const offset = $(this).offset();
offset.top = e.clientY - offset.top; offset.top = e.clientY - offset.top;
offset.left = e.clientX - offset.left; offset.left = e.clientX - offset.left;
$(window).on("mousemove", (e) => { $(window).on("pointermove", (e) => {
$(this.refs.win_overlay).show(); $(this.refs.win_overlay).show();
let left: number, top: number; let left: number, top: number;
if (this._isMaxi) { if (this._isMaxi) {
@ -415,10 +513,10 @@ namespace OS {
.css("top", `${top}px`) .css("top", `${top}px`)
.css("left", `${left}px`); .css("left", `${left}px`);
}); });
return $(window).on("mouseup", (e) => { return $(window).on("pointerup", (e) => {
$(this.refs.win_overlay).hide(); $(this.refs.win_overlay).hide();
$(window).off("mousemove", null); $(window).off("pointermove", null);
return $(window).off("mouseup", null); return $(window).off("pointerup", null);
}); });
}); });
} }
@ -452,32 +550,32 @@ namespace OS {
} }
const mouse_up_hdl = (e) => { const mouse_up_hdl = (e) => {
$(this.refs.win_overlay).hide(); $(this.refs.win_overlay).hide();
$(window).off("mousemove", mouse_move_hdl); $(window).off("pointermove", mouse_move_hdl);
return $(window).off("mouseup", mouse_up_hdl); return $(window).off("pointerup", mouse_up_hdl);
} }
$(this.refs["grip"]).on("mousedown", (e) => { $(this.refs["grip"]).on("pointerdown", (e) => {
e.preventDefault(); e.preventDefault();
offset.top = e.clientY; offset.top = e.clientY;
offset.left = e.clientX; offset.left = e.clientX;
target = this.refs.grip; target = this.refs.grip;
$(window).on("mousemove", mouse_move_hdl); $(window).on("pointermove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl); $(window).on("pointerup", mouse_up_hdl);
}); });
$(this.refs.grip_bottom).on("mousedown", (e) => { $(this.refs.grip_bottom).on("pointerdown", (e) => {
e.preventDefault(); e.preventDefault();
offset.top = e.clientY; offset.top = e.clientY;
offset.left = e.clientX; offset.left = e.clientX;
target = this.refs.grip_bottom; target = this.refs.grip_bottom;
$(window).on("mousemove", mouse_move_hdl); $(window).on("pointermove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl); $(window).on("pointerup", mouse_up_hdl);
}); });
$(this.refs.grip_right).on("mousedown", (e) => { $(this.refs.grip_right).on("pointerdown", (e) => {
e.preventDefault(); e.preventDefault();
offset.top = e.clientY; offset.top = e.clientY;
offset.left = e.clientX; offset.left = e.clientX;
target = this.refs.grip_right; target = this.refs.grip_right;
$(window).on("mousemove", mouse_move_hdl); $(window).on("pointermove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl); $(window).on("pointerup", mouse_up_hdl);
}); });
} }
/** /**
@ -488,9 +586,9 @@ namespace OS {
* @returns {void} * @returns {void}
* @memberof WindowTag * @memberof WindowTag
*/ */
private toggle_window(): void { private toggle_window(force?: boolean): void {
let h: number, w: number; let h: number, w: number;
if (!this.resizable) { if (!this.resizable && !force) {
return; return;
} }
if (this._isMaxi === false) { if (this._isMaxi === false) {
@ -500,8 +598,8 @@ namespace OS {
width: $(this).css("width"), width: $(this).css("width"),
height: $(this).css("height"), height: $(this).css("height"),
}; };
w = $(this.desktop).width(); w = $(this.desktop).width() - 2;
h = $(this.desktop).height(); h = $(this.desktop).height() - 2;
$(this).css("top", "0").css("left", "0"); $(this).css("top", "0").css("left", "0");
this.setsize({ w, h }); this.setsize({ w, h });
this._isMaxi = true; this._isMaxi = true;
@ -538,18 +636,12 @@ namespace OS {
children: [ children: [
{ {
el: "li", el: "li",
class: "afx-window-close", children: [
ref: "closebt",
},
{ {
el: "li", el: "afx-button",
class: "afx-window-minimize", ref: "btnMenu",
ref: "minbt",
}, },
{ ],
el: "li",
class: "afx-window-maximize",
ref: "maxbt",
}, },
{ {
el: "li", el: "li",
@ -562,9 +654,38 @@ namespace OS {
}, },
], ],
}, },
{
el: "li",
class: "afx-window-minimize",
children: [
{
el: "afx-button",
ref: "minbt",
}
]
},
{
el: "li",
class: "afx-window-maximize",
children: [
{
el: "afx-button",
ref: "maxbt",
}
]
},
{
el: "li",
class: "afx-window-close",
children: [
{
el: "afx-button",
ref: "closebt",
}
]
},
], ],
}, },
{ el: "div", class: "afx-clear" },
{ {
el: "div", el: "div",
ref: "yield", ref: "yield",
@ -590,6 +711,14 @@ namespace OS {
ref: "win_overlay", ref: "win_overlay",
class: "afx-window-overlay", class: "afx-window-overlay",
}, },
{
el: "afx-stack-menu",
ref: "stackmenu"
},
{
el: "afx-notification",
ref: "notification"
}
], ],
}, },
]; ];

View File

@ -24,10 +24,10 @@ interface HTMLElement {
* defined on any child of this element will be ignored. * defined on any child of this element will be ignored.
* *
* @param {JQuery.MouseEventBase} e a mouse event * @param {JQuery.MouseEventBase} e a mouse event
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]] * @param {OS.GUI.tag.StackMenuTag} m The context menu element {@link OS.GUI.tag.StackMenuTag}
* @memberof HTMLElement * @memberof HTMLElement
*/ */
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void; contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.StackMenuTag): void;
/** /**
* Mount the element and all the children on its DOM subtree. This action * Mount the element and all the children on its DOM subtree. This action
@ -51,7 +51,25 @@ interface HTMLElement {
afxml(o: OS.API.Announcer): void; afxml(o: OS.API.Announcer): void;
/** /**
* Perform DOM generation ([[afxml]]) then mount ([[sync]]) all the * Enable the drag event dispatching on this
* element
*
* This will trigger the `dragging` and `drop` event on the enabled
* element when the mouse is down, move, then up, then move
*
* The event can be listened using the traditional way,
* Example:
* ```
* elem.addEventListener('dragging', (e) => { }, false);
* elem.addEventListener('drop', (e) => { }, false);
* ```
*
* @meberof HTMLElement
*/
enable_drag(): void;
/**
* Perform DOM generation ({@link afxml}) then mount ({@link sync}) all the
* elements. * elements.
* *
* @param {OS.API.Announcer} o an AntOS observable object * @param {OS.API.Announcer} o an AntOS observable object
@ -98,7 +116,7 @@ interface Document {
namespace OS { namespace OS {
export namespace GUI { export namespace GUI {
/** /**
* [[TagLayoutType]] interface using by AFX tags to defined * TagLayoutType interface using by AFX tags to defined
* its internal DOM hierarchy * its internal DOM hierarchy
* *
* @export * @export
@ -139,7 +157,7 @@ namespace OS {
/** /**
* this is the `data-id` attribute of the element, * this is the `data-id` attribute of the element,
* can be query by the [[aid]] Tag API function. * can be query by the {@link OS.GUI.AFXTag.aid} Tag API function.
* Not to be confused with the DOM `id` attribute * Not to be confused with the DOM `id` attribute
* *
* @type {(string | number)} * @type {(string | number)}
@ -217,6 +235,20 @@ namespace OS {
* @memberof TagEventType * @memberof TagEventType
*/ */
data: T; data: T;
/**
* Original event if any
*
* @type {any}
* @memberof TagEventType
*/
originalEvent?: any;
/**
* is root tag?
*
* @type {boolean}
* @memberof TagEventType
*/
root?: boolean;
} }
/** /**
@ -245,8 +277,163 @@ namespace OS {
} }
/** /**
* Tag event callback type * Tag event callback type
*
* @export
*/ */
export type TagEventCallback<T> = (e: TagEventType<T>) => void; export type TagEventCallback<T> = (e: TagEventType<T>) => void;
/**
* Tag responsive envent callback type
*
* @export
*/
export type TagResponsiveCallback = (fullfilled: boolean) => void;
/**
* A callback record with history of last fulfilled
*
* @interface TagResponsiveCallbackRecord
*/
interface TagResponsiveCallbackRecord {
/**
* Callback function
*
* @type {TagResponsiveCallback}
* @memberof TagResponsiveCallbackRecord
*/
callback: TagResponsiveCallback,
/**
* has the event been previously fulfilled?
*
* @type {boolean}
* @memberof TagResponsiveCallbackRecord
*/
fulfilled: boolean,
}
/**
* Tag responsive validator type
*
* @export
*/
export type TagResponsiveValidator = (w: number, h: number) => boolean;
/**
* Definitions of some default tag responsive validators
*
* @export
*/
export const RESPONSIVE = {
/**
* Extra small devices (phones, 600px and down)
*/
TINY: function(w: number,_h: number){
return w <= 600;
},
/*
Small devices (portrait tablets and large phones, 600px and up)
*/
SMALL: function(w: number, _h: number){
return w <= 768;
},
/*
Medium devices (landscape tablets, 768px and up)
*/
MEDIUM: function(w: number, _h: number){
return w > 768;
},
/**
* Large devices (laptops/desktops, 992px and up)
*/
LARGE: function(w: number, _h: number){
return w > 992;
},
/**
* Extra large devices (large laptops and desktops, 1200px and up)
*/
HUGE: function(w: number, _h: number){
return w > 1200;
},
/**
* Portrait mode
*/
PORTRAIT: function(w: number, h: number){
return h > w;
},
/**
* Landscape mode
*/
LANDSCAPE: function(w: number, h: number){
return h < w;
},
}
/**
* A custom map to handle responsive events, a responsive event
* consists of a validator and a callback
*
* When avalidator return true, its corresponding callback will
* be called
*/
class ResponsiveHandle extends Map<TagResponsiveValidator,TagResponsiveCallbackRecord> {
/**
* Register a responsive envent to this map
*
* @param {TagResponsiveValidator} a validator
* @param {TagResponsiveCallback} event callback
* @memberof ResponsiveHandle
*/
on(validator: TagResponsiveValidator, callback: TagResponsiveCallback)
{
let record: TagResponsiveCallbackRecord = {
callback: callback,
fulfilled: false,
}
this.set(validator, record);
}
/**
* unregister a responsive event
*
* @param {TagResponsiveValidator} a validator
* @memberof ResponsiveHandle
*/
off(validator: TagResponsiveValidator)
{
this.delete(validator);
}
/**
* Verify validators and execute callbacks
*
* @param {number} root tag width
* @param {number} root tag height
* @memberof ResponsiveHandle
*/
morph(rootw: number, rooth: number)
{
for (const [validator, record] of this.entries()) {
const val = validator(rootw, rooth);
if(record.fulfilled && val)
{
// the record has been previously fulfilled
// and the validator returns true
// nothing changed
continue;
}
if(!record.fulfilled && !val)
{
// the record has been previously unfulfilled
// and the validator returns false
// nothing changed
continue;
}
record.fulfilled =val;
record.callback(val);
}
}
}
/** /**
* Base abstract class for tag implementation, any AFX tag should be * Base abstract class for tag implementation, any AFX tag should be
* subclass of this class * subclass of this class
@ -269,7 +456,7 @@ namespace OS {
* Reference to some of the tag's children * Reference to some of the tag's children
* element. This reference object is built * element. This reference object is built
* based on the `ref` property found in the * based on the `ref` property found in the
* tag layout [[TagLayoutType]] * tag layout {@link TagLayoutType}
* *
* @protected * @protected
* @type {GenericObject<HTMLElement>} * @type {GenericObject<HTMLElement>}
@ -287,6 +474,16 @@ namespace OS {
*/ */
protected _mounted: boolean; protected _mounted: boolean;
/**
* a {@link ResponsiveHandle} to handle all responsive event
* related to this tag
*
* @private
* @memberof AFXTag
*/
private _responsive_handle: ResponsiveHandle;
private _responsive_check: (evt: TagEventType<{w: number, h: number}>) => void;
/** /**
* Creates an instance of AFXTag. * Creates an instance of AFXTag.
* @memberof AFXTag * @memberof AFXTag
@ -299,6 +496,8 @@ namespace OS {
} }
this._mounted = false; this._mounted = false;
this.refs = {}; this.refs = {};
this._responsive_handle = new ResponsiveHandle();
this._responsive_check = undefined;
} }
/** /**
@ -369,6 +568,79 @@ namespace OS {
return $(this).attr("data-id"); return $(this).attr("data-id");
} }
/**
* Setter: Enable/disable responsive tag
*
* Getter: get responsive status
*/
set responsive(v:boolean) {
if(!v)
{
this.attsw(false,"responsive");
this.observable.off("resize",this._responsive_check);
this._responsive_check = undefined;
return;
}
if(this._responsive_check)
{
return;
}
this._responsive_check = (evt: TagEventType<{w: number, h: number}>) => {
if(!evt || !evt.root || !evt.data.w || !evt.data.h )
{
return;
}
this._responsive_handle.morph(evt.data.w, evt.data.h);
}
this.observable.on("resize", this._responsive_check);
this.attsw(true, "responsive");
}
get responsive(): boolean {
return this.hasattr("responsive");
}
/**
* Register a responsive event to this tag
*
* @param {TagResponsiveValidator} responsive validator
* @param {TagResponsiveCallback} responsive callback
* @memberof AFXTag
*/
morphon(validator: TagResponsiveValidator, callback:TagResponsiveCallback)
{
this._responsive_handle.on(validator, callback);
}
/**
* Unregister a responsive event from this tag
*
* @param {TagResponsiveValidator} responsive validator
* @memberof AFXTag
*/
morphoff(validator: TagResponsiveValidator)
{
this._responsive_handle.off(validator);
}
/**
* Attach a data to this tag
*
* This function will define a getter `domel`
* in the attached data, this getter refers to the
* current tag
*
* @returns {void}
* @memberof AFXTag
*/
attach(data: GenericObject<any>): void {
const self = this;
Object.defineProperty(data, 'domel', {
get: function () { return self },
enumerable: false,
configurable: true
})
}
/** /**
* Implementation from HTMLElement interface, * Implementation from HTMLElement interface,
* this function mount the current tag hierarchy * this function mount the current tag hierarchy
@ -443,7 +715,7 @@ namespace OS {
/** /**
* Init the current tag, this function * Init the current tag, this function
* is called before the [[mount]] function * is called before the {@link mount} function
* *
* @protected * @protected
* @abstract * @abstract
@ -472,7 +744,7 @@ namespace OS {
/** /**
* Update only the current tag, this function is * Update only the current tag, this function is
* called by [[update]] before chaining the * called by {@link update} before chaining the
* update process to its children * update process to its children
* *
* @protected * @protected
@ -589,6 +861,43 @@ namespace OS {
} }
} }
HTMLElement.prototype.enable_drag = function () {
$(this)
.on("pointerdown", (evt: JQuery.MouseEventBase) => {
const offset = $(this).offset();
offset.top = evt.clientY - offset.top;
offset.left = evt.clientX - offset.left;
const mouse_move = (
e: JQuery.MouseEventBase
) => {
const custom_event = new CustomEvent('dragging', {
detail: {
origin: evt,
current: e,
offset: offset
}
});
this.dispatchEvent(custom_event);
};
var mouse_up = (e: JQuery.MouseEventBase) => {
$(window).off("pointermove", mouse_move);
$(window).off("pointerup", mouse_up);
// trigger the drop event
const custom_event = new CustomEvent('drop', {
detail: {
origin: evt,
current: e,
offset: offset
}
});
this.dispatchEvent(custom_event);
};
$(window).on("pointermove", mouse_move);
$(window).on("pointerup", mouse_up);
});
}
HTMLElement.prototype.update = function (d): void { HTMLElement.prototype.update = function (d): void {
$(this) $(this)
.children() .children()
@ -625,6 +934,12 @@ namespace OS {
* stored in the `customElements` registry of the browser * stored in the `customElements` registry of the browser
*/ */
export namespace tag { export namespace tag {
/**
* Alias to all classes that extends {@link AFXTag}
*/
export type AFXTagTypeClass = {
new <T extends AFXTag>(): T;
};
/** /**
* Define an AFX tag as a custom element and add it to the * Define an AFX tag as a custom element and add it to the
* global `customElements` registry. If the tag is redefined, i.e. * global `customElements` registry. If the tag is redefined, i.e.
@ -632,7 +947,7 @@ namespace OS {
* new definition * new definition
* *
* @export * @export
* @template T all classes that extends [[AFXTag]] * @template T all classes that extends {@link AFXTag}
* @param {string} name name of the tag * @param {string} name name of the tag
* @param {{ new (): T }} cls the class that defines the tag * @param {{ new (): T }} cls the class that defines the tag
* @returns {void} * @returns {void}

View File

@ -21,7 +21,7 @@ interface String {
* Convert a string to VFS file handle. * Convert a string to VFS file handle.
* *
* This function will create a file handle object from the string * This function will create a file handle object from the string
* with the help of [[VFS.findHandles]] * with the help of {@link OS.API.VFS.findHandles}
* *
* @returns {OS.API.VFS.BaseFileHandle} * @returns {OS.API.VFS.BaseFileHandle}
* @memberof String * @memberof String
@ -226,7 +226,7 @@ namespace OS {
* *
* When converting a string to file handle, the system will look * When converting a string to file handle, the system will look
* for a protocol pattern in the string, if the protocol found, * for a protocol pattern in the string, if the protocol found,
* its attached handle class (found in [[VFS.handles]]) will be * its attached handle class (found in {@link VFS.handles}) will be
* used to initialize a file handle object from the string * used to initialize a file handle object from the string
* *
* ```typescript * ```typescript
@ -322,7 +322,7 @@ namespace OS {
basename: string; basename: string;
/** /**
* Once loaded, [[ready]] will be set to true and * Once loaded, {@link ready} will be set to true and
* file meta-data will be stored in this place holder * file meta-data will be stored in this place holder
* *
* @type {FileInfoType} * @type {FileInfoType}
@ -569,23 +569,24 @@ namespace OS {
/** /**
* Public read operation * Public read operation
* *
* This function calls the [[_rd]] function to perform the operation. * This function calls the {@link _rd} function to perform the operation.
* *
* If the current file is a directory, then the operation * If the current file is a directory, then the operation
* will return the meta-data of all files inside of the directory. * will return the meta-data of all files inside of the directory.
* Otherwise, file content will be returned * Otherwise, file content will be returned
* *
* @param {string} t data type * @param {any} formal t data type
* - jsonp: the response is an json object * - jsonp: the response is an json object
* - script: the response is a javascript code * - script: the response is a javascript code
* - xml, html: the response is a XML/HTML object * - xml, html: the response is a XML/HTML object
* - text: plain text * - text: plain text
* - binary * - binary
* - other user case may be user specific data
* *
* @returns {Promise<any>} a promise on the file content * @returns {Promise<any>} a promise on the file content
* @memberof BaseFileHandle * @memberof BaseFileHandle
*/ */
read(t?: string): Promise<any> { read(t?: any): Promise<any> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
@ -600,7 +601,7 @@ namespace OS {
/** /**
* Write the file cache to the actual file * Write the file cache to the actual file
* *
* This function calls the [[_wr]] function to perform the operation * This function calls the {@link _wr} function to perform the operation
* *
* @param {string} t data type * @param {string} t data type
* - `base64` * - `base64`
@ -625,7 +626,7 @@ namespace OS {
/** /**
* Sub-directory creation * Sub-directory creation
* *
* This function calls the [[_mk]] function to perform the operation * This function calls the {@link _mk} function to perform the operation
* *
* @param {string} d sub directory name * @param {string} d sub directory name
* @returns {Promise<RequestResult>} promise on the operation result * @returns {Promise<RequestResult>} promise on the operation result
@ -647,16 +648,16 @@ namespace OS {
/** /**
* Delete the file * Delete the file
* *
* This function calls the [[_rm]] function to perform the operation * This function calls the {@link _rm} function to perform the operation
* * @param {any} d user data
* @returns {Promise<RequestResult>} promise on the operation result * @returns {Promise<RequestResult>} promise on the operation result
* @memberof BaseFileHandle * @memberof BaseFileHandle
*/ */
remove(): Promise<RequestResult> { remove(data?: any): Promise<RequestResult> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
const d = await this._rm(); const d = await this._rm(data);
announcer.ostrigger("VFS", "remove",this); announcer.ostrigger("VFS", "remove",this);
return resolve(d); return resolve(d);
} catch (e_1) { } catch (e_1) {
@ -670,7 +671,7 @@ namespace OS {
* *
* Only work when the current file is a directory * Only work when the current file is a directory
* *
* This function calls the [[_up]] function to perform the operation * This function calls the {@link _up} function to perform the operation
* *
* @returns {Promise<RequestResult>} promise on the operation result * @returns {Promise<RequestResult>} promise on the operation result
* @memberof BaseFileHandle * @memberof BaseFileHandle
@ -693,7 +694,7 @@ namespace OS {
* *
* Only work with file * Only work with file
* *
* This function calls the [[_pub]] function to perform the operation * This function calls the {@link _pub} function to perform the operation
* *
* @returns {Promise<RequestResult>} promise on operation result * @returns {Promise<RequestResult>} promise on operation result
* @memberof BaseFileHandle * @memberof BaseFileHandle
@ -716,7 +717,7 @@ namespace OS {
* *
* Only work with file * Only work with file
* *
* This function calls the [[_down]] function to perform the operation * This function calls the {@link _down} function to perform the operation
* *
* @returns {Promise<any>} Promise on the operation result * @returns {Promise<any>} Promise on the operation result
* @memberof BaseFileHandle * @memberof BaseFileHandle
@ -737,7 +738,7 @@ namespace OS {
/** /**
* Move the current file to another location * Move the current file to another location
* *
* This function calls the [[_mv]] function to perform the operation * This function calls the {@link _mv} function to perform the operation
* *
* @param {string} d destination location * @param {string} d destination location
* @returns {Promise<RequestResult>} promise on the operation result * @returns {Promise<RequestResult>} promise on the operation result
@ -762,7 +763,7 @@ namespace OS {
* *
* This action depends on each file protocol * This action depends on each file protocol
* *
* This function calls the [[_exec]] function to perform the operation * This function calls the {@link _exec} function to perform the operation
* *
* @returns {Promise<any>} * @returns {Promise<any>}
* @memberof BaseFileHandle * @memberof BaseFileHandle
@ -830,11 +831,11 @@ namespace OS {
* that supports the operation * that supports the operation
* *
* @protected * @protected
* @param {string} t data type, see [[read]] * @param {any} t data type, see {@link read}
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof BaseFileHandle * @memberof BaseFileHandle
*/ */
protected _rd(t: string): Promise<RequestResult> { protected _rd(t: any): Promise<RequestResult> {
return this.unsupported("read"); return this.unsupported("read");
} }
@ -845,12 +846,11 @@ namespace OS {
* that supports the operation * that supports the operation
* *
* @protected * @protected
* @param {string} t data type, see [[write]] * @param {string} t data type, see {@link write}
* @param {*} [d]
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof BaseFileHandle * @memberof BaseFileHandle
*/ */
protected _wr(t: string, d?: any): Promise<RequestResult> { protected _wr(t: string): Promise<RequestResult> {
return this.unsupported("write"); return this.unsupported("write");
} }
@ -874,10 +874,11 @@ namespace OS {
* This function should be overridden by the file handle class * This function should be overridden by the file handle class
* that supports the operation * that supports the operation
* *
* @param {*} [d] any user data
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof BaseFileHandle * @memberof BaseFileHandle
*/ */
protected _rm(): Promise<RequestResult> { protected _rm(d: any): Promise<RequestResult> {
return this.unsupported("remove"); return this.unsupported("remove");
} }
@ -1024,7 +1025,7 @@ namespace OS {
* Otherwise, file content will be returned * Otherwise, file content will be returned
* *
* @protected * @protected
* @param {string} t data type see [[read]] * @param {string} t data type see {@link read}
* @returns {Promise<any>} * @returns {Promise<any>}
* @memberof RemoteFileHandle * @memberof RemoteFileHandle
*/ */
@ -1056,7 +1057,7 @@ namespace OS {
* Write file cache to the remote file * Write file cache to the remote file
* *
* @protected * @protected
* @param {string} t data type see [[write]] * @param {string} t data type see {@link write}
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof RemoteFileHandle * @memberof RemoteFileHandle
*/ */
@ -1292,7 +1293,7 @@ namespace OS {
/** /**
* Package file is remote file ([[RemoteFileHandle]]) located either in * Package file is remote file ({@link RemoteFileHandle}) located either in
* the local user packages location or system packages * the local user packages location or system packages
* location, it should be in the following format: * location, it should be in the following format:
* *
@ -1595,7 +1596,7 @@ namespace OS {
* Read file content stored in the file cached * Read file content stored in the file cached
* *
* @protected * @protected
* @param {string} t data type see [[read]] * @param {string} t data type see {@link read}
* @returns {Promise<any>} * @returns {Promise<any>}
* @memberof BufferFileHandle * @memberof BufferFileHandle
*/ */
@ -1616,7 +1617,7 @@ namespace OS {
* Write data to the file cache * Write data to the file cache
* *
* @protected * @protected
* @param {string} t data type, see [[write]] * @param {string} t data type, see {@link write}
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof BufferFileHandle * @memberof BufferFileHandle
*/ */
@ -1722,7 +1723,6 @@ namespace OS {
dest.parent().cache[dest.basename] = this; dest.parent().cache[dest.basename] = this;
return resolve({result: true, error: false}); return resolve({result: true, error: false});
} catch (e) { } catch (e) {
console.log(this);
return reject(__e(e)); return reject(__e(e));
} }
}); });
@ -1801,7 +1801,7 @@ namespace OS {
* Read URL content * Read URL content
* *
* @protected * @protected
* @param {string} t data type see [[read]] * @param {string} t data type see {@link read}
* @returns {Promise<any>} * @returns {Promise<any>}
* @memberof URLFileHandle * @memberof URLFileHandle
*/ */
@ -1855,7 +1855,7 @@ namespace OS {
* Read file content * Read file content
* *
* @protected * @protected
* @param {string} t data type, see [[read]] * @param {string} t data type, see {@link read}
* @returns {Promise<any>} * @returns {Promise<any>}
* @memberof SharedFileHandle * @memberof SharedFileHandle
*/ */
@ -1874,23 +1874,46 @@ namespace OS {
* write data to shared file * write data to shared file
* *
* @protected * @protected
* @param {string} t data type, see [[write]] * @param {string} t data type, see {@link write}
* @param {string} d file data
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof SharedFileHandle * @memberof SharedFileHandle
*/ */
protected _wr(t: string, d: string): Promise<RequestResult> { protected _wr(t: string): Promise<RequestResult> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await API.handle.write(this.path, d);
if (r.error) { if (t === "base64") {
const d = await API.handle.write(
this.path,
this.cache
);
if (d.error) {
return reject( return reject(
API.throwe( API.throwe(
__("{0}: {1}", r.error, this.path) __("{0}: {1}", d.error, this.path)
) )
); );
} }
return resolve(r); return resolve(d);
} else {
const r = await this.b64(t);
const result = await API.handle.write(
this.path,
r as string
);
if (result.error) {
return reject(
API.throwe(
__(
"{0}: {1}",
result.error,
this.path
)
)
);
}
return resolve(result);
}
} catch (e) { } catch (e) {
return reject(__e(e)); return reject(__e(e));
} }
@ -2075,7 +2098,6 @@ namespace OS {
if (meta.type === "dir") { if (meta.type === "dir") {
const desdir = to.asFileHandle(); const desdir = to.asFileHandle();
await desdir.mk(file.basename); await desdir.mk(file.basename);
console.log(desdir, to);
const ret = await file.read(); const ret = await file.read();
const files = ret.result.map((v: API.FileInfoType) => v.path); const files = ret.result.map((v: API.FileInfoType) => v.path);
if (files.length > 0) { if (files.length > 0) {
@ -2247,17 +2269,26 @@ namespace OS {
const data = await zfile.asFileHandle().read("binary"); const data = await zfile.asFileHandle().read("binary");
const zip = await JSZip.loadAsync(data); const zip = await JSZip.loadAsync(data);
const to = await dest_callback(zip); const to = await dest_callback(zip);
const dirs = []; const dirs = new Set<string>();
const files = []; const files = [];
for (const name in zip.files) { for (const _name in zip.files) {
const file = zip.files[name]; const name = _name.trimFromRight("/");
const file = zip.files[_name];
if (file.dir) { if (file.dir) {
dirs.push(to + "/" + name); dirs.add(to + "/" + name);
} else { } else {
const segs = name.split("/");
segs.pop();
let path = to;
for(let seg of segs)
{
path += "/" + seg;
dirs.add(path);
}
files.push(name); files.push(name);
} }
} }
await mkdirAll(dirs, true); await mkdirAll(Array.from(dirs), true);
const promises = []; const promises = [];
for (const file of files) { for (const file of files) {
promises.push(new Promise(async (res, rej) => { promises.push(new Promise(async (res, rej) => {

View File

@ -22,8 +22,18 @@
<head> <head>
<title>AntOS webOS</title> <title>AntOS webOS</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<link rel="icon" href="resources/themes/system/icons/antos-16x16.png" type="image/png" />
<link rel="preload" href="resources/themes/system/fonts/ubuntu-regular-webfont.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="resources/themes/system/fonts/ubuntu-italic-webfont.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="resources/themes/system/fonts/ubuntu-bolditalic-webfont.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="resources/themes/system/fonts/ubuntu-bold-webfont.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="resources/themes/system/fonts/fontawesome-webfont.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="resources/themes/system/fonts/bootstrap-icons.woff2" as="font" type="font/woff2" crossorigin>
<link href="resources/themes/system/system.css" rel="stylesheet"> <link href="resources/themes/system/system.css" rel="stylesheet">
<!-- default theme-->
<link href="resources/themes/system/antos.css" rel="stylesheet">
<link id="ostheme" rel="stylesheet" href=""> <link id="ostheme" rel="stylesheet" href="">
<script src="scripts/jquery-3.4.1.min.js"></script> <script src="scripts/jquery-3.4.1.min.js"></script>
<script src="scripts/antos.js"></script> <script src="scripts/antos.js"></script>

View File

@ -6,4 +6,7 @@ This application is included in the AntOS delivery as system application and
cannot be removed/uinstalled by regular user cannot be removed/uinstalled by regular user
## Change logs ## Change logs
- v0.1.10-b: improve file favorite behaviour
- v0.1.9-b: use "column"/"row" for UI direction names
- v0.1.8-b: use system css variables in theme
- v0.1.7-b: fix - file grid view double click event hanling on diffent cells of a row - v0.1.7-b: fix - file grid view double click event hanling on diffent cells of a row

View File

@ -1,48 +1,53 @@
/*afx-app-window[data-id ='files-app-window'] afx-list-view{
border-top:1px solid #A6A6A6;
}*/
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri']{ afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri']{
border-right: 0; border-right: 0;
/* border-top:1px solid #A6A6A6; */
padding:0; padding:0;
} }
afx-app-window[data-id ='files-app-window'] afx-resizer{
background-color: transparent;
/* border-left: 1px solid #cbcbcb; */
}
afx-app-window[data-id ='files-app-window'] afx-file-view afx-list-view i:before{ afx-app-window[data-id ='files-app-window'] afx-file-view afx-list-view i:before{
font-size: 32px; font-size: 32px;
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
text-align: center; text-align: center;
} }
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li{
background-color: transparent;
}
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected { afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected {
background-color: #cecece; background-color: var(--background-quaternary) !important;
color:#414339; color:var(--text-secondary) !important;
}
afx-app-window[data-id ='files-app-window'] .afx-window-top{
border-bottom: 0;
}
afx-app-window[data-id ='files-app-window'] afx-vbox[data-id = "nav-bar"]{
background-color: #dfdfdf;
}
afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row.grid_row_header div{
border-top:1px solid #A6A6A6;
} }
afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row afx-label span { afx-app-window[data-id ='files-app-window'] afx-file-view {
white-space: nowrap; background-color: var(--background-tertiary);
} }
afx-app-window[data-id ='files-app-window'] button{ afx-app-window[data-id ='files-app-window'] button{
height: 23px;
border-radius: 0; border-radius: 0;
padding-top:2px; }
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul li{
padding: 0;
padding-right: 7px;
padding-left: 7px;
border: 0;
} }
afx-app-window[data-id ='files-app-window'] input{ afx-app-window[data-id ='files-app-window'] afx-hbox[data-id ='nav-bar'] {
border-radius: 3px; border-bottom: 1px solid var(--border-primary);
}
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul li::before{
content: "\F27B";
font-family: "bootstrap-icons";
font-style: normal;
/* font-size: 41px; */
display: block;
position: absolute;
color: var(--text-secondary);
right: -7px
}
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul > afx-list-item > li:hover{
color: var(--text-secondary);
}
afx-app-window[data-id ='files-app-window'] afx-tab-bar[data-id ='path-nav'] afx-list-view > div.list-container > ul > afx-list-item > li.selected {
background-color: transparent;
border: 0;
} }

View File

@ -37,7 +37,6 @@ namespace OS {
*/ */
export class Files extends BaseApplication { export class Files extends BaseApplication {
private view: GUI.tag.FileViewTag; private view: GUI.tag.FileViewTag;
private navinput: HTMLInputElement;
private navbar: GUI.tag.HBoxTag; private navbar: GUI.tag.HBoxTag;
private currdir: API.VFS.BaseFileHandle; private currdir: API.VFS.BaseFileHandle;
private favo: GUI.tag.ListViewTag; private favo: GUI.tag.ListViewTag;
@ -50,20 +49,14 @@ namespace OS {
/** /**
* * main entry point
* *
* @returns * @returns
* @memberof Files * @memberof Files
*/ */
main(): void { main(): void {
this.view = this.find("fileview") as GUI.tag.FileViewTag; this.view = this.find("fileview") as GUI.tag.FileViewTag;
this.navinput = this.find("navinput") as HTMLInputElement;
this.navbar = this.find("nav-bar") as GUI.tag.HBoxTag; this.navbar = this.find("nav-bar") as GUI.tag.HBoxTag;
if (this.args && this.args.length > 0) {
this.currdir = this.args[0].path.asFileHandle();
} else {
this.currdir = "home://".asFileHandle();
}
this.favo = this.find("favouri") as GUI.tag.ListViewTag; this.favo = this.find("favouri") as GUI.tag.ListViewTag;
this.clipboard = undefined; this.clipboard = undefined;
this.viewType = this._api.switcher("icon", "list", "tree"); this.viewType = this._api.switcher("icon", "list", "tree");
@ -93,7 +86,7 @@ namespace OS {
ctx_menu.unshift( { ctx_menu.unshift( {
text: "__(Open with)", text: "__(Open with)",
nodes: apps, nodes: apps,
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => { onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
if (!e) { if (!e) {
return; return;
} }
@ -106,7 +99,7 @@ namespace OS {
ctx_menu = ctx_menu.concat([ ctx_menu = ctx_menu.concat([
{ {
text: "__(Extract Here)", text: "__(Extract Here)",
onmenuselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => { onmenuselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
if (!e) { if (!e) {
return; return;
} }
@ -117,7 +110,7 @@ namespace OS {
}, },
{ {
text: "__(Extract to)", text: "__(Extract to)",
onmenuselect: async (e: GUI.TagEventType<GUI.tag.MenuEventData>) => { onmenuselect: async (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
if (!e) { if (!e) {
return; return;
} }
@ -144,7 +137,7 @@ namespace OS {
ctx_menu.push( ctx_menu.push(
{ {
text: "__(Compress)", text: "__(Compress)",
onmenuselect: async (e: GUI.TagEventType<GUI.tag.MenuEventData>) => { onmenuselect: async (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
if (!e) { if (!e) {
return; return;
} }
@ -161,7 +154,7 @@ namespace OS {
} }
const path = `${d.file.path}/${d.name}`; const path = `${d.file.path}/${d.name}`;
await API.VFS.mkar(file.path, path); await API.VFS.mkar(file.path, path);
this.notify(__("Archive file created: {0}",path )); this.toast(__("Archive file created: {0}",path ));
} catch (error) { } catch (error) {
this.error(__("Unable to compress file, folder"), error); this.error(__("Unable to compress file, folder"), error);
} }
@ -171,7 +164,7 @@ namespace OS {
} }
} }
m.items = ctx_menu; m.nodes = ctx_menu;
m.show(e); m.show(e);
}; };
@ -186,6 +179,10 @@ namespace OS {
}; };
this.favo.onlistselect = (e) => { this.favo.onlistselect = (e) => {
if(this.currdir.path.startsWith(e.data.item.data.path))
{
return;
}
return this.view.path = e.data.item.data.path; return this.view.path = e.data.item.data.path;
}; };
@ -198,12 +195,6 @@ namespace OS {
return this.view.path = p.path; return this.view.path = p.path;
}; };
$(this.navinput).keyup((e) => {
if (e.keyCode === 13) {
return this.view.path = $(this.navinput).val() as string;
}
}); //enter
this.view.fetch = (path) => { this.view.fetch = (path) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let dir = path.asFileHandle(); let dir = path.asFileHandle();
@ -213,15 +204,71 @@ namespace OS {
if (d.error) { if (d.error) {
return reject(d.error); return reject(d.error);
} }
this.currdir = dir;
$(this.navinput).val(dir.path);
(this.scheme as GUI.tag.WindowTag).apptitle = dir.path;
return resolve(d.result); return resolve(d.result);
}) })
.catch((e) => reject(__e(e))); .catch((e) => reject(__e(e)));
}); });
}; };
const p_list = this.find<GUI.tag.TabBarTag>("path-nav");
p_list.ontabselect = (e) => {
const handle: API.VFS.BaseFileHandle = e.data.item.data.handle;
if(this.currdir.path == handle.path)
{
return;
}
this.view.path = handle.path;
}
this.view.onchdir = (e) => {
const dir = (e.data.path as string).asFileHandle();
this.currdir = dir;
if(dir.genealogy)
{
(this.scheme as GUI.tag.WindowTag).apptitle = dir.filename;
}
else
{
(this.scheme as GUI.tag.WindowTag).apptitle = `${dir.protocol}://`;
}
// update the path-nav
let base_vfs = `${dir.protocol}://`.asFileHandle();
let segments = [{
text: base_vfs.path,
handle: base_vfs
}]
if(dir.genealogy)
{
segments = segments.concat(dir.genealogy.map((e)=> {
base_vfs = `${base_vfs.path}/${e}`.asFileHandle();
return {
text: e,
handle: base_vfs
}
}));
}
p_list.items = segments;
p_list.scroll_to_end();
// update the current favo
const matched = this.favo.data.map((e,i) => {
return {
path: e.path,
index:i,
}
})
.filter(e => {
return dir.path.startsWith(e.path as string);
})
.sort((x,y) => {
if(x.path.length < y.path.length) return 1;
if(x == y) return 0;
return -1;
});
if(matched.length != 0)
{
// get the longest matched
this.favo.selected = matched[0].index;
}
}
this.vfs_event_flag = true; this.vfs_event_flag = true;
this.view.ondragndrop = async (e) => { this.view.ondragndrop = async (e) => {
if (!e) { if (!e) {
@ -272,11 +319,6 @@ namespace OS {
} }
this.vfs_event_flag = true; this.vfs_event_flag = true;
}; };
// application setting
if (this.setting.sidebar === undefined) {
this.setting.sidebar = true;
}
if (this.setting.nav === undefined) { if (this.setting.nav === undefined) {
this.setting.nav = true; this.setting.nav = true;
} }
@ -286,19 +328,16 @@ namespace OS {
this.applyAllSetting(); this.applyAllSetting();
// VFS mount point and event // VFS mount point and event
const mntpoints = []; const mntpoints = this.systemsetting.VFS.mountpoints.map(e => {
for(let v of this.systemsetting.VFS.mountpoints) return {
{ text: e.text,
mntpoints.push({ path: e.path,
text: v.text, icon: e.icon,
path: v.path, iconclass: e.iconclass,
icon: v.icon,
iconclass: v.iconclass,
selected: false selected: false
});
} }
});
this.favo.data = mntpoints; this.favo.data = mntpoints;
//@favo.set "selected", -1
if (this.setting.view) { if (this.setting.view) {
this.view.view = this.setting.view; this.view.view = this.setting.view;
} }
@ -317,6 +356,48 @@ namespace OS {
} }
}); });
// register responsive event
this.morphon(GUI.RESPONSIVE.MEDIUM, (fulfilled:boolean) => {
/**
* If the Window is bigger than medium size, then
* we enable the side bar
*
* otherwise, use the top bar
*/
const box = this.find("container") as GUI.tag.TileLayoutTag;
const nav = this.find("nav-bar") as GUI.tag.TileLayoutTag;
const fav = this.find("favouri") as GUI.tag.ListViewTag;
const resizer = this.find("resizer") as GUI.tag.ResizerTag;
if(fulfilled)
{
box.name = "hbox";
box.dir = "row";
nav.reversed = true;
nav.name = "vbox";
nav.dir = "column";
fav.dropdown = false;
resizer.dir = "row";
resizer.disable = false;
}
else
{
box.name = "vbox";
box.dir = "column";
nav.reversed = false;
nav.name = "hbox";
nav.dir = "row";
fav.dropdown = true;
resizer.dir = "column";
resizer.disable = true;
}
});
// bind keyboard shortcut // bind keyboard shortcut
this.bindKey("CTRL-F", () => this.bindKey("CTRL-F", () =>
this.actionFile(`${this.name}-mkf`) this.actionFile(`${this.name}-mkf`)
@ -387,6 +468,11 @@ namespace OS {
this.view.multiselect = false; this.view.multiselect = false;
} }
}); });
if (this.args && this.args.length > 0) {
this.currdir = this.args[0].path.asFileHandle();
} else {
this.currdir = "home://".asFileHandle();
}
this.view.path = this.currdir.path; this.view.path = this.currdir.path;
} }
@ -397,13 +483,10 @@ namespace OS {
return this.view.showhidden = this.setting.showhidden; return this.view.showhidden = this.setting.showhidden;
case "nav": case "nav":
return this.toggleNav(this.setting.nav); return this.toggleNav(this.setting.nav);
case "sidebar":
return this.toggleSidebar(this.setting.sidebar);
} }
} }
private mnFile(): GUI.BasicItemType{ private mnFile(): GUI.BasicItemType{
//console.log file
const arr: GUI.BasicItemType = { const arr: GUI.BasicItemType = {
text: "__(File)", text: "__(File)",
nodes: [ nodes: [
@ -437,7 +520,7 @@ namespace OS {
shortcut: "C-I", shortcut: "C-I",
}, },
], ],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) =>
this.actionFile(e.data.item.data.dataid), this.actionFile(e.data.item.data.dataid),
}; };
return arr; return arr;
@ -472,7 +555,7 @@ namespace OS {
shortcut: "C-P", shortcut: "C-P",
}, },
], ],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) =>
this.actionEdit(e.data.item.data.dataid), this.actionEdit(e.data.item.data.dataid),
}; };
} }
@ -483,17 +566,15 @@ namespace OS {
{ {
text: "__(View)", text: "__(View)",
nodes: [ nodes: [
{
text: "__(Toggle responsive)",
dataid: `${this.name}-responsive`,
},
{ {
text: "__(Refresh)", text: "__(Refresh)",
dataid: `${this.name}-refresh`, dataid: `${this.name}-refresh`,
shortcut: "C-A-R" shortcut: "C-A-R"
}, },
{
text: "__(Sidebar)",
switch: true,
checked: this.setting.sidebar,
dataid: `${this.name}-side`,
},
{ {
text: "__(Navigation bar)", text: "__(Navigation bar)",
switch: true, switch: true,
@ -531,28 +612,19 @@ namespace OS {
type: "tree", type: "tree",
}, },
], ],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => { onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
const { type } = e.data.item.data; const { type } = e.data.item.data;
this.view.view = type; this.view.view = type;
return (this.viewType[type] = true); return (this.viewType[type] = true);
}, },
}, },
], ],
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => this.actionView(e), onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => this.actionView(e),
}, },
]; ];
return menu; return menu;
} }
private toggleSidebar(b: boolean): void {
if (b) {
$(this.favo).show();
} else {
$(this.favo).hide();
}
return this.trigger("resize");
}
private toggleNav(b: boolean): void { private toggleNav(b: boolean): void {
if (b) { if (b) {
$(this.navbar).show(); $(this.navbar).show();
@ -562,22 +634,22 @@ namespace OS {
return this.trigger("resize"); return this.trigger("resize");
} }
private actionView(e: GUI.TagEventType<GUI.tag.MenuEventData>): void{ private actionView(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void{
const data = e.data.item.data; const data = e.data.item.data;
switch (data.dataid) { switch (data.dataid) {
case `${this.name}-hidden`: case `${this.name}-hidden`:
//@.view.set "showhidden", e.item.data.checked //@.view.set "showhidden", e.item.data.checked
return this.registry("showhidden", data.checked); this.setting.showhidden = data.checked;
//@.setting.showhidden = e.item.data.checked //@.setting.showhidden = e.item.data.checked
case `${this.name}-refresh`: case `${this.name}-refresh`:
this.view.path = this.currdir.path; this.view.path = this.currdir.path;
return; return;
case `${this.name}-side`: case `${this.name}-responsive`:
return this.registry("sidebar", data.checked); const win = (this.scheme as GUI.tag.WindowTag);
//@setting.sidebar = e.item.data.checked win.responsive = !win.responsive;
//@toggleSidebar e.item.data.checked return;
case `${this.name}-nav`: case `${this.name}-nav`:
return this.registry("nav", data.checked); this.setting.nav = data.checked;
} }
} }
//@setting.nav = e.item.data.checked //@setting.nav = e.item.data.checked
@ -645,7 +717,7 @@ namespace OS {
cut: true, cut: true,
files: this.view.selectedFiles.map(x => x.path.asFileHandle()), files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
}; };
return this.notify(__("{0} files cut", this.clipboard.files.length)); return this.toast(__("{0} files cut", this.clipboard.files.length));
case `${this.name}-copy`: case `${this.name}-copy`:
if (!file) { if (!file) {
@ -655,7 +727,7 @@ namespace OS {
cut: false, cut: false,
files: this.view.selectedFiles.map(x => x.path.asFileHandle()), files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
}; };
return this.notify( return this.toast(
__("{0} files copied", this.clipboard.files.length) __("{0} files copied", this.clipboard.files.length)
); );
@ -754,8 +826,7 @@ namespace OS {
.publish() .publish()
.then((r) => { .then((r) => {
return this.notify( return this.notify(
__("Shared url: {0}", r.result) __("Shared url: {0}", r.result));
);
}) })
.catch((e) => { .catch((e) => {
return this.error( return this.error(

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE", "author": "Xuan Sang LE",
"email": "xsang.le@gmail.com" "email": "xsang.le@gmail.com"
}, },
"version":"0.1.7-b", "version":"0.1.10-b",
"category":"System", "category":"System",
"iconclass":"fa fa-hdd-o", "iconclass":"fa fa-hdd-o",
"mimes":["dir"], "mimes":["dir"],

View File

@ -1,19 +1,17 @@
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="400"> <afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="500" responsive="true">
<afx-tile data-id="container" name="vbox" dir="column">
<afx-tile data-height = "35" min-width="120" data-width="180" data-id = "nav-bar" name="hbox" dir="row">
<afx-list-view data-id = "favouri" dropdown="true"></afx-list-view>
</afx-tile>
<afx-resizer data-id="resizer" dir="column" disable="true" data-width="3" data-height="0"></afx-resizer>
<afx-vbox> <afx-vbox>
<afx-hbox data-height = "23" data-id = "nav-bar"> <afx-hbox data-width="120" data-height="35" data-id="nav-bar">
<afx-button data-width = "23" data-id = "btback" iconclass = "fa fa-arrow-left"></afx-button> <afx-tab-bar data-id = "path-nav" dir="row" ></afx-tab-bar>
<input type = "text" data-id = "navinput"></input> <afx-button data-width = "40" data-id = "btback" iconclass = "fa fa-arrow-up"></afx-button>
<div data-width = "2"></div> <afx-button data-width = "40" data-id = "btgrid" iconclass = "fa fa-th"></afx-button>
<afx-button data-width = "23" data-id = "btgrid" iconclass = "fa fa-th"></afx-button> <afx-button data-width = "40" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
<div data-width = "2"></div>
<afx-button data-width = "23" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
</afx-hbox> </afx-hbox>
<div data-height="5"></div>
<afx-hbox>
<afx-list-view data-id = "favouri" data-width = "150" min-width="100">
</afx-list-view>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-file-view data-id = "fileview"></afx-file-view> <afx-file-view data-id = "fileview"></afx-file-view>
</afx-hbox>
</afx-vbox> </afx-vbox>
</afx-tile>
</afx-app-window> </afx-app-window>

View File

@ -5,6 +5,7 @@ This application is icluded in the AntOS delivery
and cannot be removed/uinstalled by regular user and cannot be removed/uinstalled by regular user
## Change logs ## Change logs
- 0.2.8-b: use system css variables in theme
- 0.2.7-b: only launch application - 0.2.7-b: only launch application
- 0.2.6-b: improve install process - 0.2.6-b: improve install process
- v0.2.5-b: add README.md - v0.2.5-b: add README.md

View File

@ -5,23 +5,7 @@ afx-app-window[data-id="marketplace-win"] afx-list-view[data-id='repo'] div.list
afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] div[data-id="desc-container"]{ afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] div[data-id="desc-container"]{
overflow-y: auto; overflow-y: auto;
overflow-x: none; overflow-x: hidden;
}
afx-app-window[data-id="marketplace-win"] afx-vbox[data-id='container'] afx-hbox {
padding-left: 10px;
}
afx-app-window[data-id="marketplace-win"] afx-label[data-id='appname'] i.label-text{
font-weight: bold;
font-size: 20px;
padding: 10px;
}
afx-app-window[data-id="marketplace-win"] div[data-id='appname']:before {
content: "\f085";
font-family: "FontAwesome";
font-size: 25px;
font-style: normal;
margin-right: 10px;
} }
afx-app-window[data-id="marketplace-win"] p[data-id='app-desc'] { afx-app-window[data-id="marketplace-win"] p[data-id='app-desc'] {
text-align: justify; text-align: justify;
@ -38,9 +22,11 @@ afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] {
padding-left:10px; padding-left:10px;
display: table; display: table;
margin: 0; margin: 0;
font-size: 13px;
} }
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] { afx-app-window[data-id="marketplace-win"] afx-tab-bar {
border-bottom: 1px solid #afafaf; border-top: 1px solid var(--border-quaternary);
border-bottom: 1px solid var(--border-quaternary);
} }
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] input{ afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] input{
border: 0; border: 0;
@ -48,13 +34,14 @@ afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="search-container"] i
} }
afx-app-window[data-id="marketplace-win"] div[data-id="searchicon"]:before{ afx-app-window[data-id="marketplace-win"] div[data-id="searchicon"]:before{
content: "\f002"; content: "\f002";
display: block;
background-color:transparent; background-color:transparent;
color:#afafaf; color: var(--text-disable);
font-family: "FontAwesome"; font-family: "FontAwesome";
padding-top: 3px; padding-left:5px;
padding-left:3px; display: flex;
/* font-size: 25px; */ flex-direction: column;
justify-content: center;
height: 100%;
} }
afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] li{ afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] li{
padding:0; padding:0;
@ -70,9 +57,13 @@ afx-app-window[data-id="marketplace-win"] ul[data-id='app-detail'] span{
afx-app-window[data-id="marketplace-win"] span.info-header{ afx-app-window[data-id="marketplace-win"] span.info-header{
font-weight: bold; font-weight: bold;
} }
afx-app-window[data-id="marketplace-win"] span.info-detail
{
word-break: break-word;
}
afx-app-window[data-id="marketplace-win"] afx-label[data-id="vstat"] { afx-app-window[data-id="marketplace-win"] afx-label[data-id="vstat"] {
font-size: 11px; font-size: 11px;
color: chocolate; color: var(--text-warning);
} }
afx-app-window[data-id="marketplace-win"] p.stat { afx-app-window[data-id="marketplace-win"] p.stat {
margin: 0; margin: 0;
@ -83,3 +74,77 @@ afx-app-window[data-id = "repository-dialog-win"] afx-list-view[data-id="repo-li
font-size: 11px; font-size: 11px;
font-style: italic; font-style: italic;
} }
afx-app-window[data-id="marketplace-win"] afx-tab-bar[data-id="catlist"] i.label-text
{
font-weight: bold;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container {
margin: 0 auto;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul
{
display: flex;
flex-flow: row wrap;
justify-content: center;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul li{
/*display: block;*/
width: 90px;
text-align: center;
font-size: 40px;
/*justify-content: normal !important;*/
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] i{
display: block;
width: 100%;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] i.label-text{
word-break: break-word;
font-size: 14px;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] i.icon-style {
width: 40px;
height: 40px;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] afx-label span
{
flex-direction: column;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul li:hover {
background-color: transparent;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"]> div.list-container > ul .afx-list-item li
{
background-color: transparent;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul .afx-list-item li:hover
{
background-color: var(--item-bg-hover);
color: var(--text-primary);
border-radius: 10px;
}
afx-app-window[data-id="marketplace-win"] afx-list-view[data-id="applist"] > div.list-container > ul .afx-list-item li.selected
{
background-color: var(--item-bg-active);
color:var(--text-tertiary);
border-radius: 10px;
}
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="app-header"] afx-button button
{
border-radius: 0;
}
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="app-header"]
{
border-radius: 0;
border-bottom: 1px solid var(--border-quaternary);
}
afx-app-window[data-id="marketplace-win"] afx-hbox[data-id="app-header"] afx-button[data-id="appname"] i.label-text
{
font-weight: bold;
}

View File

@ -24,9 +24,9 @@ namespace OS {
private installdir: string; private installdir: string;
private apps_meta: GenericObject<any>; private apps_meta: GenericObject<any>;
private applist: GUI.tag.ListViewTag; private applist: GUI.tag.ListViewTag;
private catlist: GUI.tag.ListViewTag; private catlist: GUI.tag.TabBarTag;
private container: GUI.tag.VBoxTag; private container: GUI.tag.VBoxTag;
private appname: GUI.tag.LabelTag; private appname: GUI.tag.ButtonTag;
private appdetail: HTMLUListElement; private appdetail: HTMLUListElement;
private appdesc: HTMLParagraphElement; private appdesc: HTMLParagraphElement;
private btinstall: GUI.tag.ButtonTag; private btinstall: GUI.tag.ButtonTag;
@ -39,21 +39,37 @@ namespace OS {
} }
main(): void { main(): void {
const stack_panel = this.find("stack-panel") as GUI.tag.StackPanelTag;
this.container = this.find("container") as GUI.tag.VBoxTag;
this.appname = this.find("appname") as GUI.tag.ButtonTag;
this.appdesc = this.find("app-desc") as HTMLParagraphElement;
this.appdetail = this.find("app-detail") as HTMLUListElement;
this.btinstall = this.find("bt-install") as GUI.tag.ButtonTag;
this.btremove = this.find("bt-remove") as GUI.tag.ButtonTag;
this.btexec = this.find("bt-exec") as GUI.tag.ButtonTag;
this.searchbox = this.find("searchbox") as HTMLInputElement;
this.appname.onbtclick = (_) => {
this.applist.selected = -1;
stack_panel.navigateBack();
}
this.installdir = this.systemsetting.system.pkgpaths.user; this.installdir = this.systemsetting.system.pkgpaths.user;
// test repository // test repository
this.apps_meta = {}; this.apps_meta = {};
this.applist = this.find("applist") as GUI.tag.ListViewTag; this.applist = this.find("applist") as GUI.tag.ListViewTag;
this.catlist = this.find("catlist") as GUI.tag.ListViewTag; this.catlist = this.find("catlist") as GUI.tag.TabBarTag;
this.applist.onlistselect = (e) => { this.applist.onlistselect = (e) => {
const data = e.data.item.data; const data = e.data.item.data;
return this.appDetail(data); this.appDetail(data);
stack_panel.navigateNext();
}; };
this.catlist.onlistselect = (e) => { this.catlist.ontabselect = (e) => {
const selected = this.catlist.selected; const selected = this.catlist.selected as number;
if(selected < 0) if(selected < 0)
return; return;
if(selected === 0) if(selected === 0)
{ {
return this.resetAppList(); return this.resetAppList();
@ -93,14 +109,7 @@ namespace OS {
this.applist.data = result; this.applist.data = result;
}; };
this.container = this.find("container") as GUI.tag.VBoxTag;
this.appname = this.find("appname") as GUI.tag.LabelTag;
this.appdesc = this.find("app-desc") as HTMLParagraphElement;
this.appdetail = this.find("app-detail") as HTMLUListElement;
this.btinstall = this.find("bt-install") as GUI.tag.ButtonTag;
this.btremove = this.find("bt-remove") as GUI.tag.ButtonTag;
this.btexec = this.find("bt-exec") as GUI.tag.ButtonTag;
this.searchbox = this.find("searchbox") as HTMLInputElement;
$(this.container).css("visibility", "hidden"); $(this.container).css("visibility", "hidden");
this.btexec.onbtclick = (_e) => { this.btexec.onbtclick = (_e) => {
const el = this.applist.selectedItem; const el = this.applist.selectedItem;
@ -117,10 +126,10 @@ namespace OS {
try { try {
if (this.btinstall.data.dirty) { if (this.btinstall.data.dirty) {
await this.updatePackage(); await this.updatePackage();
return this.notify(__("Package updated")); return this.toast(__("Package updated"));
} }
const n = await this.remoteInstall(); const n = await this.remoteInstall();
return this.notify(__("Package installed: {0}", n)); return this.toast(__("Package installed: {0}", n));
} catch (error) { } catch (error) {
return this.error(error.toString(), error); return this.error(error.toString(), error);
} }
@ -129,7 +138,7 @@ namespace OS {
this.btremove.onbtclick = async () => { this.btremove.onbtclick = async () => {
try { try {
await this.uninstall(); await this.uninstall();
return this.notify(__("Packaged uninstalled")); return this.toast(__("Packaged uninstalled"));
} catch (e) { } catch (e) {
return this.error(e.toString(), e); return this.error(e.toString(), e);
} }
@ -173,14 +182,18 @@ namespace OS {
switch (e.which) { switch (e.which) {
case 37: case 37:
return e.preventDefault(); return e.preventDefault();
/*
case 38: case 38:
this.applist.selectPrev(); this.applist.selectPrev();
return e.preventDefault(); return e.preventDefault();
*/
case 39: case 39:
return e.preventDefault(); return e.preventDefault();
/*
case 40: case 40:
this.applist.selectNext(); this.applist.selectNext();
return e.preventDefault(); return e.preventDefault();
*/
case 13: case 13:
return e.preventDefault(); return e.preventDefault();
default: default:
@ -326,7 +339,7 @@ namespace OS {
iconclass: "bi bi-gear-wide" iconclass: "bi bi-gear-wide"
}); });
}); });
this.catlist.data = cat_list_data; this.catlist.items = cat_list_data;
this.catlist.selected = 0; this.catlist.selected = 0;
} }
private add_meta_from(k:string, v: API.PackageMetaType) private add_meta_from(k:string, v: API.PackageMetaType)
@ -375,6 +388,8 @@ namespace OS {
this.appname.text = d.name; this.appname.text = d.name;
const status = this.find("vstat") as GUI.tag.LabelTag; const status = this.find("vstat") as GUI.tag.LabelTag;
status.text = ""; status.text = "";
$(this.appdesc).empty();
$(this.appdetail).empty();
if (d.description) { if (d.description) {
d.description d.description
.asFileHandle() .asFileHandle()
@ -386,13 +401,11 @@ namespace OS {
); );
}) })
.catch((_e) => { .catch((_e) => {
this.notify( this.error(
__("Unable to read package description") __("Unable to read package description"), _e
); );
return $(this.appdesc).empty(); return $(this.appdesc).empty();
}); });
} else {
$(this.appdesc).empty();
} }
const pkgcache = this.systemsetting.system.packages; const pkgcache = this.systemsetting.system.packages;
this.btinstall.text = "__(Install)"; this.btinstall.text = "__(Install)";
@ -423,7 +436,6 @@ namespace OS {
$(this.btexec).hide(); $(this.btexec).hide();
} }
$(this.appdetail).empty();
for (let k in d) { for (let k in d) {
const v = d[k]; const v = d[k];
if (k !== "name" && k !== "description" && k !== "domel") { if (k !== "name" && k !== "description" && k !== "domel") {
@ -432,7 +444,7 @@ namespace OS {
.append( .append(
$("<span class= 'info-header'>").html(k) $("<span class= 'info-header'>").html(k)
) )
.append($("<span>").html(v)) .append($("<span class = 'info-detail'>").html(v))
); );
} }
} }
@ -455,7 +467,7 @@ namespace OS {
}, },
], ],
onchildselect: ( onchildselect: (
e: GUI.TagEventType<GUI.tag.MenuEventData> e: GUI.TagEventType<GUI.tag.StackMenuEventData>
) => { ) => {
return this.menuOptionsHandle(e.data.item.data.id); return this.menuOptionsHandle(e.data.item.data.id);
}, },
@ -475,7 +487,7 @@ namespace OS {
case "install": case "install":
this.localInstall() this.localInstall()
.then((n) => { .then((n) => {
return this.notify( return this.toast(
__("Package installed: {0}", n) __("Package installed: {0}", n)
); );
}) })
@ -589,7 +601,7 @@ namespace OS {
const dep = this.checkDependencies(pkgname); const dep = this.checkDependencies(pkgname);
if (dep.notfound.size != 0) { if (dep.notfound.size != 0) {
this.openDialog("TextDialog", { this.openDialog("TextDialog", {
disable: true, disable: false,
title: __("Unresolved dependencies"), title: __("Unresolved dependencies"),
value: __( value: __(
"Unable to install: The package `{0}` depends on these packages, but they are not found:\n{1}", "Unable to install: The package `{0}` depends on these packages, but they are not found:\n{1}",
@ -722,7 +734,7 @@ namespace OS {
if (r.error) { if (r.error) {
throw __("Cannot uninstall package: {0}", r.error).__(); throw __("Cannot uninstall package: {0}", r.error).__();
} }
this.notify(__("Package uninstalled")); this.toast(__("Package uninstalled"));
// stop all the services if any // stop all the services if any
if (app.services) { if (app.services) {
for (let srv of Array.from(app.services)) { for (let srv of Array.from(app.services)) {
@ -775,7 +787,7 @@ namespace OS {
} }
this.bulkUninstall([...dep.uninstall]) this.bulkUninstall([...dep.uninstall])
.then((_b) => { .then((_b) => {
this.notify(__("Uninstall successfully")); this.toast(__("Uninstall successfully"));
}) })
.catch((err) => { .catch((err) => {
this.error(__("Unable to uninstall package(s): {0}", err.toString()), err); this.error(__("Unable to uninstall package(s): {0}", err.toString()), err);

View File

@ -7,7 +7,7 @@
"author": "Xuan Sang LE", "author": "Xuan Sang LE",
"email": "xsang.le@gmail.com" "email": "xsang.le@gmail.com"
}, },
"version":"0.2.7-b", "version":"0.2.8-b",
"category":"System", "category":"System",
"iconclass":"fa fa-shopping-bag", "iconclass":"fa fa-shopping-bag",
"mimes":["none"], "mimes":["none"],

View File

@ -1,31 +1,39 @@
<afx-app-window data-id = "marketplace-win" apptitle="MarketPlace" width="650" height="400"> <afx-app-window data-id = "marketplace-win" apptitle="MarketPlace" width="650" height="500">
<afx-hbox > <afx-vbox >
<afx-vbox data-width = "350"> <afx-stack-panel data-id = "stack-panel" dir = "column" tabbarheight= "40">
<afx-hbox data-height= "23" data-id="search-container">
<afx-vbox>
<afx-hbox data-height= "30" data-id="search-container">
<div data-width="17" data-id="searchicon"></div> <div data-width="17" data-id="searchicon"></div>
<input data-id = "searchbox" ></input> <input data-id = "searchbox" ></input>
</afx-hbox> </afx-hbox>
<afx-hbox> <afx-tab-bar data-id = "catlist" data-height="45"></afx-tab-bar>
<afx-list-view data-id = "catlist" dropdown = "false" data-width="35%"></afx-list-view>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-list-view data-id = "applist" dropdown = "false"></afx-list-view> <afx-list-view data-id = "applist" dropdown = "false"></afx-list-view>
</afx-hbox>
</afx-vbox> </afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-hbox>
<afx-vbox data-id = "container"> <afx-vbox data-id = "container">
<afx-label data-id = "appname" data-height = "45"></afx-label> <afx-hbox data-height = "35" data-id="app-header">
<afx-hbox data-height = "50"> <afx-button data-id = "appname" iconclass = "bi bi-backspace-fill"></afx-button>
<div style = "text-align:left;"> <div style = "display: flex;justify-content: flex-end;">
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button> <afx-button data-id = "bt-remove" text = "__(Uninstall)" iconclass = "bi bi-trash-fill"></afx-button>
<afx-button data-id = "bt-exec" text = "__(Launch)"></afx-button> <afx-button data-id = "bt-exec" text = "__(Launch)" iconclass = "fa fa-cog"></afx-button>
<afx-button data-id = "bt-install" text = "__(Install)" ></afx-button> <afx-button data-id = "bt-install" text = "__(Install)" iconclass = "bi bi-cloud-download-fill" ></afx-button>
<p class="stat"><afx-label data-id="vstat"></afx-label></p>
</div> </div>
</afx-hbox> </afx-hbox>
<p class="stat" data-height="15"><afx-label data-id="vstat"></afx-label></p>
<div data-id="desc-container"> <div data-id="desc-container">
<p data-id = "app-desc"></p> <p data-id = "app-desc"></p>
<ul data-id = "app-detail"></ul> <ul data-id = "app-detail"></ul>
</div> </div>
</afx-vbox> </afx-vbox>
</afx-hbox>
</afx-stack-panel>
</afx-hbox> </afx-hbox>
</afx-app-window> </afx-app-window>

View File

@ -6,4 +6,5 @@ It has very barebone features: open/edit/save text file.
Text/Code editor with fancy features can be optionally installed via the Market Place Text/Code editor with fancy features can be optionally installed via the Market Place
## Change logs ## Change logs
- v0.1.2-b: use system css variables in theme
- v0.1.1-b: update README - v0.1.1-b: update README

View File

@ -3,5 +3,5 @@ afx-app-window[data-id="NotePad"] textarea[data-id="editor"]
margin: 0; margin: 0;
padding:10px; padding:10px;
border: 0; border: 0;
background-color: transparent; background-color: var(--background-tertiary);
} }

View File

@ -7,7 +7,7 @@
"author": "Xuan Sang LE", "author": "Xuan Sang LE",
"email": "mrsang@iohub.dev" "email": "mrsang@iohub.dev"
}, },
"version": "0.1.1-b", "version": "0.1.2-b",
"category": "Utility", "category": "Utility",
"iconclass": "bi bi-pen", "iconclass": "bi bi-pen",
"mimes": [ "mimes": [

View File

@ -89,7 +89,7 @@ namespace OS {
this.applist.buttons = [ this.applist.buttons = [
{ {
text: "+", iconclass: "bi bi-plus",
onbtclick: () => { onbtclick: () => {
const apps = (() => { const apps = (() => {
const result = []; const result = [];
@ -128,7 +128,7 @@ namespace OS {
}, },
}, },
{ {
text: "-", iconclass: "bi bi-dash",
onbtclick: () => { onbtclick: () => {
const item = this.applist.selectedItem; const item = this.applist.selectedItem;
if (!item) { if (!item) {

View File

@ -67,7 +67,7 @@ namespace OS {
this.wplist.buttons = [ this.wplist.buttons = [
{ {
text: "+", iconclass: "bi bi-plus",
onbtclick: (e) => { onbtclick: (e) => {
return this.parent return this.parent
.openDialog("FileDialog", { .openDialog("FileDialog", {

View File

@ -10,5 +10,7 @@ In case of system anormaly after the modification, the system settings can be re
by simply removing the setting file by simply removing the setting file
## Change logs ## Change logs
- v0.1.5-b: fix VFS setting dialog bugs
- v0.1.4-b: use system css variables in theme
- v0.1.2-b: minor bug fix on UI - v0.1.2-b: minor bug fix on UI
- v0.1.2-b: add README - v0.1.2-b: add README

View File

@ -47,7 +47,7 @@ namespace OS {
this.applist = this.find("applist") as GUI.tag.ListViewTag; this.applist = this.find("applist") as GUI.tag.ListViewTag;
this.srvlist.buttons = [ this.srvlist.buttons = [
{ {
text: "+", iconclass: "bi bi-plus",
onbtclick: () => { onbtclick: () => {
let services = []; let services = [];
for (var k in setting.system.packages) { for (var k in setting.system.packages) {
@ -75,7 +75,7 @@ namespace OS {
}, },
}, },
{ {
text: "-", iconclass: "bi bi-dash",
onbtclick: () => { onbtclick: () => {
const item = this.srvlist.selectedItem; const item = this.srvlist.selectedItem;
if (!item) { if (!item) {
@ -90,7 +90,7 @@ namespace OS {
this.applist.buttons = [ this.applist.buttons = [
{ {
text: "+", iconclass: "bi bi-plus",
onbtclick: () => { onbtclick: () => {
const apps = (() => { const apps = (() => {
const result = []; const result = [];
@ -127,7 +127,7 @@ namespace OS {
}, },
}, },
{ {
text: "-", iconclass: "bi bi-dash",
onbtclick: () => { onbtclick: () => {
const item = this.applist.selectedItem; const item = this.applist.selectedItem;
if (!item) { if (!item) {

View File

@ -96,25 +96,18 @@ namespace OS {
} }
VFSSettingDialog.scheme = `\ VFSSettingDialog.scheme = `\
<afx-app-window width='250' height='180' apptitle = "__(Mount Points)"> <afx-app-window width='250' height='200' apptitle = "__(Mount Points)">
<afx-vbox> <afx-vbox padding="10">
<afx-hbox> <afx-hbox>
<div data-width = "10" ></div>
<afx-vbox> <afx-vbox>
<div data-height="10" ></div> <afx-input label= "__(Name)" data-id="txtName"></afx-input>
<afx-label data-height="30" text = "__(Name)" ></afx-label> <afx-input label= "__(Path)" data-id="txtPath"></afx-input>
<input type = "text" data-id= "txtName" ></input>
<div data-height="3" ></div>
<afx-label data-height="30" text = "__(Path)" ></afx-label>
<input type = "text" data-id= "txtPath" ></input>
<div data-height="10" ></div>
<afx-hbox data-height="30"> <afx-hbox data-height="30">
<div ></div> <div ></div>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button> <afx-button data-id = "btnOk" text = "__(Ok)" data-width = "content" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button> <afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "content" ></afx-button>
</afx-hbox> </afx-hbox>
</afx-vbox> </afx-vbox>
<div data-width = "10" ></div>
</afx-hbox> </afx-hbox>
</afx-vbox> </afx-vbox>
</afx-app-window>\ </afx-app-window>\
@ -144,7 +137,7 @@ namespace OS {
this.ppath = this.find("ppath") as GUI.tag.LabelTag; this.ppath = this.find("ppath") as GUI.tag.LabelTag;
this.mplist.buttons = [ this.mplist.buttons = [
{ {
text: "+", iconclass: "bi bi-plus",
onbtclick: async () => { onbtclick: async () => {
const d = await this.parent.openDialog( const d = await this.parent.openDialog(
new VFSSettingDialog(), new VFSSettingDialog(),
@ -162,7 +155,7 @@ namespace OS {
}, },
}, },
{ {
text: "-", iconclass: "bi bi-dash",
onbtclick: async () => { onbtclick: async () => {
const item = this.mplist.selectedItem; const item = this.mplist.selectedItem;
if (!item) { if (!item) {

View File

@ -1,32 +1,17 @@
afx-app-window[data-id = "setting-window"] afx-tab-container afx-tab-bar afx-list-view > div > ul li{
float:none;
border-radius: 0;
font-weight: bold;
padding-top:3px;
padding-bottom: 3px;
border:0;
}
afx-app-window[data-id = "setting-window"] afx-tab-bar{
border-right: 1px solid #cbcbcb;
}
afx-app-window[data-id = "setting-window"] afx-label.header{ afx-app-window[data-id = "setting-window"] afx-label.header i.label-text{
font-weight: bold; font-weight: bold;
} }
afx-app-window[data-id = "setting-window"] div.footer{
border-right: 1px solid #cbcbcb;
}
/*APPEARANCE*/ /*APPEARANCE*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] div[data-id = "wp-preview"]{ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] div[data-id = "wp-preview"]{
display: block; display: block;
border:1px solid #cbcbcb; border:1px solid var(--border-quaternary);
border-radius: 10px; border-radius: 10px;
} }
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-list-view[data-id="wplist"] afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-list-view[data-id="wplist"]
{ {
border:1px solid #cbcbcb; border:1px solid var(--border-quaternary);
padding:2px;
} }
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-resizer{ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-resizer{
border:0; border:0;
@ -35,7 +20,7 @@ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-re
/*VFS*/ /*VFS*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-list-view[data-id="mplist"] afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-list-view[data-id="mplist"]
{ {
border: 1px solid #cbcbcb; border: 1px solid var(--border-quaternary);
} }
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.btnsel button{ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.btnsel button{
@ -46,15 +31,15 @@ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.bt
/*LANGUAGES*/ /*LANGUAGES*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="locale"] afx-list-view[data-id="lglist"] afx-app-window[data-id = "setting-window"] afx-hbox[data-id="locale"] afx-list-view[data-id="lglist"]
{ {
border: 1px solid #cbcbcb; border: 1px solid var(--border-quaternary);
} }
/*STARTUP*/ /*STARTUP*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="startup"] afx-list-view afx-app-window[data-id = "setting-window"] afx-hbox[data-id="startup"] afx-list-view
{ {
border: 1px solid #cbcbcb; border: 1px solid var(--border-quaternary);
} }
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="app-services"] afx-list-view afx-app-window[data-id = "setting-window"] afx-hbox[data-id="app-services"] afx-list-view
{ {
border: 1px solid #cbcbcb; border: 1px solid var(--border-quaternary);
} }

View File

@ -98,14 +98,14 @@ namespace OS {
* @memberof Setting * @memberof Setting
*/ */
main(): void{ main(): void{
//this.containter = this.find("container") as GUI.tag.TabContainerTag; const containter = this.find("container") as GUI.tag.TabContainerTag;
new Setting.AppearanceHandle(this.find("appearance"), this); new Setting.AppearanceHandle(this.find("appearance"), this);
new Setting.VFSHandle(this.find("vfs"), this); new Setting.VFSHandle(this.find("vfs"), this);
new Setting.LocaleHandle(this.find("locale"), this); new Setting.LocaleHandle(this.find("locale"), this);
new Setting.StartupHandle(this.find("startup"), this); new Setting.StartupHandle(this.find("startup"), this);
new Setting.AppAndServiceHandle(this.find("app-services"), this); new Setting.AppAndServiceHandle(this.find("app-services"), this);
containter.selectedIndex = 0;
(this.find("btnsave") as GUI.tag.ButtonTag ).onbtclick = (e) => { (this.find("btnsave") as GUI.tag.ButtonTag ).onbtclick = (e) => {
this._api this._api
.setting() .setting()
@ -130,6 +130,17 @@ namespace OS {
); );
}); });
}; };
this.morphon(GUI.RESPONSIVE.MEDIUM, (fulfilled:boolean) => {
if(fulfilled)
{
containter.dir = "row";
}
else
{
containter.dir = "column";
}
});
} }
} }
Setting.singleton = true; Setting.singleton = true;

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE", "author": "Xuan Sang LE",
"email": "xsang.le@gmail.com" "email": "xsang.le@gmail.com"
}, },
"version":"0.1.3-b", "version":"0.1.5-b",
"category":"System", "category":"System",
"iconclass":"fa fa-wrench", "iconclass":"fa fa-wrench",
"mimes":["none"] "mimes":["none"]

View File

@ -1,28 +1,24 @@
<afx-app-window data-id = "setting-window" apptitle="Setting" width="650" height="400"> <afx-app-window data-id = "setting-window" apptitle="Setting" width="600" height="500" responsive="true">
<afx-vbox> <afx-vbox>
<afx-tab-container data-id = "container" dir = "row" tabbarwidth= "150"> <afx-tab-container data-id = "container" dir = "column" tabbarheight= "40" tabbarwidth="160">
<afx-hbox tabname="__(Appearance)" data-id="appearance" iconclass = "fa fa-paint-brush"> <afx-hbox tabname="__(Appearance)" data-id="appearance" iconclass = "fa fa-paint-brush">
<div data-width="10"></div> <div data-width="10"></div>
<afx-vbox> <afx-vbox>
<div data-height="5"></div> <afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="30"></afx-label>
<afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="23"></afx-label>
<afx-hbox> <afx-hbox>
<afx-list-view data-width="150" data-id="wplist"></afx-list-view> <afx-list-view data-width="150" data-id="wplist"></afx-list-view>
<afx-resizer data-width="2"></afx-resizer> <afx-resizer data-width="2"></afx-resizer>
<afx-vbox>
<div data-id = "wp-preview"></div> <div data-id = "wp-preview"></div>
</afx-hbox>
<div data-height="5"></div> <div data-height="5"></div>
<afx-hbox data-height="25"> <afx-hbox data-height="35">
<afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view> <afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
<div data-width="5"></div> <div data-width="5"></div>
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view> <afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
</afx-hbox> </afx-hbox>
</afx-vbox> <afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="30"></afx-label>
</afx-hbox> <afx-list-view data-height="35" data-id="theme-list" dropdown="true"></afx-list-view>
<div data-height="5"></div>
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="23"></afx-label>
<afx-list-view data-height="30" data-id="theme-list" dropdown="true"></afx-list-view>
<div data-height="5"></div> <div data-height="5"></div>
</afx-vbox> </afx-vbox>
<div data-width="10"></div> <div data-width="10"></div>
@ -31,22 +27,19 @@
<afx-hbox data-id="vfs" tabname = "__(VFS)" iconclass = "fa fa-inbox"> <afx-hbox data-id="vfs" tabname = "__(VFS)" iconclass = "fa fa-inbox">
<div data-width="10"></div> <div data-width="10"></div>
<afx-vbox> <afx-vbox>
<div data-height="5"></div> <afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="30"></afx-label>
<afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="23"></afx-label>
<afx-list-view data-id="mplist"></afx-list-view> <afx-list-view data-id="mplist"></afx-list-view>
<div data-height="5"></div> <afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="30"></afx-label>
<afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="23"></afx-label> <afx-hbox data-height = "40" >
<afx-hbox data-height = "25" >
<div data-width="16"></div> <div data-width="16"></div>
<afx-label data-id="dpath"></afx-label> <afx-label data-id="dpath"></afx-label>
<afx-button text="" iconclass = "fa fa-arrow-up" data-id="btndpath" data-width="20" class="btnsel"></afx-button> <afx-button text="" iconclass = "fa fa-arrow-up" data-id="btndpath" data-width="40" class="btnsel"></afx-button>
</afx-hbox> </afx-hbox>
<div data-height="5"></div> <afx-label text = "__(Local packages path)" iconclass = "fa fa-cube" class = "header" data-height="30"></afx-label>
<afx-label text = "__(Local packages path)" iconclass = "fa fa-cube" class = "header" data-height="23"></afx-label> <afx-hbox data-height = "40" >
<afx-hbox data-height = "25" >
<div data-width="16"></div> <div data-width="16"></div>
<afx-label data-id="ppath"></afx-label> <afx-label data-id="ppath"></afx-label>
<afx-button text="" data-id="btnppath" iconclass = "fa fa-arrow-up" data-width="20" class="btnsel"></afx-button> <afx-button text="" data-id="btnppath" iconclass = "fa fa-arrow-up" data-width="40" class="btnsel"></afx-button>
</afx-hbox> </afx-hbox>
<div data-height="10"></div> <div data-height="10"></div>
</afx-vbox> </afx-vbox>
@ -56,8 +49,7 @@
<afx-hbox data-id="locale" tabname = "__(Languages)"iconclass = "fa fa-globe"> <afx-hbox data-id="locale" tabname = "__(Languages)"iconclass = "fa fa-globe">
<div data-width="10"></div> <div data-width="10"></div>
<afx-vbox> <afx-vbox>
<div data-height="5"></div> <afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="30"></afx-label>
<afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="23"></afx-label>
<afx-list-view data-id="lglist"></afx-list-view> <afx-list-view data-id="lglist"></afx-list-view>
<div data-height="10"></div> <div data-height="10"></div>
</afx-vbox> </afx-vbox>
@ -67,10 +59,10 @@
<afx-hbox data-id="startup" tabname = "__(Startup)" iconclass = "fa fa-cog"> <afx-hbox data-id="startup" tabname = "__(Startup)" iconclass = "fa fa-cog">
<div data-width="10"></div> <div data-width="10"></div>
<afx-vbox> <afx-vbox>
<afx-label text = "__(Startup services)" iconclass = "fa fa-tasks" class = "header" data-height="23"></afx-label> <afx-label text = "__(Startup services)" iconclass = "fa fa-tasks" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="srvlist"></afx-list-view> <afx-list-view data-id="srvlist"></afx-list-view>
<div data-height="5"></div> <div data-height="5"></div>
<afx-label text = "__(Startup applications)" iconclass = "fa fa-adn" class = "header" data-height="23"></afx-label> <afx-label text = "__(Startup applications)" iconclass = "fa fa-adn" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="applist"></afx-list-view> <afx-list-view data-id="applist"></afx-list-view>
<div data-height="10"></div> <div data-height="10"></div>
</afx-vbox> </afx-vbox>
@ -80,10 +72,10 @@
<afx-hbox data-id="app-services" tabname = "__(Apps. and Services)" iconclass = "fa fa-adn"> <afx-hbox data-id="app-services" tabname = "__(Apps. and Services)" iconclass = "fa fa-adn">
<div data-width="10"></div> <div data-width="10"></div>
<afx-vbox> <afx-vbox>
<afx-label text = "__(Services)" iconclass = "fa fa-tasks" class = "header" data-height="23"></afx-label> <afx-label text = "__(Services)" iconclass = "fa fa-tasks" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="sys-srvlist"></afx-list-view> <afx-list-view data-id="sys-srvlist"></afx-list-view>
<div data-height="5"></div> <div data-height="5"></div>
<afx-label text = "__(Pinned applications)" iconclass = "fa fa-adn" class = "header" data-height="23"></afx-label> <afx-label text = "__(Pinned applications)" iconclass = "fa fa-adn" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="sys-applist"></afx-list-view> <afx-list-view data-id="sys-applist"></afx-list-view>
<div data-height="10"></div> <div data-height="10"></div>
</afx-vbox> </afx-vbox>
@ -91,10 +83,10 @@
</afx-hbox> </afx-hbox>
</afx-tab-container> </afx-tab-container>
<afx-hbox data-height="35"> <afx-hbox data-height="45">
<div data-width = "150" class = "footer"></div> <div></div>
<div style="text-align:right" > <div style="text-align:right" >
<afx-button text="__(Save)" data-id="btnsave" iconclass="fa fa-save" style="margin-right:10px;" ></afx-button> <afx-button text="__(Save)" data-id="btnsave" iconclass="fa fa-save" style="margin-right:5px;" ></afx-button>
</div> </div>
</afx-hbox> </afx-hbox>

View File

@ -1,6 +0,0 @@
# Syslog: System notification management and service
Provide system wise notification service (Push Notification)
## Change logs
-v0.1.2-b: add README

View File

@ -1,86 +0,0 @@
afx-overlay[data-id = "notifyzone"]{
/*opacity: 0.85;*/
overflow-y: auto;
overflow-x: hidden;
padding:3px;
margin: 0;
}
afx-overlay[data-id = "notifyzone"] afx-button button{
width: 100%;
border-radius: 0;
}
afx-list-view[data-id = "notifylist"]
{
padding:0;
}
afx-list-view[data-id = "notifylist"] > div.list-container > ul li{
border:1px solid #464646;
border-radius: 3px;
margin-bottom: 5px;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
padding-top: 5px;
padding-bottom: 5px;
}
afx-overlay[data-id = "feedzone"]{
overflow: hidden;
background-color:transparent;
right:5px;
margin: 0;
padding:0;
top:0;
}
afx-list-view[data-id = "notifeed"]
{
padding:0;
margin:0;
}
afx-list-view[data-id = "notifeed"] li{
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
border:1px solid #262626;
border-radius: 6px;
margin-bottom: 2px;
z-index: 99999;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
}
afx-app-window[data-id ='Syslog'] div[data-id ='container']{
overflow: auto;
}
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-error {
display: block;
}
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-error i::before {
color: chocolate;
}
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-time{
display: block;
padding-left: 10px;
}
afx-app-window[data-id ='Syslog'] .afx-bug-list-item-time i.label-text{
font-size: 10px;
font-style: italic;
}
afx-app-window[data-id ='Syslog'] afx-bug-list-item li.selected {
background-color: #116cd6;
color: white;
}
afx-app-window[data-id ='Syslog'] pre {
padding: 10px;
margin:0;
user-select: text;
cursor: text;
}
afx-app-window[data-id ='Syslog'] input{
height: 100%;
}

View File

@ -1,20 +0,0 @@
{
"app": "Syslog",
"pkgname": "Syslog",
"services": [
"Calendar",
"PushNotification"
],
"name": "System log",
"description": "Core services and system log",
"info": {
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com",
"credit": "dedicated to some one here",
"licences": "GPLv3"
},
"version": "0.1.2-b",
"category": "System",
"iconclass": "fa fa-bug",
"mimes": []
}

View File

@ -1,4 +1,4 @@
module_files = Calendar.js PushNotification.js Syslog.js module_files = SystemReport.js
libfiles = libfiles =
@ -7,5 +7,5 @@ cssfiles = main.css
copyfiles = package.json scheme.html README.md copyfiles = package.json scheme.html README.md
PKG_NAME=Syslog PKG_NAME=SystemReport
include ../pkg.mk include ../pkg.mk

View File

@ -0,0 +1,8 @@
# SystemReport: System notification management and service
Provide system wise notification service (Push Notification)
## Change logs
- v0.1.4-b: use system css variables in theme
- v0.1.3-b: Rename from Syslog to SystemReport, move all services to SystemServices package
- v0.1.2-b: add README

View File

@ -110,33 +110,33 @@ detail:
* *
* *
* @export * @export
* @class Syslog * @class SystemReport
* @extends {BaseApplication} * @extends {BaseApplication}
*/ */
export class Syslog extends BaseApplication { export class SystemReport extends BaseApplication {
private loglist: TAG.ListViewTag; private loglist: TAG.ListViewTag;
private logdetail: HTMLElement; private logdetail: HTMLElement;
private srv: PushNotification; private srv: PushNotification;
constructor(args: AppArgumentsType[]) { constructor(args: AppArgumentsType[]) {
super("Syslog", args); super("SystemReport", args);
} }
/** /**
* *
* *
* @memberof Syslog * @memberof SystemReport
*/ */
/** /**
* *
* *
* @memberof Syslog * @memberof SystemReport
*/ */
main(): void { main(): void {
this.loglist = this.find("loglist") as TAG.ListViewTag; this.loglist = this.find("loglist") as TAG.ListViewTag;
this.logdetail = this.find("logdetail"); this.logdetail = this.find("logdetail");
this._gui this._gui
.pushService("Syslog/PushNotification") .pushService("SystemServices/PushNotification")
.then((srv) => { .then((srv) => {
this.srv = srv as PushNotification; this.srv = srv as PushNotification;
@ -231,7 +231,7 @@ detail:
* *
* *
* @param {GenericObject<any>} log * @param {GenericObject<any>} log
* @memberof Syslog * @memberof SystemReport
*/ */
addLog(log: GenericObject<any>): void { addLog(log: GenericObject<any>): void {
this.loglist.push(log); this.loglist.push(log);
@ -241,7 +241,7 @@ detail:
* *
* *
* @returns {void} * @returns {void}
* @memberof Syslog * @memberof SystemReport
*/ */
cleanup(): void { cleanup(): void {
if (this.srv) { if (this.srv) {
@ -250,6 +250,6 @@ detail:
} }
} }
Syslog.singleton = true; SystemReport.singleton = true;
} }
} }

View File

@ -0,0 +1,36 @@
afx-app-window[data-id ='SystemReport'] div[data-id ='container']{
overflow: auto;
}
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-error {
display: block;
}
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-error i::before {
color: var(--text-error);
}
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-time{
display: block;
padding-left: 10px;
}
afx-app-window[data-id ='SystemReport'] .afx-bug-list-item-time i.label-text{
font-size: 10px;
font-style: italic;
}
/*
afx-app-window[data-id ='SystemReport'] afx-bug-list-item li.selected {
background-color: #116cd6;
color: white;
}*/
afx-app-window[data-id ='SystemReport'] pre {
padding: 10px;
margin:0;
user-select: text;
cursor: text;
}
afx-app-window[data-id ='SystemReport'] input{
height: 100%;
}

View File

@ -0,0 +1,15 @@
{
"app": "SystemReport",
"pkgname": "SystemReport",
"name": "System report",
"description": "System reports",
"info": {
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com",
"licences": "GPLv3"
},
"version": "0.1.4-b",
"category": "System",
"iconclass": "fa fa-bug",
"mimes": []
}

View File

@ -1,4 +1,4 @@
<afx-app-window data-id="Syslog" width='500' height='350' apptitle = "__(System error log)" > <afx-app-window data-id="SystemReport" width='600' height='450' apptitle = "__(System error log)" responsive="true">
<afx-hbox> <afx-hbox>
<afx-list-view data-id = "loglist" data-width="200"> </afx-list-view> <afx-list-view data-id = "loglist" data-width="200"> </afx-list-view>
<afx-resizer data-width = "2" ></afx-resizer> <afx-resizer data-width = "2" ></afx-resizer>
@ -7,11 +7,11 @@
<pre><code data-id="logdetail"></code></pre> <pre><code data-id="logdetail"></code></pre>
</div> </div>
<div data-height="10" ></div> <div data-height="10" ></div>
<afx-hbox style="text-align:right;" data-height = "27"> <afx-hbox style="text-align:right;" data-height = "35">
<afx-button data-width ="20" <afx-button data-width ="content"
tooltip = "ct:__(Clear all logs)" iconclass = "fa fa-trash-o" data-id = "btclean" ></afx-button> tooltip = "ct:__(Clear all logs)" iconclass = "fa fa-trash-o" data-id = "btclean" ></afx-button>
<input type = "text" data-id = "txturi" ></input> <input type = "text" data-id = "txturi" ></input>
<afx-button data-width ="80" text = "__(Report)" <afx-button data-width ="content" text = "__(Report)"
iconclass = "fa fa-bug" data-id = "btnreport" ></afx-button> iconclass = "fa fa-bug" data-id = "btnreport" ></afx-button>
<div data-width="10" ></div> <div data-width="10" ></div>
</afx-hbox> </afx-hbox>

View File

@ -26,9 +26,8 @@ namespace OS {
export class Calendar extends BaseService { export class Calendar extends BaseService {
constructor(args: AppArgumentsType[]) { constructor(args: AppArgumentsType[]) {
super("Calendar", args); super("Calendar", args);
//@iconclass = "fa fa-commenting"
this.text = ""; this.text = "";
this.iconclass = "fa fa-calendar"; this.iconclass = "bi bi-calendar3";
} }
init(): void { init(): void {
@ -36,11 +35,11 @@ namespace OS {
this.watch(1000, () => { this.watch(1000, () => {
const now = new Date(); const now = new Date();
this.text = now.toString(); this.text = now.toString();
(this.domel as GUI.tag.SimpleMenuEntryTag).text = this.text; this.update();
}); });
} }
awake(e: GUI.TagEventType<GUI.tag.MenuEventData>): void { awake(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void {
this.openDialog("CalendarDialog").then((d) => console.log(d)); this.openDialog("CalendarDialog").then((d) => console.log(d));
} }
// do nothing // do nothing

View File

@ -0,0 +1,11 @@
module_files = Calendar.js PushNotification.js
libfiles =
cssfiles = main.css
copyfiles = package.json README.md
PKG_NAME=SystemServices
include ../pkg.mk

View File

@ -31,12 +31,10 @@ namespace OS {
private cb: (e: JQuery.ClickEvent) => void; private cb: (e: JQuery.ClickEvent) => void;
private view: boolean; private view: boolean;
private mlist: TAG.ListViewTag; private mlist: TAG.ListViewTag;
private mfeed: TAG.ListViewTag;
private nzone: TAG.OverlayTag; private nzone: TAG.OverlayTag;
private fzone: TAG.OverlayTag;
logs: GenericObject<any>[]; logs: GenericObject<any>[];
logmon: Syslog; logmon: SystemReport;
/** /**
*Creates an instance of PushNotification. *Creates an instance of PushNotification.
@ -48,6 +46,7 @@ namespace OS {
this.iconclass = "fa fa-bars"; this.iconclass = "fa fa-bars";
this.cb = undefined; this.cb = undefined;
this.logs = []; this.logs = [];
this.text = __("Notification");
this.logmon = undefined; this.logmon = undefined;
} }
@ -69,9 +68,7 @@ namespace OS {
*/ */
main(): void { main(): void {
this.mlist = this.find("notifylist") as TAG.ListViewTag; this.mlist = this.find("notifylist") as TAG.ListViewTag;
this.mfeed = this.find("notifeed") as TAG.ListViewTag;
this.nzone = this.find("notifyzone") as TAG.OverlayTag; this.nzone = this.find("notifyzone") as TAG.OverlayTag;
this.fzone = this.find("feedzone") as TAG.OverlayTag;
(this.find("btclear") as TAG.ButtonTag).onbtclick = (e) => (this.find("btclear") as TAG.ButtonTag).onbtclick = (e) =>
(this.mlist.data = []); (this.mlist.data = []);
(this.find("bterrlog") as TAG.ButtonTag).onbtclick = (e) => (this.find("bterrlog") as TAG.ButtonTag).onbtclick = (e) =>
@ -82,18 +79,12 @@ namespace OS {
this.subscribe("info", (o) => this.pushout("INFO", o)); this.subscribe("info", (o) => this.pushout("INFO", o));
this.nzone.height = "100%"; this.nzone.height = "100%";
this.fzone.height = "100%";
$(this.nzone) $(this.nzone)
.css("right", 0) .css("right", 0)
.css("top", "0") .css("top", "0")
.css("bottom", "0") .css("bottom", "0")
.hide(); .hide();
$(this.fzone)
//.css("z-index", 99999)
.css("bottom", "0")
.css("bottom", "0")
.hide();
} }
/** /**
@ -104,7 +95,7 @@ namespace OS {
* @memberof PushNotification * @memberof PushNotification
*/ */
private showLogReport(): void { private showLogReport(): void {
this._gui.launch("Syslog", []); this._gui.launch("SystemReport", []);
} }
/** /**
@ -166,16 +157,7 @@ namespace OS {
* @memberof PushNotification * @memberof PushNotification
*/ */
private notifeed(d: GenericObject<any>): void { private notifeed(d: GenericObject<any>): void {
let timer: number; GUI.toast(d,{timeout: 3, location: GUI.ANCHOR.NORTH});
this.mfeed.unshift(d);
$(this.fzone).show();
timer = window.setTimeout(() => {
this.mfeed.delete(d.domel);
if (this.mfeed.data.length === 0) {
$(this.fzone).hide();
}
return clearTimeout(timer);
}, 3000);
} }
/** /**
@ -184,7 +166,7 @@ namespace OS {
* @param {GUI.TagEventType} evt * @param {GUI.TagEventType} evt
* @memberof PushNotification * @memberof PushNotification
*/ */
awake(evt: GUI.TagEventType<GUI.tag.MenuEventData>): void { awake(evt: GUI.TagEventType<GUI.tag.StackMenuEventData>): void {
if (this.view) { if (this.view) {
$(this.nzone).hide(); $(this.nzone).hide();
} else { } else {
@ -223,16 +205,12 @@ namespace OS {
const scheme = `\ const scheme = `\
<div> <div>
<afx-overlay data-id = "notifyzone" width = "250px"> <afx-overlay data-id = "notifyzone" width = "250px">
<afx-hbox data-height="30"> <afx-hbox data-height="35">
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button> <afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button> <afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "40"></afx-button>
</afx-hbox> </afx-hbox>
<afx-list-view data-id="notifylist"></afx-list-view> <afx-list-view data-id="notifylist"></afx-list-view>
</afx-overlay> </afx-overlay>
<afx-overlay data-id = "feedzone" width = "250">
<afx-list-view data-id = "notifeed">
</afx-list-view>
</afx-overlay>
</div>\ </div>\
`; `;
} }

View File

@ -0,0 +1,9 @@
## System services
Basic services:
- Push notification
- Calendar service
### change logs
- 0.1.1-a: use system css variables in theme

View File

@ -0,0 +1,27 @@
afx-overlay[data-id = "notifyzone"]{
/*opacity: 0.85;*/
overflow-y: auto;
overflow-x: hidden;
padding:3px;
margin: 0;
}
afx-overlay[data-id = "notifyzone"] afx-button button{
width: 100%;
border-radius: 0;
}
afx-list-view[data-id = "notifylist"]
{
padding:0;
}
afx-list-view[data-id = "notifylist"] > div.list-container > ul li{
border:1px solid var(--border-quaternary);
background-color: var(--item-bg-odd);
border-radius: 3px;
margin-bottom: 5px;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
padding-top: 5px;
padding-bottom: 5px;
}

View File

@ -0,0 +1,15 @@
{
"pkgname": "SystemServices",
"services": ["PushNotification", "Calendar"],
"name": "System services",
"description": "System services",
"info": {
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com",
"licences": "GPLv3"
},
"version": "0.1.1-a",
"category": "System",
"iconclass": "fa fa-cog",
"mimes": []
}

View File

@ -1,56 +0,0 @@
afx-app-window div.afx-window-wrapper{
border:1px solid #262626;
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
border-radius: 0px;
background-color:#363636;
}
afx-app-window.unactive > div.afx-window-wrapper{
background-color: #464646;
}
afx-app-window ul.afx-window-top{
height: 20px;
border-bottom: 1px solid #262626;
}
afx-app-window div.afx-window-overlay {
top: 22px;
}
afx-app-window ul.afx-window-top li{
margin-left: 3px;
margin-top:4px;
}
afx-app-window ul.afx-window-top .afx-window-close,.afx-window-minimize,.afx-window-maximize{
width: 10px;
height: 10px;
border-radius: 0;
}
afx-app-window ul li.afx-window-close{
background-color: #Fc605b;
float:left;
}
afx-app-window ul li.afx-window-minimize{
background-color: #fec041;
float:left;
}
afx-app-window ul li.afx-window-maximize{
background-color: #35cc4b;
float:left;
}
afx-app-window ul li.afx-window-title{
margin-top:1px;
text-align: center;
}
afx-app-window div.afx-window-content
{
background-color:#363636;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
afx-app-window.unactive div.afx-window-content
{
background-color:#464646;
}

View File

@ -1,22 +0,0 @@
afx-button button{
padding: 4px;
border: 1px solid #262626;
background-color: #464646;
color: white;
border-radius: 3px;
font-family: "Ubuntu";
}
afx-button button[disabled]{
color: #868686;
}
afx-button i.icon-style {
width: 16px;
height: 16px;
display: inline-block;
}
afx-button button:active, afx-button button.btactive {
background-color: #2786F3;
color: white;
border: 1px solid #363636;
}

View File

@ -1,53 +0,0 @@
afx-calendar-view div{
text-align: center;
}
afx-calendar-view > div {
font-weight: bold;
}
afx-calendar-view i.prevmonth, afx-calendar-view i.nextmonth{
display: inline-block;
width: 16px;
height: 16px;
cursor: pointer;
}
afx-calendar-view i.prevmonth{
margin-right: 20px;
}
afx-calendar-view i.nextmonth{
margin-left: 20px;
}
afx-calendar-view i.prevmonth:before{
content: "\f104";
font-family: "FontAwesome";
font-size: 16px;
font-style: normal;
/*position:absolute;
top:25%;
right:5px;*/
}
afx-calendar-view i.nextmonth:before{
content: "\f105";
font-family: "FontAwesome";
font-size: 16px;
font-style: normal;
margin-left: 20px;
/*position:absolute;
top:25%;
right:5px;*/
}
afx-calendar-view afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell
{
background-color: transparent;
}
afx-calendar-view afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell.afx-grid-cell-selected
{
background-color: #116cd6;
color:white;
border-radius: 6px;
}

View File

@ -1,35 +0,0 @@
afx-apps-dock{
bottom: 0px;
top: 0px;
width: 32px;
background-color:#363636;
padding:0;
padding-top: 0;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border:1px solid #262626;
box-shadow: none;
}
afx-apps-dock afx-button button{
width: 32px;
height: 32px;
font-size: 19px;
margin-bottom: 0;
padding:0px;
background-color: transparent;
border:0;
border-radius: 0;
}
afx-apps-dock afx-button afx-label i.icon-style{
width: 24px;
height: 24px;
margin-left: 2px;
margin-bottom: 0px;
border:0;
}
afx-apps-dock afx-button.selected > button {
background-color: #464646;
color: white;
border: 1px solid #464646;
}

View File

@ -1,24 +0,0 @@
afx-grid-view afx-grid-row:nth-child(even) afx-grid-cell
{
background-color: #3b3b3b;
}
afx-grid-view afx-grid-row:nth-child(odd) afx-grid-cell
{
background-color: #363636;
}
afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell
{
background-color: #116cd6;
color:white;
}
afx-grid-view afx-grid-row.afx-grid-row-selected afx-grid-cell.afx-grid-cell-selected
{
font-weight: normal;
}
afx-grid-view .grid_row_header afx-grid-cell{
border:0;
}

View File

@ -1,90 +0,0 @@
afx-list-view > div.list-container > ul li{
padding: 5px;
padding-top:3px;
padding-bottom: 3px;
padding-right: 10px;
background-color: #363636;
}
afx-list-view > div.list-container > ul afx-list-item:nth-child(even) li{
background-color:#3b3b3b;
}
afx-list-view i.closable{
width: 16px;
height: 16px;
}
afx-list-view i.closable:before{
font-size: 10px;
margin-left: 10px;
color: #868686;
}
afx-list-view > div.list-container > ul li > i {
margin-right: 3px;
}
afx-list-view > div.list-container > ul > afx-list-item > li.selected{
background-color: #116cd6;
color:white;
}
afx-list-view.dropdown > div.list-container > ul{
border:1px solid #262626;
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
border-radius: 3px;
max-height: 150px;
background-color: #363636;
border-top-left-radius: 0px;
z-index: 10;
}
afx-list-view.dropdown div.list-container div{
color: white;
padding-top:3px;
padding-bottom: 3px;
border:1px solid #262626;
border-radius: 3px;
background-color: transparent;
height: 17px;
}
afx-list-view.dropdown div.list-container div > afx-label{
padding-left:3px;
}
afx-list-view.dropdown div.list-container div:before {
content: "\f107";
font-family: "FontAwesome";
font-size: 11px;
font-style: normal;
position: absolute;
top:25%;
right: 5px;
}
afx-list-view.dropdown > div.list-container > ul li:hover{
background-color: #464646;
}
afx-list-view ul.complex-content{
padding: 0;
margin: 0;
background-color: transparent;
}
afx-list-view ul.complex-content li{
padding:0;
background-color: transparent;
color:#5e5f59;
list-style: none;
}
afx-list-view > div.list-container > ul li.selected ul.complex-content li{
color:white;
}
afx-list-view div.button_container afx-button{
margin-right: 2px;
}
afx-list-view div.button_container afx-button button{
border-radius: 0;
padding-left:5px;
padding-top:1px;
padding-bottom: 1px;
padding-right: 5px;
}

View File

@ -1,63 +0,0 @@
afx-menu afx-switch span{
padding-top: 3px;
font-size: 16px;
height: 19px;
}
afx-menu span.shortcut{
text-align: right;
margin-left: 3px;
}
afx-menu li:hover > a afx-switch span:before{
color:white;
}
afx-menu afx-menu ul {
padding: 0;
border:1px solid #262626;
border-radius: 5px;
border-top-left-radius: 0px;
/*box-shadow: 2px 2px 2px #cbcbcb;*/
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
background-color: #363636;
}
afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{
padding-left: 5px;
padding-right: 5px;
}
afx-menu afx-menu li{
min-width: 150px;
width: calc(100% - 10px);
}
afx-menu li:hover {
background-color: #2786F3;
}
afx-menu li:hover > a {
color: white;
}
afx-menu afx-menu .afx_submenu:before, afx-menu ul.context .afx_submenu:before{
content: "\f054";
font-family: "FontAwesome";
font-size: 10px;
right:5px;
color: #414339;
position:absolute;
top:25%;
}
afx-menu ul.context{
border:1px solid #262626;
border-radius: 5px;
border-top-left-radius: 0px;
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
background-color: #363636;
}
afx-menu ul.context li{
min-width: 150px;
width: calc(100% - 10px);
}
afx-menu afx-label span {
height: 22px !important;
}

View File

@ -1,3 +0,0 @@
afx-overlay {
background-color: rgba(54, 54, 54, 0.7);
}

View File

@ -1,13 +0,0 @@
afx-resizer.vertical {
background-color: transparent;
border-top: 1px solid #262626;
}
afx-resizer.horizontal {
background-color: transparent;
border-left: 1px solid #262626;
}
afx-resizer.horizontal:hover, afx-resizer.vertical:hover
{
background-color: #116cd6;
}

View File

@ -1,19 +0,0 @@
afx-slider div.container{
border-radius: 3px;
height: 5px;
background-color: #868686;
}
afx-slider div.progress {
background-color: #116cd6;
border-radius: 3px;
}
afx-slider div.dragpoint {
width: 15px;
height: 15px;
border:1px solid #262626;
border-radius: 15px;
background-color:#868686;
}

View File

@ -1,119 +0,0 @@
afx-sys-panel > div{
background-color: #363636;
border-bottom: 1px solid #262626;
box-shadow: none;
height: 22px;
}
afx-sys-panel .afx-panel-os-menu li
{
font-weight: bold;
background-color: #e7414d;
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
}
afx-sys-panel .afx-panel-os-menu a {
color: white;
}
afx-sys-panel afx-menu.afx-panel-os-stray afx-menu {
left: calc(100% - 170px);
}
afx-sys-panel afx-menu.afx-panel-os-stray afx-menu li.afx_submenu a{
margin-left: 10px;
}
afx-sys-panel afx-menu.afx-panel-os-stray afx-menu li.afx_submenu:before {
content: "\f054";
font-family: "FontAwesome";
font-size: 10px;
position:absolute;
text-align: left;
left:5px;
top:25%;
}
afx-sys-panel afx-menu.afx-panel-os-stray afx-menu ul{
border:1px solid #262626;
border-radius: 5px;
border-top-right-radius: 0px;
}
afx-sys-panel afx-menu.afx-panel-os-stray afx-menu li{
min-width: 150px;
}
afx-sys-panel afx-overlay
{
background-color: #363636;
border: 1px solid #262626;
}
afx-sys-panel afx-list-view[data-id="applist"],
afx-sys-panel afx-list-view[data-id="catlist"],
afx-sys-panel afx-resizer
{
border-top: 1px solid #262626;
border-bottom: 1px solid #262626;
}
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li,
afx-sys-panel afx-list-view[data-id="catlist"] > div.list-container > ul li
{
padding-top: 5px;
padding-bottom: 5px;
background-color: transparent;
}
afx-sys-panel afx-hbox[data-id="btlist"] afx-button button
{
border: 0;
border-left: 1px solid #262626;
border-top: 1px solid #262626;
}
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li:hover,
afx-sys-panel afx-list-view[data-id="catlist"] > div.list-container > ul li:hover
{
background-color: #cecece;
color: #262626;
}
afx-sys-panel afx-list-view[data-id="applist"] > div.list-container > ul li.selected,
afx-sys-panel afx-list-view[data-id="catlist"] > div.list-container > ul li.selected
{
background-color: #116cd6;
color:white;
}
afx-sys-panel afx-list-view[data-id="catlist"] .label-text {
font-weight: bold;
}
afx-sys-panel afx-list-view[data-id="applist"] afx-label.search-header {
font-weight: bold;
}
afx-sys-panel afx-list-view[data-id="applist"] afx-label i {
margin-right: 10px;
width: 16px;
height: 16px;
}
afx-sys-panel afx-list-view[data-id="applist"] afx-label i.bi::before
{
width: 16px;
}
afx-sys-panel div[data-id="searchicon"]:before{
content: "\f002";
display: block;
background-color:transparent;
color:#afafaf;
font-family: "FontAwesome";
padding-left:3px;
font-size: 25px;
}
afx-sys-panel input{
border:0;
height: 25px;
color:#afafaf;
background-color: transparent;
}

View File

@ -1,15 +0,0 @@
afx-tab-bar afx-list-view > div.list-container > ul > afx-list-item > li.selected
{
background-color: #464646;
color:white;
}
afx-tab-bar afx-list-view > div.list-container > ul li{
border-top-left-radius: 5px;
border-top-right-radius: 5px;
padding-bottom: 2px;
padding-right:15px;
padding-top:2px;
border:1px solid #262626;
}

View File

@ -1,25 +0,0 @@
afx-tree-view div{
padding:3px;
}
afx-tree-view i.icon-style {
width: 16px;
height: 16px;
}
afx-tree-view div.afx_tree_item_selected{
background-color: #116cd6;
color:white;
}
afx-tree-view div.afx_tree_item_selected:hover{
background-color: #116cd6;
color:white;
}
afx-tree-view .afx_folder_item{
font-weight: bold;
}
/*
afx-tree-view .afx_tree_item_odd{
background-color: #464646;
}*/

View File

@ -1,84 +1,34 @@
html,body{ :root {
font-family: "Ubuntu"; --antos-ant-color: #5F548E;
font-size: 13px;
color: white;
}
#workspace {
top: 23px;
}
#desktop{ --text-primary: white;
top:0; --text-secondary: #bb86fc;
left: 35px; --text-tertiary: white;
} --text-disable: #868686;
#desktop > div > ul afx-list-item { --text-warning: orangered;
width: 70px; --text-error: #df3154;
color: white; --text-info: green;
padding:3px;
}
#desktop > div > ul afx-list-item li.selected { --background-primary: #333333;
background-color: #116cd6; --background-secondary: #363636;
color:white; --background-tertiary: #464646;
border-radius: 6px; --background-quaternary: #464646;
width: 70px;
color: white;
padding:3px;
}
#desktop > div > ul afx-list-item i.file:before{ --background-overlay: rgba(54,54,54,0.7);
content: "\f15b\a";
font-family: "FontAwesome";
font-size: 32px;
display: block;
color: white;
font-style: normal;
font-weight: normal;
}
#desktop > div > ul afx-list-item i.dir:before{ --icon-primary: white;
display: block; --icon-secondary: #868686;
content: "\f07b\a"; --icon-tertiary: #76D2F9;
font-family: "FontAwesome";
font-size: 32px;
color: #76D2F9;
font-weight: normal;
font-style: normal;
}
#systooltip { --item-bg-odd: #474747;
border:1px solid #363636; --item-bg-even: #3b3b3b;
border-radius: 3px; --item-bg-active: #116cd6;
padding-left:3px; --item-bg-hover: #464646;
padding-right:3px;
box-shadow: none;
background-color: #464646;
}
input { --border-primary:#262626;
outline: none; --border-secondary: #363636;
padding: 2px; --border-tertiary: #bb86fc;
height:23px; --border-quaternary: #646363;
border: 1px solid #262626;
background-color:#464646;
color: white;
border-radius: 3px;
box-sizing: border-box;
font-family: "Ubuntu";
font-size: 13px;
}
textarea { --shadow-primary: rgba(0,0,0,0.65);
color: white;
background-color: #464646;
outline: none;
border: 1px solid #262626;
box-sizing: border-box;
}
a:link,
a:visited,
a:hover
{
color:#df3154;
} }

View File

@ -1,55 +0,0 @@
afx-app-window div.afx-window-wrapper{
border:1px solid #a6a6a6;
box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.65);
border-radius: 5px;
background-color:#dfdfdf;
}
afx-app-window.unactive > div.afx-window-wrapper{
background-color: #f6f6f6;
}
afx-app-window ul.afx-window-top{
height: 20px;
border-bottom: 1px solid #a6a6a6;
}
afx-app-window div.afx-window-overlay {
top: 22px;
}
afx-app-window ul.afx-window-top li{
margin-left: 3px;
margin-top:4px;
}
afx-app-window ul.afx-window-top .afx-window-close,.afx-window-minimize,.afx-window-maximize{
width: 11px;
height: 11px;
border-radius: 10px;
}
afx-app-window ul li.afx-window-close{
background-color: #Fc605b;
float:left;
}
afx-app-window ul li.afx-window-minimize{
background-color: #fec041;
float:left;
}
afx-app-window ul li.afx-window-maximize{
background-color: #35cc4b;
float:left;
}
afx-app-window ul li.afx-window-title{
margin-top:1px;
text-align: center;
}
afx-app-window div.afx-window-content
{
background-color: white;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
afx-app-window.unactive div.afx-window-content
{
background-color:white;
}

View File

@ -1,23 +0,0 @@
afx-button button{
padding: 4px;
border: 1px solid #a6a6a6;
background-color: #f6F6F6;
color: #414339;
border-radius: 6px;
font-family: "Ubuntu";
}
afx-button button[disabled]{
color: #a6a6a6;
}
afx-button i.icon-style {
width: 16px;
height: 16px;
display: inline-block;
}
afx-button button:active, afx-button button.btactive {
background-color: #2786F3;
color: white;
border: 1px solid #dedede;
}

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