Compare commits

..

No commits in common. "04da2a9d39bd210ae8405b52094684384d422cae" and "13969511a593899ff742b34ea52d679efc0d2583" have entirely different histories.

165 changed files with 5504 additions and 7483 deletions

20
.drone.yml Normal file
View File

@ -0,0 +1,20 @@
---
kind: pipeline
type: exec
name: default
platform:
os: linux
arch: amd64
clone:
disable: true
steps:
- name: download
commands:
- cd /opt/www/htdocs/os && wget https://github.com/lxsang/antos/raw/1.2.1/release/antos-1.2.1.tar.gz
- name: build
commands:
- cd /opt/www/htdocs/os && tar xvzf antos-1.2.1.tar.gz
- rm /opt/www/htdocs/os/antos-1.2.1.tar.gz
trigger:
branch:
- 1.2.1

14
Jenkinsfile vendored
View File

@ -22,28 +22,20 @@ 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@5.0
npm install typescript
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/, doc/', fingerprint: true
archiveArtifacts artifacts: 'd.ts/, build/', fingerprint: true
//}
}
}

View File

@ -8,8 +8,9 @@ TSC=./node_modules/typescript/bin/tsc
UGLIFYJS=./node_modules/terser/bin/terser
UGLIFYCSS=./node_modules/uglifycss/uglifycss
VERSION?=2.0.0-b
BUILDID?=master
VERSION=1.2.1
BRANCH = b
BUILDID=$(shell git rev-parse --short HEAD)
GSED=sed
UNAME_S := $(shell uname -s)
@ -26,6 +27,7 @@ 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 \
@ -38,17 +40,14 @@ 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/StackMenuTag.js \
dist/core/tags/StackPanelTag.js \
dist/core/tags/InputTag.js \
dist/core/tags/NotificationTag.js
dist/core/tags/DesktopTag.js
javascripts= dist/core/core.js \
dist/core/settings.js \
dist/core/handles/RemoteHandle.js \
dist/core/Announcerment.js \
dist/core/vfs.js \
dist/core/db.js \
dist/core/BaseModel.js \
dist/core/BaseApplication.js \
dist/core/BaseService.js \
@ -61,7 +60,7 @@ javascripts= dist/core/core.js \
antfx = $(tags) \
dist/core/Announcerment.js
packages = SystemServices SystemReport Files MarketPlace Setting NotePad
packages = Syslog Files MarketPlace Setting NotePad
main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/
@ -121,7 +120,7 @@ build_javascripts: ts
(cat "$${f}"; echo) >> dist/antos.js;\
rm "$${f}";\
done
echo 'OS.VERSION.version_string = "$(VERSION)-$(BUILDID)";' >> dist/antos.js
echo 'OS.VERSION.version_string = "$(VERSION)-$(BRANCH)-$(BUILDID)";' >> dist/antos.js
cp dist/antos.js $(BUILDDIR)/scripts/
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
rm -r dist/core
@ -146,10 +145,8 @@ 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)"
@ -224,25 +221,13 @@ ar:
echo -n $(VERSION) > release/latest
release: main uglify
.PHONY: doc release clean
doc:
# 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)
./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS API" --out $(DOCDIR)
test: build_javascripts
jest
install_dev:
npm init -y
npm install terser
npm install uglifycss
npm install typescript@5.0
npm install @types/jquery
npm i typedoc@0.24
npm i typedoc-plugin-merge-modules
clean:
rm -rf $(BUILDDIR)/resources
rm -rf $(BUILDDIR)/scripts

View File

