mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-04-28 09:36:44 +02:00
Compare commits
92 Commits
f4ac0d639d
...
04da2a9d39
Author | SHA1 | Date | |
---|---|---|---|
|
04da2a9d39 | ||
|
8c20cfec5e | ||
|
24788a421c | ||
|
e479fe43c9 | ||
|
4a46104710 | ||
|
5605d6c35a | ||
|
6ac2429dba | ||
|
297d471c1d | ||
|
d95f9382f3 | ||
|
d00fd3bd82 | ||
|
cbeab0d0f2 | ||
|
a86da11532 | ||
|
984cdae438 | ||
|
3e201bfbba | ||
|
3d09a20512 | ||
|
87dad4eded | ||
|
e6bf4d5352 | ||
|
a5257bf108 | ||
|
eac84a3ab8 | ||
|
6523fafe91 | ||
|
acd36a7a29 | ||
|
add5ef77c8 | ||
|
4d59b104b9 | ||
|
0ac886f644 | ||
|
f52e9a38d2 | ||
|
a74cf39152 | ||
|
082a85644b | ||
|
a00acf4bbf | ||
|
bff2c94fa9 | ||
|
0f2ab549e8 | ||
|
cd6a6c373a | ||
|
56ca945b0c | ||
|
82f35f791e | ||
|
b30a2bb44c | ||
|
2c64dfe00d | ||
|
1d1218acbd | ||
|
0d8daa36e8 | ||
|
d72a4c954b | ||
|
5f92e41021 | ||
|
d3540d7575 | ||
|
60137fb4fb | ||
|
7196f9ff57 | ||
|
1063ae9c4f | ||
|
649c7d942a | ||
|
b490ae9d42 | ||
|
ae91401965 | ||
|
d482d2cad4 | ||
|
b2ec7cc8db | ||
|
db006345a9 | ||
|
bf793ec204 | ||
|
cb9ccb576f | ||
|
ea160c2ccb | ||
|
edb427d6c3 | ||
|
be72a62156 | ||
|
77b89c44f7 | ||
|
242df06a28 | ||
|
c52e4b649e | ||
|
30c63748b0 | ||
|
84cfc87ce0 | ||
|
f21a958ea0 | ||
|
81d13c1601 | ||
|
9f07a86901 | ||
|
7b3072576c | ||
|
be73a3c7ae | ||
|
038823a7cb | ||
|
70301d8817 | ||
|
9d1c66fe50 | ||
|
6948b0e339 | ||
|
b35812bb43 | ||
|
11fb8c97af | ||
|
d9314fc829 | ||
|
722e672947 | ||
|
93b58c7aa2 | ||
|
9e7c7e6d78 | ||
|
25e9efff46 | ||
|
9baee31c01 | ||
|
de7f027940 | ||
|
870b1ec105 | ||
|
03cee726ed | ||
|
303fc9ba20 | ||
|
243dfa7a89 | ||
|
bc77329294 | ||
|
e1c1895070 | ||
|
0b80a29d00 | ||
|
b026b96bf2 | ||
|
3c25d8b52e | ||
|
b5863702cb | ||
|
2ae390ad4b | ||
|
2e887851c5 | ||
|
1a6ece4e7c | ||
|
d5d6a16a85 | ||
|
cd294f58a6 |
20
.drone.yml
20
.drone.yml
@ -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
14
Jenkinsfile
vendored
@ -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
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
35
Makefile
35
Makefile
@ -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
|
||||
|
44
README.md
44
README.md
@ -1,28 +1,23 @@
|
||||
# antOS v1.2.1
|
||||
[](https://ci.iohub.dev/job/gitea-sync/job/antos/job/master/)
|
||||
[](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
|
||||
|
||||

|
||||
`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
|
||||
|
BIN
antos-shot.png
BIN
antos-shot.png
Binary file not shown.
Before Width: | Height: | Size: 300 KiB |
2195
d.ts/antos.d.ts
vendored
2195
d.ts/antos.d.ts
vendored
File diff suppressed because it is too large
Load Diff
3
d.ts/jquery.d.ts
vendored
3
d.ts/jquery.d.ts
vendored
@ -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
BIN
release/antos-2.0.0.tar.gz
Normal file
Binary file not shown.
@ -1 +1 @@
|
||||
1.2.1
|
||||
2.0.0
|
@ -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();
|
||||
};
|
@ -107,21 +107,19 @@ namespace OS {
|
||||
* @interface AnnouncerListenerType
|
||||
*/
|
||||
export interface AnnouncerListenerType {
|
||||
[index: number]: {
|
||||
/**
|
||||
* The event name
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
e: string;
|
||||
/**
|
||||
* The event name
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
e: string;
|
||||
|
||||
/**
|
||||
* The event callback
|
||||
*
|
||||
*/
|
||||
f: (d: any) => void;
|
||||
}[];
|
||||
}
|
||||
/**
|
||||
* The event callback
|
||||
*
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,80 +96,90 @@ namespace OS {
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
init(): void {
|
||||
this.off("*");
|
||||
this.on("exit", () => this.quit(false));
|
||||
// first register some base event to the app
|
||||
this.on("focus", () => {
|
||||
this.sysdock.selectedApp = this;
|
||||
this.appmenu.pid = this.pid;
|
||||
this.appmenu.items = this.baseMenu() || [];
|
||||
OS.PM.pidactive = this.pid;
|
||||
this.appmenu.onmenuselect = (
|
||||
d: GUI.tag.MenuEventData
|
||||
): void => {
|
||||
return this.trigger("menuselect", d);
|
||||
};
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
return this.dialog.show();
|
||||
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.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
|
||||
OS.PM.pidactive = this.pid;
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
return this.dialog.show();
|
||||
}
|
||||
});
|
||||
this.on("hide", () => {
|
||||
this.sysdock.selectedApp = null;
|
||||
if (this.dialog) {
|
||||
return this.dialog.hide();
|
||||
}
|
||||
});
|
||||
this.on("menuselect", (d) => {
|
||||
switch (d.data.item.data.dataid) {
|
||||
case `${this.name}-about`:
|
||||
return this.openDialog("AboutDialog");
|
||||
case `${this.name}-exit`:
|
||||
return this.trigger("exit", undefined);
|
||||
}
|
||||
});
|
||||
this.on("apptitlechange", () => this.sysdock.update(this));
|
||||
this.subscribe("appregistry", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.message as string);
|
||||
}
|
||||
});
|
||||
|
||||
this.updateLocale(this.systemsetting.system.locale);
|
||||
await this.loadScheme();
|
||||
this.applyAllSetting();
|
||||
}
|
||||
});
|
||||
this.on("hide", () => {
|
||||
this.sysdock.selectedApp = null;
|
||||
this.appmenu.items = [];
|
||||
this.appmenu.pid = -1;
|
||||
if (this.dialog) {
|
||||
return this.dialog.hide();
|
||||
}
|
||||
});
|
||||
this.on("menuselect", (d) => {
|
||||
switch (d.data.item.data.dataid) {
|
||||
case `${this.name}-about`:
|
||||
return this.openDialog("AboutDialog");
|
||||
case `${this.name}-exit`:
|
||||
return this.trigger("exit", undefined);
|
||||
}
|
||||
});
|
||||
this.on("apptitlechange", () => this.sysdock.update(this));
|
||||
this.subscribe("appregistry", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.message as string);
|
||||
}
|
||||
});
|
||||
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
|
||||
if(o.u_data != this.pid)
|
||||
catch(e)
|
||||
{
|
||||
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);
|
||||
nok(__e(e));
|
||||
}
|
||||
});
|
||||
this.updateLocale(this.systemsetting.system.locale);
|
||||
return this.loadScheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* API function to register responsive UI event to the current window tag
|
||||
*
|
||||
* @protected
|
||||
* @param {GUI.TagResponsiveValidator} responsive validator
|
||||
* @param {GUI.TagResponsiveCallback} responsive callback
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected morphon(validator: GUI.TagResponsiveValidator, callback: GUI.TagResponsiveCallback)
|
||||
{
|
||||
const win = this.scheme as GUI.tag.WindowTag;
|
||||
win.morphon(validator, callback);
|
||||
}
|
||||
/**
|
||||
* API function to unregister responsive UI event from current window tag
|
||||
*
|
||||
* @protected
|
||||
* @param {GUI.TagResponsiveValidator} responsive validator
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected morphoff(validator: GUI.TagResponsiveValidator)
|
||||
{
|
||||
const win = this.scheme as GUI.tag.WindowTag;
|
||||
win.morphoff(validator);
|
||||
}
|
||||
/**
|
||||
* Render the application UI by first loading its scheme
|
||||
* 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)
|
||||
{
|
||||
@ -400,7 +369,17 @@ namespace OS {
|
||||
title(): string | FormattedString {
|
||||
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;
|
||||
|
@ -58,22 +58,15 @@ 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;
|
||||
if (this.scheme) {
|
||||
$(this.scheme).remove();
|
||||
}
|
||||
if (this.dialog) {
|
||||
return this.dialog.quit();
|
||||
}
|
||||
protected destroy(): void
|
||||
{
|
||||
if (this.scheme) {
|
||||
$(this.scheme).remove();
|
||||
}
|
||||
}
|
||||
|
||||
@ -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-calendar-view data-id = "cal" ></afx-calendar-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>
|
||||
<div data-height="10" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<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="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>
|
||||
</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-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>
|
||||
<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="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-grid-view data-id = "grid" ></afx-grid-view>
|
||||
<div data-height="10" ></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "50" ></afx-button>
|
||||
</afx-hbox>
|
||||
<div data-height="10" ></div>
|
||||
</afx-vbox>
|
||||
<div data-width = "10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-app-window width='300' height='350' apptitle = "Info" >
|
||||
<afx-vbox padding = "10">
|
||||
<afx-grid-view data-id = "grid" ></afx-grid-view>
|
||||
<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-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>
|
||||
<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="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-vbox>
|
||||
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
|
||||
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" ></input>
|
||||
<afx-hbox data-height = '30'>
|
||||
<div style=' text-align:right;'>
|
||||
<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 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>
|
||||
<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>
|
||||
</afx-vbox>
|
||||
</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>
|
||||
{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>
|
||||
</afx-vbox>
|
||||
<div data-width="10" ></div>
|
||||
</afx-hbox>
|
||||
<afx-vbox padding = "5">
|
||||
{1}
|
||||
<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>`;
|
||||
|
||||
|
||||
@ -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")
|
||||
.appendTo(div);
|
||||
.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>`;
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
398
src/core/core.ts
398
src/core/core.ts
@ -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,12 +608,11 @@ 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];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const mt = arr[0].match(/\d+/g);
|
||||
if (!mt) {
|
||||
API.throwe(
|
||||
@ -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,34 +1296,35 @@ 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({
|
||||
type: "POST",
|
||||
url: p,
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(
|
||||
d,
|
||||
function (k, v) {
|
||||
if (k === "domel") {
|
||||
return undefined;
|
||||
}
|
||||
return v;
|
||||
},
|
||||
4
|
||||
),
|
||||
dataType: "json",
|
||||
success: null,
|
||||
})
|
||||
.done(function (data) {
|
||||
API.loaded(q, p, "OK");
|
||||
return resolve(data);
|
||||
return API.Task(async (resolve, reject) => {
|
||||
try {
|
||||
$.ajax({
|
||||
type: "POST",
|
||||
url: p,
|
||||
contentType: "application/json",
|
||||
data: JSON.stringify(
|
||||
d,
|
||||
function (k, v) {
|
||||
if (k === "domel") {
|
||||
return undefined;
|
||||
}
|
||||
return v;
|
||||
},
|
||||
4
|
||||
),
|
||||
dataType: "json",
|
||||
success: null,
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
API.loaded(q, p, "FAIL");
|
||||
return reject(API.throwe(s));
|
||||
});
|
||||
.done(function (data) {
|
||||
return resolve(data);
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
reject(e);
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1228,21 +1340,17 @@ namespace OS {
|
||||
* @returns {Promise<ArrayBuffer>} a promise on the returned binary data
|
||||
*/
|
||||
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,39 +1366,41 @@ 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 =
|
||||
const o =
|
||||
$("<input>")
|
||||
.attr("type","file")
|
||||
.attr("multiple","true");
|
||||
o.on("change", function () {
|
||||
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({
|
||||
url: p,
|
||||
data: formd,
|
||||
type: "POST",
|
||||
contentType: false,
|
||||
processData: false,
|
||||
})
|
||||
.done(function (data) {
|
||||
API.loaded(q, p, "OK");
|
||||
resolve(data);
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
API.loaded(q, p, "FAIL");
|
||||
o.remove();
|
||||
reject(API.throwe(s));
|
||||
});
|
||||
.attr("type", "file")
|
||||
.attr("multiple", "true");
|
||||
o.on("change", async () => {
|
||||
try {
|
||||
const files = (o[0] as HTMLInputElement).files;
|
||||
const formd = new FormData();
|
||||
formd.append("path", d);
|
||||
jQuery.each(files, (i, file) => {
|
||||
formd.append(`upload-${i}`, file);
|
||||
});
|
||||
const ret = await API.Task((ok, nok) => {
|
||||
$.ajax({
|
||||
url: p,
|
||||
data: formd,
|
||||
type: "POST",
|
||||
contentType: false,
|
||||
processData: false,
|
||||
})
|
||||
.done(function (data) {
|
||||
ok(data);
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
//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([]));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
202
src/core/db.ts
202
src/core/db.ts
@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
296
src/core/gui.ts
296
src/core/gui.ts
@ -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,29 +174,30 @@ 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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -241,6 +285,87 @@ namespace OS {
|
||||
return dialog.init();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@ -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>\
|
||||
`;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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 {
|
||||
addapp(item: AppDockItemType): void {
|
||||
const collection = this.items.filter(it => it.app.name == item.app.name);
|
||||
let bt = undefined;
|
||||
if(collection.length == 0)
|
||||
{
|
||||
this.add_button(item.app.name, item);
|
||||
}
|
||||
else
|
||||
{
|
||||
bt = collection[0].domel;
|
||||
item.domel = bt;
|
||||
$(bt).addClass("plural");
|
||||
}
|
||||
this.items.push(item);
|
||||
const el = $("<afx-button>");
|
||||
const bt = el[0] as ButtonTag;
|
||||
el.appendTo(this);
|
||||
el[0].uify(this.observable);
|
||||
bt.set(item);
|
||||
bt.data = item.app;
|
||||
item.domel = bt;
|
||||
$(bt).attr("tooltip", `cr:${item.app.title()}`);
|
||||
bt.onbtclick = (e) => {
|
||||
e.id = this.aid;
|
||||
//e.data.item = item;
|
||||
this._onappselect(e);
|
||||
item.app.show();
|
||||
};
|
||||
this.selectedApp = item.app;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
case "new":
|
||||
GUI.launch(app.name, []);
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
switch (evt.data.item.data.dataid) {
|
||||
case "new":
|
||||
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);
|
||||
|
@ -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.
|
||||
@ -64,6 +79,27 @@ namespace OS {
|
||||
$(this).attr("iconclass", v);
|
||||
(this.refs.label as LabelTag).iconclass = v;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the icon class on the right side of the button, this property
|
||||
* allows to style the button icon using CSS
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set iconclass$(v: string) {
|
||||
$(this).attr("iconclass_end", v);
|
||||
(this.refs.label as LabelTag).iconclass$ = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSS class of the label icon on the right side
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set iconclass_end(v: string) {
|
||||
this.iconclass$ = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the text of the button
|
||||
|
@ -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" },
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) => {
|
||||
const globalof = $(this.refs.mlist).offset();
|
||||
evt.preventDefault();
|
||||
const offset = $(el).offset();
|
||||
offset.top = evt.clientY - offset.top;
|
||||
offset.left = evt.clientX - offset.left;
|
||||
const mouse_move = function (
|
||||
e: JQuery.MouseEventBase
|
||||
) {
|
||||
let top = e.clientY - offset.top - globalof.top;
|
||||
let left =
|
||||
e.clientX - globalof.left - offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
top = top < 0 ? 0 : top;
|
||||
return $(el)
|
||||
.css("top", `${top}px`)
|
||||
.css("left", `${left}px`);
|
||||
};
|
||||
|
||||
var mouse_up = function (e: JQuery.MouseEventBase) {
|
||||
$(window).off("mousemove", mouse_move);
|
||||
return $(window).off("mouseup", mouse_up);
|
||||
};
|
||||
$(window).on("mousemove", mouse_move);
|
||||
return $(window).on("mouseup", mouse_up);
|
||||
});
|
||||
.css("position", "absolute");
|
||||
el.enable_drag();
|
||||
$(el).on("dragging", (evt) => {
|
||||
const e = evt.originalEvent as CustomEvent;
|
||||
const globalof = $(this.refs.mlist).offset();
|
||||
const offset = e.detail.offset;
|
||||
let top = e.detail.current.clientY - offset.top - globalof.top;
|
||||
let left =
|
||||
e.detail.current.clientX - globalof.left - offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
top = top < 0 ? 0 : top;
|
||||
$(el)
|
||||
.css("top", `${top}px`)
|
||||
.css("left", `${left}px`);
|
||||
})
|
||||
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;
|
||||
|
@ -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,9 +58,9 @@ namespace OS {
|
||||
super();
|
||||
|
||||
this.refs.yield = this;
|
||||
this._onselect = (e) => {};
|
||||
this._onselect = (e) => { };
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set item select event handle
|
||||
*
|
||||
@ -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
|
||||
@ -486,7 +507,7 @@ namespace OS {
|
||||
private _ondragndrop: TagEventCallback<
|
||||
DnDEventDataType<GridRowTag>
|
||||
>;
|
||||
|
||||
|
||||
/**
|
||||
* Creates an instance of GridViewTag.
|
||||
* @memberof GridViewTag
|
||||
@ -506,7 +527,7 @@ namespace OS {
|
||||
this._ondragndrop = v;
|
||||
this.dragndrop = this.dragndrop;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Setter: Enable/disable drag and drop event in the list
|
||||
*
|
||||
@ -516,13 +537,11 @@ namespace OS {
|
||||
*/
|
||||
set dragndrop(v: boolean) {
|
||||
this.attsw(v, "dragndrop");
|
||||
if(!v)
|
||||
{
|
||||
if (!v) {
|
||||
$(this.refs.container).off("mousedown", this._onmousedown);
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this.refs.container).on(
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -824,22 +836,22 @@ namespace OS {
|
||||
get resizable(): boolean {
|
||||
return this.hasattr("resizable");
|
||||
}
|
||||
/**
|
||||
* Sort the grid using a sort function
|
||||
*
|
||||
* @param {context: any} context of the executed function
|
||||
* @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data
|
||||
* * @param {index: number} current header index
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
sort(context: any, fn: (a:GenericObject<any>[], b:GenericObject<any>[], index?: number) => number): void {
|
||||
/**
|
||||
* Sort the grid using a sort function
|
||||
*
|
||||
* @param {context: any} context of the executed function
|
||||
* @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data
|
||||
* * @param {index: number} current header index
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
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];
|
||||
@ -1059,8 +1084,8 @@ namespace OS {
|
||||
$(this.refs.container).css(
|
||||
"height",
|
||||
$(this).height() -
|
||||
$(this.refs.header).height() +
|
||||
"px"
|
||||
$(this.refs.header).height() +
|
||||
"px"
|
||||
);
|
||||
} else {
|
||||
$(this.refs.container).css(
|
||||
@ -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
265
src/core/tags/InputTag.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -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,14 +526,22 @@ 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[]}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
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;
|
||||
}
|
||||
@ -1030,10 +1161,11 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// set the label content event it is hidden
|
||||
const label = this.refs.drlabel as LabelTag;
|
||||
label.set(e.data.data);
|
||||
if (this.dropdown) {
|
||||
const label = this.refs.drlabel as LabelTag;
|
||||
label.set(e.data.data);
|
||||
$(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
189
src/core/tags/NotificationTag.ts
Normal file
189
src/core/tags/NotificationTag.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
const top = Math.floor(
|
||||
($(this.refs.prg).height() -
|
||||
$(this.refs.point).height()) /
|
||||
2
|
||||
);
|
||||
$(this.refs.point)
|
||||
.css("left", ow + "px")
|
||||
.css("top", top + "px");
|
||||
}
|
||||
//if (this.enable) {
|
||||
const ow = w - ($(this.refs.point).outerWidth() / 2.0);
|
||||
const top = Math.floor(
|
||||
($(this.refs.prg).height() +
|
||||
$(this.refs.point).height()) /
|
||||
2 + 3
|
||||
);
|
||||
$(this.refs.point)
|
||||
.css("left", ow + "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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
573
src/core/tags/StackMenuTag.ts
Normal file
573
src/core/tags/StackMenuTag.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
76
src/core/tags/StackPanelTag.ts
Normal file
76
src/core/tags/StackPanelTag.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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' });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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.
|
||||
@ -22,6 +25,8 @@ namespace OS {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
private _padding: number;
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
@ -29,7 +34,9 @@ namespace OS {
|
||||
* @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,30 +186,34 @@ 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.length - 1] === "%") {
|
||||
dw =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiWidth) /
|
||||
100;
|
||||
} else {
|
||||
dw = parseInt(attv);
|
||||
}
|
||||
$(this).css("width", `${dw}px`);
|
||||
ocwidth += dw;
|
||||
} else {
|
||||
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)) *
|
||||
avaiWidth) /
|
||||
100;
|
||||
} else {
|
||||
dw = parseInt(attv);
|
||||
}
|
||||
$(this).css("width", `${dw}px`);
|
||||
ocwidth += dw;
|
||||
});
|
||||
|
||||
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,30 +239,34 @@ 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.length - 1] === "%") {
|
||||
dh =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiheight) /
|
||||
100;
|
||||
} else {
|
||||
dh = parseInt(attv);
|
||||
}
|
||||
$(this).css("height", `${dh}px`);
|
||||
ocheight += dh;
|
||||
} else {
|
||||
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)) *
|
||||
avaiheight) /
|
||||
100;
|
||||
} else {
|
||||
dh = parseInt(attv);
|
||||
}
|
||||
$(this).css("height", `${dh}px`);
|
||||
ocheight += dh;
|
||||
});
|
||||
|
||||
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 },
|
||||
});
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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
|
||||
*
|
||||
@ -79,7 +87,7 @@ namespace OS {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* blur overlay: If active the window overlay will be shown
|
||||
* on inactive (blur event)
|
||||
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -174,6 +192,24 @@ namespace OS {
|
||||
get height(): number {
|
||||
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",
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-minimize",
|
||||
ref: "minbt",
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-maximize",
|
||||
ref: "maxbt",
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "btnMenu",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
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"
|
||||
}
|
||||
],
|
||||
},
|
||||
];
|
||||
|
@ -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);
|
||||
|
125
src/core/vfs.ts
125
src/core/vfs.ts
@ -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) {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__("{0}: {1}", r.error, this.path)
|
||||
)
|
||||
|
||||
if (t === "base64") {
|
||||
const d = await API.handle.write(
|
||||
this.path,
|
||||
this.cache
|
||||
);
|
||||
if (d.error) {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__("{0}: {1}", d.error, this.path)
|
||||
)
|
||||
);
|
||||
}
|
||||
return resolve(d);
|
||||
} else {
|
||||
const r = await this.b64(t);
|
||||
const result = await API.handle.write(
|
||||
this.path,
|
||||
r as string
|
||||
);
|
||||
if (result.error) {
|
||||
return reject(
|
||||
API.throwe(
|
||||
__(
|
||||
"{0}: {1}",
|
||||
result.error,
|
||||
this.path
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
return resolve(result);
|
||||
}
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
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) => {
|
||||
|
@ -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>
|
||||
|
@ -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
|
@ -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;
|
||||
|
||||
afx-app-window[data-id ='files-app-window'] afx-list-view[data-id='favouri'] li.selected {
|
||||
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;
|
||||
}
|
@ -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(
|
||||
|
@ -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"],
|
||||
|
@ -1,19 +1,17 @@
|
||||
<afx-app-window data-id = "files-app-window" apptitle="Files" width="600" height="400">
|
||||
<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>
|
||||
<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-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-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>
|
||||
<afx-file-view data-id = "fileview"></afx-file-view>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
</afx-vbox>
|
||||
</afx-tile>
|
||||
</afx-app-window>
|
@ -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
|
@ -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;
|
||||
@ -82,4 +73,78 @@ afx-app-window[data-id="marketplace-win"] p.stat {
|
||||
afx-app-window[data-id = "repository-dialog-win"] afx-list-view[data-id="repo-list"] ul.complex-content > 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;
|
||||
}
|
@ -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);
|
||||
|
@ -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"],
|
||||
|
@ -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">
|
||||
<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-vbox>
|
||||
<afx-resizer data-width = "3" ></afx-resizer>
|
||||
<afx-vbox data-id = "container">
|
||||
<afx-label data-id = "appname" data-height = "45"></afx-label>
|
||||
<afx-hbox data-height = "50">
|
||||
<div style = "text-align:left;">
|
||||
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button>
|
||||
<afx-button data-id = "bt-exec" text = "__(Launch)"></afx-button>
|
||||
<afx-button data-id = "bt-install" text = "__(Install)" ></afx-button>
|
||||
<p class="stat"><afx-label data-id="vstat"></afx-label></p>
|
||||
</div>
|
||||
<afx-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>
|
||||
<div data-id="desc-container">
|
||||
<p data-id = "app-desc"></p>
|
||||
<ul data-id = "app-detail"></ul>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
<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-hbox>
|
||||
|
||||
<afx-vbox data-id = "container">
|
||||
<afx-hbox data-height = "35" data-id="app-header">
|
||||
<afx-button data-id = "appname" iconclass = "bi bi-backspace-fill"></afx-button>
|
||||
<div style = "display: flex;justify-content: flex-end;">
|
||||
<afx-button data-id = "bt-remove" text = "__(Uninstall)" iconclass = "bi bi-trash-fill"></afx-button>
|
||||
<afx-button data-id = "bt-exec" text = "__(Launch)" iconclass = "fa fa-cog"></afx-button>
|
||||
<afx-button data-id = "bt-install" text = "__(Install)" iconclass = "bi bi-cloud-download-fill" ></afx-button>
|
||||
</div>
|
||||
</afx-hbox>
|
||||
<p class="stat" data-height="15"><afx-label data-id="vstat"></afx-label></p>
|
||||
<div data-id="desc-container">
|
||||
<p data-id = "app-desc"></p>
|
||||
<ul data-id = "app-detail"></ul>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
|
||||
</afx-stack-panel>
|
||||
|
||||
|
||||
|
||||
|
||||
</afx-hbox>
|
||||
</afx-app-window>
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
@ -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": [
|
||||
|
@ -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) {
|
||||
|
@ -67,7 +67,7 @@ namespace OS {
|
||||
|
||||
this.wplist.buttons = [
|
||||
{
|
||||
text: "+",
|
||||
iconclass: "bi bi-plus",
|
||||
onbtclick: (e) => {
|
||||
return this.parent
|
||||
.openDialog("FileDialog", {
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
@ -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;
|
||||
|
@ -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"]
|
||||
|
@ -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>
|
||||
<div data-height="5"></div>
|
||||
<afx-hbox data-height="25">
|
||||
<afx-list-view data-id = "wpsize" dropdown="true"></afx-list-view>
|
||||
<div data-width="5"></div>
|
||||
<afx-list-view data-id = "wprepeat" dropdown="true"></afx-list-view>
|
||||
</afx-hbox>
|
||||
</afx-vbox>
|
||||
<div data-id = "wp-preview"></div>
|
||||
</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-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-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>
|
||||
|
||||
|
@ -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
|
@ -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%;
|
||||
}
|
@ -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": []
|
||||
}
|
@ -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
|
8
src/packages/SystemReport/README.md
Normal file
8
src/packages/SystemReport/README.md
Normal 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
|
@ -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;
|
||||
}
|
||||
}
|
36
src/packages/SystemReport/main.css
Normal file
36
src/packages/SystemReport/main.css
Normal 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%;
|
||||
}
|
15
src/packages/SystemReport/package.json
Normal file
15
src/packages/SystemReport/package.json
Normal 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": []
|
||||
}
|
@ -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>
|
@ -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
|
11
src/packages/SystemServices/Makefile
Normal file
11
src/packages/SystemServices/Makefile
Normal 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
|
@ -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>\
|
||||
`;
|
||||
}
|
9
src/packages/SystemServices/README.md
Normal file
9
src/packages/SystemServices/README.md
Normal 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
|
27
src/packages/SystemServices/main.css
Normal file
27
src/packages/SystemServices/main.css
Normal 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;
|
||||
}
|
15
src/packages/SystemServices/package.json
Normal file
15
src/packages/SystemServices/package.json
Normal 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": []
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
afx-overlay {
|
||||
background-color: rgba(54, 54, 54, 0.7);
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}*/
|
@ -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;
|
||||
|
||||
--background-overlay: rgba(54,54,54,0.7);
|
||||
|
||||
#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;
|
||||
}
|
||||
--icon-primary: white;
|
||||
--icon-secondary: #868686;
|
||||
--icon-tertiary: #76D2F9;
|
||||
|
||||
#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;
|
||||
}
|
||||
--item-bg-odd: #474747;
|
||||
--item-bg-even: #3b3b3b;
|
||||
--item-bg-active: #116cd6;
|
||||
--item-bg-hover: #464646;
|
||||
|
||||
#systooltip {
|
||||
border:1px solid #363636;
|
||||
border-radius: 3px;
|
||||
padding-left:3px;
|
||||
padding-right:3px;
|
||||
box-shadow: none;
|
||||
background-color: #464646;
|
||||
}
|
||||
--border-primary:#262626;
|
||||
--border-secondary: #363636;
|
||||
--border-tertiary: #bb86fc;
|
||||
--border-quaternary: #646363;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
@ -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
Loading…
x
Reference in New Issue
Block a user