Compare commits

...

92 Commits

Author SHA1 Message Date
DanyLE
04da2a9d39 fix: Allow passing Global version number and build ID to frontend build
All checks were successful
gitea-sync/antos-frontend/pipeline/head This commit looks good
2024-03-17 21:18:11 +01:00
DanyLE
8c20cfec5e doc: update README.md 2024-03-17 21:18:11 +01:00
DanyLE
24788a421c refactor: remove unused files 2024-03-17 21:18:11 +01:00
DanyLE
e479fe43c9 fix: init npm before installing packages 2024-03-17 21:18:11 +01:00
DanyLE
4a46104710 feat: add install_dev to Makefile to install dependencies before build 2024-03-17 21:18:11 +01:00
DanyLE
5605d6c35a fix: simulate contextmenu on mobile device 2024-03-17 21:18:11 +01:00
DanyLE
6ac2429dba fix: list view item shall propagate click event to parent 2024-03-17 21:18:11 +01:00
DanyLE
297d471c1d fix: dblclick event does not fire on mobile device (IOS) 2024-03-17 21:18:11 +01:00
DanyLE
d95f9382f3 fix: page scale problem on mobile 2024-03-17 21:18:11 +01:00
DanyLE
d00fd3bd82 fix: invalid background settings 2024-03-17 21:18:11 +01:00
DanyLE
cbeab0d0f2 style: update file UI style 2024-03-17 21:18:11 +01:00
DanyLE
a86da11532 fix: minor UI bugs on File and Setting apps 2024-03-17 21:18:11 +01:00
DanyLE
984cdae438 fix: remove debug message 2024-03-17 21:18:11 +01:00
DanyLE
3e201bfbba fix: use row/column as common directives for all UI horizontal/vertical direction 2024-03-17 21:18:11 +01:00
DanyLE
3d09a20512 update: antos API declaration 2024-03-17 21:18:11 +01:00
DanyLE
87dad4eded update: use latest UI API on system applications 2024-03-17 21:18:11 +01:00
DanyLE
e6bf4d5352 feat: add APIs that support responsive UI on antos tags 2024-03-17 21:18:11 +01:00
DanyLE
a5257bf108 fix: use CSS variable to define color palette for UI theme 2024-03-17 21:18:11 +01:00
DanyLE
eac84a3ab8 fix: encode URI component when get file from VFS API 2024-03-17 21:18:11 +01:00
DanyLE
6523fafe91 fix: upload API only submit a task when files are selected 2024-03-17 21:18:11 +01:00
DanyLE
acd36a7a29 cleanup code 2024-03-17 21:18:11 +01:00
DanyLE
add5ef77c8 feat: use a separated setting file for each application instead of a single system setting files 2024-03-17 21:18:11 +01:00
DanyLE
4d59b104b9 feat: Introduce API.Task API that allow to track promise object via AntOS announcement system 2024-03-17 21:18:11 +01:00
Dany LE
0ac886f644 Update Jenkinsfile 2024-03-17 21:18:11 +01:00
Dany LE
f52e9a38d2 Update Jenkinsfile 2024-03-17 21:18:11 +01:00
Dany LE
a74cf39152 fix: clean up repo before build 2024-03-17 21:18:11 +01:00
Dany LE
082a85644b fix(Jenkinsfile): use typescript 5.0 as typedoc 0.24 depends on it 2024-03-17 21:18:11 +01:00
DanyLE
a00acf4bbf fix: support passing arguments when pushing a service 2024-03-17 21:18:11 +01:00
DanyLE
bff2c94fa9 remove support for VDB, applications that used SQLite database can now use API provided by the libsqlite package (on MarketPlace) 2024-03-17 21:18:11 +01:00
DanyLE
0f2ab549e8 fix: doc generation use latest typedoc version 2024-03-17 21:18:11 +01:00
DanyLE
cd6a6c373a fix: operator not permit on newer version of typescript 2024-03-17 21:18:11 +01:00
DanyLE
56ca945b0c fix: jquery delaration file compatibility with current typescript sdk 2024-03-17 21:18:11 +01:00
DanyLE
82f35f791e fix: extractZip bug introduced by last commit 2024-03-17 21:18:11 +01:00
DanyLE
b30a2bb44c fix: creating missing directories from file paths when they are not specified in zip meta-data 2024-03-17 21:18:11 +01:00
DanyLE
2c64dfe00d fix: calendar tag displays wrong date at the final week of month in some case 2024-03-17 21:18:11 +01:00
DanyLE
1d1218acbd gridview: allow to update row data 2024-03-17 21:18:11 +01:00
DanyLE
0d8daa36e8 safer way to attach element to data via getter 2024-03-17 21:18:11 +01:00
DanyLE
d72a4c954b Remove old menu element, use stackmenu instead 2024-03-17 21:18:11 +01:00
DanyLE
5f92e41021 ListView: add API to scroll the list to top/bottom 2024-03-17 21:18:11 +01:00
DanyLE
d3540d7575 fix: label shall only allow to show text instead of html content 2024-03-17 21:18:11 +01:00
DanyLE
60137fb4fb support icoclass_end in Label and Button HTML attribute 2024-03-17 21:18:11 +01:00
DanyLE
7196f9ff57 Update style for GridView, FileView and ListView 2024-03-17 21:18:11 +01:00
DanyLE
1063ae9c4f Only break-word in notification tag 2024-03-17 21:18:11 +01:00
DanyLE
649c7d942a 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-17 21:18:11 +01:00
DanyLE
b490ae9d42 fix: Dialog scheme not found when it is defined outside of the dialogs namespace 2024-03-17 21:18:11 +01:00
DanyLE
ae91401965 allow to specify user data in some low level VFS interface API 2024-03-17 21:18:11 +01:00
DanyLE
d482d2cad4 add support for drop custom event when drag is enable on an HTMLElement 2024-03-17 21:18:11 +01:00
DanyLE
b2ec7cc8db Add custom dragging event support for all HTMLElement 2024-03-17 21:18:11 +01:00
DanyLE
db006345a9 sportlight only focus on searchbar when on desktop 2024-03-17 21:18:11 +01:00
DanyLE
bf793ec204 Clean up code 2024-03-17 21:18:11 +01:00
DanyLE
cb9ccb576f Hide application when use click on active application dock item 2024-03-17 21:18:11 +01:00
DanyLE
ea160c2ccb 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-17 21:18:11 +01:00
DanyLE
edb427d6c3 fix: notification style 2024-03-17 21:18:11 +01:00
DanyLE
be72a62156 cleanup system services package 2024-03-17 21:18:11 +01:00
DanyLE
77b89c44f7 Fix: style + typo 2024-03-17 21:18:11 +01:00
DanyLE
242df06a28 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-17 21:18:11 +01:00
DanyLE
c52e4b649e fix: window menu display bug 2024-03-17 21:18:11 +01:00
DanyLE
30c63748b0 Hide spotlight when an application is selected on appdock 2024-03-17 21:18:11 +01:00
DanyLE
84cfc87ce0 Re introduce the vboxchange, hboxchange events as many applications use it 2024-03-17 21:18:11 +01:00
DanyLE
f21a958ea0 Change color theme of the startup menu 2024-03-17 21:18:11 +01:00
DanyLE
81d13c1601 Update favicon to new color 2024-03-17 21:18:11 +01:00
DanyLE
9f07a86901 Update favicon 2024-03-17 21:18:11 +01:00
Dany LE
7b3072576c Update Makefile 2024-03-17 21:18:11 +01:00
Dany LE
be73a3c7ae Update Jenkinsfile 2024-03-17 21:18:11 +01:00
Dany LE
038823a7cb Update README.md 2024-03-17 21:18:11 +01:00
DanyLE
70301d8817 Update Jenkinsfile 2024-03-17 21:18:11 +01:00
DanyLE
9d1c66fe50 Fix make file 2024-03-17 21:18:11 +01:00
DanyLE
6948b0e339 Fix make file 2024-03-17 21:18:11 +01:00
DanyLE
b35812bb43 Update icons + add documentation build in Jenkinsfile 2024-03-17 21:18:11 +01:00
DanyLE
11fb8c97af Add favicon to the page 2024-03-17 21:18:11 +01:00
DanyLE
d9314fc829 Add official AntOS icon 2024-03-17 21:18:11 +01:00
Dany LE
722e672947 Update README.md 2024-03-17 21:18:11 +01:00
Dany LE
93b58c7aa2 Add files via upload 2024-03-17 21:18:11 +01:00
Dany LE
9e7c7e6d78 Update README.md 2024-03-17 21:18:11 +01:00
Dany LE
25e9efff46 Add files via upload 2024-03-17 21:18:11 +01:00
Dany LE
9baee31c01 Add files via upload 2024-03-17 21:18:11 +01:00
DanyLE
de7f027940 update README 2024-03-17 21:18:11 +01:00
DanyLE
870b1ec105 update Syslog application + README 2024-03-17 21:18:11 +01:00
DanyLE
03cee726ed 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-17 21:18:11 +01:00
DanyLE
303fc9ba20 fix minor bug on appdock contextmenu handling 2024-03-17 21:18:11 +01:00
DanyLE
243dfa7a89 Update dark theme 2024-03-17 21:18:11 +01:00
DanyLE
bc77329294 Improve UI 2024-03-17 21:18:11 +01:00
DanyLE
e1c1895070 Improve Firefox support + fix list view drag and drop bug 2024-03-17 21:18:11 +01:00
DanyLE
0b80a29d00 improve file icon view 2024-03-17 21:18:11 +01:00
DanyLE
b026b96bf2 Redesign the login form, preload all web font fonts on front page 2024-03-17 21:18:11 +01:00
DanyLE
3c25d8b52e Support pinned app in dock + remove old pinned apps UI 2024-03-17 21:18:11 +01:00
DanyLE
b5863702cb 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-17 21:18:11 +01:00
DanyLE
2ae390ad4b generate 2.0.0 release archive 2024-03-17 21:18:11 +01:00
DanyLE
2e887851c5 Add input tag and update all base dialogs to support mobile devices 2024-03-17 21:18:11 +01:00
DanyLE
1a6ece4e7c 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-17 21:18:11 +01:00
DanyLE
d5d6a16a85 Update version to 2.0.0-a 2024-03-17 21:18:11 +01:00
DanyLE
cd294f58a6 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-17 21:18:11 +01:00
165 changed files with 7480 additions and 5501 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,9 +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-b
BRANCH = b BUILDID?=master
BUILDID=$(shell git rev-parse --short HEAD)
GSED=sed GSED=sed
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
@ -27,7 +26,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 +38,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 +61,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)/
@ -120,7 +121,7 @@ build_javascripts: ts
(cat "$${f}"; echo) >> dist/antos.js;\ (cat "$${f}"; echo) >> dist/antos.js;\
rm "$${f}";\ rm "$${f}";\
done done
echo 'OS.VERSION.version_string = "$(VERSION)-$(BRANCH)-$(BUILDID)";' >> dist/antos.js echo 'OS.VERSION.version_string = "$(VERSION)-$(BUILDID)";' >> dist/antos.js
cp dist/antos.js $(BUILDDIR)/scripts/ cp dist/antos.js $(BUILDDIR)/scripts/
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
rm -r dist/core rm -r dist/core
@ -145,8 +146,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 +224,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)-$(BUILDID) API" --out $(DOCDIR)
./node_modules/.bin/typedoc --hideGenerator --plugin typedoc-plugin-merge-modules --entryPointStrategy expand --name "AntOS $(VERSION)-$(BUILDID) API" --out $(DOCDIR)
test: build_javascripts 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,28 +1,23 @@
# 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 web-based desktop platform that features a window manager, application APIs, GUI toolkit, file system abstractions, application store, and an API and SDK for in-browser application development. The purpose of this project is to enable users to easily set up a self-hosted, cloud-based working environment using only a web browser. The front-end can connect to a remote server and act as a virtual desktop environment (VDE). Frontend implementation of AntOS remote desktop environment: [https://github.com/antos-rde](https://github.com/antos-rde).
AntOS can be used in several application contexts, such as: The frontend is developed in typescript/javascript + CSS, it provides the
- Providing visual tools to access and control resources on remote servers and embedded Linux environments 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.
- Providing and developing SaaS web-based applications
- Self-hosting a cloud-based working environment
- Creating a customized, user-friendly interface for managing and interacting with cloud-based resources and services
- Setting up a collaborative, online workspace for remote teams and distributed organizations
- Building a web-based operating system that can run on various devices, including laptops, tablets, and smartphones
- Creating a virtualized environment for testing and deploying web-based applications in a sandboxed environment
- Building a platform for creating and hosting web-based educational or training content
- Setting up a web-based development environment for prototyping and building web-based applications quickly and easily
- Etc, You name it!
With the provided application API and SDK, AntOS facilitates the development and deployment of user-specific applications inside the VDE environment ## Build
![https://github.com/lxsang/antos/raw/master/antos-shot.png](https://github.com/lxsang/antos/raw/master/antos-shot.png) `Nodejs` and `npm` is necessary to build the project:
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos) ```sh
# install dependencies packages
make install_dev
# build release
BUILDDIR=/path/to/output make release
# see more in Makefile for more build target
```
## Demo ## 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. 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: If one want to run AntOS VDE locally in their system, a docker image is available at:
@ -31,13 +26,14 @@ If one want to run AntOS VDE locally in their system, a docker image is availabl
## AntOS applications (Available on the MarketPlace) ## AntOS applications (Available on the MarketPlace)
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps) [https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
## Documentation ## Frontend Documentation
- Documentation: [https://doc.iohub.dev/antos](https://doc.iohub.dev/antos) - API: [https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/](https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/)
- API: [https://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
## 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
@ -79,7 +75,7 @@ If one want to run AntOS VDE locally in their system, a docker image is availabl
- 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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

2195
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,21 +107,19 @@ namespace OS {
* @interface AnnouncerListenerType * @interface AnnouncerListenerType
*/ */
export interface AnnouncerListenerType { export interface AnnouncerListenerType {
[index: number]: { /**
/** * The event name
* The event name *
* * @type {string}
* @type {string} */
*/ e: string;
e: string;
/** /**
* The event callback * The event callback
* *
*/ */
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,80 +96,90 @@ namespace OS {
* @returns {void} * @returns {void}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
init(): void { init(): Promise<any> {
this.off("*"); return new Promise(async (ok, nok) =>{
this.on("exit", () => this.quit(false)); try {
// first register some base event to the app this.off("*");
this.on("focus", () => { this.on("exit", () => this.quit(false));
this.sysdock.selectedApp = this; // first register some base event to the app
this.appmenu.pid = this.pid; this.on("focus", () => {
this.appmenu.items = this.baseMenu() || []; //if(this.sysdock.selectedApp != this)
OS.PM.pidactive = this.pid; this.sysdock.selectedApp = this;
this.appmenu.onmenuselect = ( (this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
d: GUI.tag.MenuEventData OS.PM.pidactive = this.pid;
): void => { this.trigger("focused", undefined);
return this.trigger("menuselect", d); if (this.dialog) {
}; return this.dialog.show();
this.trigger("focused", undefined); }
if (this.dialog) { });
return this.dialog.show(); this.on("hide", () => {
} this.sysdock.selectedApp = null;
}); if (this.dialog) {
this.on("hide", () => { return this.dialog.hide();
this.sysdock.selectedApp = null; }
this.appmenu.items = []; });
this.appmenu.pid = -1; this.on("menuselect", (d) => {
if (this.dialog) { switch (d.data.item.data.dataid) {
return this.dialog.hide(); case `${this.name}-about`:
} return this.openDialog("AboutDialog");
}); case `${this.name}-exit`:
this.on("menuselect", (d) => { return this.trigger("exit", undefined);
switch (d.data.item.data.dataid) { }
case `${this.name}-about`: });
return this.openDialog("AboutDialog"); this.on("apptitlechange", () => this.sysdock.update(this));
case `${this.name}-exit`: this.subscribe("appregistry", (m) => {
return this.trigger("exit", undefined); if (m.name === this.name) {
} this.applySetting(m.message as string);
}); }
this.on("apptitlechange", () => this.sysdock.update(this)); });
this.subscribe("appregistry", (m) => {
if (m.name === this.name) {
this.applySetting(m.message as string);
}
});
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != this.pid)
{
return;
}
this._pending_task.push(o.id);
this.trigger("loading", undefined);
});
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => { this.updateLocale(this.systemsetting.system.locale);
const i = this._pending_task.indexOf(o.id); await this.loadScheme();
if (i >= 0) { this.applyAllSetting();
this._pending_task.splice(i, 1);
} }
if (this._pending_task.length === 0) { catch(e)
// set time out {
if(!this._loading_toh) nok(__e(e));
this._loading_toh = setTimeout(() => this.animation_check(),1000);
} }
}); });
this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme();
} }
/**
* API function to register responsive UI event to the current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @param {GUI.TagResponsiveCallback} responsive callback
* @returns {void}
* @memberof BaseApplication
*/
protected morphon(validator: GUI.TagResponsiveValidator, callback: GUI.TagResponsiveCallback)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphon(validator, callback);
}
/**
* API function to unregister responsive UI event from current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @returns {void}
* @memberof BaseApplication
*/
protected morphoff(validator: GUI.TagResponsiveValidator)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphoff(validator);
}
/** /**
* Render the application UI by first loading its scheme * 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,22 +58,15 @@ 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 (this.scheme) {
if (!evt.prevent) { $(this.scheme).remove();
delete this._observable;
if (this.scheme) {
$(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> <afx-calendar-view data-id = "cal" ></afx-calendar-view>
<div data-width = "10" ></div> <div data-height="35" style = 'text-align: right;'>
<afx-vbox> <afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
<div data-height="10" ></div> <afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
<afx-calendar-view data-id = "cal" ></afx-calendar-view> </div>
<div data-height="10" ></div>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
<div data-height="10" ></div>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-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> <afx-color-picker data-id = "cpicker" ></afx-color-picker>
<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-color-picker data-id = "cpicker" ></afx-color-picker> </div>
<div data-height="10" ></div>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
<div data-height="10" ></div>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-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> <afx-grid-view data-id = "grid" ></afx-grid-view>
<div data-width = "10" ></div> <div data-height="35" style="text-align: right;">
<afx-vbox> <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
<div data-height="10" ></div> </div>
<afx-grid-view data-id = "grid" ></afx-grid-view>
<div data-height="10" ></div>
<afx-hbox data-height="30">
<div ></div>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
<div data-height="10" ></div>
</afx-vbox>
<div data-width = "10" ></div>
</afx-hbox>
</afx-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> <afx-list-view data-id = "list" ></afx-list-view>
<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-list-view data-id = "list" ></afx-list-view> </div>
<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>\
`; `;
@ -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-vbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view> <afx-list-view data-id = "location" dropdown = "true" data-height = "35"></afx-list-view>
<afx-vbox> <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> <afx-input data-height = '52' label = "__(Target file/folder)" type = "text" data-id = "filename" ></afx-input>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" ></input> <div style=' text-align:right;' data-height="35">
<afx-hbox data-height = '30'> <afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<div style=' text-align:right;'> <afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button> </div>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button> </afx-vbox>
</div>
<div data-width="5"></div>
</afx-hbox>
</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> {1}
<afx-vbox> <div data-height="35" style = "text-align: right;">
<div data-height="5" ></div> <afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
{1} <afx-button data-id = "btnCancel" text = "__(Cancel)"></afx-button>
<afx-hbox data-height="30"> </div>
<div ></div> </afx-vbox>
<afx-button data-id = "btnOk" text = "__(Ok)" data-width = "40" ></afx-button>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
</afx-hbox>
<div data-height="5" ></div>
</afx-vbox>
<div data-width="10" ></div>
</afx-hbox>
</afx-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")
.appendTo(div); .css("height", "35px")
.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/antos-rde/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,34 +1296,35 @@ 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", data: JSON.stringify(
data: JSON.stringify( d,
d, function (k, v) {
function (k, v) { if (k === "domel") {
if (k === "domel") { return undefined;
return undefined; }
} return v;
return v; },
}, 4
4 ),
), dataType: "json",
dataType: "json", success: null,
success: null,
})
.done(function (data) {
API.loaded(q, p, "OK");
return resolve(data);
}) })
.fail(function (j, s, e) { .done(function (data) {
API.loaded(q, p, "FAIL"); return resolve(data);
return reject(API.throwe(s)); })
}); .fail(function (j, s, e) {
reject(e);
});
}
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,39 +1366,41 @@ 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 () => {
const files = (o[0] as HTMLInputElement).files; try {
const n_files = files.length; const files = (o[0] as HTMLInputElement).files;
if (n_files > 0) const formd = new FormData();
API.loading(q, p); formd.append("path", d);
const formd = new FormData(); jQuery.each(files, (i, file) => {
formd.append("path", d); formd.append(`upload-${i}`, file);
jQuery.each(files, (i, file) => { });
formd.append(`upload-${i}`, file); const ret = await API.Task((ok, nok) => {
}); $.ajax({
return $.ajax({ url: p,
url: p, data: formd,
data: formd, type: "POST",
type: "POST", contentType: false,
contentType: false, processData: false,
processData: false, })
}) .done(function (data) {
.done(function (data) { ok(data);
API.loaded(q, p, "OK"); })
resolve(data); .fail(function (j, s, e) {
}) //o.remove();
.fail(function (j, s, e) { nok(API.throwe(s));
API.loaded(q, p, "FAIL"); });
o.remove(); });
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));
}); });
}); });
@ -1440,7 +1508,7 @@ namespace OS {
* @returns {Promise<void>} a promise on the result data * @returns {Promise<void>} a promise on the result data
*/ */
export function requires(l: string, force: boolean = false): Promise<void> { export function requires(l: string, force: boolean = false): Promise<void> {
return new Promise(async (resolve, reject) =>{ return new Promise(async (resolve, reject) => {
try { try {
if (!API.shared[l] || force) { if (!API.shared[l] || force) {
const libfp = l.asFileHandle(); const libfp = l.asFileHandle();
@ -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,29 +174,30 @@ 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 {
const collection = this.items.filter(it => it.app.name == item.app.name);
let bt = undefined;
if(collection.length == 0)
{
this.add_button(item.app.name, item);
}
else
{
bt = collection[0].domel;
item.domel = bt;
$(bt).addClass("plural");
}
this.items.push(item); this.items.push(item);
const el = $("<afx-button>");
const bt = el[0] as ButtonTag;
el.appendTo(this);
el[0].uify(this.observable);
bt.set(item);
bt.data = item.app;
item.domel = bt;
$(bt).attr("tooltip", `cr:${item.app.title()}`);
bt.onbtclick = (e) => {
e.id = this.aid;
//e.data.item = item;
this._onappselect(e);
item.app.show();
};
this.selectedApp = item.app; 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]) { case "new":
return app[item.dataid](); GUI.launch(bt.data.name as string, []);
} break;
else case "hide":
{ collection.forEach((el,_) => el.app.hide());
switch (item.dataid) { break;
case "new": case "quit":
GUI.launch(app.name, []); 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();
const globalof = $(this.refs.mlist).offset(); $(el).on("dragging", (evt) => {
evt.preventDefault(); const e = evt.originalEvent as CustomEvent;
const offset = $(el).offset(); const globalof = $(this.refs.mlist).offset();
offset.top = evt.clientY - offset.top; const offset = e.detail.offset;
offset.left = evt.clientX - offset.left; let top = e.detail.current.clientY - offset.top - globalof.top;
const mouse_move = function ( let left =
e: JQuery.MouseEventBase e.detail.current.clientX - globalof.left - offset.left;
) { left = left < 0 ? 0 : left;
let top = e.clientY - offset.top - globalof.top; top = top < 0 ? 0 : top;
let left = $(el)
e.clientX - globalof.left - offset.left; .css("top", `${top}px`)
left = left < 0 ? 0 : left; .css("left", `${left}px`);
top = top < 0 ? 0 : top; })
return $(el) return el;
.css("top", `${top}px`)
.css("left", `${left}px`);
};
var mouse_up = function (e: JQuery.MouseEventBase) {
$(window).off("mousemove", mouse_move);
return $(window).off("mouseup", mouse_up);
};
$(window).on("mousemove", mouse_move);
return $(window).on("mouseup", mouse_up);
});
} }
/** /**
@ -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
@ -57,7 +58,7 @@ namespace OS {
super(); super();
this.refs.yield = this; this.refs.yield = this;
this._onselect = (e) => {}; this._onselect = (e) => { };
} }
/** /**
@ -89,13 +90,32 @@ 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
* *
* @protected * @protected
* @memberof GridRowTag * @memberof GridRowTag
*/ */
protected mount(): void {} protected mount(): void { }
/** /**
* Init the tag before mounting: reset the data placeholder * Init the tag before mounting: reset the data placeholder
@ -124,7 +144,7 @@ namespace OS {
* @protected * @protected
* @memberof GridRowTag * @memberof GridRowTag
*/ */
protected calibrate(): void {} protected calibrate(): void { }
/** /**
* This function does nothing in this tag * This function does nothing in this tag
@ -133,7 +153,7 @@ namespace OS {
* @param {*} [d] * @param {*} [d]
* @memberof GridRowTag * @memberof GridRowTag
*/ */
protected reload(d?: any): void {} protected reload(d?: any): void { }
} }
/** /**
@ -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:
@ -277,14 +298,14 @@ namespace OS {
$(this).attr("class", "afx-grid-cell"); $(this).attr("class", "afx-grid-cell");
this.oncelldbclick = this.oncellselect = ( this.oncelldbclick = this.oncellselect = (
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
@ -358,7 +379,7 @@ namespace OS {
* @protected * @protected
* @memberof SimpleGridCellTag * @memberof SimpleGridCellTag
*/ */
protected init(): void {} protected init(): void { }
/** /**
* This function do nothing in this tag * This function do nothing in this tag
@ -366,10 +387,10 @@ namespace OS {
* @protected * @protected
* @memberof SimpleGridCellTag * @memberof SimpleGridCellTag
*/ */
protected calibrate(): void {} protected calibrate(): void { }
/** /**
* The layout of the cell with a simple [[LabelTag]] * The layout of the cell with a simple {@link LabelTag}
* *
* @returns * @returns
* @memberof SimpleGridCellTag * @memberof SimpleGridCellTag
@ -516,13 +537,11 @@ 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
); );
@ -576,10 +595,10 @@ namespace OS {
this.dragndrop = false; this.dragndrop = false;
this._oncellselect = this._onrowselect = this._oncelldbclick = ( this._oncellselect = this._onrowselect = this._oncelldbclick = (
e: TagEventType<CellEventData> e: TagEventType<CellEventData>
): void => {}; ): void => { };
this._ondragndrop = ( this._ondragndrop = (
e: TagEventType<DnDEventDataType<GridRowTag>> e: TagEventType<DnDEventDataType<GridRowTag>>
) => {}; ) => { };
} }
/** /**
@ -589,7 +608,7 @@ namespace OS {
* @param {*} [d] * @param {*} [d]
* @memberof GridViewTag * @memberof GridViewTag
*/ */
protected reload(d?: any): void {} protected reload(d?: any): void { }
/** /**
* set the cell select event callback * set the cell select event callback
@ -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];
@ -751,44 +773,34 @@ 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);
} }
} }
@ -824,22 +836,22 @@ namespace OS {
get resizable(): boolean { get resizable(): boolean {
return this.hasattr("resizable"); return this.hasattr("resizable");
} }
/** /**
* Sort the grid using a sort function * Sort the grid using a sort function
* *
* @param {context: any} context of the executed function * @param {context: any} context of the executed function
* @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data * @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data
* * @param {index: number} current header index * * @param {index: number} current header index
* @returns {void} * @returns {void}
* @memberof GridViewTag * @memberof GridViewTag
*/ */
sort(context: any, fn: (a:GenericObject<any>[], b:GenericObject<any>[], index?: number) => number): void { sort(context: any, fn: (a: GenericObject<any>[], b: GenericObject<any>[], index?: number) => number): void {
const index = this._header.indexOf(context); const index = this._header.indexOf(context);
const __fn = (a, b) => { const __fn = (a, b) => {
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,25 +930,22 @@ 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;
} }
} }
@ -1010,7 +1035,7 @@ namespace OS {
if (this.selectedRow === row) { if (this.selectedRow === row) {
return; return;
} }
if(this.selectedRow) if (this.selectedRow)
this.selectedRow.selected = false; this.selectedRow.selected = false;
evt.data.items = [row]; evt.data.items = [row];
this._selectedRows = [row]; this._selectedRows = [row];
@ -1059,8 +1084,8 @@ namespace OS {
$(this.refs.container).css( $(this.refs.container).css(
"height", "height",
$(this).height() - $(this).height() -
$(this.refs.header).height() + $(this.refs.header).height() +
"px" "px"
); );
} else { } else {
$(this.refs.container).css( $(this.refs.container).css(
@ -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 {
} }
} }
// set the label content event it is hidden
const label = this.refs.drlabel as LabelTag;
label.set(e.data.data);
if (this.dropdown) { if (this.dropdown) {
const label = this.refs.drlabel as LabelTag;
label.set(e.data.data);
$(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,30 +186,34 @@ 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") {
if (attv[attv.length - 1] === "%") {
dw =
(parseInt(attv.slice(0, -1)) *
avaiWidth) /
100;
} else {
dw = parseInt(attv);
}
$(this).css("width", `${dw}px`);
ocwidth += dw;
} else {
$(this).css("flex-grow", "1"); $(this).css("flex-grow", "1");
auto_width.push(this); auto_width.push(this);
return;
} }
if(attv == "content")
{
ocwidth += $(this).width();
return;
}
if (attv[attv.length - 1] === "%") {
dw =
(parseInt(attv.slice(0, -1)) *
avaiWidth) /
100;
} else {
dw = parseInt(attv);
}
$(this).css("width", `${dw}px`);
ocwidth += dw;
}); });
const csize = (avaiWidth - ocwidth) / auto_width.length; 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,30 +239,34 @@ 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") {
if (attv[attv.length - 1] === "%") {
dh =
(parseInt(attv.slice(0, -1)) *
avaiheight) /
100;
} else {
dh = parseInt(attv);
}
$(this).css("height", `${dh}px`);
ocheight += dh;
} else {
$(this).css("flex-grow", "1"); $(this).css("flex-grow", "1");
auto_height.push(this); auto_height.push(this);
return;
} }
if(attv == "content")
{
ocheight += $(this).height();
return;
}
if (attv[attv.length - 1] === "%") {
dh =
(parseInt(attv.slice(0, -1)) *
avaiheight) /
100;
} else {
dh = parseInt(attv);
}
$(this).css("height", `${dh}px`);
ocheight += dh;
}); });
const csize = (avaiheight - ocheight) / auto_height.length; 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: "afx-button",
{ ref: "btnMenu",
el: "li", },
class: "afx-window-minimize", ],
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)}
@ -162,7 +180,7 @@ namespace OS {
* @type {number|string} * @type {number|string}
* @memberof TagLayoutType * @memberof TagLayoutType
*/ */
width?: number|string; width?: number | string;
/** /**
** `data-height` of the element, not to be confused with ** `data-height` of the element, not to be confused with
@ -171,7 +189,7 @@ namespace OS {
* @type {number|string} * @type {number|string}
* @memberof TagLayoutType * @memberof TagLayoutType
*/ */
height?: number|string; height?: number | string;
} }
/** /**
@ -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>}
@ -288,7 +475,17 @@ namespace OS {
protected _mounted: boolean; protected _mounted: boolean;
/** /**
*Creates an instance of AFXTag. * a {@link ResponsiveHandle} to handle all responsive event
* related to this tag
*
* @private
* @memberof AFXTag
*/
private _responsive_handle: ResponsiveHandle;
private _responsive_check: (evt: TagEventType<{w: number, h: number}>) => void;
/**
* Creates an instance of AFXTag.
* @memberof AFXTag * @memberof AFXTag
*/ */
constructor() { constructor() {
@ -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
@ -489,7 +761,7 @@ namespace OS {
* @protected * @protected
* @memberof AFXTag * @memberof AFXTag
*/ */
protected calibrate(): void {} protected calibrate(): void { }
/** /**
* This function parses the input layout object * This function parses the input layout object
@ -589,11 +861,48 @@ namespace OS {
} }
} }
HTMLElement.prototype.enable_drag = function () {
$(this)
.on("pointerdown", (evt: JQuery.MouseEventBase) => {
const offset = $(this).offset();
offset.top = evt.clientY - offset.top;
offset.left = evt.clientX - offset.left;
const mouse_move = (
e: JQuery.MouseEventBase
) => {
const custom_event = new CustomEvent('dragging', {
detail: {
origin: evt,
current: e,
offset: offset
}
});
this.dispatchEvent(custom_event);
};
var mouse_up = (e: JQuery.MouseEventBase) => {
$(window).off("pointermove", mouse_move);
$(window).off("pointerup", mouse_up);
// trigger the drop event
const custom_event = new CustomEvent('drop', {
detail: {
origin: evt,
current: e,
offset: offset
}
});
this.dispatchEvent(custom_event);
};
$(window).on("pointermove", mouse_move);
$(window).on("pointerup", mouse_up);
});
}
HTMLElement.prototype.update = function (d): void { HTMLElement.prototype.update = function (d): void {
$(this) $(this)
.children() .children()
.each(function () { .each(function () {
if(this.update) if (this.update)
return this.update(d); return this.update(d);
}); });
}; };
@ -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,14 +947,14 @@ 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}
*/ */
export function define<T extends AFXTag>( export function define<T extends AFXTag>(
name: string, name: string,
cls: { new (): T } cls: { new(): T }
): void { ): void {
try { try {
customElements.define(name, cls); customElements.define(name, cls);

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") {
return reject( const d = await API.handle.write(
API.throwe( this.path,
__("{0}: {1}", r.error, this.path) this.cache
)
); );
if (d.error) {
return reject(
API.throwe(
__("{0}: {1}", d.error, this.path)
)
);
}
return resolve(d);
} else {
const r = await this.b64(t);
const result = await API.handle.write(
this.path,
r as string
);
if (result.error) {
return reject(
API.throwe(
__(
"{0}: {1}",
result.error,
this.path
)
)
);
}
return resolve(result);
} }
return resolve(r);
} catch (e) { } 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 {
} background-color: var(--background-quaternary) !important;
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected { color:var(--text-secondary) !important;
background-color: #cecece;
color:#414339;
}
afx-app-window[data-id ='files-app-window'] .afx-window-top{
border-bottom: 0;
}
afx-app-window[data-id ='files-app-window'] afx-vbox[data-id = "nav-bar"]{
background-color: #dfdfdf;
}
afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row.grid_row_header div{
border-top:1px solid #A6A6A6;
} }
afx-app-window[data-id ='files-app-window'] afx-grid-view afx-grid-row afx-label span { 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-vbox> <afx-tile data-id="container" name="vbox" dir="column">
<afx-hbox data-height = "23" data-id = "nav-bar"> <afx-tile data-height = "35" min-width="120" data-width="180" data-id = "nav-bar" name="hbox" dir="row">
<afx-button data-width = "23" data-id = "btback" iconclass = "fa fa-arrow-left"></afx-button> <afx-list-view data-id = "favouri" dropdown="true"></afx-list-view>
<input type = "text" data-id = "navinput"></input> </afx-tile>
<div data-width = "2"></div> <afx-resizer data-id="resizer" dir="column" disable="true" data-width="3" data-height="0"></afx-resizer>
<afx-button data-width = "23" data-id = "btgrid" iconclass = "fa fa-th"></afx-button> <afx-vbox>
<div data-width = "2"></div> <afx-hbox data-width="120" data-height="35" data-id="nav-bar">
<afx-button data-width = "23" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button> <afx-tab-bar data-id = "path-nav" dir="row" ></afx-tab-bar>
</afx-hbox> <afx-button data-width = "40" data-id = "btback" iconclass = "fa fa-arrow-up"></afx-button>
<div data-height="5"></div> <afx-button data-width = "40" data-id = "btgrid" iconclass = "fa fa-th"></afx-button>
<afx-hbox> <afx-button data-width = "40" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
<afx-list-view data-id = "favouri" data-width = "150" min-width="100"> </afx-hbox>
</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">
<div data-width="17" data-id="searchicon"></div> <afx-vbox>
<input data-id = "searchbox" ></input> <afx-hbox data-height= "30" data-id="search-container">
</afx-hbox> <div data-width="17" data-id="searchicon"></div>
<afx-hbox> <input data-id = "searchbox" ></input>
<afx-list-view data-id = "catlist" dropdown = "false" data-width="35%"></afx-list-view>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-list-view data-id = "applist" dropdown = "false" ></afx-list-view>
</afx-hbox>
</afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox data-id = "container">
<afx-label data-id = "appname" data-height = "45"></afx-label>
<afx-hbox data-height = "50">
<div style = "text-align:left;">
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button>
<afx-button data-id = "bt-exec" text = "__(Launch)"></afx-button>
<afx-button data-id = "bt-install" text = "__(Install)" ></afx-button>
<p class="stat"><afx-label data-id="vstat"></afx-label></p>
</div>
</afx-hbox> </afx-hbox>
<div data-id="desc-container"> <afx-tab-bar data-id = "catlist" data-height="45"></afx-tab-bar>
<p data-id = "app-desc"></p> <afx-list-view data-id = "applist" dropdown = "false"></afx-list-view>
<ul data-id = "app-detail"></ul> </afx-vbox>
</div>
</afx-vbox> <afx-hbox>
<afx-vbox data-id = "container">
<afx-hbox data-height = "35" data-id="app-header">
<afx-button data-id = "appname" iconclass = "bi bi-backspace-fill"></afx-button>
<div style = "display: flex;justify-content: flex-end;">
<afx-button data-id = "bt-remove" text = "__(Uninstall)" iconclass = "bi bi-trash-fill"></afx-button>
<afx-button data-id = "bt-exec" text = "__(Launch)" iconclass = "fa fa-cog"></afx-button>
<afx-button data-id = "bt-install" text = "__(Install)" iconclass = "bi bi-cloud-download-fill" ></afx-button>
</div>
</afx-hbox>
<p class="stat" data-height="15"><afx-label data-id="vstat"></afx-label></p>
<div data-id="desc-container">
<p data-id = "app-desc"></p>
<ul data-id = "app-detail"></ul>
</div>
</afx-vbox>
</afx-hbox>
</afx-stack-panel>
</afx-hbox> </afx-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.1-b: update README - v0.1.2-b: use system css variables in theme
- 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.2-b: minor bug fix on UI - v0.1.5-b: fix VFS setting dialog bugs
-v0.1.2-b: add README - v0.1.4-b: use system css variables in theme
- v0.1.2-b: minor bug fix on UI
- v0.1.2-b: add README

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>
<div data-height="5"></div>
<afx-hbox data-height="25">
<afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
<div data-width="5"></div>
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
</afx-hbox>
</afx-vbox>
</afx-hbox> </afx-hbox>
<div data-height="5"></div> <div data-height="5"></div>
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="23"></afx-label> <afx-hbox data-height="35">
<afx-list-view data-height="30" data-id="theme-list" dropdown="true"></afx-list-view> <afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
<div data-width="5"></div>
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
</afx-hbox>
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="30"></afx-label>
<afx-list-view data-height="35" data-id="theme-list" dropdown="true"></afx-list-view>
<div data-height="5"></div> <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;
}

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