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 {
sh'''
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 uglifycss
npm install typescript
npm install typescript@5.0
npm install @types/jquery
npm i typedoc@0.24
npm i typedoc-plugin-merge-modules
buildir="build"
[ -d "$buildir" ] && rm -rf "$buildir"
export BUILDDIR="$WORKSPACE/$buildir/opt/www/htdocs/os"
[ -d "doc" ] && rm -rf doc
mkdir doc
export DOCDIR="$WORKSPACE/doc"
make release
make doc
'''
script {
// only useful for any master branch
//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
UGLIFYCSS=./node_modules/uglifycss/uglifycss
VERSION=1.2.1
BRANCH = b
BUILDID=$(shell git rev-parse --short HEAD)
VERSION?=2.0.0-b
BUILDID?=master
GSED=sed
UNAME_S := $(shell uname -s)
@ -27,7 +26,6 @@ tags = dist/core/tags/tag.js \
dist/core/tags/ListViewTag.js \
dist/core/tags/SwitchTag.js \
dist/core/tags/NSpinnerTag.js \
dist/core/tags/MenuTag.js \
dist/core/tags/GridViewTag.js \
dist/core/tags/TabBarTag.js \
dist/core/tags/TabContainerTag.js \
@ -40,14 +38,17 @@ tags = dist/core/tags/tag.js \
dist/core/tags/OverlayTag.js \
dist/core/tags/AppDockTag.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 \
dist/core/settings.js \
dist/core/handles/RemoteHandle.js \
dist/core/Announcerment.js \
dist/core/vfs.js \
dist/core/db.js \
dist/core/BaseModel.js \
dist/core/BaseApplication.js \
dist/core/BaseService.js \
@ -60,7 +61,7 @@ javascripts= dist/core/core.js \
antfx = $(tags) \
dist/core/Announcerment.js
packages = Syslog Files MarketPlace Setting NotePad
packages = SystemServices SystemReport Files MarketPlace Setting NotePad
main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/
@ -120,7 +121,7 @@ build_javascripts: ts
(cat "$${f}"; echo) >> dist/antos.js;\
rm "$${f}";\
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/
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
rm -r dist/core
@ -145,8 +146,10 @@ build_themes: antos_light antos_dark
-rm -rf $(BUILDDIR)/resources/themes/system/*
-mkdir -p $(BUILDDIR)/resources/themes/system
cp -r src/themes/system/fonts $(BUILDDIR)/resources/themes/system
cp -r src/themes/system/icons $(BUILDDIR)/resources/themes/system
cp -r src/themes/system/wp $(BUILDDIR)/resources/themes/system
for f in src/themes/system/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/system.css;done
for f in src/themes/default/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/antos.css;done
antos_light:
@echo "$(BLUE)Building themes name: antos-light$(NC)"
@ -221,13 +224,25 @@ ar:
echo -n $(VERSION) > release/latest
release: main uglify
.PHONY: doc release clean
doc:
./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS API" --out $(DOCDIR)
# npm install typedoc --save-dev
# npm install typedoc-plugin-merge-modules --save-dev
# ./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS $(VERSION)-$(BUILDID) API" --out $(DOCDIR)
./node_modules/.bin/typedoc --hideGenerator --plugin typedoc-plugin-merge-modules --entryPointStrategy expand --name "AntOS $(VERSION)-$(BUILDID) API" --out $(DOCDIR)
test: build_javascripts
jest
install_dev:
npm init -y
npm install terser
npm install uglifycss
npm install typescript@5.0
npm install @types/jquery
npm i typedoc@0.24
npm i typedoc-plugin-merge-modules
clean:
rm -rf $(BUILDDIR)/resources
rm -rf $(BUILDDIR)/scripts

View File

@ -1,28 +1,23 @@
# antOS v1.2.1
[![Build Status](https://ci.iohub.dev/buildStatus/icon?job=gitea-sync%2Fantos%2Fmaster)](https://ci.iohub.dev/job/gitea-sync/job/antos/job/master/)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flxsang%2Fantos.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
# AntOS frontend
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:
- Providing visual tools to access and control resources on remote servers and embedded Linux environments
- Providing and developing SaaS web-based applications
- Self-hosting a cloud-based working environment
- Creating a customized, user-friendly interface for managing and interacting with cloud-based resources and services
- Setting up a collaborative, online workspace for remote teams and distributed organizations
- Building a web-based operating system that can run on various devices, including laptops, tablets, and smartphones
- Creating a virtualized environment for testing and deploying web-based applications in a sandboxed environment
- Building a platform for creating and hosting web-based educational or training content
- Setting up a web-based development environment for prototyping and building web-based applications quickly and easily
- Etc, You name it!
The frontend is developed in typescript/javascript + CSS, it provides the
Core API, web-based window manager, application APIs, a GUI toolkit, and file system abstractions. It also includes an application store and an SDK for in-browser application development, deployment, and packaging. The frontend is designed to work across devices, including desktop computers and mobile devices.
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.
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)
[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://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
- API: [https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/](https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/)
## Change logs
* 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
- 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
@ -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
- 9c06d88 - AntOS load automatically custom VFS handles if available
- c23cb1b - Improve core API: - improve OS exit API - improve VFS API
* V.1.2.0 Improvement GUI API
### V.1.2.0 Improvement GUI API
- [x] File dialog should remember last opened folder
- [x] Add dynamic key-value dialog that work on any object
- [x] Window list panel should show window title in tooltip when mouse hovering on application icon

Binary file not shown.

Before

Width:  |  Height:  |  Size: 300 KiB

2173
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;
export = Sizzle;
interface SizzleStatic {
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",
() => (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();
};

View File

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

View File

@ -35,9 +35,21 @@ namespace OS {
*/
export abstract class BaseApplication extends BaseModel {
/**
* Placeholder of all settings specific to the application.
* The settings stored in this object will be saved to system
* setting when logout and can be reused in the next login session
* Watcher of all settings specific to the application.
* The settings stored in this object will be saved to application folder
* in JSON format as .settings.json and will be loaded automatically
* when application is initialized.
*
* This object is globally 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>}
* @memberof BaseApplication
@ -61,31 +73,6 @@ namespace OS {
*/
sysdock: GUI.tag.AppDockTag;
/**
* Reference to the system application menu located
* on the system panel
*
* @type {GUI.tag.MenuTag}
* @memberof BaseApplication
*/
appmenu: GUI.tag.MenuTag;
/**
* Loading animation check timeout
*
* @private
* @memberof BaseApplication
*/
private _loading_toh: any;
/**
* Store pending loading task
*
* @private
* @type {number[]}
* @memberof BaseApplication
*/
private _pending_task: number[];
/**
*Creates an instance of BaseApplication.
* @param {string} name application name
@ -94,13 +81,8 @@ namespace OS {
*/
constructor(name: string, args: AppArgumentsType[]) {
super(name, args);
if (!setting.applications[this.name]) {
setting.applications[this.name] = {};
}
this.setting = setting.applications[this.name];
this.setting = (this.constructor as any).setting_wdg;
this.keycomb = {};
this._loading_toh = undefined;
this._pending_task = [];
}
/**
@ -114,20 +96,17 @@ namespace OS {
* @returns {void}
* @memberof BaseApplication
*/
init(): void {
init(): Promise<any> {
return new Promise(async (ok, nok) =>{
try {
this.off("*");
this.on("exit", () => this.quit(false));
// first register some base event to the app
this.on("focus", () => {
//if(this.sysdock.selectedApp != this)
this.sysdock.selectedApp = this;
this.appmenu.pid = this.pid;
this.appmenu.items = this.baseMenu() || [];
(this.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
OS.PM.pidactive = this.pid;
this.appmenu.onmenuselect = (
d: GUI.tag.MenuEventData
): void => {
return this.trigger("menuselect", d);
};
this.trigger("focused", undefined);
if (this.dialog) {
return this.dialog.show();
@ -135,8 +114,6 @@ namespace OS {
});
this.on("hide", () => {
this.sysdock.selectedApp = null;
this.appmenu.items = [];
this.appmenu.pid = -1;
if (this.dialog) {
return this.dialog.hide();
}
@ -155,39 +132,54 @@ namespace OS {
this.applySetting(m.message as string);
}
});
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != this.pid)
{
return;
}
this._pending_task.push(o.id);
this.trigger("loading", undefined);
});
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
this._pending_task.splice(i, 1);
}
if (this._pending_task.length === 0) {
// set time out
if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000);
}
});
this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme();
await this.loadScheme();
this.applyAllSetting();
}
catch(e)
{
nok(__e(e));
}
});
}
/**
* API function to register responsive UI event to the current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @param {GUI.TagResponsiveCallback} responsive callback
* @returns {void}
* @memberof BaseApplication
*/
protected morphon(validator: GUI.TagResponsiveValidator, callback: GUI.TagResponsiveCallback)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphon(validator, callback);
}
/**
* API function to unregister responsive UI event from current window tag
*
* @protected
* @param {GUI.TagResponsiveValidator} responsive validator
* @returns {void}
* @memberof BaseApplication
*/
protected morphoff(validator: GUI.TagResponsiveValidator)
{
const win = this.scheme as GUI.tag.WindowTag;
win.morphoff(validator);
}
/**
* Render the application UI by first loading its scheme
* and then mount this scheme to the DOM tree
*
* @protected
* @returns {void}
* @returns {Promise<any>}
* @memberof BaseApplication
*/
protected loadScheme(): void {
protected loadScheme(): Promise<any> {
//now load the scheme
const path = `${this.meta().path}/scheme.html`;
return this.render(path);
@ -195,9 +187,8 @@ namespace OS {
/**
* API function to perform an heavy task.
* This function will trigger the global `loading`
* event at the beginning of the task, and the `loaded`
* event after finishing the task
* This function will create a Task that is tracked by any
* task manager implementation
*
* @protected
* @param {Promise<any>} promise the promise on a task to be performed
@ -205,15 +196,11 @@ namespace OS {
* @memberof BaseApplication
*/
protected load(promise: Promise<any>): Promise<void> {
const q = this._api.mid();
return new Promise(async (resolve, reject) => {
this._api.loading(q, this.name);
return this._api.Task(async (resolve, reject) => {
try {
await promise;
this._api.loaded(q, this.name, "OK");
return resolve();
return resolve(undefined);
} catch (e) {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e));
}
});
@ -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
*
@ -360,9 +332,6 @@ namespace OS {
* @memberof BaseApplication
*/
blur(): void {
if (this.appmenu && this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
this.trigger("blur", undefined);
if(this.dialog)
{
@ -401,6 +370,16 @@ namespace OS {
return (this.scheme as GUI.tag.WindowTag).apptitle;
}
/**
* Getter to access the application window instance
*
* @memberof BaseApplication
*/
get window(): GUI.tag.WindowTag
{
return this.scheme as GUI.tag.WindowTag;
}
/**
* Function called when the application exit.
* If the input exit event is prevented, the application
@ -414,9 +393,6 @@ namespace OS {
protected onexit(evt: BaseEvent): void {
this.cleanup(evt);
if (!evt.prevent) {
if (this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
$(this.scheme).remove();
}
}
@ -435,7 +411,7 @@ namespace OS {
* Base menu definition. This function
* returns the based menu definition of all applications.
* Other application specific menu entries
* should be defined in [[menu]] function
* should be defined in {@link menu} function
*
* @protected
* @returns {GUI.BasicItemType[]}
@ -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
* specific task before exiting or to prevent the application
* to be exited
@ -489,23 +494,6 @@ namespace OS {
* @memberof BaseApplication
*/
protected cleanup(e: BaseEvent): void {}
/**
* Check if the loading tasks ended,
* if it the case, stop the animation
*
* @private
* @memberof BaseApplication
*/
private animation_check(): void {
if(this._pending_task.length === 0)
{
this.trigger("loaded", undefined);
}
if(this._loading_toh)
clearTimeout(this._loading_toh);
this._loading_toh = undefined;
}
}
BaseApplication.type = ModelType.Application;

View File

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

View File

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

View File

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

View File

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

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
*/
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
*
@ -66,7 +108,7 @@ namespace OS {
/**
* Item children, usually used by tree view or menu item
* 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[]}
* @memberof BasicItemType
@ -94,7 +136,6 @@ namespace OS {
* is allowed at a time. A dialog may have sub dialog
*/
export var dialog: BaseDialog;
/**
* Placeholder for system shortcuts
*/
@ -105,7 +146,7 @@ namespace OS {
* UI elements, then insert this UI scheme to the DOM tree.
*
* This function renders the UI of the application before calling the
* application's [[main]] function
* application's `main` function
*
* @export
* @param {string} html html scheme string
@ -133,28 +174,29 @@ namespace OS {
/**
* Load an application scheme file then render
* it with [[htmlToScheme]]
* it with {@link htmlToScheme}
*
* @export
* @param {string} path VFS path to the scheme file
* @param {BaseModel} app the target application
* @param {(HTMLElement | string)} parent The parent HTML element where the application is rendered.
* @return {Promise<any>} a promise object
*/
export function loadScheme(
path: string,
app: BaseModel,
parent: HTMLElement | string
): void {
path.asFileHandle()
.read()
.then(function (x) {
if (!x) {
return;
}
): Promise<any> {
return new Promise(async (ok,nok) =>{
try {
const x = await path.asFileHandle().read();
htmlToScheme(x, app, parent);
})
.catch((e) => {
announcer.oserror(__("Cannot load scheme: {0}", path), e);
ok(true);
}
catch(e)
{
nok(__e(e));
}
});
}
@ -165,6 +207,7 @@ namespace OS {
*/
export function clearTheme(): void {
$("head link#ostheme").attr("href", "");
$("body").attr("theme", "");
}
/**
@ -181,6 +224,7 @@ namespace OS {
}
const path = `resources/themes/${name}/${name}.css`;
$("head link#ostheme").attr("href", path);
$("body").attr("theme", name);
}
@ -192,7 +236,7 @@ namespace OS {
*/
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
* type in the system packages meta-data
@ -392,7 +517,7 @@ namespace OS {
/**
* Kill an running processes of an application, then
* unregister the application prototype definition
* from the [[application]] namespace.
* from the {@link application} namespace.
*
* This process is similar to uninstall the application
* from the current system state
@ -445,7 +570,7 @@ namespace OS {
* in the system.
*
* 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
*
* First it tries to load the package with the app name is also the
@ -520,9 +645,10 @@ namespace OS {
*
* @export
* @param {string} ph
* @param {AppArgumentsType[]} [params] service arguments
* @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) {
const arr = ph.split("/");
const srv = arr[1];
@ -538,7 +664,8 @@ namespace OS {
}
const d = await PM.createProcess(
srv,
application[srv]
application[srv],
params
);
return resolve(d);
}
@ -598,6 +725,42 @@ namespace OS {
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(
app,
application[app],
@ -643,10 +806,7 @@ namespace OS {
const data = {
icon: null,
iconclass: meta.iconclass || "",
app,
onbtclick() {
return app.toggle();
},
app
};
// TODO: this path is not good, need to create a blob of it
if (meta.icon) {
@ -657,15 +817,11 @@ namespace OS {
if (!meta.icon && !meta.iconclass) {
data.iconclass = "fa fa-cogs";
}
const dock = $("#sysdock")[0] as tag.AppDockTag;
const dock = systemDock();
app.sysdock = dock;
app.init();
app.observable.one("rendered", function () {
app.appmenu = $(
"[data-id = 'appmenu']",
"#syspanel"
)[0] as tag.MenuTag;
dock.newapp(data);
dock.addapp(data);
});
}
@ -714,7 +870,7 @@ namespace OS {
* @returns
*/
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.
*
* 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.
*
* @param {JQuery.MouseEventBase} event mouse event
@ -753,7 +909,7 @@ namespace OS {
function bindContextMenu(event: JQuery.MouseEventBase): void {
var handle = function (e: HTMLElement) {
if (e.contextmenuHandle) {
const m = $("#contextmenu")[0] as tag.MenuTag;
const m = $("#contextmenu")[0] as tag.StackMenuTag;
m.onmenuselect = () => { };
return e.contextmenuHandle(event, m);
} else {
@ -896,9 +1052,9 @@ namespace OS {
const scheme = $.parseHTML(schemes.ws);
$("#wrapper").append(scheme);
announcer.observable.one("sysdockloaded", () => {
announcer.one("sysdockloaded", () => {
$(window).on("keydown", function (event) {
const dock = $("#sysdock")[0] as tag.AppDockTag;
const dock = systemDock();
if (!dock) {
return;
}
@ -939,11 +1095,11 @@ namespace OS {
});
// system menu and dock
$("#syspanel")[0].uify(undefined);
$("#sysdock")[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
$(document).on("mouseover", function (e) {
const el: any = $(e.target).closest("[tooltip]");
@ -957,6 +1113,8 @@ namespace OS {
);
});
// mount it
const nottification = $("#sys_notification")[0] as tag.NotificationTag;
nottification.uify(undefined);
desktop().uify(undefined);
}
@ -972,7 +1130,7 @@ namespace OS {
/**
* 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
*/
@ -983,29 +1141,33 @@ namespace OS {
.replace("[ANTOS_VERSION]", OS.VERSION.version_string)
);
$("#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 = {
username: $("#txtuser").val() as string,
password: $("#txtpass").val() as string,
};
const err_label = $("#login_error")[0] as tag.LabelTag;
try {
const d = await API.handle.login(data);
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);
} 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) {
return $("#btlogin").trigger("click");
}
});
$("#txtuser").keyup(function (e) {
if (e.which === 13) {
return $("#btlogin").trigger("click");
return $("#btlogin button").trigger("click");
}
});
}
@ -1033,8 +1195,8 @@ namespace OS {
// load theme
loadTheme(setting.appearance.theme, true);
wallpaper(undefined);
OS.announcer.observable.one("syspanelloaded", async function () {
OS.announcer.observable.on("systemlocalechange", (_) =>
OS.announcer.one("syspanelloaded", async function () {
OS.announcer.on("systemlocalechange", (_) =>
$("#syspanel")[0].update()
);
@ -1085,10 +1247,10 @@ namespace OS {
});
// 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);
});
Ant.OS.announcer.observable.on("fail", function (d) {
Ant.OS.announcer.on("fail", function (d) {
console.log(d.u_data);
});
}
@ -1105,22 +1267,28 @@ namespace OS {
schemes.ws = `\
<afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-desktop id = "desktop" dir="vertical" ></afx-desktop>
<afx-desktop id = "desktop" dir="column" ></afx-desktop>
<afx-notification id="sys_notification" ></afx-notification>
</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>
<textarea id="clipboard"></textarea>\
`;
schemes.login = `\
<div id = "login_form">
<p>Welcome to AntOS, please login</p>
<input id = "txtuser" type = "text" value = "demo" ></input>
<input id = "txtpass" type = "password" value = "demo" ></input>
<button id = "btlogin">Login</button>
<div id = "login_error"></div>
</div>
<afx-vbox id = "login_form">
<afx-label data-height="35" text="Welcome to AntOS, please login"></afx-label>
<afx-vbox padding = "10">
<afx-input data-height="52" id = "txtuser" type = "text" value = "demo" label="User name"></afx-input>
<div data-height="10"></div>
<afx-input data-height="52" id = "txtpass" type = "password" value = "demo" label="Password"></afx-input>
<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>\
`;
}

View File

@ -158,7 +158,7 @@ namespace OS {
*
* @export
* @param {string} p a VFS file path e.g. home://test/
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or a list of FileInfoType
*/
export function scandir(p: string): Promise<RequestResult> {
@ -203,7 +203,7 @@ namespace OS {
*
* @export
* @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or an object of FileInfoType
*/
export function fileinfo(p: string): Promise<RequestResult> {
@ -226,12 +226,12 @@ namespace OS {
* - xml, html: the response is a XML/HTML object
* - text: plain text
*
* @returns {Promise<any>} A promise on a [[RequestResult]]
* which contains an error or an object of [[FileInfoType]]
* @returns {Promise<any>} A promise on a {@link RequestResult}
* which contains an error or an object of {@link FileInfoType}
*/
export function readfile(p: string, t: string): Promise<any> {
const path = `${API.REST}/VFS/get/`;
return API.get(path + p, t);
return API.get(path + encodeURIComponent(p), t);
}
/**
@ -240,7 +240,7 @@ namespace OS {
* @export
* @param {string} s VFS source file path
* @param {string} d VFS destination file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or a success response
*/
export function move(s: string, d: string): Promise<RequestResult> {
@ -253,7 +253,7 @@ namespace OS {
*
* @export
* @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* which contains an error or a success response
*/
export function remove(p: string): Promise<RequestResult> {
@ -278,7 +278,7 @@ namespace OS {
*
* @export
* @param {PackageCommandType} d a package command of type PackageCommandType
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/
export function packages(
d: PackageCommandType
@ -292,7 +292,7 @@ namespace OS {
*
* @export
* @param {string} d VFS destination directory path
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/
export function upload(d: string): Promise<RequestResult> {
const path = `${API.REST}/VFS/upload`;
@ -305,7 +305,7 @@ namespace OS {
* @export
* @param {string} p path to the VFS file
* @param {string} d file data encoded in Base 64
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/
export function write(
p: string,
@ -371,8 +371,8 @@ namespace OS {
* Check if a user is logged in
*
* @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
* contains an error or a [[UserSettingType]] object
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult} that
* contains an error or a {@link OS.setting.UserSettingType} object
*/
export function auth(): Promise<RequestResult> {
const p = `${API.REST}/user/auth`;
@ -383,9 +383,9 @@ namespace OS {
* Perform a login operation
*
* @export
* @param {UserLoginType} d user data [[UserLoginType]]
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
* contains an error or a [[UserSettingType]] object
* @param {UserLoginType} d user data {@link UserLoginType}
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult} that
* contains an error or a {@link OS.setting.UserSettingType} object
*/
export function login(d: UserLoginType): Promise<RequestResult> {
const p = `${API.REST}/user/login`;
@ -396,7 +396,7 @@ namespace OS {
* Perform a logout operation
*
* @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/
export function logout(): Promise<RequestResult> {
const p = `${API.REST}/user/logout`;
@ -407,65 +407,12 @@ namespace OS {
* Save the current user settings
*
* @export
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
*/
export function setting(): Promise<RequestResult> {
const p = `${API.REST}/system/settings`;
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.BaseService;
/**
* Alias to all classes that extends [[BaseModel]]
* Alias to all classes that extends {@link BaseModel}
*/
export type ModelTypeClass = {
new <T extends BaseModel>(args: AppArgumentsType[]): T;
@ -35,7 +35,7 @@ namespace OS {
* @export
* @param {string} app class name string
* @param {ProcessTypeClass} cls prototype class
* @param {GUI.AppArgumentsType[]} [args] process arguments
* @param {AppArgumentsType[]} [args] process arguments
* @returns {Promise<ProcessType>} a promise on the created process
*/
export function createProcess(
@ -136,10 +136,28 @@ namespace OS {
if (i >= 0) {
if (application[app.name].type === ModelType.Application) {
GUI.undock(app as application.BaseApplication);
// save setting file if any
if(PM.processes[app.name].length == 1)
{
const app_class = application[app.name] as typeof OS.application.BaseApplication;
let file = `${app_class.meta.path}/.settings.json`.asFileHandle();
if(file.protocol !== "home")
{
file = `home:///.antos/settings/${app.name}.json`.asFileHandle();
}
file.cache = app_class.setting_wdg;
//file.cache = JSON.stringify(app_class.setting_wdg, undefined, 4);
file
.write("object")
.catch((e) =>{
return announcer.osinfo(
__("Unable to save settings for application {0}: {1}", app.name, e.toString()));
});
}
} else {
GUI.detachservice(app as application.BaseService);
}
announcer.unregister(app);
// announcer.unregister(app);
delete PM.processes[app.name][i];
PM.processes[app.name].splice(i, 1);
}

View File

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

View File

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

View File

@ -21,10 +21,25 @@ namespace OS {
/**
* Custom user data
*
* @type {GenericObject<any>}
* @type {any}
* @memberof ButtonTag
*/
data: GenericObject<any>;
private _data: any;
/**
* Custom user data setter/gettter
*
* @memberof ButtonTag
*/
set data(v: any)
{
this._data = v;
this.set(v);
}
get data(): any
{
return this._data;
}
/**
*Creates an instance of ButtonTag.
@ -65,6 +80,27 @@ namespace OS {
(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
*

View File

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

View File

@ -61,6 +61,19 @@ namespace OS {
* @memberof DesktopTag
*/
protected mount(): void {
/**
* TRICKY HACK
* When focusing on a window which overflows the desktop,
* the desktop scrolls automatically to bottom,
* even when `overflow: 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)
{
this.observer.disconnect();
@ -87,7 +100,14 @@ namespace OS {
this.onready = (_) => {
this.observable = OS.announcer.observable;
window.onresize = () => {
announcer.trigger("desktopresize", undefined);
const evt = {
id: this.aid,
data: {
width: $(this).width(),
height: $(this).height()
}
}
announcer.trigger("desktopresize", evt);
this.calibrate();
};
@ -134,8 +154,8 @@ namespace OS {
{ text: __("Refresh"), dataid: "desktop-refresh" } as GUI.BasicItemType,
];
menu = menu.concat(setting.desktop.menu.map(e => e));
m.items = menu;
m.onmenuselect = (evt: TagEventType<tag.MenuEventData>) => {
m.nodes = menu;
m.onmenuselect = (evt) => {
if (!evt.data || !evt.data.item) return;
const item = evt.data.item.data;
switch (item.dataid) {
@ -162,7 +182,7 @@ namespace OS {
};
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)) {
return;
}

View File

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

View File

@ -48,30 +48,6 @@ namespace OS {
this._onready = v;
}
/**
* Setter:
*
* Set the direction of the list item layout.
* Two directions are available:
* - `vertical`
* - `horizontal`
*
* This setter acts as a DOM attribute
*
* Getter:
*
* Get the currently set direction of list
* item layout
*
* @memberof FloatListTag
*/
set dir(v: string) {
$(this).attr("dir", v);
this.calibrate();
}
get dir(): string {
return $(this).attr("dir");
}
/**
* Disable the dropdown option in this list
@ -125,6 +101,7 @@ namespace OS {
* @memberof FloatListTag
*/
protected mount(): void {
$(this.refs.current).hide();
$(this.refs.container)
.css("width", "100%")
.css("height", "100%");
@ -147,49 +124,26 @@ namespace OS {
*/
push(v: GenericObject<any>) {
const el = super.push(v);
this.enable_drag(el);
return el;
}
/**
* Enable drag and drop on the list
*
* @private
* @param {ListViewItemTag} el the list item DOM element
* @memberof FloatListTag
*/
private enable_drag(el: ListViewItemTag): void {
$(el)
.css("user-select", "none")
.css("cursor", "default")
.css("display", "block")
.css("position", "absolute")
.on("mousedown", (evt) => {
.css("position", "absolute");
el.enable_drag();
$(el).on("dragging", (evt) => {
const e = evt.originalEvent as CustomEvent;
const globalof = $(this.refs.mlist).offset();
evt.preventDefault();
const offset = $(el).offset();
offset.top = evt.clientY - offset.top;
offset.left = evt.clientX - offset.left;
const mouse_move = function (
e: JQuery.MouseEventBase
) {
let top = e.clientY - offset.top - globalof.top;
const offset = e.detail.offset;
let top = e.detail.current.clientY - offset.top - globalof.top;
let left =
e.clientX - globalof.left - offset.left;
e.detail.current.clientX - globalof.left - offset.left;
left = left < 0 ? 0 : left;
top = top < 0 ? 0 : top;
return $(el)
$(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);
});
})
return el;
}
/**
@ -215,7 +169,7 @@ namespace OS {
.css("left", `${cleft}px`);
const w = $(e).width();
const h = $(e).height();
if (this.dir === "vertical") {
if (this.dir === "column") {
ctop += h + 20;
if (ctop + h > gh) {
ctop = 20;

View File

@ -35,10 +35,11 @@ namespace OS {
/**
* Data placeholder for a collection of cell data
*
* @private
* @type {GenericObject<any>[]}
* @memberof GridRowTag
*/
data: GenericObject<any>[];
private _data: GenericObject<any>[];
/**
* placeholder for the row select event callback
@ -57,7 +58,7 @@ namespace OS {
super();
this.refs.yield = this;
this._onselect = (e) => {};
this._onselect = (e) => { };
}
/**
@ -89,13 +90,32 @@ namespace OS {
return this.hasattr("selected");
}
/**
* setter: set row data
*
* getter: get row data
*/
set data(v: GenericObject<any>[]) {
this._data = v;
if(v)
{
this.attach(v);
}
for (let celi = 0; celi < this.children.length; celi++) {
const cel = (this.children[celi] as GridCellPrototype);
cel.data = v[celi];
}
}
get data(): GenericObject<any>[] {
return this._data;
}
/**
* Mount the tag, do nothing
*
* @protected
* @memberof GridRowTag
*/
protected mount(): void {}
protected mount(): void { }
/**
* Init the tag before mounting: reset the data placeholder
@ -124,7 +144,7 @@ namespace OS {
* @protected
* @memberof GridRowTag
*/
protected calibrate(): void {}
protected calibrate(): void { }
/**
* This function does nothing in this tag
@ -133,7 +153,7 @@ namespace OS {
* @param {*} [d]
* @memberof GridRowTag
*/
protected reload(d?: any): void {}
protected reload(d?: any): void { }
}
/**
@ -209,7 +229,7 @@ namespace OS {
* Setter:
*
* Set the data of the cell, this will trigger
* the [[ondatachange]] function
* the {@link ondatachange} function
*
* Getter:
*
@ -220,6 +240,7 @@ namespace OS {
set data(v: GenericObject<any>) {
if (!v) return;
this._data = v;
this.attach(v);
this.ondatachange();
if (!v.selected) {
return;
@ -234,7 +255,7 @@ namespace OS {
* Setter:
*
* Set/unset the current cell as selected.
* This will trigger the [[cellselect]]
* This will trigger the {@link cellselect}
* event
*
* Getter:
@ -277,14 +298,14 @@ namespace OS {
$(this).attr("class", "afx-grid-cell");
this.oncelldbclick = this.oncellselect = (
e: TagEventType<GridCellPrototype>
): void => {};
): void => { };
this.selected = false;
$(this).css("display", "block");
$(this).on("click",(e) => {
//$(this).css("display", "block");
$(this).on("click", (e) => {
let evt = { id: this.aid, data: this };
return this.cellselect(evt, false);
});
$(this).on("dblclick", (e) => {
$(this).on(OS.mobile?"dbltap":"dblclick", (e) => {
let evt = { id: this.aid, data: this };
return this.cellselect(evt, true);
});
@ -324,7 +345,7 @@ namespace OS {
/**
* Simple grid cell defines a grid cell with
* an [[LabelTag]] as it cell layout
* an {@link LabelTag} as it cell layout
*
* @export
* @class SimpleGridCellTag
@ -358,7 +379,7 @@ namespace OS {
* @protected
* @memberof SimpleGridCellTag
*/
protected init(): void {}
protected init(): void { }
/**
* This function do nothing in this tag
@ -366,10 +387,10 @@ namespace OS {
* @protected
* @memberof SimpleGridCellTag
*/
protected calibrate(): void {}
protected calibrate(): void { }
/**
* The layout of the cell with a simple [[LabelTag]]
* The layout of the cell with a simple {@link LabelTag}
*
* @returns
* @memberof SimpleGridCellTag
@ -516,12 +537,10 @@ namespace OS {
*/
set dragndrop(v: boolean) {
this.attsw(v, "dragndrop");
if(!v)
{
if (!v) {
$(this.refs.container).off("mousedown", this._onmousedown);
}
else
{
else {
$(this.refs.container).on(
"mousedown",
this._onmousedown
@ -576,10 +595,10 @@ namespace OS {
this.dragndrop = false;
this._oncellselect = this._onrowselect = this._oncelldbclick = (
e: TagEventType<CellEventData>
): void => {};
): void => { };
this._ondragndrop = (
e: TagEventType<DnDEventDataType<GridRowTag>>
) => {};
) => { };
}
/**
@ -589,7 +608,7 @@ namespace OS {
* @param {*} [d]
* @memberof GridViewTag
*/
protected reload(d?: any): void {}
protected reload(d?: any): void { }
/**
* set the cell select event callback
@ -642,8 +661,7 @@ namespace OS {
set cellitem(v: string) {
const currci = this.cellitem;
$(this).attr("cellitem", v);
if(v != currci)
{
if (v != currci) {
// force render data
$(this.refs.grid).empty();
this.rows = this.rows;
@ -678,11 +696,15 @@ namespace OS {
)[0] as GridCellPrototype;
element.uify(this.observable);
element.data = item;
item.domel = element;
element.oncellselect = (e) => {
if(element.data.sort)
{
if (element.data.sort) {
this.sort(element.data, element.data.sort);
if (element.data.desc) {
$(element).attr("sort", "desc");
}
else {
$(element).attr("sort", "asc");
}
}
};
i++;
@ -691,7 +713,7 @@ namespace OS {
const rz = $(`<afx-resizer>`).appendTo(
this.refs.header
)[0] as ResizerTag;
$(rz).css("width", "3px");
$(rz).css("width", "1px");
let next_item = undefined;
if (i < v.length) {
next_item = v[i];
@ -751,44 +773,34 @@ namespace OS {
*/
set rows(rows: GenericObject<any>[][]) {
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
const ndrows = rows.length;
const ncrows = this.refs.grid.children.length;
const nmin = ndrows < ncrows? ndrows: ncrows;
if(this.selectedRow)
{
const nmin = ndrows < ncrows ? ndrows : ncrows;
if (this.selectedRow) {
this.selectedRow.selected = false;
this._selectedRow = undefined;
this._selectedRows = [];
}
for(let i = 0; i < nmin; i++)
{
for (let i = 0; i < nmin; i++) {
const rowel = (this.refs.grid.children[i] as GridRowTag);
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
if(ndrows < ncrows)
{
if (ndrows < ncrows) {
const arr = Array.prototype.slice.call(this.refs.grid.children);
const blacklist = arr.slice(nmin, ncrows);
for(const r of blacklist)
{
for (const r of blacklist) {
this.delete(r);
}
}
// or add more rows
else if(ndrows > ncrows)
{
for(let i = nmin; i < ndrows; i++)
{
else if (ndrows > ncrows) {
for (let i = nmin; i < ndrows; i++) {
this.push(rows[i], false);
}
}
@ -833,13 +845,13 @@ namespace OS {
* @returns {void}
* @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 __fn = (a, b) => {
return fn.call(context,a, b, index);
return fn.call(context, a, b, index);
}
this._rows.sort(__fn);
context.__f = ! context.__f;
context.desc = !context.desc;
this.rows = this._rows;
}
/**
@ -871,6 +883,25 @@ namespace OS {
}
$(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
@ -878,7 +909,7 @@ namespace OS {
* @param {GenericObject<any>[]} row list of cell data
* @param {boolean} flag indicates where the row is add to beginning or end
* of the row
* @memberof GridViewTags
* @memberof GridViewTag
*/
push(row: GenericObject<any>[], flag: boolean): void {
const rowel = $("<afx-grid-row>").css(
@ -899,25 +930,22 @@ namespace OS {
const el = rowel[0] as GridRowTag;
rowel[0].uify(this.observable);
el.data = row;
row.domel = rowel[0];
for (let cell of row) {
let tag = this.cellitem;
if (cell.tag) {
({ tag } = cell);
tag = cell.tag;
}
const el = $(`<${tag}>`).appendTo(rowel);
cell.domel = el[0];
const element = el[0] as GridCellPrototype;
element.uify(this.observable);
element.oncellselect = (e) => this.cellselect(e, false);
element.oncelldbclick = (e) => this.cellselect(e, true);
element.data = cell;
}
el.data = row;
el.onrowselect = (e) => this.rowselect({
id: el.aid,
data: {item: el}
data: { item: el }
});
}
@ -997,12 +1025,9 @@ namespace OS {
}
evt.data.items = this.selectedRows;
} else {
if(this.selectedRows.length > 0)
{
for(const item of this.selectedRows)
{
if(item != row)
{
if (this.selectedRows.length > 0) {
for (const item of this.selectedRows) {
if (item != row) {
item.selected = false;
}
}
@ -1010,7 +1035,7 @@ namespace OS {
if (this.selectedRow === row) {
return;
}
if(this.selectedRow)
if (this.selectedRow)
this.selectedRow.selected = false;
evt.data.items = [row];
this._selectedRows = [row];
@ -1110,8 +1135,8 @@ namespace OS {
let i = 0;
for (let v of colssize) {
template += `${v}px `;
i++;
template_header += `${v}px `;
i++;
if (i < colssize.length && this.resizable) {
template_header += "3px ";
}
@ -1121,6 +1146,10 @@ namespace OS {
"grid-template-columns",
template_header
);
if(this.resizable)
{
$(this.refs.grid).css("column-gap","3px");
}
}
/**
@ -1145,8 +1174,7 @@ namespace OS {
to: undefined,
};
this._onmousedown = (e) => {
if(this.multiselect || this.selectedRows == undefined || this.selectedRows.length == 0)
{
if (this.multiselect || this.selectedRows == undefined || this.selectedRows.length == 0) {
return;
}
let el: any = $(e.target).closest("afx-grid-row");
@ -1154,8 +1182,7 @@ namespace OS {
return;
}
el = el[0];
if(!this.selectedRows.includes(el))
{
if (!this.selectedRows.includes(el)) {
return;
}
this._dnd.from = this.selectedRows;
@ -1205,7 +1232,6 @@ namespace OS {
.css("top", top + "px")
.css("left", left + "px");
};
return this.calibrate();
}
@ -1222,6 +1248,7 @@ namespace OS {
{
el: "div",
ref: "container",
class: "grid_content_container",
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");
$(this.refs.iclass)
.css("flex-shrink",0);
$(this.refs.iclass_end)
.css("flex-shrink",0);
$(this.refs.i)
.css("flex-shrink",0);
$(this.refs.text)
@ -66,6 +68,7 @@ namespace OS {
this.iconclass = undefined;
this.text = undefined;
this.selectable = false;
this.iconclass$ = undefined;
}
/**
@ -94,6 +97,49 @@ namespace OS {
$(this.refs.i).hide();
}
}
/**
* set horizontal aligment of the label content
*
* @param {string} v shall be "left, right, or center"
*/
set halign(v: string)
{
let align = "center";
switch(v)
{
case "left":
align = "flex-start";
break;
case "right":
align = "flex-end";
break;
default:
break;
}
$(this.refs.container).css("justify-content", align);
}
/**
* set horizontal aligment of the label content
*
* @param {string} v shall be "top, bottom, or center"
*/
set valign(v: string)
{
let align = "center";
switch(v)
{
case "top":
align = "flex-start";
break;
case "bottom":
align = "flex-end";
break;
default:
break;
}
$(this.refs.container).css("align-items", align);
}
/**
* Set the CSS class of the label icon
@ -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
*
@ -120,9 +191,9 @@ namespace OS {
*/
set text(v: string | FormattedString) {
this._text = v;
if (v && v !== "") {
if (v) {
$(this.refs.text).show();
$(this.refs.text).html(v.__());
$(this.refs.text).text(v.__());
} else {
$(this.refs.text).hide();
}
@ -174,6 +245,7 @@ namespace OS {
{ el: "i", ref: "iclass" },
{ el: "i", ref: "i", class: "icon-style" },
{ el: "i", ref: "text", class: "label-text" },
{ el: "i", ref: "iclass_end" },
],
},
];

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@ -63,6 +63,14 @@ namespace OS {
*/
private _history: GenericObject<any>;
/**
* This placeholder store the callback for the menu open event
*
* @private
* @type {(el: StackMenuTag) => void}
* @memberof WindowTag
*/
private _onmenuopen: (el: StackMenuTag) => void;
/**
* This placeholder stores the offset of the virtual desktop element
*
@ -97,6 +105,15 @@ namespace OS {
return this.hasattr("blur-overlay");
}
/**
* Setter: set menu open event handler
*
* @memberof WindowTag
*/
set onmenuopen(f: (el: StackMenuTag) => void)
{
this._onmenuopen = f;
}
/**
* Init window tag
* - `shown`: false
@ -117,6 +134,7 @@ namespace OS {
this.minimizable = true;
this.resizable = true;
this.apptitle = "Untitled";
this._onmenuopen = undefined;
}
/**
@ -175,6 +193,24 @@ namespace OS {
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
*
@ -237,6 +273,15 @@ namespace OS {
return $(this).attr("apptitle");
}
/**
* Get the notification tag
*
* @memberof WindowTag
*/
get notification(): NotificationTag
{
return this.refs.notification as NotificationTag;
}
/**
* Resize all the children of the window based on its width and height
*
@ -262,22 +307,56 @@ namespace OS {
* @memberof WindowTag
*/
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.refs["minbt"]).on("click", (e) => {
min_btn.onbtclick =(_) => {
return this.observable.trigger("hide", {
id: this.aid,
});
});
$(this.refs["maxbt"]).on("click", (e) => {
};
btn_menu.onbtclick = (e) => {
e.data.stopPropagation();
if($(stackmenu).is(":hidden"))
{
if(this._onmenuopen)
{
this._onmenuopen(stackmenu);
}
else
{
stackmenu.reset();
}
stackmenu.show();
}
else
{
$(stackmenu).hide();
}
};
max_btn.onbtclick = (_) => {
return this.toggle_window();
});
};
$(this.refs["closebt"]).on("click", (e) => {
close_btn.onbtclick = (_) => {
return this.observable.trigger("exit", {
id: this.aid,
});
});
};
stackmenu.onmenuselect = (e) => {
if(!e.data.item.data.nodes)
{
stackmenu.selected = -1;
}
}
const left = ($(this.desktop).width() - this.width) / 2;
const top = ($(this.desktop).height() - this.height) / 2;
$(this)
@ -285,7 +364,7 @@ namespace OS {
.css("left", `${left}px`)
.css("top", `${top}px`)
.css("z-index", 10);
$(this).on("mousedown", (e) => {
$(this).on("pointerdown", (e) => {
if (this._shown) {
return;
}
@ -293,7 +372,6 @@ namespace OS {
id: this.aid,
});
});
//$(this.refs.win_overlay).css("background-color", "red");
$(this.refs["dragger"]).on("dblclick", (e) => {
return this.toggle_window();
});
@ -347,7 +425,26 @@ namespace OS {
h: this.height,
});
$(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,
});
}
@ -371,6 +468,7 @@ namespace OS {
this.observable.trigger("resize", {
id: this.aid,
data: o,
root: true
});
}
@ -384,12 +482,12 @@ namespace OS {
$(this.refs["dragger"])
.css("user-select", "none")
.css("cursor", "default");
$(this.refs["dragger"]).on("mousedown", (e) => {
e.preventDefault();
$(this.refs.dragger).on("pointerdown", (e) => {
e.originalEvent.preventDefault();
const offset = $(this).offset();
offset.top = e.clientY - offset.top;
offset.left = e.clientX - offset.left;
$(window).on("mousemove", (e) => {
$(window).on("pointermove", (e) => {
$(this.refs.win_overlay).show();
let left: number, top: number;
if (this._isMaxi) {
@ -415,10 +513,10 @@ namespace OS {
.css("top", `${top}px`)
.css("left", `${left}px`);
});
return $(window).on("mouseup", (e) => {
return $(window).on("pointerup", (e) => {
$(this.refs.win_overlay).hide();
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
$(window).off("pointermove", null);
return $(window).off("pointerup", null);
});
});
}
@ -452,32 +550,32 @@ namespace OS {
}
const mouse_up_hdl = (e) => {
$(this.refs.win_overlay).hide();
$(window).off("mousemove", mouse_move_hdl);
return $(window).off("mouseup", mouse_up_hdl);
$(window).off("pointermove", mouse_move_hdl);
return $(window).off("pointerup", mouse_up_hdl);
}
$(this.refs["grip"]).on("mousedown", (e) => {
$(this.refs["grip"]).on("pointerdown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip;
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
});
$(this.refs.grip_bottom).on("mousedown", (e) => {
$(this.refs.grip_bottom).on("pointerdown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip_bottom;
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
});
$(this.refs.grip_right).on("mousedown", (e) => {
$(this.refs.grip_right).on("pointerdown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip_right;
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
});
}
/**
@ -488,9 +586,9 @@ namespace OS {
* @returns {void}
* @memberof WindowTag
*/
private toggle_window(): void {
private toggle_window(force?: boolean): void {
let h: number, w: number;
if (!this.resizable) {
if (!this.resizable && !force) {
return;
}
if (this._isMaxi === false) {
@ -500,8 +598,8 @@ namespace OS {
width: $(this).css("width"),
height: $(this).css("height"),
};
w = $(this.desktop).width();
h = $(this.desktop).height();
w = $(this.desktop).width() - 2;
h = $(this.desktop).height() - 2;
$(this).css("top", "0").css("left", "0");
this.setsize({ w, h });
this._isMaxi = true;
@ -538,18 +636,12 @@ namespace OS {
children: [
{
el: "li",
class: "afx-window-close",
ref: "closebt",
},
children: [
{
el: "li",
class: "afx-window-minimize",
ref: "minbt",
el: "afx-button",
ref: "btnMenu",
},
{
el: "li",
class: "afx-window-maximize",
ref: "maxbt",
],
},
{
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",
ref: "yield",
@ -590,6 +711,14 @@ namespace OS {
ref: "win_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.
*
* @param {JQuery.MouseEventBase} e a mouse event
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]]
* @param {OS.GUI.tag.StackMenuTag} m The context menu element {@link OS.GUI.tag.StackMenuTag}
* @memberof HTMLElement
*/
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void;
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.StackMenuTag): void;
/**
* Mount the element and all the children on its DOM subtree. This action
@ -51,7 +51,25 @@ interface HTMLElement {
afxml(o: OS.API.Announcer): void;
/**
* Perform DOM generation ([[afxml]]) then mount ([[sync]]) all the
* Enable the drag event dispatching on this
* element
*
* This will trigger the `dragging` and `drop` event on the enabled
* element when the mouse is down, move, then up, then move
*
* The event can be listened using the traditional way,
* Example:
* ```
* elem.addEventListener('dragging', (e) => { }, false);
* elem.addEventListener('drop', (e) => { }, false);
* ```
*
* @meberof HTMLElement
*/
enable_drag(): void;
/**
* Perform DOM generation ({@link afxml}) then mount ({@link sync}) all the
* elements.
*
* @param {OS.API.Announcer} o an AntOS observable object
@ -98,7 +116,7 @@ interface Document {
namespace OS {
export namespace GUI {
/**
* [[TagLayoutType]] interface using by AFX tags to defined
* TagLayoutType interface using by AFX tags to defined
* its internal DOM hierarchy
*
* @export
@ -139,7 +157,7 @@ namespace OS {
/**
* this is the `data-id` attribute of the element,
* can be query by the [[aid]] Tag API function.
* can be query by the {@link OS.GUI.AFXTag.aid} Tag API function.
* Not to be confused with the DOM `id` attribute
*
* @type {(string | number)}
@ -162,7 +180,7 @@ namespace OS {
* @type {number|string}
* @memberof TagLayoutType
*/
width?: number|string;
width?: number | string;
/**
** `data-height` of the element, not to be confused with
@ -171,7 +189,7 @@ namespace OS {
* @type {number|string}
* @memberof TagLayoutType
*/
height?: number|string;
height?: number | string;
}
/**
@ -217,6 +235,20 @@ namespace OS {
* @memberof TagEventType
*/
data: T;
/**
* Original event if any
*
* @type {any}
* @memberof TagEventType
*/
originalEvent?: any;
/**
* is root tag?
*
* @type {boolean}
* @memberof TagEventType
*/
root?: boolean;
}
/**
@ -245,8 +277,163 @@ namespace OS {
}
/**
* Tag event callback type
*
* @export
*/
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
* subclass of this class
@ -269,7 +456,7 @@ namespace OS {
* Reference to some of the tag's children
* element. This reference object is built
* based on the `ref` property found in the
* tag layout [[TagLayoutType]]
* tag layout {@link TagLayoutType}
*
* @protected
* @type {GenericObject<HTMLElement>}
@ -288,7 +475,17 @@ namespace OS {
protected _mounted: boolean;
/**
*Creates an instance of AFXTag.
* a {@link ResponsiveHandle} to handle all responsive event
* related to this tag
*
* @private
* @memberof AFXTag
*/
private _responsive_handle: ResponsiveHandle;
private _responsive_check: (evt: TagEventType<{w: number, h: number}>) => void;
/**
* Creates an instance of AFXTag.
* @memberof AFXTag
*/
constructor() {
@ -299,6 +496,8 @@ namespace OS {
}
this._mounted = false;
this.refs = {};
this._responsive_handle = new ResponsiveHandle();
this._responsive_check = undefined;
}
/**
@ -369,6 +568,79 @@ namespace OS {
return $(this).attr("data-id");
}
/**
* Setter: Enable/disable responsive tag
*
* Getter: get responsive status
*/
set responsive(v:boolean) {
if(!v)
{
this.attsw(false,"responsive");
this.observable.off("resize",this._responsive_check);
this._responsive_check = undefined;
return;
}
if(this._responsive_check)
{
return;
}
this._responsive_check = (evt: TagEventType<{w: number, h: number}>) => {
if(!evt || !evt.root || !evt.data.w || !evt.data.h )
{
return;
}
this._responsive_handle.morph(evt.data.w, evt.data.h);
}
this.observable.on("resize", this._responsive_check);
this.attsw(true, "responsive");
}
get responsive(): boolean {
return this.hasattr("responsive");
}
/**
* Register a responsive event to this tag
*
* @param {TagResponsiveValidator} responsive validator
* @param {TagResponsiveCallback} responsive callback
* @memberof AFXTag
*/
morphon(validator: TagResponsiveValidator, callback:TagResponsiveCallback)
{
this._responsive_handle.on(validator, callback);
}
/**
* Unregister a responsive event from this tag
*
* @param {TagResponsiveValidator} responsive validator
* @memberof AFXTag
*/
morphoff(validator: TagResponsiveValidator)
{
this._responsive_handle.off(validator);
}
/**
* Attach a data to this tag
*
* This function will define a getter `domel`
* in the attached data, this getter refers to the
* current tag
*
* @returns {void}
* @memberof AFXTag
*/
attach(data: GenericObject<any>): void {
const self = this;
Object.defineProperty(data, 'domel', {
get: function () { return self },
enumerable: false,
configurable: true
})
}
/**
* Implementation from HTMLElement interface,
* this function mount the current tag hierarchy
@ -443,7 +715,7 @@ namespace OS {
/**
* Init the current tag, this function
* is called before the [[mount]] function
* is called before the {@link mount} function
*
* @protected
* @abstract
@ -472,7 +744,7 @@ namespace OS {
/**
* Update only the current tag, this function is
* called by [[update]] before chaining the
* called by {@link update} before chaining the
* update process to its children
*
* @protected
@ -489,7 +761,7 @@ namespace OS {
* @protected
* @memberof AFXTag
*/
protected calibrate(): void {}
protected calibrate(): void { }
/**
* 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 {
$(this)
.children()
.each(function () {
if(this.update)
if (this.update)
return this.update(d);
});
};
@ -625,6 +934,12 @@ namespace OS {
* stored in the `customElements` registry of the browser
*/
export namespace tag {
/**
* Alias to all classes that extends {@link AFXTag}
*/
export type AFXTagTypeClass = {
new <T extends AFXTag>(): T;
};
/**
* Define an AFX tag as a custom element and add it to the
* global `customElements` registry. If the tag is redefined, i.e.
@ -632,14 +947,14 @@ namespace OS {
* new definition
*
* @export
* @template T all classes that extends [[AFXTag]]
* @template T all classes that extends {@link AFXTag}
* @param {string} name name of the tag
* @param {{ new (): T }} cls the class that defines the tag
* @returns {void}
*/
export function define<T extends AFXTag>(
name: string,
cls: { new (): T }
cls: { new(): T }
): void {
try {
customElements.define(name, cls);

View File

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

View File

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

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
## 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

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

View File

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

View File

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

View File

@ -1,19 +1,17 @@
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="400">
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="500" responsive="true">
<afx-tile data-id="container" name="vbox" dir="column">
<afx-tile data-height = "35" min-width="120" data-width="180" data-id = "nav-bar" name="hbox" dir="row">
<afx-list-view data-id = "favouri" dropdown="true"></afx-list-view>
</afx-tile>
<afx-resizer data-id="resizer" dir="column" disable="true" data-width="3" data-height="0"></afx-resizer>
<afx-vbox>
<afx-hbox data-height = "23" data-id = "nav-bar">
<afx-button data-width = "23" data-id = "btback" iconclass = "fa fa-arrow-left"></afx-button>
<input type = "text" data-id = "navinput"></input>
<div data-width = "2"></div>
<afx-button data-width = "23" data-id = "btgrid" iconclass = "fa fa-th"></afx-button>
<div data-width = "2"></div>
<afx-button data-width = "23" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
<afx-hbox data-width="120" data-height="35" data-id="nav-bar">
<afx-tab-bar data-id = "path-nav" dir="row" ></afx-tab-bar>
<afx-button data-width = "40" data-id = "btback" iconclass = "fa fa-arrow-up"></afx-button>
<afx-button data-width = "40" data-id = "btgrid" iconclass = "fa fa-th"></afx-button>
<afx-button data-width = "40" data-id = "btlist" iconclass = "fa fa-th-list"></afx-button>
</afx-hbox>
<div data-height="5"></div>
<afx-hbox>
<afx-list-view data-id = "favouri" data-width = "150" min-width="100">
</afx-list-view>
<afx-resizer data-width = "3" ></afx-resizer>
<afx-file-view data-id = "fileview"></afx-file-view>
</afx-hbox>
</afx-vbox>
</afx-tile>
</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
## Change logs
- 0.2.8-b: use system css variables in theme
- 0.2.7-b: only launch application
- 0.2.6-b: improve install process
- v0.2.5-b: add README.md

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

View File

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

View File

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

View File

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

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
## 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;
padding:10px;
border: 0;
background-color: transparent;
background-color: var(--background-tertiary);
}

View File

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

View File

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

View File

@ -67,7 +67,7 @@ namespace OS {
this.wplist.buttons = [
{
text: "+",
iconclass: "bi bi-plus",
onbtclick: (e) => {
return this.parent
.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
## Change logs
-v0.1.2-b: minor bug fix on UI
-v0.1.2-b: add README
- v0.1.5-b: fix VFS setting dialog bugs
- v0.1.4-b: use system css variables in theme
- v0.1.2-b: minor bug fix on UI
- v0.1.2-b: add README

View File

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

View File

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

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;
}
afx-app-window[data-id = "setting-window"] div.footer{
border-right: 1px solid #cbcbcb;
}
/*APPEARANCE*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] div[data-id = "wp-preview"]{
display: block;
border:1px solid #cbcbcb;
border:1px solid var(--border-quaternary);
border-radius: 10px;
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-list-view[data-id="wplist"]
{
border:1px solid #cbcbcb;
padding:2px;
border:1px solid var(--border-quaternary);
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-resizer{
border:0;
@ -35,7 +20,7 @@ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-re
/*VFS*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-list-view[data-id="mplist"]
{
border: 1px solid #cbcbcb;
border: 1px solid var(--border-quaternary);
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.btnsel button{
@ -46,15 +31,15 @@ afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.bt
/*LANGUAGES*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="locale"] afx-list-view[data-id="lglist"]
{
border: 1px solid #cbcbcb;
border: 1px solid var(--border-quaternary);
}
/*STARTUP*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="startup"] afx-list-view
{
border: 1px solid #cbcbcb;
border: 1px solid var(--border-quaternary);
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="app-services"] afx-list-view
{
border: 1px solid #cbcbcb;
border: 1px solid var(--border-quaternary);
}

View File

@ -98,14 +98,14 @@ namespace OS {
* @memberof Setting
*/
main(): void{
//this.containter = this.find("container") as GUI.tag.TabContainerTag;
const containter = this.find("container") as GUI.tag.TabContainerTag;
new Setting.AppearanceHandle(this.find("appearance"), this);
new Setting.VFSHandle(this.find("vfs"), this);
new Setting.LocaleHandle(this.find("locale"), this);
new Setting.StartupHandle(this.find("startup"), this);
new Setting.AppAndServiceHandle(this.find("app-services"), this);
containter.selectedIndex = 0;
(this.find("btnsave") as GUI.tag.ButtonTag ).onbtclick = (e) => {
this._api
.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;

View File

@ -6,7 +6,7 @@
"author": "Xuan Sang LE",
"email": "xsang.le@gmail.com"
},
"version":"0.1.3-b",
"version":"0.1.5-b",
"category":"System",
"iconclass":"fa fa-wrench",
"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-tab-container data-id = "container" dir = "row" tabbarwidth= "150">
<afx-tab-container data-id = "container" dir = "column" tabbarheight= "40" tabbarwidth="160">
<afx-hbox tabname="__(Appearance)" data-id="appearance" iconclass = "fa fa-paint-brush">
<div data-width="10"></div>
<afx-vbox>
<div data-height="5"></div>
<afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="23"></afx-label>
<afx-label text = "__(Wallpaper)" iconclass = "fa fa-image" class = "header" data-height="30"></afx-label>
<afx-hbox>
<afx-list-view data-width="150" data-id="wplist"></afx-list-view>
<afx-resizer data-width="2"></afx-resizer>
<afx-vbox>
<div data-id = "wp-preview"></div>
</afx-hbox>
<div data-height="5"></div>
<afx-hbox data-height="25">
<afx-hbox data-height="35">
<afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
<div data-width="5"></div>
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
</afx-hbox>
</afx-vbox>
</afx-hbox>
<div data-height="5"></div>
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="23"></afx-label>
<afx-list-view data-height="30" data-id="theme-list" dropdown="true"></afx-list-view>
<afx-label text = "__(Theme)" iconclass = "fa fa-window-restore" class = "header" data-height="30"></afx-label>
<afx-list-view data-height="35" data-id="theme-list" dropdown="true"></afx-list-view>
<div data-height="5"></div>
</afx-vbox>
<div data-width="10"></div>
@ -31,22 +27,19 @@
<afx-hbox data-id="vfs" tabname = "__(VFS)" iconclass = "fa fa-inbox">
<div data-width="10"></div>
<afx-vbox>
<div data-height="5"></div>
<afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="23"></afx-label>
<afx-label text = "__(Mount points)" iconclass = "fa fa-folder" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="mplist"></afx-list-view>
<div data-height="5"></div>
<afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="23"></afx-label>
<afx-hbox data-height = "25" >
<afx-label text = "__(Desktop path)" iconclass = "fa fa-desktop" class = "header" data-height="30"></afx-label>
<afx-hbox data-height = "40" >
<div data-width="16"></div>
<afx-label data-id="dpath"></afx-label>
<afx-button text="" iconclass = "fa fa-arrow-up" data-id="btndpath" data-width="20" class="btnsel"></afx-button>
<afx-button text="" iconclass = "fa fa-arrow-up" data-id="btndpath" data-width="40" class="btnsel"></afx-button>
</afx-hbox>
<div data-height="5"></div>
<afx-label text = "__(Local packages path)" iconclass = "fa fa-cube" class = "header" data-height="23"></afx-label>
<afx-hbox data-height = "25" >
<afx-label text = "__(Local packages path)" iconclass = "fa fa-cube" class = "header" data-height="30"></afx-label>
<afx-hbox data-height = "40" >
<div data-width="16"></div>
<afx-label data-id="ppath"></afx-label>
<afx-button text="" data-id="btnppath" iconclass = "fa fa-arrow-up" data-width="20" class="btnsel"></afx-button>
<afx-button text="" data-id="btnppath" iconclass = "fa fa-arrow-up" data-width="40" class="btnsel"></afx-button>
</afx-hbox>
<div data-height="10"></div>
</afx-vbox>
@ -56,8 +49,7 @@
<afx-hbox data-id="locale" tabname = "__(Languages)"iconclass = "fa fa-globe">
<div data-width="10"></div>
<afx-vbox>
<div data-height="5"></div>
<afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="23"></afx-label>
<afx-label text = "__(System locale)" iconclass = "fa fa-globe" class = "header" data-height="30"></afx-label>
<afx-list-view data-id="lglist"></afx-list-view>
<div data-height="10"></div>
</afx-vbox>
@ -67,10 +59,10 @@
<afx-hbox data-id="startup" tabname = "__(Startup)" iconclass = "fa fa-cog">
<div data-width="10"></div>
<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>
<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>
<div data-height="10"></div>
</afx-vbox>
@ -80,10 +72,10 @@
<afx-hbox data-id="app-services" tabname = "__(Apps. and Services)" iconclass = "fa fa-adn">
<div data-width="10"></div>
<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>
<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>
<div data-height="10"></div>
</afx-vbox>
@ -91,10 +83,10 @@
</afx-hbox>
</afx-tab-container>
<afx-hbox data-height="35">
<div data-width = "150" class = "footer"></div>
<afx-hbox data-height="45">
<div></div>
<div style="text-align:right" >
<afx-button text="__(Save)" data-id="btnsave" iconclass="fa fa-save" style="margin-right:10px;" ></afx-button>
<afx-button text="__(Save)" data-id="btnsave" iconclass="fa fa-save" style="margin-right:5px;" ></afx-button>
</div>
</afx-hbox>

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 =
@ -7,5 +7,5 @@ cssfiles = main.css
copyfiles = package.json scheme.html README.md
PKG_NAME=Syslog
PKG_NAME=SystemReport
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
* @class Syslog
* @class SystemReport
* @extends {BaseApplication}
*/
export class Syslog extends BaseApplication {
export class SystemReport extends BaseApplication {
private loglist: TAG.ListViewTag;
private logdetail: HTMLElement;
private srv: PushNotification;
constructor(args: AppArgumentsType[]) {
super("Syslog", args);
super("SystemReport", args);
}
/**
*
*
* @memberof Syslog
* @memberof SystemReport
*/
/**
*
*
* @memberof Syslog
* @memberof SystemReport
*/
main(): void {
this.loglist = this.find("loglist") as TAG.ListViewTag;
this.logdetail = this.find("logdetail");
this._gui
.pushService("Syslog/PushNotification")
.pushService("SystemServices/PushNotification")
.then((srv) => {
this.srv = srv as PushNotification;
@ -231,7 +231,7 @@ detail:
*
*
* @param {GenericObject<any>} log
* @memberof Syslog
* @memberof SystemReport
*/
addLog(log: GenericObject<any>): void {
this.loglist.push(log);
@ -241,7 +241,7 @@ detail:
*
*
* @returns {void}
* @memberof Syslog
* @memberof SystemReport
*/
cleanup(): void {
if (this.srv) {
@ -250,6 +250,6 @@ detail:
}
}
Syslog.singleton = true;
SystemReport.singleton = true;
}
}

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-list-view data-id = "loglist" data-width="200"> </afx-list-view>
<afx-resizer data-width = "2" ></afx-resizer>
@ -7,11 +7,11 @@
<pre><code data-id="logdetail"></code></pre>
</div>
<div data-height="10" ></div>
<afx-hbox style="text-align:right;" data-height = "27">
<afx-button data-width ="20"
<afx-hbox style="text-align:right;" data-height = "35">
<afx-button data-width ="content"
tooltip = "ct:__(Clear all logs)" iconclass = "fa fa-trash-o" data-id = "btclean" ></afx-button>
<input type = "text" data-id = "txturi" ></input>
<afx-button data-width ="80" text = "__(Report)"
<afx-button data-width ="content" text = "__(Report)"
iconclass = "fa fa-bug" data-id = "btnreport" ></afx-button>
<div data-width="10" ></div>
</afx-hbox>

View File

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

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

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

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