@ -1,23 +1,28 @@
# AntOS frontend
# antOS v1.2.1
[![Build Status](https://ci.iohub.dev/buildStatus/icon?job=gitea-sync%2Fantos%2Fmaster)](https://ci.iohub.dev/job/gitea-sync/job/antos/job/master/)
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flxsang%2Fantos.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
Frontend implementation of AntOS remote desktop environment: [https://github.com/antos-rde](https://github.com/antos-rde).
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).
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.
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!
## Build
With the provided application API and SDK, AntOS facilitates the development and deployment of user-specific applications inside the VDE environment
`Nodejs` and `npm` is necessary to build the project:
![https://github.com/lxsang/antos/raw/master/antos-shot.png](https://github.com/lxsang/antos/raw/master/antos-shot.png)
```sh
# install dependencies packages
make install_dev
# build release
BUILDDIR=/path/to/output make release
# see more in Makefile for more build target
```
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
## 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:
@ -26,14 +31,13 @@ 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)
## Frontend Documentation
## Documentation
- API: [https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/](https://ci.iohub.dev/public/antos%2Drelease/doc/2.0.x/)
- Documentation: [https://doc.iohub.dev/antos](https://doc.iohub.dev/antos)
- API: [https://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
## Change logs
### v.2.0.0
- Work In Progress: The UI is redesigned to support mobile device
### V1.2.1
* 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
@ -75,7 +79,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 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 KiB

2195
d.ts/antos.d.ts vendored

File diff suppressed because it is too large Load Diff

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

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

Binary file not shown.

View File

@ -1 +1 @@
2.0.0
1.2.1

View File

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

View File

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

View File

@ -35,21 +35,9 @@ namespace OS {
*/
export abstract class BaseApplication extends BaseModel {
/**
* 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
* Placeholder of all settings specific to the application.
* The settings stored in this object will be saved to system
* setting when logout and can be reused in the next login session
*
* @type {GenericObject<any>}
* @memberof BaseApplication
@ -73,6 +61,31 @@ 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
@ -81,8 +94,13 @@ namespace OS {
*/
constructor(name: string, args: AppArgumentsType[]) {
super(name, args);
this.setting = (this.constructor as any).setting_wdg;
if (!setting.applications[this.name]) {
setting.applications[this.name] = {};
}
this.setting = setting.applications[this.name];
this.keycomb = {};
this._loading_toh = undefined;
this._pending_task = [];
}
/**
@ -96,90 +114,80 @@ namespace OS {
* @returns {void}
* @memberof BaseApplication
*/
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();
}
catch(e)
{
nok(__e(e));
init(): void {
this.off("*");
this.on("exit", () => this.quit(false));
// first register some base event to the app
this.on("focus", () => {
this.sysdock.selectedApp = this;
this.appmenu.pid = this.pid;
this.appmenu.items = this.baseMenu() || [];
OS.PM.pidactive = this.pid;
this.appmenu.onmenuselect = (
d: GUI.tag.MenuEventData
): void => {
return this.trigger("menuselect", d);
};
this.trigger("focused", undefined);
if (this.dialog) {
return this.dialog.show();
}
});
this.on("hide", () => {
this.sysdock.selectedApp = null;
this.appmenu.items = [];
this.appmenu.pid = -1;
if (this.dialog) {
return this.dialog.hide();
}
});
this.on("menuselect", (d) => {
switch (d.data.item.data.dataid) {
case `${this.name}-about`:
return this.openDialog("AboutDialog");
case `${this.name}-exit`:
return this.trigger("exit", undefined);
}
});
this.on("apptitlechange", () => this.sysdock.update(this));
this.subscribe("appregistry", (m) => {
if (m.name === this.name) {
this.applySetting(m.message as string);
}
});
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != this.pid)
{
return;
}
this._pending_task.push(o.id);
this.trigger("loading", undefined);
});
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
this._pending_task.splice(i, 1);
}
if (this._pending_task.length === 0) {
// set time out
if(!this._loading_toh)
this._loading_toh = setTimeout(() => this.animation_check(),1000);
}
});
this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme();
}
/**
* 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 {Promise<any>}
* @returns {void}
* @memberof BaseApplication
*/
protected loadScheme(): Promise<any> {
protected loadScheme(): void {
//now load the scheme
const path = `${this.meta().path}/scheme.html`;
return this.render(path);
@ -187,8 +195,9 @@ namespace OS {
/**
* API function to perform an heavy task.
* This function will create a Task that is tracked by any
* task manager implementation
* This function will trigger the global `loading`
* event at the beginning of the task, and the `loaded`
* event after finishing the task
*
* @protected
* @param {Promise<any>} promise the promise on a task to be performed
@ -196,11 +205,15 @@ namespace OS {
* @memberof BaseApplication
*/
protected load(promise: Promise<any>): Promise<void> {
return this._api.Task(async (resolve, reject) => {
const q = this._api.mid();
return new Promise(async (resolve, reject) => {
this._api.loading(q, this.name);
try {
await promise;
return resolve(undefined);
this._api.loaded(q, this.name, "OK");
return resolve();
} catch (e) {
this._api.loaded(q, this.name, "FAIL");
return reject(__e(e));
}
});
@ -315,6 +328,21 @@ 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
*
@ -332,6 +360,9 @@ namespace OS {
* @memberof BaseApplication
*/
blur(): void {
if (this.appmenu && this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
this.trigger("blur", undefined);
if(this.dialog)
{
@ -370,16 +401,6 @@ namespace OS {
return (this.scheme as GUI.tag.WindowTag).apptitle;
}
/**
* Getter to access the application window instance
*
* @memberof BaseApplication
*/
get window(): GUI.tag.WindowTag
{
return this.scheme as GUI.tag.WindowTag;
}
/**
* Function called when the application exit.
* If the input exit event is prevented, the application
@ -393,6 +414,9 @@ 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();
}
}
@ -411,7 +435,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 {@link menu} function
* should be defined in [[menu]] function
*
* @protected
* @returns {GUI.BasicItemType[]}
@ -455,36 +479,7 @@ namespace OS {
}
/**
* 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.
* The cleanup function that is called by [[onexit]] function.
* Application need to override this function to perform some
* specific task before exiting or to prevent the application
* to be exited
@ -494,6 +489,23 @@ namespace OS {
* @memberof BaseApplication
*/
protected cleanup(e: BaseEvent): void {}
/**
* Check if the loading tasks ended,
* if it the case, stop the animation
*
* @private
* @memberof BaseApplication
*/
private animation_check(): void {
if(this._pending_task.length === 0)
{
this.trigger("loaded", undefined);
}
if(this._loading_toh)
clearTimeout(this._loading_toh);
this._loading_toh = undefined;
}
}
BaseApplication.type = ModelType.Application;

View File

@ -58,15 +58,22 @@ namespace OS {
}
/**
* Purge the model from the system
* Exit the sub-window
*
* @protected
* @memberof BaseModel
* @returns {void}
* @memberof SubWindow
*/
protected destroy(): void
{
if (this.scheme) {
$(this.scheme).remove();
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();
}
}
}
@ -211,19 +218,6 @@ namespace OS {
}
}
/**
* Show the dialog
*
* @memberof BaseDialog
*/
show(): void {
this.trigger("focus", undefined);
this.trigger("focused", undefined);
if (this.dialog) {
this.dialog.show();
}
}
}
/**
@ -236,7 +230,6 @@ 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
@ -250,7 +243,7 @@ namespace OS {
/**
* If the `markup` variable is not provided, then
* the {@link init} function will find the scheme definition
* the [[init]] function will find the scheme definition
* in this class variable
*
* @static
@ -288,12 +281,13 @@ namespace OS {
return GUI.htmlToScheme(this.markup, this, this.host);
} else {
// a file handle
this.render(this.markup.path);
return this.render(this.markup.path);
}
} else if (
this.constructor.scheme
GUI.dialogs[this.name] &&
GUI.dialogs[this.name].scheme
) {
const html: string = this.constructor.scheme;
const html: string = GUI.dialogs[this.name].scheme;
return GUI.htmlToScheme(html.trim(), this, this.host);
} else {
this.error(__("Unable to find dialog scheme"));
@ -326,7 +320,6 @@ namespace OS {
}
win.resizable = false;
win.minimizable = false;
win.menu = undefined;
$(win).trigger("focus");
}
}
@ -372,15 +365,23 @@ namespace OS {
*/
main(): void {
super.main();
const input = this.find("txtInput") as GUI.tag.InputTag;
if(this.data)
{
input.set(this.data);
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
}
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
if (this.handle) {
this.handle(input.value);
this.handle($input.val());
}
return this.quit();
};
@ -391,39 +392,49 @@ namespace OS {
return this.quit();
};
input.on("keyup", (e) => {
$input.on("keyup", (e) => {
if (e.which !== 13) {
return;
}
if (this.handle) {
this.handle(input.value);
this.handle($input.val());
}
return this.quit();
});
input.trigger("focus");
$input.trigger("focus");
}
}
/**
* Scheme definition of the Prompt dialog
*/
PromptDialog.scheme = `\
<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-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-vbox>
</afx-app-window>\
`;
/**
* A text dialog is similar to a {@link PromptDialog} nut allows
* A text dialog is similar to a [[PromptDialog]] nut allows
* user to input multi-line text.
*
* Refer to {@link PromptDialog} for the definition of input and callback data
* Refer to [[PromptDialog]] for the definition of input and callback data
* of the dialog
*
* @export
@ -446,11 +457,15 @@ namespace OS {
*/
main(): void {
super.main();
const input = this.find("txtInput") as tag.InputTag;
if(this.data)
input.set(this.data);
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);
}
(this.find("btn-Ok") as tag.ButtonTag).onbtclick = (_e) => {
const value = input.value;
const value = $input.val();
if (!value || value === "") {
return;
}
@ -466,7 +481,7 @@ namespace OS {
return this.quit();
};
input.trigger("focus");
$input.focus();
}
}
/**
@ -474,12 +489,21 @@ namespace OS {
*/
TextDialog.scheme = `\
<afx-app-window data-id = "TextDialog" width='400' height='300'>
<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-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>
</afx-app-window>\
`;
@ -544,13 +568,23 @@ namespace OS {
* Scheme definition
*/
CalendarDialog.scheme = `\
<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-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-vbox>
</afx-app-window>\
`;
@ -565,7 +599,7 @@ namespace OS {
* title: string // window title
* }
* ```
* Callback data: {@link ColorType} object
* Callback data: [[ColorType]] object
*
* @export
* @class ColorPickerDialog
@ -613,13 +647,23 @@ namespace OS {
* Scheme definition
*/
ColorPickerDialog.scheme = `\
<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-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-vbox>
</afx-app-window>\
`;
@ -683,12 +727,22 @@ namespace OS {
* Scheme definition
*/
InfoDialog.scheme = `\
<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-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-vbox>
</afx-app-window>\
`;
@ -754,13 +808,22 @@ namespace OS {
* Scheme definition
*/
YesNoDialog.scheme = `\
<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-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-vbox>
</afx-app-window>\
`;
@ -831,13 +894,22 @@ namespace OS {
* Scheme definition
*/
SelectionDialog.scheme = `\
<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-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-vbox>
</afx-app-window>\
`;
@ -881,11 +953,18 @@ namespace OS {
iconclass: mt.iconclass,
text: `${mt.name}(v${mt.version})`,
});
$(this.find("mydesc")).html(`${mt.description} <br/> ${mt.info.author} (${mt.info.email})`);
$(this.find("mydesc")).html(mt.description);
// 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()
@ -909,19 +988,25 @@ namespace OS {
* Scheme definition
*/
AboutDialog.scheme = `\
<afx-app-window data-id = 'about-window' width='550' height='450'>
<afx-vbox padding = "5">
<afx-app-window data-id = 'about-window' width='450' height='400'>
<afx-vbox>
<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>
<div data-height="35" style = "text-align: right;">
<afx-button data-id = "btnCancel" text = "__(Cancel)" ></afx-button>
</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>
</afx-vbox>
</afx-app-window>\
`;
@ -984,7 +1069,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 tag.InputTag;
const filename = this.find("filename") as HTMLInputElement;
fileview.fetch = (path: string) =>
new Promise(function (resolve, reject) {
if (!path) {
@ -1041,7 +1126,7 @@ namespace OS {
}
fileview.onfileselect = function (e) {
if (e.data.type === "file") {
return filename.value = e.data.filename;
return $(filename).val(e.data.filename);
}
};
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
@ -1088,7 +1173,7 @@ namespace OS {
}
}
const name = filename.value;
const name = $(filename).val();
if (this.handle) {
this.handle({ file: f, name });
}
@ -1102,13 +1187,9 @@ namespace OS {
};
if (this.data && this.data.file) {
$(filename).show();
filename.value = (this.data.file.basename || "Untitled");
this.trigger("resize");
}
else
{
$(filename).hide();
$(filename)
.css("display", "block")
.val(this.data.file.basename || "Untitled");
this.trigger("resize");
}
if (this.data && this.data.hidden) {
@ -1134,16 +1215,21 @@ namespace OS {
* Scheme definition
*/
FileDialog.scheme = `\
<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 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>\
`;
@ -1225,15 +1311,17 @@ namespace OS {
* @memberof MultiInputDialog
*/
init(): void {
let height = 85;
let height = 60;
let html = "";
if (this.data && this.data.model) {
const model = this.data.model;
for (const key in model) {
html += `\
<afx-input data-height="52" text="{0}" type="text" name = {1} ></afx-input>
<afx-label data-height="25" text="{0}" ></afx-label>
<input data-height="25" type="text" name="{1}" ></input>
<div data-height="10" ></div>
`.format(model[key], key);
height += 52;
height += 60;
}
}
this.markup = MultiInputDialog.scheme.format(height, html);
@ -1276,13 +1364,20 @@ namespace OS {
*/
MultiInputDialog.scheme = `\
<afx-app-window width='350' height='{0}'>
<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-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-app-window>`;
@ -1368,22 +1463,28 @@ 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("flex", "1")
.css("width", "120px")
.css("height", "23px")
.val(key)
.appendTo(div);
$("<input>")
.attr("type", "text")
.css("flex", "1")
.css("width", "200px")
.css("height", "23px")
.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", () => {
@ -1391,13 +1492,12 @@ namespace OS {
})
.appendTo(div);
}
else
{
else {
$("<div>")
.css("width", "40px")
.css("height", "35px")
.appendTo(div);
.css("width", "23px")
.appendTo(div);
}
}
}
@ -1406,18 +1506,23 @@ namespace OS {
* Scheme definition
*/
KeyValueDialog.scheme = `\
<afx-app-window width='400' height='350'>
<afx-vbox padding = "10">
<afx-app-window width='350' height='300'>
<afx-hbox>
<div data-width="10" ></div>
<afx-vbox>
<div data-height="5" ></div>
<afx-label text="__(Enter key-value data)" data-height="30"></afx-label>
<div data-id="container"></div>
<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 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>
<div data-height="5" ></div>
</afx-vbox>
<div data-width="10" ></div>
</afx-hbox>
</afx-app-window>`;
}
}

View File

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

View File

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

View File

@ -39,8 +39,8 @@ interface String {
/**
* Parse the current string and convert it
* to an object of type {@link OS.Version} if the string
* is in the format recognized by {@link OS.Version},
* to an object of type [[Version]] if the string
* is in the format recognized by [[Version]],
* e.g.: `1.0.1-a`
*
* @returns {OS.Version}
@ -98,7 +98,7 @@ interface String {
format(...args: any[]): string;
/**
* Create a {@link OS.FormattedString} object using the current
* Create a [[FormattedString]] object using the current
* string and the input parameters
*
* @param {...any[]} args
@ -157,28 +157,9 @@ 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
* {@link timestamp} function
* [[timestamp]] function
*
* @interface Date
*/
@ -210,7 +191,7 @@ interface GenericObject<T> {
}
/**
* Global function to create a {@link OS.FormattedString} from
* Global function to create a [[FormattedString]] from
* a formatted string and a list of parameters. Example
*
* ```typescript
@ -233,71 +214,6 @@ 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
@ -370,7 +286,7 @@ namespace OS {
/**
* The value of the format pattern represented
* in {@link fs}
* in [[fs]]
*
* @type {any[]}
* @memberof FormattedString
@ -589,8 +505,10 @@ 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;
@ -608,7 +526,8 @@ 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];
}
}
@ -632,7 +551,8 @@ namespace OS {
this.patch = Number(mt[2]);
}
}
get version_string(): string {
get version_string(): string
{
return this.string;
}
@ -877,27 +797,23 @@ namespace OS {
/**
* Variable represents the current AntOS version, it
* is an instance of {@link OS.Version}
* is an instance of [[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/antos-rde/antos";
export const REPOSITORY: string = "https://github.com/lxsang/antos";
/**
* Indicate whether the current de
*/
export var mobile: boolean = false;
/**
* Register a model prototype to the system namespace.
* There are two types of model to be registered, if the model
* 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.
* 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.
*
* When a model is loaded in the system, its prototype is registered
* for later uses
@ -943,6 +859,7 @@ namespace OS {
$("#wrapper").empty();
GUI.clearTheme();
announcer.observable = new API.Announcer();
announcer.quota = 0;
resetSetting();
PM.processes = {};
PM.pidalloc = 0;
@ -950,7 +867,7 @@ namespace OS {
/**
* Booting up AntOS. This function checks whether the user
* is successfully logged in, then call {@link OS.GUI.startAntOS}, otherwise
* is successfully logged in, then call [[startAntOS]], otherwise
* it shows the login screen
*
* @export
@ -958,7 +875,6 @@ 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) {
@ -983,7 +899,7 @@ namespace OS {
/**
* Perform the system shutdown operation. This function calls all
* clean up handles in {@link cleanupHandles}, then save the system setting
* clean up handles in [[cleanupHandles]], then save the system setting
* before exiting
*
* @export
@ -1005,7 +921,7 @@ namespace OS {
}
/**
* Register a callback to the system {@link cleanupHandles}
* Register a callback to the system [[cleanupHandles]]
*
* @export
* @param {string} n callback string name
@ -1043,7 +959,7 @@ namespace OS {
export interface PackageMetaType {
/**
* The application class name, if the package has only services
* this property is ignored and {@link pkgname} should be specified
* this property is ignored and [[pkgname]] should be specified
*
* @type {string}
* @memberof PackageMetaType
@ -1051,7 +967,7 @@ namespace OS {
app?: string;
/**
* Package name, in case of {@link app} being undefined, this property
* Package name, in case of [[app]] being undefined, this property
* need to be specified
*
* @type {string}
@ -1190,7 +1106,7 @@ namespace OS {
/**
* Package version, should be in a format conforming
* to the version definition in {@link Version} class
* to the version definition in [[Version]] class
*
* @type {string}
* @memberof PackageMetaType
@ -1247,41 +1163,14 @@ namespace OS {
export var lang: GenericObject<string> = {};
/**
* 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
* Re-export the system announcement [[getMID]] function to the
* core API
*
* @export
* @param {Promise} a Promise object
* @returns {number}
*/
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));
}
});
export function mid(): number {
return announcer.getMID();
}
/**
@ -1296,35 +1185,34 @@ namespace OS {
* @returns {Promise<any>} a promise on the result data
*/
export function post(p: string, d: any): Promise<any> {
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,
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);
})
.done(function (data) {
return resolve(data);
})
.fail(function (j, s, e) {
reject(e);
});
}
catch (e) {
reject(__e(e));
}
.fail(function (j, s, e) {
API.loaded(q, p, "FAIL");
return reject(API.throwe(s));
});
});
}
@ -1340,17 +1228,21 @@ namespace OS {
* @returns {Promise<ArrayBuffer>} a promise on the returned binary data
*/
export function blob(p: string): Promise<ArrayBuffer> {
return API.Task(function (resolve, reject) {
return new Promise(function (resolve, reject) {
const q = announcer.getMID();
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();
});
}
@ -1366,41 +1258,39 @@ namespace OS {
* @returns {Promise<any>}
*/
export function upload(p: string, d: string): Promise<any> {
return new Promise((resolve, reject) => {
return new Promise(function (resolve, reject) {
const q = announcer.getMID();
//insert a temporal file selector
const o =
$("<input>")
.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));
}
.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));
});
});
return o.trigger("click");
});
@ -1427,6 +1317,44 @@ 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
*
@ -1440,7 +1368,7 @@ namespace OS {
* @returns {Promise<any>} a Promise on the requested data
*/
export function get(p: string, t: string = undefined): Promise<any> {
return API.Task(function (resolve, reject) {
return new Promise(function (resolve, reject) {
const conf: any = {
type: "GET",
url: p,
@ -1448,11 +1376,15 @@ 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));
});
});
@ -1508,7 +1440,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();
@ -1574,8 +1506,8 @@ namespace OS {
* Fetch the package meta-data from the server
*
* @export
* @returns {Promise<RequestResult>} Promise on a {@link RequestResult}.
* A success request result should contain a list of {@link PackageMetaType}
* @returns {Promise<RequestResult>} Promise on a [[RequestResult]].
* A success request result should contain a list of [[PackageMetaType]]
*/
export function fetch(): Promise<RequestResult> {
return API.handle.packages({
@ -1621,7 +1553,7 @@ namespace OS {
* Save the current user setting
*
* @export
* @returns {Promise<RequestResult>} promise on a {@link RequestResult}
* @returns {Promise<RequestResult>} promise on a [[RequestResult]]
*/
export function setting(): Promise<RequestResult> {
return API.handle.setting();
@ -1667,7 +1599,7 @@ namespace OS {
* text in spotlight.
*
* This function will call all the search handles stored
* in {@link searchHandle} and build the search result based
* in [[searchHandle]] and build the search result based
* on output of these handle
*
* @export
@ -1680,11 +1612,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);
}
}
@ -1692,7 +1624,7 @@ namespace OS {
}
/**
* Register a search handle to the global {@link searchHandle}
* Register a search handle to the global [[searchHandle]]
*
* @export
* @param {string} name handle name string
@ -1851,37 +1783,5 @@ 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 Normal file
View File

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

View File

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

View File

@ -158,7 +158,7 @@ namespace OS {
*
* @export
* @param {string} p a VFS file path e.g. home://test/
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* @returns {Promise<RequestResult>} A promise on a [[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 {@link RequestResult}
* @returns {Promise<RequestResult>} A promise on a [[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 {@link RequestResult}
* which contains an error or an object of {@link FileInfoType}
* @returns {Promise<any>} A promise on a [[RequestResult]]
* which contains an error or an object of [[FileInfoType]]
*/
export function readfile(p: string, t: string): Promise<any> {
const path = `${API.REST}/VFS/get/`;
return API.get(path + encodeURIComponent(p), t);
return API.get(path + 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 {@link RequestResult}
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
* which contains an error or a success response
*/
export function move(s: string, d: string): Promise<RequestResult> {
@ -253,7 +253,7 @@ namespace OS {
*
* @export
* @param {string} p VFS file path
* @returns {Promise<RequestResult>} A promise on a {@link RequestResult}
* @returns {Promise<RequestResult>} A promise on a [[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 {@link RequestResult}
* @returns {Promise<RequestResult>} a promise on a [[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 {@link RequestResult}
* @returns {Promise<RequestResult>} a promise on a [[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 {@link RequestResult}
* @returns {Promise<RequestResult>} a promise on a [[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 {@link RequestResult} that
* contains an error or a {@link OS.setting.UserSettingType} object
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
* contains an error or a [[UserSettingType]] object
*/
export function auth(): Promise<RequestResult> {
const p = `${API.REST}/user/auth`;
@ -383,9 +383,9 @@ namespace OS {
* Perform a login operation
*
* @export
* @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
* @param {UserLoginType} d user data [[UserLoginType]]
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
* contains an error or a [[UserSettingType]] object
*/
export function login(d: UserLoginType): Promise<RequestResult> {
const p = `${API.REST}/user/login`;
@ -396,7 +396,7 @@ namespace OS {
* Perform a logout operation
*
* @export
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
*/
export function logout(): Promise<RequestResult> {
const p = `${API.REST}/user/logout`;
@ -407,12 +407,65 @@ namespace OS {
* Save the current user settings
*
* @export
* @returns {Promise<RequestResult>} a promise on a {@link RequestResult}
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
*/
export function setting(): Promise<RequestResult> {
const p = `${API.REST}/system/settings`;
return API.post(p, OS.setting);
}
/**
* This is the low level function of AntOS VDB API.
* It requests the server API to perform some simple
* SQL query.
*
* @export
* @param {string} cmd action to perform: save, delete, get, select
* @param {GenericObject<any>} d data object of the request based on each action:
* - save:
* ```
* { table: "table name", data: [record data object]}
* ```
* - get:
* ```
* { table: "table name", id: [record id]}
* ```
* - delete:
* ```
* { table: "table name", id: [record id]}
* or
* { table: "table name", cond: [conditional object]}
* ```
* - select:
* ```
* { table: "table name", cond: [conditional object]}
* ```
* @returns {Promise<RequestResult>} a promise of [[RequestResult]] on the
* query data
*
* A conditional object represents a SQL condition statement as an object,
* example: `pid = 10 AND cid = 2 ORDER BY date DESC`
* ```
* {
* exp: {
* "and": {
* pid: 10,
* cid: 2
* }
* },
* order: {
* date: "DESC"
* }
* }
* ```
*/
export function dbquery(
cmd: string,
d: GenericObject<any>
): Promise<RequestResult> {
const path = `${API.REST}/VDB/${cmd}`;
return API.post(path, d);
}
}
}
}

View File

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

View File

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

View File

@ -15,7 +15,7 @@ namespace OS {
* @type {application.BaseApplication}
* @memberof AppDockItemType
*/
app?: application.BaseApplication;
app: application.BaseApplication;
/**
* Reference to the DOM element of
@ -38,12 +38,15 @@ namespace OS {
*/
export class AppDockTag extends AFXTag {
/**
* Cache of touch event
* variable holds the application select event
* callback handle
*
* @private
* @meberof AppDockTag
* @type {TagEventCallback<any>}
* @memberof AppDockTag
*/
private _previous_touch: {x: number, y: number};
private _onappselect: TagEventCallback<any>;
/**
* Items data of the dock
*
@ -69,6 +72,7 @@ namespace OS {
*/
constructor() {
super();
this._onappselect = (e) => {};
}
/**
@ -94,6 +98,9 @@ namespace OS {
break;
}
}
if (i !== -1) {
$(this.items[i].domel).attr("tooltip", `cr:${app.title()}`);
}
}
/**
@ -104,8 +111,6 @@ namespace OS {
*/
protected init(): void {
this._items = [];
this._previous_touch = {x: 0, y:0};
}
/**
@ -150,7 +155,7 @@ namespace OS {
let el = undefined;
for (let it of this.items) {
it.app.blur();
$(it.domel).removeClass("selected");
$(it.domel).removeClass();
if (v && v === it.app) {
el = it;
}
@ -162,11 +167,6 @@ 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,63 +188,6 @@ 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.
@ -254,84 +197,25 @@ namespace OS {
* @param {AppDockItemType} item an application dock item entry
* @memberof AppDockTag
*/
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");
}
newapp(item: AppDockItemType): void {
this.items.push(item);
const el = $("<afx-button>");
const bt = el[0] as ButtonTag;
el.appendTo(this);
el[0].uify(this.observable);
bt.set(item);
bt.data = item.app;
item.domel = bt;
$(bt).attr("tooltip", `cr:${item.app.title()}`);
bt.onbtclick = (e) => {
e.id = this.aid;
//e.data.item = item;
this._onappselect(e);
item.app.show();
};
this.selectedApp = item.app;
}
/**
* 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
@ -352,11 +236,9 @@ 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.update_button(el);
$($(this).children()[i]).remove();
}
}
@ -371,37 +253,34 @@ namespace OS {
if (e.target === this) {
return;
}
m.hide();
const bt = ($(e.target).closest(
"afx-button"
)[0] as any) as ButtonTag;
const name = bt.data.name as string;
const collection = this.items.filter(it => it.app.name == name);
m.nodes = [
const app = bt.data as application.BaseApplication;
m.items = [
{ text: "__(New window)", dataid: "new" },
{ text: "__(Hide all)", dataid: "hide" },
{ text: "__(Close all)", dataid: "quit" },
{ text: "__(Show)", dataid: "show" },
{ text: "__(Hide)", dataid: "hide" },
{ text: "__(Close)", dataid: "quit" },
];
m.onmenuselect = function (evt) {
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;
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;
}
}
};
const offset = $(bt).offset();
$(m)
.css("left", offset.left)
.css("bottom", $(this).height());
return m.show();
return m.show(e);
};
announcer.trigger("sysdockloaded", undefined);
GUI.bindKey("CTRL-ALT-2", (e) =>{
@ -439,60 +318,6 @@ namespace OS {
}
this.items[index].app.trigger("focus");
});
this.addEventListener("touchstart", e => {
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, { passive: true});
this.addEventListener("touchmove", e => {
const offset = {x:0, y:0};
offset.x = this._previous_touch.x - e.touches[0].pageX ;
offset.y = this._previous_touch.y - e.touches[0].pageY;
(this as any).scrollLeft += offset.x;
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, { passive: true});
this.addEventListener("wheel", (evt)=>{
(this as any).scrollLeft += (evt as WheelEvent).deltaY;
},{ passive: true});
announcer.on("app-pinned", (_) => {
this.refresh_pinned_app();
});
this.refresh_pinned_app();
}
/**
* refresh the pinned application list
*
* @private
* @memberof AppDockTag
*/
private refresh_pinned_app(): void
{
if(!setting.system.startup.pinned)
return;
// unpin all application on the dock
$(this).children().each((i,e) => {
(e as ButtonTag).data.pinned = false;
});
// pin all setting application on the dock
setting.system.startup.pinned
.filter((el) =>{
const app = setting.system.packages[el];
return app && app.app
})
.forEach((name) => {
const app = setting.system.packages[name];
const item = {
icon: app.icon,
iconclass: app.iconclass,
app: undefined
};
this.add_button(name, item, true);
});
// update to remove the button
$(this).children().each((i,e) => {
this.update_button(e as ButtonTag);
});
}
}
define("afx-apps-dock", AppDockTag);

View File

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

View File

@ -113,10 +113,8 @@ namespace OS {
* @memberof CalendarTag
*/
protected mount(): void {
(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();
$(this.refs.prev).on("click",(e) => this.prevmonth());
$(this.refs.next).on("click",(e) => this.nextmonth());
const grid = this.refs.grid as GridViewTag;
grid.header = [
{ text: "__(Sun)" },
@ -140,7 +138,7 @@ namespace OS {
* This function triggers the date select event
*
* @private
* @param {TagEventType} e AFX tag event data {@link TagEventType}
* @param {TagEventType} e AFX tag event data [[TagEventType]]
* @returns {void}
* @memberof CalendarTag
*/
@ -251,8 +249,6 @@ 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(
@ -296,11 +292,14 @@ namespace OS {
week_day++;
}
for (
let i = 0; i < 7 - row.length; i++
let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2;
asc2 ? i <= end2 : i >= end2;
asc2 ? i++ : i--
) {
row.push({ text: "" });
}
rows.push(row);
const grid = this.refs.grid as GridViewTag;
grid.rows = rows;
(this.refs.mlbl as LabelTag).text = `${
months[this._month]
@ -320,9 +319,9 @@ namespace OS {
el: "div",
ref: "ctrl",
children: [
{ el: "afx-button", class: "prevmonth", ref: "prev" },
{ el: "i", class: "prevmonth", ref: "prev" },
{ el: "afx-label", ref: "mlbl" },
{ el: "afx-button", class: "nextmonth", ref: "next" },
{ el: "i", class: "nextmonth", ref: "next" },
],
},
{ el: "afx-grid-view", ref: "grid" },

View File

@ -61,19 +61,6 @@ 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();
@ -100,14 +87,7 @@ namespace OS {
this.onready = (_) => {
this.observable = OS.announcer.observable;
window.onresize = () => {
const evt = {
id: this.aid,
data: {
width: $(this).width(),
height: $(this).height()
}
}
announcer.trigger("desktopresize", evt);
announcer.trigger("desktopresize", undefined);
this.calibrate();
};
@ -154,8 +134,8 @@ namespace OS {
{ text: __("Refresh"), dataid: "desktop-refresh" } as GUI.BasicItemType,
];
menu = menu.concat(setting.desktop.menu.map(e => e));
m.nodes = menu;
m.onmenuselect = (evt) => {
m.items = menu;
m.onmenuselect = (evt: TagEventType<tag.MenuEventData>) => {
if (!evt.data || !evt.data.item) return;
const item = evt.data.item.data;
switch (item.dataid) {
@ -182,7 +162,7 @@ namespace OS {
};
this.refresh();
announcer.on("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
announcer.observable.on("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
if (["read", "publish", "download"].includes(d.message as string)) {
return;
}

View File

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

View File

@ -48,6 +48,30 @@ 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
@ -101,7 +125,6 @@ namespace OS {
* @memberof FloatListTag
*/
protected mount(): void {
$(this.refs.current).hide();
$(this.refs.container)
.css("width", "100%")
.css("height", "100%");
@ -124,26 +147,49 @@ 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");
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;
.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);
});
}
/**
@ -169,7 +215,7 @@ namespace OS {
.css("left", `${cleft}px`);
const w = $(e).width();
const h = $(e).height();
if (this.dir === "column") {
if (this.dir === "vertical") {
ctop += h + 20;
if (ctop + h > gh) {
ctop = 20;

View File

@ -35,11 +35,10 @@ namespace OS {
/**
* Data placeholder for a collection of cell data
*
* @private
* @type {GenericObject<any>[]}
* @memberof GridRowTag
*/
private _data: GenericObject<any>[];
data: GenericObject<any>[];
/**
* placeholder for the row select event callback
@ -58,7 +57,7 @@ namespace OS {
super();
this.refs.yield = this;
this._onselect = (e) => { };
this._onselect = (e) => {};
}
/**
@ -90,32 +89,13 @@ 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
@ -144,7 +124,7 @@ namespace OS {
* @protected
* @memberof GridRowTag
*/
protected calibrate(): void { }
protected calibrate(): void {}
/**
* This function does nothing in this tag
@ -153,7 +133,7 @@ namespace OS {
* @param {*} [d]
* @memberof GridRowTag
*/
protected reload(d?: any): void { }
protected reload(d?: any): void {}
}
/**
@ -229,7 +209,7 @@ namespace OS {
* Setter:
*
* Set the data of the cell, this will trigger
* the {@link ondatachange} function
* the [[ondatachange]] function
*
* Getter:
*
@ -240,7 +220,6 @@ namespace OS {
set data(v: GenericObject<any>) {
if (!v) return;
this._data = v;
this.attach(v);
this.ondatachange();
if (!v.selected) {
return;
@ -255,7 +234,7 @@ namespace OS {
* Setter:
*
* Set/unset the current cell as selected.
* This will trigger the {@link cellselect}
* This will trigger the [[cellselect]]
* event
*
* Getter:
@ -298,14 +277,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(OS.mobile?"dbltap":"dblclick", (e) => {
$(this).on("dblclick", (e) => {
let evt = { id: this.aid, data: this };
return this.cellselect(evt, true);
});
@ -345,7 +324,7 @@ namespace OS {
/**
* Simple grid cell defines a grid cell with
* an {@link LabelTag} as it cell layout
* an [[LabelTag]] as it cell layout
*
* @export
* @class SimpleGridCellTag
@ -379,7 +358,7 @@ namespace OS {
* @protected
* @memberof SimpleGridCellTag
*/
protected init(): void { }
protected init(): void {}
/**
* This function do nothing in this tag
@ -387,10 +366,10 @@ namespace OS {
* @protected
* @memberof SimpleGridCellTag
*/
protected calibrate(): void { }
protected calibrate(): void {}
/**
* The layout of the cell with a simple {@link LabelTag}
* The layout of the cell with a simple [[LabelTag]]
*
* @returns
* @memberof SimpleGridCellTag
@ -537,11 +516,13 @@ 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
);
@ -595,10 +576,10 @@ namespace OS {
this.dragndrop = false;
this._oncellselect = this._onrowselect = this._oncelldbclick = (
e: TagEventType<CellEventData>
): void => { };
): void => {};
this._ondragndrop = (
e: TagEventType<DnDEventDataType<GridRowTag>>
) => { };
) => {};
}
/**
@ -608,7 +589,7 @@ namespace OS {
* @param {*} [d]
* @memberof GridViewTag
*/
protected reload(d?: any): void { }
protected reload(d?: any): void {}
/**
* set the cell select event callback
@ -661,7 +642,8 @@ 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;
@ -696,15 +678,11 @@ 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++;
@ -713,7 +691,7 @@ namespace OS {
const rz = $(`<afx-resizer>`).appendTo(
this.refs.header
)[0] as ResizerTag;
$(rz).css("width", "1px");
$(rz).css("width", "3px");
let next_item = undefined;
if (i < v.length) {
next_item = v[i];
@ -773,34 +751,44 @@ namespace OS {
*/
set rows(rows: GenericObject<any>[][]) {
this._rows = rows;
if (!rows) return;
for (const el of this._header) {
$(el.domel).attr("sort", "none");
}
if(!rows) return;
// 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);
}
}
@ -836,22 +824,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.desc = !context.desc;
context.__f = ! context.__f;
this.rows = this._rows;
}
/**
@ -883,25 +871,6 @@ 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
@ -909,7 +878,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 GridViewTag
* @memberof GridViewTags
*/
push(row: GenericObject<any>[], flag: boolean): void {
const rowel = $("<afx-grid-row>").css(
@ -930,22 +899,25 @@ 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;
({ tag } = cell);
}
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}
});
}
@ -1025,9 +997,12 @@ 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;
}
}
@ -1035,7 +1010,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];
@ -1084,8 +1059,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(
@ -1135,8 +1110,8 @@ namespace OS {
let i = 0;
for (let v of colssize) {
template += `${v}px `;
template_header += `${v}px `;
i++;
template_header += `${v}px `;
if (i < colssize.length && this.resizable) {
template_header += "3px ";
}
@ -1146,10 +1121,6 @@ namespace OS {
"grid-template-columns",
template_header
);
if(this.resizable)
{
$(this.refs.grid).css("column-gap","3px");
}
}
/**
@ -1174,7 +1145,8 @@ 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");
@ -1182,7 +1154,8 @@ namespace OS {
return;
}
el = el[0];
if (!this.selectedRows.includes(el)) {
if(!this.selectedRows.includes(el))
{
return;
}
this._dnd.from = this.selectedRows;
@ -1232,6 +1205,7 @@ namespace OS {
.css("top", top + "px")
.css("left", left + "px");
};
return this.calibrate();
}
@ -1248,7 +1222,6 @@ namespace OS {
{
el: "div",
ref: "container",
class: "grid_content_container",
children: [{ el: "div", ref: "grid" }],
},
];

View File

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

View File

@ -38,8 +38,6 @@ 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)
@ -68,7 +66,6 @@ namespace OS {
this.iconclass = undefined;
this.text = undefined;
this.selectable = false;
this.iconclass$ = undefined;
}
/**
@ -97,49 +94,6 @@ 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
@ -157,31 +111,6 @@ 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
*
@ -191,9 +120,9 @@ namespace OS {
*/
set text(v: string | FormattedString) {
this._text = v;
if (v) {
if (v && v !== "") {
$(this.refs.text).show();
$(this.refs.text).text(v.__());
$(this.refs.text).html(v.__());
} else {
$(this.refs.text).hide();
}
@ -245,7 +174,6 @@ namespace OS {
{ el: "i", ref: "iclass" },
{ el: "i", ref: "i", class: "icon-style" },
{ el: "i", ref: "text", class: "label-text" },
{ el: "i", ref: "iclass_end" },
],
},
];

View File

@ -6,7 +6,7 @@ namespace OS {
*/
export type ListItemEventData = TagEventDataType<ListViewItemTag>;
/**
* A list item represent the individual view of an item in the {@link OS.GUI.tag.ListViewTag}.
* A list item represent the individual view of an item in the [[ListView]].
* 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,11 +134,9 @@ namespace OS {
*
* @memberof ListViewItemTag
*/
/*
set onctxmenu(v: TagEventCallback<ListViewItemTag>) {
this._onctxmenu = v;
}
*/
/**
* Set the item click event handle
@ -174,23 +172,20 @@ namespace OS {
* @memberof ListViewItemTag
*/
protected mount(): void {
$(this).addClass("afx-list-item");
/*
$(this.refs.item).on(OS.mobile?"longtouch":"contextmenu", (e) => {
$(this.refs.item).attr("dataref", "afx-list-item");
$(this.refs.item).on("contextmenu", (e) => {
this._onctxmenu({ id: this.aid, data: this });
});
*/
$(this.refs.item).on("click",(e) => {
this._onclick({ id: this.aid, data: this, originalEvent: e });
//e.stopPropagation();
this._onclick({ 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.item).on("dblclick",(e) => {
this._ondbclick({ id: this.aid, data: this });
});
$(this.refs.btcl).on("click",(e) => {
this._onclose({ id: this.aid, data: this, originalEvent: e });
this._onclose({ id: this.aid, data: this });
e.preventDefault();
e.stopPropagation();
});
@ -200,29 +195,21 @@ 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 {@link itemlayout}
* be defined in [[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:children,
children: [
this.itemlayout(),
{ el: "i", class: "closable", ref: "btcl" },
],
},
];
}
@ -231,7 +218,7 @@ namespace OS {
* Setter:
*
* Set the data of the list item. This will
* trigger the {@link ondatachange} function
* trigger the [[ondatachange]] function
*
* Getter:
*
@ -241,10 +228,6 @@ namespace OS {
*/
set data(v: GenericObject<any>) {
this._data = v;
if(v)
{
this.attach(v);
}
this.ondatachange();
}
get data(): GenericObject<any> {
@ -257,10 +240,10 @@ namespace OS {
*
* @protected
* @abstract
* @returns {TagLayoutType | TagLayoutType[]}
* @returns {TagLayoutType}
* @memberof ListViewItemTag
*/
protected abstract itemlayout(): TagLayoutType | TagLayoutType[];
protected abstract itemlayout(): TagLayoutType;
/**
* This function is called when the item data is changed.
@ -346,101 +329,14 @@ namespace OS {
* List item custom layout definition
*
* @protected
* @returns {TagLayoutType | TagLayoutType[]}
* @returns {TagLayoutType}
* @memberof SimpleListItemTag
*/
protected itemlayout(): TagLayoutType | TagLayoutType[] {
protected itemlayout(): 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
@ -526,7 +422,7 @@ namespace OS {
/**
* A collection of selected items in the list.
* The maximum size of this collection is 1 if
* the {@link multiselect} feature is disabled
* the [[multiselect]] feature is disabled
*
* @private
* @type {ListViewItemTag[]}
@ -534,14 +430,6 @@ namespace OS {
*/
private _selectedItems: ListViewItemTag[];
/**
* The anchor element that the list view positioned on
* This is helpful when rendering dropdown list
* @private
* @type{HTMLElement}
* @memberof ListViewTag
*/
private _anchor: HTMLElement;
/**
* Data placeholder of the list
*
@ -551,9 +439,6 @@ 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
@ -583,8 +468,6 @@ namespace OS {
) => {};
this._selectedItems = [];
this._selectedItem = undefined;
this._drop = (e) => {this.dropoff(e)};
this._show = (e) => {this.showlist(e)};
}
/**
@ -599,9 +482,10 @@ namespace OS {
this.dropdown = false;
this.selected = -1;
this.dragndrop = false;
this._anchor = undefined;
$(this)
.css("display", "flex")
.css("flex-direction", "column");
this.itemtag = "afx-list-item";
$(this).addClass("afx-list-view");
}
/**
@ -624,19 +508,32 @@ 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", this._drop);
$(this.refs.current).on("click", this._show);
$(this.refs.mlist).hide();
$(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");
this.calibrate();
} else {
$(document).off("click", this._drop);
$(this.refs.current).off("click", this._show);
$(this.refs.current).hide();
$(this.refs.mlist).show();
$(document).off("click", drop);
$(this.refs.current).off("click", show);
}
}
@ -745,7 +642,7 @@ namespace OS {
* Button layout allows to add some custom
* behaviors to the list.
*
* Each button data should define the {@link OS.GUI.tag.ButtonTag.onbtclick}
* Each button data should define the [[onbtclick]]
* event handle to specify the custom behavior
*
* When the list is configured as dropdown. The buttons
@ -779,28 +676,7 @@ 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
*
@ -893,13 +769,7 @@ 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 (
@ -938,7 +808,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
*/
@ -962,11 +832,9 @@ 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);
@ -981,6 +849,7 @@ namespace OS {
return this.iclose(e);
};
element.data = item;
item.domel = el[0];
return element;
}
@ -1086,12 +955,12 @@ namespace OS {
/**
* This function triggers the double click event on an item
*
* @protected
* @private
* @param {TagEventType} e tag event object
* @returns
* @memberof ListViewTag
*/
protected idbclick(e: TagEventType<ListViewItemTag>) {
private idbclick(e: TagEventType<ListViewItemTag>) {
const evt: TagEventType<ListItemEventData> = {
id: this.aid,
data: { item: e.data },
@ -1103,12 +972,12 @@ namespace OS {
/**
* This function triggers the list item select event
*
* @protected
* @private
* @param {TagEventType} e tag event object
* @returns
* @memberof ListViewTag
*/
protected iselect(e: TagEventType<ListViewItemTag>) {
private iselect(e: TagEventType<ListViewItemTag>) {
if (!e.data) {
return;
}
@ -1162,10 +1031,9 @@ 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 };
@ -1191,12 +1059,12 @@ namespace OS {
return;
}
let el: any = $(e.target).closest(
`[list-id='${this.aid}']`
"li[dataref='afx-list-item']"
);
if (el.length === 0) {
return;
}
el = el[0];
el = el.parent()[0];
if(!this.selectedItems.includes(el))
{
return;
@ -1212,12 +1080,12 @@ namespace OS {
$(window).off("mousemove", this._onmousemove);
$("#systooltip").hide();
let el: any = $(e.target).closest(
`[list-id='${this.aid}']`
"li[dataref='afx-list-item']"
);
if (el.length === 0) {
return;
}
el = el[0];
el = el.parent()[0];
if (this._dnd.from.includes(el)) {
return;
}
@ -1258,20 +1126,9 @@ 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();
}
@ -1310,31 +1167,16 @@ namespace OS {
if (!this.dropdown) {
return;
}
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`);
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`
);
} else {
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).css("top", "100%");
}
$(this.refs.mlist).show();
}
@ -1354,40 +1196,6 @@ 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
*
@ -1399,12 +1207,9 @@ namespace OS {
if (!this.dropdown) {
return;
}
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%");
const w = `${$(this).width()}px`;
$(this.refs.container).css("width", w);
$(this.refs.current).css("width", w);
$(this.refs.mlist).css("width", w);
}
@ -1439,7 +1244,6 @@ namespace OS {
define("afx-list-view", ListViewTag);
define("afx-list-item", SimpleListItemTag);
define("afx-dbline-list-item", DoubleLineListItemTag);
}
}
}

826
src/core/tags/MenuTag.ts Normal file
View File

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

View File

@ -114,6 +114,38 @@ namespace OS {
* @memberof NSpinnerTag
*/
calibrate(): void {
$(this.refs.holder).css(
"width",
$(this).width() - 20 + "px"
);
$(this.refs.holder).css("height", $(this).height() + "px");
$(this.refs.spinner)
.css("width", "20px")
.css("height", $(this).height() + "px");
$(this.refs.incr)
.css("height", $(this).height() / 2 - 2 + "px")
.css("position", "relative");
$(this.refs.decr)
.css("height", $(this).height() / 2 - 2 + "px")
.css("position", "relative");
$(this.refs.spinner)
.find("li")
.css("display", "block")
.css("text-align", "center")
.css("vertical-align", "middle");
$(this.refs.spinner)
.find("i")
.css("font-size", "16px")
.css("position", "absolute");
const fn = function (ie: HTMLElement, pos: string) {
const el = $(ie).find("i");
el.css(
pos,
($(ie).height() - el.height()) / 2 + "px"
).css("left", ($(ie).width() - el.width()) / 2 + "px");
};
fn(this.refs.decr, "bottom");
fn(this.refs.incr, "top");
}
/**

View File

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

View File

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

View File

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

View File

@ -65,7 +65,6 @@ namespace OS {
this.enable = true;
this._max = 100;
this._value = 0;
this.precision = false;
this._onchange = this._onchanging = () => {};
}
@ -99,17 +98,7 @@ 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
*
@ -121,15 +110,15 @@ namespace OS {
this.attsw(v, "enable");
if (v) {
$(this)
.on("pointerover",() => {
.on("mouseover",() => {
return $(this.refs.point).show();
})
.on("pointerout",() => {
.on("mouseout",() => {
return $(this.refs.point).hide();
});
} else {
$(this.refs.point).hide();
$(this).off("pointerover").off("pointerout");
$(this).off("mouseover").off("mouseout");
}
}
get enable(): boolean {
@ -199,16 +188,7 @@ namespace OS {
*/
calibrate(): void {
if (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.value = this.max;
}
$(this.refs.container).css("width", $(this).width() + "px");
const w =
@ -217,17 +197,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).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");
//}
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");
}
}
/**
@ -240,10 +220,10 @@ namespace OS {
$(this.refs.point)
.css("user-select", "none")
.css("cursor", "default");
$(this).on("pointerdown", (e) => {
$(this.refs.point).on("mousedown", (e) => {
e.preventDefault();
const offset = $(this.refs.container).offset();
$(window).on("pointermove", (e) => {
$(window).on("mousemove", (e) => {
let left = e.clientX - offset.left;
left = left < 0 ? 0 : left;
const maxw = $(this.refs.container).width();
@ -256,13 +236,13 @@ namespace OS {
});
});
$(window).on("pointerup", (e) => {
$(window).on("mouseup", (e) => {
this._onchange({
id: this.aid,
data: this.value,
});
$(window).off("pointermove", null);
return $(window).off("pointerup", null);
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
});
});
}

View File

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

View File

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

View File

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

View File

@ -39,13 +39,6 @@ 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
@ -64,8 +57,6 @@ namespace OS {
*/
protected init(): void {
this.selected = -1;
this.dir = "row";
this._previous_touch = {x: 0, y:0};
}
/**
@ -91,30 +82,6 @@ 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
*
@ -177,16 +144,6 @@ 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
@ -222,75 +179,6 @@ namespace OS {
this._ontabselect(e);
return this.observable.trigger("tabselect", e);
};
const list_container = $(".list-container", this.refs.list);
list_container.each((i,el) => {
el.addEventListener("touchstart", e => {
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, {passive: true});
el.addEventListener("touchmove", e => {
const offset = {x:0, y:0};
offset.x = this._previous_touch.x - e.touches[0].pageX ;
offset.y = this._previous_touch.y - e.touches[0].pageY;
if(this.dir == "row")
{
el.scrollLeft += offset.x;
}
else
{
el.scrollTop += offset.y;
}
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
}, {passive: true});
el.addEventListener("wheel", (evt)=>{
if(this.dir == "row")
{
el.scrollLeft += (evt as WheelEvent).deltaY;
}
else
{
el.scrollTop += (evt as WheelEvent).deltaY;
}
}, {passive: true});
});
}
/**
* Scroll the tabbar to end
*
* @memberof TabBarTag
*/
scroll_to_end()
{
const list_container = $(".list-container", this.refs.list)[0];
if(this.dir == "column")
{
list_container.scrollTo({ top: list_container.scrollHeight, behavior: 'smooth' });
}
else
{
list_container.scrollTo({ left: list_container.scrollWidth, behavior: 'smooth' });
}
}
/**
* Scroll the tabbar to begin
*
* @memberof TabBarTag
*/
scroll_to_start()
{
const list_container = $(".list-container", this.refs.list)[0];
if(this.dir == "column")
{
list_container.scrollTo({ top: 0, behavior: 'smooth' });
}
else
{
list_container.scrollTo({ left: 0, behavior: 'smooth' });
}
}
/**

View File

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

View File

@ -1,9 +1,6 @@
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.
@ -26,17 +23,13 @@ namespace OS {
super();
}
private _padding: number;
/**
* Do nothing
*
* @protected
* @memberof TileLayoutTag
*/
protected init(): void {
this.padding = 0;
}
protected init(): void {}
/**
* Do nothing
*
@ -80,66 +73,17 @@ namespace OS {
*
* @memberof TileLayoutTag
*/
set dir(v: TileItemDirection) {
set dir(v: "row" | "column") {
if (!v) {
return;
}
$(this).attr("dir", v);
this.reversed = this.reversed;
$(this.refs.yield).css("flex-direction", v);
this.calibrate();
}
get dir(): TileItemDirection {
get dir(): "row" | "column" {
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
@ -151,7 +95,9 @@ namespace OS {
protected mount(): void {
$(this).css("display", "block");
$(this.refs.yield)
.css("display", "flex");
.css("display", "flex")
.css("width", "100%")
.css("height", "100%");
this.observable.on("resize", (e) => this.calibrate());
return this.calibrate();
}
@ -163,10 +109,6 @@ 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();
}
@ -186,34 +128,30 @@ namespace OS {
private hcalibrate(): void {
const auto_width = [];
let ocwidth = 0;
const avaiWidth = $(this).width() - this.padding * 2;
const avaiheight = $(this).innerHeight() - this.padding * 2;
const avaiheight = $(this).height();
const avaiWidth = $(this).width();
//$(this.refs.yield).css("height", `${avaiheight}px`);
$(this.refs.yield)
.children()
.each(function (e) {
$(this).css("height", "100%");
let attv = $(this).attr("data-width");
let dw = 0;
if (!attv || attv == "grow") {
if (attv && attv !== "grow") {
if (attv[attv.length - 1] === "%") {
dw =
(parseInt(attv.slice(0, -1)) *
avaiWidth) /
100;
} else {
dw = parseInt(attv);
}
$(this).css("width", `${dw}px`);
ocwidth += dw;
} else {
$(this).css("flex-grow", "1");
auto_width.push(this);
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;
@ -222,7 +160,7 @@ namespace OS {
$(v).css("width", `${csize}px`)
);
}
this.observable.trigger("hboxchange", {
return this.observable.trigger("hboxchange", {
id: this.aid,
data: { w: avaiWidth, h: avaiheight },
});
@ -239,34 +177,30 @@ namespace OS {
private vcalibrate(): void {
const auto_height = [];
let ocheight = 0;
const avaiheight = $(this).innerHeight() - this.padding * 2;
const avaiwidth = $(this).width() - this.padding * 2;
const avaiheight = $(this).height();
const avaiwidth = $(this).width();
//$(this.refs.yield).css("height", `${avaiheight}px`);
$(this.refs.yield)
.children()
.each(function (e) {
let dh = 0;
$(this).css("width", "100%");
let attv = $(this).attr("data-height");
if (!attv || attv == "grow") {
if (attv && attv !== "grow") {
if (attv[attv.length - 1] === "%") {
dh =
(parseInt(attv.slice(0, -1)) *
avaiheight) /
100;
} else {
dh = parseInt(attv);
}
$(this).css("height", `${dh}px`);
ocheight += dh;
} else {
$(this).css("flex-grow", "1");
auto_height.push(this);
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;
@ -276,7 +210,7 @@ namespace OS {
);
}
this.observable.trigger("vboxchange", {
return this.observable.trigger("vboxchange", {
id: this.aid,
data: { w: avaiwidth, h: avaiheight },
});

View File

@ -111,7 +111,7 @@ namespace OS {
* Placeholder for the `fetch` function of the node.
* This function is used to fetch the child nodes of the
* current nodes. This function should return a promise on
* a list of {@link TreeViewDataType}
* a list of [[TreeViewDataType]]
*
* @memberof TreeViewItemPrototype
*/
@ -164,7 +164,7 @@ namespace OS {
* Setter:
*
* Set the data of the current node. This will trigger the
* {@link ondatachange} function
* [[ondatachange]] function
*
* Getter:
*
@ -182,7 +182,7 @@ namespace OS {
this.treepath = v.path;
}
this.selected = v.selected;
this.attach(v);
v.domel = this;
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(OS.mobile?"dbltap":"dblclick", (e) => {
$(this.refs.wrapper).on("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 {@link itemlayout} function
* be defined in the [[itemlayout]] function
*
* @protected
* @returns {TagLayoutType[]}
@ -464,8 +464,8 @@ namespace OS {
}
/**
* SimpleTreeViewItem extends {@link TreeViewItemPrototype} and
* define it inner layout using a {@link LabelTag}
* SimpleTreeViewItem extends [[TreeViewItemPrototype]] and
* define it inner layout using a [[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 {@link TreeViewDataType}
* a list of [[TreeViewDataType]]
*
* @memberof TreeViewTag
*/

View File

@ -63,14 +63,6 @@ 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
*
@ -105,15 +97,6 @@ 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
@ -134,7 +117,6 @@ namespace OS {
this.minimizable = true;
this.resizable = true;
this.apptitle = "Untitled";
this._onmenuopen = undefined;
}
/**
@ -193,24 +175,6 @@ namespace OS {
return this._height;
}
/**
* Set the application menu content
*
* @memberof WindowTag
*/
set menu(v: GenericObject<any>[])
{
if(!v || v.length == 0)
{
$(this.refs.btnMenu).hide();
}
else
{
(this.refs.stackmenu as StackMenuTag).nodes = v;
$(this.refs.btnMenu).show();
}
}
/**
* Setter: enable/disable window minimizable
*
@ -273,15 +237,6 @@ 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
*
@ -307,56 +262,22 @@ 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) { };
min_btn.onbtclick =(_) => {
$(this.refs["minbt"]).on("click", (e) => {
return this.observable.trigger("hide", {
id: this.aid,
});
};
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();
};
});
close_btn.onbtclick = (_) => {
$(this.refs["maxbt"]).on("click", (e) => {
return this.toggle_window();
});
$(this.refs["closebt"]).on("click", (e) => {
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)
@ -364,7 +285,7 @@ namespace OS {
.css("left", `${left}px`)
.css("top", `${top}px`)
.css("z-index", 10);
$(this).on("pointerdown", (e) => {
$(this).on("mousedown", (e) => {
if (this._shown) {
return;
}
@ -372,6 +293,7 @@ namespace OS {
id: this.aid,
});
});
//$(this.refs.win_overlay).css("background-color", "red");
$(this.refs["dragger"]).on("dblclick", (e) => {
return this.toggle_window();
});
@ -425,26 +347,7 @@ namespace OS {
h: this.height,
});
$(this).attr("tabindex", 0).css("outline", "none");
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", {
return this.observable.trigger("rendered", {
id: this.aid,
});
}
@ -468,7 +371,6 @@ namespace OS {
this.observable.trigger("resize", {
id: this.aid,
data: o,
root: true
});
}
@ -482,12 +384,12 @@ namespace OS {
$(this.refs["dragger"])
.css("user-select", "none")
.css("cursor", "default");
$(this.refs.dragger).on("pointerdown", (e) => {
e.originalEvent.preventDefault();
$(this.refs["dragger"]).on("mousedown", (e) => {
e.preventDefault();
const offset = $(this).offset();
offset.top = e.clientY - offset.top;
offset.left = e.clientX - offset.left;
$(window).on("pointermove", (e) => {
$(window).on("mousemove", (e) => {
$(this.refs.win_overlay).show();
let left: number, top: number;
if (this._isMaxi) {
@ -513,10 +415,10 @@ namespace OS {
.css("top", `${top}px`)
.css("left", `${left}px`);
});
return $(window).on("pointerup", (e) => {
return $(window).on("mouseup", (e) => {
$(this.refs.win_overlay).hide();
$(window).off("pointermove", null);
return $(window).off("pointerup", null);
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
});
});
}
@ -550,32 +452,32 @@ namespace OS {
}
const mouse_up_hdl = (e) => {
$(this.refs.win_overlay).hide();
$(window).off("pointermove", mouse_move_hdl);
return $(window).off("pointerup", mouse_up_hdl);
$(window).off("mousemove", mouse_move_hdl);
return $(window).off("mouseup", mouse_up_hdl);
}
$(this.refs["grip"]).on("pointerdown", (e) => {
$(this.refs["grip"]).on("mousedown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip;
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
});
$(this.refs.grip_bottom).on("pointerdown", (e) => {
$(this.refs.grip_bottom).on("mousedown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip_bottom;
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
});
$(this.refs.grip_right).on("pointerdown", (e) => {
$(this.refs.grip_right).on("mousedown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip_right;
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
});
}
/**
@ -586,9 +488,9 @@ namespace OS {
* @returns {void}
* @memberof WindowTag
*/
private toggle_window(force?: boolean): void {
private toggle_window(): void {
let h: number, w: number;
if (!this.resizable && !force) {
if (!this.resizable) {
return;
}
if (this._isMaxi === false) {
@ -598,8 +500,8 @@ namespace OS {
width: $(this).css("width"),
height: $(this).css("height"),
};
w = $(this.desktop).width() - 2;
h = $(this.desktop).height() - 2;
w = $(this.desktop).width();
h = $(this.desktop).height();
$(this).css("top", "0").css("left", "0");
this.setsize({ w, h });
this._isMaxi = true;
@ -636,12 +538,18 @@ namespace OS {
children: [
{
el: "li",
children: [
{
el: "afx-button",
ref: "btnMenu",
},
],
class: "afx-window-close",
ref: "closebt",
},
{
el: "li",
class: "afx-window-minimize",
ref: "minbt",
},
{
el: "li",
class: "afx-window-maximize",
ref: "maxbt",
},
{
el: "li",
@ -654,38 +562,9 @@ 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",
@ -711,14 +590,6 @@ namespace OS {
ref: "win_overlay",
class: "afx-window-overlay",
},
{
el: "afx-stack-menu",
ref: "stackmenu"
},
{
el: "afx-notification",
ref: "notification"
}
],
},
];

View File

@ -24,10 +24,10 @@ interface HTMLElement {
* defined on any child of this element will be ignored.
*
* @param {JQuery.MouseEventBase} e a mouse event
* @param {OS.GUI.tag.StackMenuTag} m The context menu element {@link OS.GUI.tag.StackMenuTag}
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]]
* @memberof HTMLElement
*/
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.StackMenuTag): void;
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void;
/**
* Mount the element and all the children on its DOM subtree. This action
@ -51,25 +51,7 @@ interface HTMLElement {
afxml(o: OS.API.Announcer): void;
/**
* 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
* Perform DOM generation ([[afxml]]) then mount ([[sync]]) all the
* elements.
*
* @param {OS.API.Announcer} o an AntOS observable object
@ -116,7 +98,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
@ -157,7 +139,7 @@ namespace OS {
/**
* this is the `data-id` attribute of the element,
* can be query by the {@link OS.GUI.AFXTag.aid} Tag API function.
* can be query by the [[aid]] Tag API function.
* Not to be confused with the DOM `id` attribute
*
* @type {(string | number)}
@ -180,7 +162,7 @@ namespace OS {
* @type {number|string}
* @memberof TagLayoutType
*/
width?: number | string;
width?: number|string;
/**
** `data-height` of the element, not to be confused with
@ -189,7 +171,7 @@ namespace OS {
* @type {number|string}
* @memberof TagLayoutType
*/
height?: number | string;
height?: number|string;
}
/**
@ -235,20 +217,6 @@ 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;
}
/**
@ -277,163 +245,8 @@ 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
@ -456,7 +269,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 {@link TagLayoutType}
* tag layout [[TagLayoutType]]
*
* @protected
* @type {GenericObject<HTMLElement>}
@ -475,17 +288,7 @@ namespace OS {
protected _mounted: boolean;
/**
* a {@link ResponsiveHandle} to handle all responsive event
* related to this tag
*
* @private
* @memberof AFXTag
*/
private _responsive_handle: ResponsiveHandle;
private _responsive_check: (evt: TagEventType<{w: number, h: number}>) => void;
/**
* Creates an instance of AFXTag.
*Creates an instance of AFXTag.
* @memberof AFXTag
*/
constructor() {
@ -496,8 +299,6 @@ namespace OS {
}
this._mounted = false;
this.refs = {};
this._responsive_handle = new ResponsiveHandle();
this._responsive_check = undefined;
}
/**
@ -568,79 +369,6 @@ 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
@ -715,7 +443,7 @@ namespace OS {
/**
* Init the current tag, this function
* is called before the {@link mount} function
* is called before the [[mount]] function
*
* @protected
* @abstract
@ -744,7 +472,7 @@ namespace OS {
/**
* Update only the current tag, this function is
* called by {@link update} before chaining the
* called by [[update]] before chaining the
* update process to its children
*
* @protected
@ -761,7 +489,7 @@ namespace OS {
* @protected
* @memberof AFXTag
*/
protected calibrate(): void { }
protected calibrate(): void {}
/**
* This function parses the input layout object
@ -861,48 +589,11 @@ 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);
});
};
@ -934,12 +625,6 @@ 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.
@ -947,14 +632,14 @@ namespace OS {
* new definition
*
* @export
* @template T all classes that extends {@link AFXTag}
* @template T all classes that extends [[AFXTag]]
* @param {string} name name of the tag
* @param {{ new (): T }} cls the class that defines the tag
* @returns {void}
*/
export function define<T extends AFXTag>(
name: string,
cls: { new(): T }
cls: { new (): T }
): void {
try {
customElements.define(name, cls);

View File

@ -21,7 +21,7 @@ interface String {
* Convert a string to VFS file handle.
*
* This function will create a file handle object from the string
* with the help of {@link OS.API.VFS.findHandles}
* with the help of [[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 {@link VFS.handles}) will be
* its attached handle class (found in [[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, {@link ready} will be set to true and
* Once loaded, [[ready]] will be set to true and
* file meta-data will be stored in this place holder
*
* @type {FileInfoType}
@ -569,24 +569,23 @@ namespace OS {
/**
* Public read operation
*
* This function calls the {@link _rd} function to perform the operation.
* This function calls the [[_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 {any} formal t data type
* @param {string} 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?: any): Promise<any> {
read(t?: string): Promise<any> {
return new Promise(async (resolve, reject) => {
try {
const r = await this.onready();
@ -601,7 +600,7 @@ namespace OS {
/**
* Write the file cache to the actual file
*
* This function calls the {@link _wr} function to perform the operation
* This function calls the [[_wr]] function to perform the operation
*
* @param {string} t data type
* - `base64`
@ -626,7 +625,7 @@ namespace OS {
/**
* Sub-directory creation
*
* This function calls the {@link _mk} function to perform the operation
* This function calls the [[_mk]] function to perform the operation
*
* @param {string} d sub directory name
* @returns {Promise<RequestResult>} promise on the operation result
@ -648,16 +647,16 @@ namespace OS {
/**
* Delete the file
*
* This function calls the {@link _rm} function to perform the operation
* @param {any} d user data
* This function calls the [[_rm]] function to perform the operation
*
* @returns {Promise<RequestResult>} promise on the operation result
* @memberof BaseFileHandle
*/
remove(data?: any): Promise<RequestResult> {
remove(): Promise<RequestResult> {
return new Promise(async (resolve, reject) => {
try {
const r = await this.onready();
const d = await this._rm(data);
const d = await this._rm();
announcer.ostrigger("VFS", "remove",this);
return resolve(d);
} catch (e_1) {
@ -671,7 +670,7 @@ namespace OS {
*
* Only work when the current file is a directory
*
* This function calls the {@link _up} function to perform the operation
* This function calls the [[_up]] function to perform the operation
*
* @returns {Promise<RequestResult>} promise on the operation result
* @memberof BaseFileHandle
@ -694,7 +693,7 @@ namespace OS {
*
* Only work with file
*
* This function calls the {@link _pub} function to perform the operation
* This function calls the [[_pub]] function to perform the operation
*
* @returns {Promise<RequestResult>} promise on operation result
* @memberof BaseFileHandle
@ -717,7 +716,7 @@ namespace OS {
*
* Only work with file
*
* This function calls the {@link _down} function to perform the operation
* This function calls the [[_down]] function to perform the operation
*
* @returns {Promise<any>} Promise on the operation result
* @memberof BaseFileHandle
@ -738,7 +737,7 @@ namespace OS {
/**
* Move the current file to another location
*
* This function calls the {@link _mv} function to perform the operation
* This function calls the [[_mv]] function to perform the operation
*
* @param {string} d destination location
* @returns {Promise<RequestResult>} promise on the operation result
@ -763,7 +762,7 @@ namespace OS {
*
* This action depends on each file protocol
*
* This function calls the {@link _exec} function to perform the operation
* This function calls the [[_exec]] function to perform the operation
*
* @returns {Promise<any>}
* @memberof BaseFileHandle
@ -831,11 +830,11 @@ namespace OS {
* that supports the operation
*
* @protected
* @param {any} t data type, see {@link read}
* @param {string} t data type, see [[read]]
* @returns {Promise<RequestResult>}
* @memberof BaseFileHandle
*/
protected _rd(t: any): Promise<RequestResult> {
protected _rd(t: string): Promise<RequestResult> {
return this.unsupported("read");
}
@ -846,11 +845,12 @@ namespace OS {
* that supports the operation
*
* @protected
* @param {string} t data type, see {@link write}
* @param {string} t data type, see [[write]]
* @param {*} [d]
* @returns {Promise<RequestResult>}
* @memberof BaseFileHandle
*/
protected _wr(t: string): Promise<RequestResult> {
protected _wr(t: string, d?: any): Promise<RequestResult> {
return this.unsupported("write");
}
@ -874,11 +874,10 @@ 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(d: any): Promise<RequestResult> {
protected _rm(): Promise<RequestResult> {
return this.unsupported("remove");
}
@ -1025,7 +1024,7 @@ namespace OS {
* Otherwise, file content will be returned
*
* @protected
* @param {string} t data type see {@link read}
* @param {string} t data type see [[read]]
* @returns {Promise<any>}
* @memberof RemoteFileHandle
*/
@ -1057,7 +1056,7 @@ namespace OS {
* Write file cache to the remote file
*
* @protected
* @param {string} t data type see {@link write}
* @param {string} t data type see [[write]]
* @returns {Promise<RequestResult>}
* @memberof RemoteFileHandle
*/
@ -1293,7 +1292,7 @@ namespace OS {
/**
* Package file is remote file ({@link RemoteFileHandle}) located either in
* Package file is remote file ([[RemoteFileHandle]]) located either in
* the local user packages location or system packages
* location, it should be in the following format:
*
@ -1596,7 +1595,7 @@ namespace OS {
* Read file content stored in the file cached
*
* @protected
* @param {string} t data type see {@link read}
* @param {string} t data type see [[read]]
* @returns {Promise<any>}
* @memberof BufferFileHandle
*/
@ -1617,7 +1616,7 @@ namespace OS {
* Write data to the file cache
*
* @protected
* @param {string} t data type, see {@link write}
* @param {string} t data type, see [[write]]
* @returns {Promise<RequestResult>}
* @memberof BufferFileHandle
*/
@ -1723,6 +1722,7 @@ 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 {@link read}
* @param {string} t data type see [[read]]
* @returns {Promise<any>}
* @memberof URLFileHandle
*/
@ -1855,7 +1855,7 @@ namespace OS {
* Read file content
*
* @protected
* @param {string} t data type, see {@link read}
* @param {string} t data type, see [[read]]
* @returns {Promise<any>}
* @memberof SharedFileHandle
*/
@ -1874,46 +1874,23 @@ namespace OS {
* write data to shared file
*
* @protected
* @param {string} t data type, see {@link write}
* @param {string} t data type, see [[write]]
* @param {string} d file data
* @returns {Promise<RequestResult>}
* @memberof SharedFileHandle
*/
protected _wr(t: string): Promise<RequestResult> {
protected _wr(t: string, d: string): Promise<RequestResult> {
return new Promise(async (resolve, reject) => {
try {
if (t === "base64") {
const d = await API.handle.write(
this.path,
this.cache
const r = await API.handle.write(this.path, d);
if (r.error) {
return reject(
API.throwe(
__("{0}: {1}", r.error, this.path)
)
);
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));
}
@ -2098,6 +2075,7 @@ 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) {
@ -2269,26 +2247,17 @@ namespace OS {
const data = await zfile.asFileHandle().read("binary");
const zip = await JSZip.loadAsync(data);
const to = await dest_callback(zip);
const dirs = new Set<string>();
const dirs = [];
const files = [];
for (const _name in zip.files) {
const name = _name.trimFromRight("/");
const file = zip.files[_name];
for (const name in zip.files) {
const file = zip.files[name];
if (file.dir) {
dirs.add(to + "/" + name);
dirs.push(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(Array.from(dirs), true);
await mkdirAll(dirs, true);
const promises = [];
for (const file of files) {
promises.push(new Promise(async (res, rej) => {

View File

@ -22,18 +22,8 @@
<head>
<title>AntOS webOS</title>
<meta charset="UTF-8">
<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>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="resources/themes/system/system.css" rel="stylesheet">
<!-- default theme-->
<link href="resources/themes/system/antos.css" rel="stylesheet">
<link id="ostheme" rel="stylesheet" href="">
<script src="scripts/jquery-3.4.1.min.js"></script>
<script src="scripts/antos.js"></script>

View File

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

View File

@ -1,53 +1,48 @@
/*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.selected {
background-color: var(--background-quaternary) !important;
color:var(--text-secondary) !important;
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-file-view {
background-color: var(--background-tertiary);
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'] button{
height: 23px;
border-radius: 0;
}
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;
padding-top:2px;
}
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;
afx-app-window[data-id ='files-app-window'] input{
border-radius: 3px;
}

View File

@ -37,6 +37,7 @@ 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;
@ -49,14 +50,20 @@ 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");
@ -86,7 +93,7 @@ namespace OS {
ctx_menu.unshift( {
text: "__(Open with)",
nodes: apps,
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
if (!e) {
return;
}
@ -99,7 +106,7 @@ namespace OS {
ctx_menu = ctx_menu.concat([
{
text: "__(Extract Here)",
onmenuselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
onmenuselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
if (!e) {
return;
}
@ -110,7 +117,7 @@ namespace OS {
},
{
text: "__(Extract to)",
onmenuselect: async (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
onmenuselect: async (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
if (!e) {
return;
}
@ -137,7 +144,7 @@ namespace OS {
ctx_menu.push(
{
text: "__(Compress)",
onmenuselect: async (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
onmenuselect: async (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
if (!e) {
return;
}
@ -154,7 +161,7 @@ namespace OS {
}
const path = `${d.file.path}/${d.name}`;
await API.VFS.mkar(file.path, path);
this.toast(__("Archive file created: {0}",path ));
this.notify(__("Archive file created: {0}",path ));
} catch (error) {
this.error(__("Unable to compress file, folder"), error);
}
@ -164,7 +171,7 @@ namespace OS {
}
}
m.nodes = ctx_menu;
m.items = ctx_menu;
m.show(e);
};
@ -179,10 +186,6 @@ 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;
};
@ -195,6 +198,12 @@ 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();
@ -204,71 +213,15 @@ 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) {
@ -319,6 +272,11 @@ 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;
}
@ -328,16 +286,19 @@ namespace OS {
this.applyAllSetting();
// VFS mount point and event
const mntpoints = this.systemsetting.VFS.mountpoints.map(e => {
return {
text: e.text,
path: e.path,
icon: e.icon,
iconclass: e.iconclass,
const mntpoints = [];
for(let v of this.systemsetting.VFS.mountpoints)
{
mntpoints.push({
text: v.text,
path: v.path,
icon: v.icon,
iconclass: v.iconclass,
selected: false
}
});
});
}
this.favo.data = mntpoints;
//@favo.set "selected", -1
if (this.setting.view) {
this.view.view = this.setting.view;
}
@ -356,48 +317,6 @@ 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`)
@ -468,11 +387,6 @@ 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;
}
@ -483,10 +397,13 @@ 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: [
@ -520,7 +437,7 @@ namespace OS {
shortcut: "C-I",
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) =>
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) =>
this.actionFile(e.data.item.data.dataid),
};
return arr;
@ -555,7 +472,7 @@ namespace OS {
shortcut: "C-P",
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) =>
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) =>
this.actionEdit(e.data.item.data.dataid),
};
}
@ -566,15 +483,17 @@ 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,
@ -612,19 +531,28 @@ namespace OS {
type: "tree",
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => {
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
const { type } = e.data.item.data;
this.view.view = type;
return (this.viewType[type] = true);
},
},
],
onchildselect: (e: GUI.TagEventType<GUI.tag.StackMenuEventData>) => this.actionView(e),
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => 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();
@ -634,22 +562,22 @@ namespace OS {
return this.trigger("resize");
}
private actionView(e: GUI.TagEventType<GUI.tag.StackMenuEventData>): void{
private actionView(e: GUI.TagEventType<GUI.tag.MenuEventData>): void{
const data = e.data.item.data;
switch (data.dataid) {
case `${this.name}-hidden`:
//@.view.set "showhidden", e.item.data.checked
this.setting.showhidden = data.checked;
return this.registry("showhidden", data.checked);
//@.setting.showhidden = e.item.data.checked
case `${this.name}-refresh`:
this.view.path = this.currdir.path;
return;
case `${this.name}-responsive`:
const win = (this.scheme as GUI.tag.WindowTag);
win.responsive = !win.responsive;
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}-nav`:
this.setting.nav = data.checked;
return this.registry("nav", data.checked);
}
}
//@setting.nav = e.item.data.checked
@ -717,7 +645,7 @@ namespace OS {
cut: true,
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
};
return this.toast(__("{0} files cut", this.clipboard.files.length));
return this.notify(__("{0} files cut", this.clipboard.files.length));
case `${this.name}-copy`:
if (!file) {
@ -727,7 +655,7 @@ namespace OS {
cut: false,
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
};
return this.toast(
return this.notify(
__("{0} files copied", this.clipboard.files.length)
);
@ -826,7 +754,8 @@ namespace OS {
.publish()
.then((r) => {
return this.notify(
__("Shared url: {0}", r.result));
__("Shared url: {0}", r.result)
);
})
.catch((e) => {
return this.error(

View File

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

View File

@ -1,17 +1,19 @@
<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-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-file-view data-id = "fileview"></afx-file-view>
</afx-vbox>
</afx-tile>
</afx-hbox>
</afx-vbox>
</afx-app-window>

View File

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

View File

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

View File

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

View File

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

View File

@ -1,39 +1,31 @@
<afx-app-window data-id = "marketplace-win" apptitle="MarketPlace" width="650" height="500">
<afx-vbox >
<afx-stack-panel data-id = "stack-panel" dir = "column" tabbarheight= "40">
<afx-vbox>
<afx-hbox data-height= "30" data-id="search-container">
<div data-width="17" data-id="searchicon"></div>
<input data-id = "searchbox" ></input>
</afx-hbox>
<afx-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-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-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>
<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-vbox>
</afx-hbox>
</afx-stack-panel>
</afx-hbox>
<div data-id="desc-container">
<p data-id = "app-desc"></p>
<ul data-id = "app-detail"></ul>
</div>
</afx-vbox>
</afx-hbox>
</afx-app-window>

View File

@ -6,5 +6,4 @@ 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.2-b: use system css variables in theme
- v0.1.1-b: update README
-v0.1.1-b: update README

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,17 +1,32 @@
afx-app-window[data-id = "setting-window"] afx-label.header i.label-text{
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{
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 var(--border-quaternary);
border:1px solid #cbcbcb;
border-radius: 10px;
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-list-view[data-id="wplist"]
{
border:1px solid var(--border-quaternary);
border:1px solid #cbcbcb;
padding:2px;
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="appearance"] afx-resizer{
border:0;
@ -20,7 +35,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 var(--border-quaternary);
border: 1px solid #cbcbcb;
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="vfs"] afx-button.btnsel button{
@ -31,15 +46,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 var(--border-quaternary);
border: 1px solid #cbcbcb;
}
/*STARTUP*/
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="startup"] afx-list-view
{
border: 1px solid var(--border-quaternary);
border: 1px solid #cbcbcb;
}
afx-app-window[data-id = "setting-window"] afx-hbox[data-id="app-services"] afx-list-view
{
border: 1px solid var(--border-quaternary);
border: 1px solid #cbcbcb;
}

View File

@ -98,14 +98,14 @@ namespace OS {
* @memberof Setting
*/
main(): void{
const containter = this.find("container") as GUI.tag.TabContainerTag;
//this.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,17 +130,6 @@ namespace OS {
);
});
};
this.morphon(GUI.RESPONSIVE.MEDIUM, (fulfilled:boolean) => {
if(fulfilled)
{
containter.dir = "row";
}
else
{
containter.dir = "column";
}
});
}
}
Setting.singleton = true;

View File

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

View File

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

View File

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

View File

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

View File

@ -31,10 +31,12 @@ 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: SystemReport;
logmon: Syslog;
/**
*Creates an instance of PushNotification.
@ -46,7 +48,6 @@ namespace OS {
this.iconclass = "fa fa-bars";
this.cb = undefined;
this.logs = [];
this.text = __("Notification");
this.logmon = undefined;
}
@ -68,7 +69,9 @@ 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) =>
@ -79,12 +82,18 @@ 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();
}
/**
@ -95,7 +104,7 @@ namespace OS {
* @memberof PushNotification
*/
private showLogReport(): void {
this._gui.launch("SystemReport", []);
this._gui.launch("Syslog", []);
}
/**
@ -157,7 +166,16 @@ namespace OS {
* @memberof PushNotification
*/
private notifeed(d: GenericObject<any>): void {
GUI.toast(d,{timeout: 3, location: GUI.ANCHOR.NORTH});
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);
}
/**
@ -166,7 +184,7 @@ namespace OS {
* @param {GUI.TagEventType} evt
* @memberof PushNotification
*/
awake(evt: GUI.TagEventType<GUI.tag.StackMenuEventData>): void {
awake(evt: GUI.TagEventType<GUI.tag.MenuEventData>): void {
if (this.view) {
$(this.nzone).hide();
} else {
@ -205,12 +223,16 @@ namespace OS {
const scheme = `\
<div>
<afx-overlay data-id = "notifyzone" width = "250px">
<afx-hbox data-height="35">
<afx-hbox data-height="30">
<afx-button text = "__(Clear all)" data-id = "btclear" ></afx-button>
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "40"></afx-button>
<afx-button iconclass = "fa fa-bug" data-id = "bterrlog" data-width = "25"></afx-button>
</afx-hbox>
<afx-list-view data-id="notifylist"></afx-list-view>
</afx-overlay>
<afx-overlay data-id = "feedzone" width = "250">
<afx-list-view data-id = "notifeed">
</afx-list-view>
</afx-overlay>
</div>\
`;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<afx-app-window data-id="SystemReport" width='600' height='450' apptitle = "__(System error log)" responsive="true">
<afx-app-window data-id="Syslog" width='500' height='350' apptitle = "__(System error log)" >
<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 = "35">
<afx-button data-width ="content"
<afx-hbox style="text-align:right;" data-height = "27">
<afx-button data-width ="20"
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 ="content" text = "__(Report)"
<afx-button data-width ="80" text = "__(Report)"
iconclass = "fa fa-bug" data-id = "btnreport" ></afx-button>
<div data-width="10" ></div>
</afx-hbox>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,27 +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 var(--border-quaternary);
background-color: var(--item-bg-odd);
border-radius: 3px;
margin-bottom: 5px;
-ms-word-break: break-all;
word-break: break-all;
word-break: break-word;
padding-top: 5px;
padding-bottom: 5px;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
afx-color-picker canvas.color-palette, afx-color-picker div.color-sample{
border: 1px solid var(--border-primary);
border: 1px solid #262626;
/*border-radius: 3px;*/
}

View File

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

View File

@ -1,19 +1,27 @@
afx-file-view afx-label.status{
background-color: var(--background-tertiary);
border-top: 1px solid var(--border-primary);
padding:3px;
right: 0px;
height: 15px;
background-color: #464646;
border-top: 1px solid #262626;
}
afx-file-view afx-list-view > div.list-container > ul li{
width:70px;
/*height: 60px;*/
background-color: transparent;
text-align: center;
margin-right: 5px;
margin-bottom: 5px;
}
afx-file-view afx-list-view > div.list-container > ul .afx-list-item:nth-child(even) li {
afx-file-view afx-list-view > div.list-container > ul afx-list-item:nth-child(even) li {
background-color: transparent;
}
afx-file-view afx-list-view i.dir:before{
content: "\f07b";
font-family: "FontAwesome";
font-size: 35px;
color: var(--icon-tertiary);
font-size: 32px;
color: #76D2F9;
font-weight: normal;
font-style: normal;
}
@ -26,34 +34,37 @@ afx-file-view afx-list-view i{
afx-file-view afx-list-view i.file:before{
content: "\f016";
font-family: "FontAwesome";
font-size: 30px;
color: var(--text-primary);
font-size: 28px;
color: white;
font-style: normal;
font-weight: normal;
}
afx-file-view afx-list-view > div.list-container > ul > .afx-list-item > li.selected,
afx-file-view afx-list-view > div.list-container > ul > .afx-list-item > li.selected i.dir::before
afx-file-view afx-list-view > div.list-container > ul > afx-list-item > li.selected
{
background-color: var(--item-bg-active);
color:var(--text-tertiary);
background-color: transparent;
}
afx-file-view afx-list-view div.list-container > ul li.selected i.label-text {
background-color: #116cd6;
color:white;
border-radius: 6px;
}
afx-file-view afx-grid-view{
padding:0;
}
afx-file-view afx-grid-view i.file:before{
content: "\f016";
font-family: "FontAwesome";
color: var(--text-primary);
font-size: 16px;
color: white;
font-style: normal;
font-weight: normal;
}
afx-file-view afx-grid-view i.dir:before{
content: "\f07b";
font-family: "FontAwesome";
color: var(--icon-tertiary);
font-size: 16px;
color: #76D2F9;
font-style: normal;
font-weight: normal;
}
@ -61,23 +72,38 @@ afx-file-view afx-grid-view i{
margin-right: 5px;
}
afx-file-view afx-grid-view afx-grid-row.afx-grid-row-selected i:before{
color:var(--text-tertiary);
color:white;
}
afx-file-view afx-grid-view afx-grid-row afx-grid-cell
{
padding: 3px;
}
afx-file-view afx-grid-view .grid_row_header afx-grid-cell{
background-color: #464646;
border-top: 1px solid #262626;
border-right: 1px solid #262626;
border-bottom: 1px solid #262626;
padding: 3px;
}
afx-file-view afx-tree-view .afx-tree-view-folder-close:before{
content: "\f07b";
font-family: "FontAwesome";
color:var(--icon-tertiary);
font-size: 16px;
color:#76D2F9;
}
afx-file-view afx-tree-view .afx-tree-view-folder-open:before{
content: "\f07c";
font-family: "FontAwesome";
color:var(--icon-tertiary);
color:#76D2F9;
font-size: 16px;
}
afx-file-view afx-tree-view .afx-tree-view-item:before{
content: "\f016";
font-family: "FontAwesome";
font-size: 16px;
font-style: normal;
font-weight: normal;
}
@ -86,17 +112,18 @@ afx-file-view afx-tree-view div.afx_tree_item_selected, afx-file-view afx-tree-v
background-color: transparent;
}
afx-file-view afx-tree-view div.afx_tree_item_selected ul{
background-color: var(--item-bg-active);
color:var(--text-tertiary);
border-radius: 0;
afx-file-view afx-tree-view li.itemname{
padding:3px;
padding-right: 5px;
}
afx-file-view afx-tree-view div.afx_tree_item_selected .itemname{
background-color: #116cd6;
color:white;
border-radius: 3px;
}
afx-file-view afx-tree-view div.afx_tree_item_selected i:before,
afx-file-view afx-tree-view div.afx_tree_item_selected .afx-tree-view-folder-close:before,
afx-file-view afx-tree-view div.afx_tree_item_selected .afx-tree-view-folder-open:before
{
color:var(--text-tertiary);
afx-file-view afx-tree-view div.afx_tree_item_selected i.file:before{
color:white;
}
afx-file-view afx-tree-view .afx_folder_item{

View File

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

View File

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

View File

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

View File

@ -1,5 +1,6 @@
afx-nspinner ul li{
border: 1px solid var(--border-primary);
border: 1px solid #262626;
width: 100%;
}
afx-nspinner ul li.incr{
@ -14,5 +15,11 @@ afx-nspinner ul li.decr{
}
afx-nspinner ul li:hover{
color:var(--item-bg-active);
color:#116cD6;
}
afx-nspinner ul li.incr i:before{
font-size: 16px;
}
afx-nspinner ul li.decr i:before{
font-size: 16px;
}

View File

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

View File

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

View File

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

View File

@ -11,7 +11,7 @@ afx-switch span:before{
}
afx-switch span.swon:before{
content: "\f205";
color: var(--item-bg-active);
color: #116cd6;
font-style: normal;
font-weight: normal;
}

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