Merge pull request #45 from lxsang/1.2.1

release 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
* 14b72ef - Fix dragndrop bug on Fileview (grid mod)
* c96919e - fix: correct jenkins build demo stage
* 1cf7181 - Fix fileview status incorrect, add more build stage to Jenkinsfile
* 255f9dc - update readme file, and include it to delivery
* d08b33a - fix ar generation problem: with new version format
* da5bbda - Allow to set version number and build ID to the current Antos build
* 699c697 - update login form style
* 2fd4bb5 - Bug fix + improvement
* 6cbb463 - Fileview: list view display modified date instead of mime
* f7081ae - Include current Antos version to login screen
* 5d17c42 - Makefile read current version from gcode
* 583a0c0 - update version number in code
* c0603cd - Minor style fixes on menus and dropdown list
* 8b029c2 - fix minor visual bug on grid view, list view and tree view
* 86bcaf9 - visual bug fix on label: inline block by default
* 61de957 - Visual improvements
* 52af4b6 - fix visualize bug after style changes
* e63cae1 - style improvement on Label, FileView, GridView, system menu  and app Panel
* f97a45b - Add more control to mem file + bug fix on File
* fdcc5ce - allow to create memory-based temporal VFS file system
* 81d78aa - robusify VFS mem file handling
* d109d6a - fix: file name display inconsitent between views
* c26e27d - Fix multiple dialogs focus bug
* 8b23ebe - Loading animation is now based on the current context (global or application context)
* 2cdd8f9 - support dnd and grid sort
* 079af3b - fix type conversion error in gridview tag
* a6d725e - User a custom tag to manage the desktop instead of GUI
* 0624f42 - API improvement & bug fix: - subscribed global event doesnt unsubcribed when process is killed - process killall API doesnt work as expected - improve core API
* 3a24df1 - update announcement system
* e345a61 - update bootstrap icons to v.1.7.1
* b3d38cc - allow multiple files upload in single request
* 66e96cc - update VFS API
* 86a94a8 - update GUI API
* 27ac7c0 - Minor bug fix on desktop handling
* 99e0d02 - enable setting blur overlay window
* 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
This commit is contained in:
Dany LE 2022-09-16 11:47:05 +02:00 committed by GitHub
commit 4cd271cc51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
63 changed files with 10456 additions and 8848 deletions

62
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,62 @@
def remote = [:]
remote.name = 'workstation'
remote.host = 'workstation'
remote.user = 'dany'
remote.identityFile = '/var/jenkins_home/.ssh/id_rsa'
remote.allowAnyHosts = true
remote.agent = false
remote.logLevel = 'INFO'
pipeline{
agent { node{ label'master' }}
options {
// Limit build history with buildDiscarder option:
// daysToKeepStr: history is only kept up to this many days.
// numToKeepStr: only this many build logs are kept.
// artifactDaysToKeepStr: artifacts are only kept up to this many days.
// artifactNumToKeepStr: only this many builds have their artifacts kept.
buildDiscarder(logRotator(numToKeepStr: "1"))
// Enable timestamps in build log console
timestamps()
// Maximum time to run the whole pipeline before canceling it
timeout(time: 1, unit: 'HOURS')
// Use Jenkins ANSI Color Plugin for log console
ansiColor('xterm')
// Limit build concurrency to 1 per branch
disableConcurrentBuilds()
}
stages
{
stage('Prebuild build') {
steps {
sshCommand remote: remote, command: '''
set -e
export WORKSPACE=$(realpath "./jenkins/workspace/antos")
cd $WORKSPACE
npm install terser
npm install uglifycss
npm install typescript
npm install @types/jquery
'''
}
}
stage('Build release') {
steps {
sshCommand remote: remote, command: '''
set -e
export WORKSPACE=$(realpath "./jenkins/workspace/antos")
cd $WORKSPACE
[ -d build ] && rm -rf build
export BUILDDIR="$WORKSPACE/build/opt/www/htdocs/os"
make release
'''
script {
// only useful for any master branch
//if (env.BRANCH_NAME =~ /^master/) {
archiveArtifacts artifacts: 'd.ts/, build/', fingerprint: true
//}
}
}
}
}
}

View File

@ -4,8 +4,13 @@ BUILDDIR?=/opt/www/htdocs/os
DOCDIR?=/opt/www/htdocs/doc/antos DOCDIR?=/opt/www/htdocs/doc/antos
BLUE=\033[1;34m BLUE=\033[1;34m
NC=\033[0m NC=\033[0m
TSC=./node_modules/typescript/bin/tsc
UGLIFYJS=./node_modules/terser/bin/terser
UGLIFYCSS=./node_modules/uglifycss/uglifycss
VERSION=1.2.0 VERSION=1.2.1
BRANCH = b
BUILDID=$(shell git rev-parse --short HEAD)
GSED=sed GSED=sed
UNAME_S := $(shell uname -s) UNAME_S := $(shell uname -s)
@ -34,7 +39,8 @@ tags = dist/core/tags/tag.js \
dist/core/tags/FileViewTag.js \ dist/core/tags/FileViewTag.js \
dist/core/tags/OverlayTag.js \ dist/core/tags/OverlayTag.js \
dist/core/tags/AppDockTag.js \ dist/core/tags/AppDockTag.js \
dist/core/tags/SystemPanelTag.js dist/core/tags/SystemPanelTag.js \
dist/core/tags/DesktopTag.js
javascripts= dist/core/core.js \ javascripts= dist/core/core.js \
dist/core/settings.js \ dist/core/settings.js \
@ -58,6 +64,7 @@ packages = Syslog Files MarketPlace Setting NotePad
main: initd build_javascripts build_themes libs build_packages languages main: initd build_javascripts build_themes libs build_packages languages
- cp src/index.html $(BUILDDIR)/ - cp src/index.html $(BUILDDIR)/
- cp README.md $(BUILDDIR)/
initd: initd:
- mkdir -p $(BUILDDIR) - mkdir -p $(BUILDDIR)
@ -68,7 +75,7 @@ lite: build_javascripts build_themes build_packages
ts: ts:
-rm -rf dist -rm -rf dist
tsc -p tsconfig.json $(TSC) -p tsconfig.json
cat `find dist/core/ -name "*.d.ts"` > d.ts/antos.d.ts cat `find dist/core/ -name "*.d.ts"` > d.ts/antos.d.ts
rm `find dist/ -name "*.d.ts"` rm `find dist/ -name "*.d.ts"`
cat d.ts/core.d.ts d.ts/jquery.d.ts d.ts/antos.d.ts > /tmp/corelib.d.ts cat d.ts/core.d.ts d.ts/jquery.d.ts d.ts/antos.d.ts > /tmp/corelib.d.ts
@ -85,7 +92,7 @@ standalone_tags: ts
rm "$${f}";\ rm "$${f}";\
done done
echo "var Ant=this;" >> dist/afx.js echo "var Ant=this;" >> dist/afx.js
terser dist/afx.js --compress --mangle --output $(BUILDDIR)/afx.js $(UGLIFYJS) dist/afx.js --compress --mangle --output $(BUILDDIR)/afx.js
# standalone theme # standalone theme
@for f in src/themes/system/afx-*.css; do \ @for f in src/themes/system/afx-*.css; do \
@ -101,7 +108,7 @@ standalone_tags: ts
(cat "$${f}"; echo) >> $(BUILDDIR)/afx.css; \ (cat "$${f}"; echo) >> $(BUILDDIR)/afx.css; \
fi;\ fi;\
done done
# uglifycss --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css # $(UGLIFYCSS) --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css
rm -r dist/core rm -r dist/core
build_javascripts: ts build_javascripts: ts
@ -113,6 +120,7 @@ build_javascripts: ts
(cat "$${f}"; echo) >> dist/antos.js;\ (cat "$${f}"; echo) >> dist/antos.js;\
rm "$${f}";\ rm "$${f}";\
done done
echo 'OS.VERSION.version_string = "$(VERSION)-$(BRANCH)-$(BUILDID)";' >> dist/antos.js
cp dist/antos.js $(BUILDDIR)/scripts/ cp dist/antos.js $(BUILDDIR)/scripts/
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
rm -r dist/core rm -r dist/core
@ -173,27 +181,34 @@ package:
pkgar: pkgar:
read -r -p "Enter package name: " PKG;\ read -r -p "Enter package name: " PKG;\
echo $$PKG | make package &&\ echo $$PKG | make package &&\
test -f $(BUILDDIR)/packages/$$PKG/main.js && terser $(BUILDDIR)/packages/$$PKG/main.js --compress --mangle --output $(BUILDDIR)/packages/$$PKG/main.js;\ test -f $(BUILDDIR)/packages/$$PKG/main.js && $(UGLIFYJS) $(BUILDDIR)/packages/$$PKG/main.js --compress --mangle --output $(BUILDDIR)/packages/$$PKG/main.js;\
test -f $(BUILDDIR)/packages/$$PKG/main.css && uglifycss --output $(BUILDDIR)/packages/$$PKG/main.css $(BUILDDIR)/packages/$$PKG/main.css;\ test -f $(BUILDDIR)/packages/$$PKG/main.css && $(UGLIFYCSS) --output $(BUILDDIR)/packages/$$PKG/main.css $(BUILDDIR)/packages/$$PKG/main.css;\
cd $(BUILDDIR)/packages/$$PKG && zip -r "$$PKG.zip" ./ ; \ cd $(BUILDDIR)/packages/$$PKG && zip -r "$$PKG.zip" ./ ; \
cd ../../ && (test -d repo/$$PKG || mkdir repo/$$PKG) && mv packages/$$PKG/"$$PKG.zip" repo/$$PKG && touch repo/$$PKG/$$PKG.md && rm -r packages/$$PKG cd ../../ && (test -d repo/$$PKG || mkdir repo/$$PKG) && mv packages/$$PKG/"$$PKG.zip" repo/$$PKG && touch repo/$$PKG/$$PKG.md && rm -r packages/$$PKG
uglify: uglify:
# sudo npm install terser -g # sudo npm install $(UGLIFYJS) -g
# #
terser $(BUILDDIR)/scripts/antos.js --compress --mangle --output $(BUILDDIR)/scripts/antos.js mv $(BUILDDIR)/scripts/antos.js $(BUILDDIR)/scripts/antos_src.js
cp $(BUILDDIR)/scripts/antos_src.js ./
$(UGLIFYJS) antos_src.js --compress --mangle --output antos.js --source-map "url='antos.js.map'"
mv antos.js* $(BUILDDIR)/scripts/
rm antos_src.js
# uglify tags # uglify tags
# npm install uglifycss -g # npm install $(UGLIFYCSS) -g
# uglify the css # uglify the css
uglifycss --output $(BUILDDIR)/resources/themes/antos_light/antos_light.css $(BUILDDIR)/resources/themes/antos_light/antos_light.css $(UGLIFYCSS) --output $(BUILDDIR)/resources/themes/antos_light/antos_light.css $(BUILDDIR)/resources/themes/antos_light/antos_light.css
uglifycss --output $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css $(UGLIFYCSS) --output $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css
uglifycss --output $(BUILDDIR)/resources/themes/system/system.css $(BUILDDIR)/resources/themes/system/system.css $(UGLIFYCSS) --output $(BUILDDIR)/resources/themes/system/system.css $(BUILDDIR)/resources/themes/system/system.css
#uglify each packages #uglify each packages
for d in $(packages); do\ for d in $(packages); do\
echo "Uglifying $$d";\ echo "Uglifying $$d";\
test -f $(BUILDDIR)/packages/$$d/main.js && terser $(BUILDDIR)/packages/$$d/main.js --compress --mangle --output $(BUILDDIR)/packages/$$d/main.js;\ test -f $(BUILDDIR)/packages/$$d/main.js && \
test -f $(BUILDDIR)/packages/$$d/main.css && uglifycss --output $(BUILDDIR)/packages/$$d/main.css $(BUILDDIR)/packages/$$d/main.css;\ $(UGLIFYJS) $(BUILDDIR)/packages/$$d/main.js \
--compress --mangle --output $(BUILDDIR)/packages/$$d/main.js;\
test -f $(BUILDDIR)/packages/$$d/main.css && $(UGLIFYCSS) --output $(BUILDDIR)/packages/$$d/main.css $(BUILDDIR)/packages/$$d/main.css;\
done done
ar: ar:

View File

@ -1,4 +1,4 @@
# antOS v1.2.0 # antOS v1.2.1
[![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) [![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)
@ -11,7 +11,8 @@ development and deployment of user specific applications inside de VDE environme
Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos) Github: [https://github.com/lxsang/antos](https://github.com/lxsang/antos)
## Change logs ## Change logs
* Improvement GUI API * V.1.2.1 WIP - with Jenkinsfile support and webhooks
* V.1.2.0 Improvement GUI API
- [x] File dialog should remember last opened folder - [x] File dialog should remember last opened folder
- [x] Add dynamic key-value dialog that work on any object - [x] Add dynamic key-value dialog that work on any object
- [x] Window list panel should show window title in tooltip when mouse hovering on application icon - [x] Window list panel should show window title in tooltip when mouse hovering on application icon

15028
d.ts/antos.d.ts vendored

File diff suppressed because it is too large Load Diff

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

Binary file not shown.

View File

@ -1 +1 @@
1.2.0 1.2.1

View File

@ -18,6 +18,63 @@
namespace OS { namespace OS {
export namespace API { export namespace API {
/**
* Data type exchanged via
* the global Announcement interface
*
* @export
* @interface AnnouncementDataType
*/
export interface AnnouncementDataType<T> {
/**
* message string
*
* @type {string| FormattedString}
* @memberof AppAnnouncementDataType
*/
message: string | FormattedString;
/**
* Process ID
*
* @type {number}
* @memberof AppAnnouncementDataType
*/
id: number;
/**
* App name
*
* @type {string | FormattedString}
* @memberof AppAnnouncementDataType
*/
name: string | FormattedString;
/**
* Icon file
*
* @type {string}
* @memberof AppAnnouncementDataType
*/
icon?: string;
/**
* App icon class
*
* @type {string}
* @memberof AppAnnouncementDataType
*/
iconclass?: string;
/**
* User specific data
*
* @type {*}
* @memberof AppAnnouncementDataType
*/
u_data?: T;
}
/** /**
* Observable entry type definition * Observable entry type definition
* *
@ -251,10 +308,10 @@ namespace OS {
* *
* @export * @export
* @param {string} e event name * @param {string} e event name
* @param {(d: any) => void} f event callback * @param {(d: API.AnnouncementDataType<any>) => void} f event callback
* @param {GUI.BaseModel} a the process (Application/service) related to the callback * @param {GUI.BaseModel} a the process (Application/service) related to the callback
*/ */
export function on(e: string, f: (d: any) => void, a: BaseModel): void { export function on(e: string, f: (d: API.AnnouncementDataType<any>) => void, a: BaseModel): void {
if (!announcer.listeners[a.pid]) { if (!announcer.listeners[a.pid]) {
announcer.listeners[a.pid] = []; announcer.listeners[a.pid] = [];
} }
@ -282,7 +339,7 @@ namespace OS {
* @param {Error} e error to be reported * @param {Error} e error to be reported
*/ */
export function osfail(m: string | FormattedString, e: Error): void { export function osfail(m: string | FormattedString, e: Error): void {
announcer.ostrigger("fail", { m, e }); announcer.ostrigger("fail", m, e );
} }
/** /**
@ -294,7 +351,7 @@ namespace OS {
* @param {Error} e error to be reported * @param {Error} e error to be reported
*/ */
export function oserror(m: string | FormattedString, e: Error): void { export function oserror(m: string | FormattedString, e: Error): void {
announcer.ostrigger("error", { m, e }); announcer.ostrigger("error", m, e );
} }
/** /**
@ -304,18 +361,24 @@ namespace OS {
* @param {(string | FormattedString)} m notification message * @param {(string | FormattedString)} m notification message
*/ */
export function osinfo(m: string | FormattedString): void { export function osinfo(m: string | FormattedString): void {
announcer.ostrigger("info", { m, e: null }); announcer.ostrigger("info", m);
} }
/** /**
* trigger a specific global event *
* *
* @export * @export
* @param {string} e event name * @param {string} e event name
* @param {*} d event data * @param {(string| FormattedString)} m event message
* @param {*} [d] user data
*/ */
export function ostrigger(e: string, d: any): void { export function ostrigger(e: string, m: string| FormattedString, d?: any): void {
announcer.trigger(e, { id: 0, data: d, name: "OS" }); const aob: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
aob.id = 0;
aob.message = m;
aob.u_data = d;
aob.name = "OS";
announcer.trigger(e, aob);
} }
/** /**

View File

@ -70,6 +70,22 @@ namespace OS {
*/ */
appmenu: GUI.tag.MenuTag; appmenu: GUI.tag.MenuTag;
/**
* Loading animation check timeout
*
* @private
* @memberof BaseApplication
*/
private _loading_toh: any;
/**
* Store pending loading task
*
* @private
* @type {number[]}
* @memberof BaseApplication
*/
private _pending_task: number[];
/** /**
*Creates an instance of BaseApplication. *Creates an instance of BaseApplication.
* @param {string} name application name * @param {string} name application name
@ -83,11 +99,8 @@ namespace OS {
} }
this.setting = setting.applications[this.name]; this.setting = setting.applications[this.name];
this.keycomb = {}; this.keycomb = {};
this.subscribe("appregistry", (m) => { this._loading_toh = undefined;
if (m.name === this.name) { this._pending_task = [];
this.applySetting(m.data.m);
}
});
} }
/** /**
@ -109,11 +122,13 @@ namespace OS {
this.sysdock.selectedApp = this; this.sysdock.selectedApp = this;
this.appmenu.pid = this.pid; this.appmenu.pid = this.pid;
this.appmenu.items = this.baseMenu() || []; this.appmenu.items = this.baseMenu() || [];
OS.PM.pidactive = this.pid;
this.appmenu.onmenuselect = ( this.appmenu.onmenuselect = (
d: GUI.tag.MenuEventData d: GUI.tag.MenuEventData
): void => { ): void => {
return this.trigger("menuselect", d); return this.trigger("menuselect", d);
}; };
this.trigger("focused", undefined);
if (this.dialog) { if (this.dialog) {
return this.dialog.show(); return this.dialog.show();
} }
@ -135,6 +150,31 @@ namespace OS {
} }
}); });
this.on("apptitlechange", () => this.sysdock.update(this)); 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); this.updateLocale(this.systemsetting.system.locale);
return this.loadScheme(); return this.loadScheme();
} }
@ -225,12 +265,11 @@ namespace OS {
* Update the application local from the system * Update the application local from the system
* locale or application specific locale configuration * locale or application specific locale configuration
* *
* @private
* @param {string} name locale name e.g. `en_GB` * @param {string} name locale name e.g. `en_GB`
* @returns {void} * @returns {void}
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected updateLocale(name: string): void { updateLocale(name: string): void {
const meta = this.meta(); const meta = this.meta();
if (!meta || !meta.locales) { if (!meta || !meta.locales) {
return; return;
@ -324,7 +363,11 @@ namespace OS {
if (this.appmenu && this.pid === this.appmenu.pid) { if (this.appmenu && this.pid === this.appmenu.pid) {
this.appmenu.items = []; this.appmenu.items = [];
} }
return this.trigger("blur", undefined); this.trigger("blur", undefined);
if(this.dialog)
{
this.dialog.blur();
}
} }
/** /**
@ -446,6 +489,23 @@ namespace OS {
* @memberof BaseApplication * @memberof BaseApplication
*/ */
protected cleanup(e: BaseEvent): void {} protected cleanup(e: BaseEvent): void {}
/**
* Check if the loading tasks ended,
* if it the case, stop the animation
*
* @private
* @memberof BaseApplication
*/
private animation_check(): void {
if(this._pending_task.length === 0)
{
this.trigger("loaded", undefined);
}
if(this._loading_toh)
clearTimeout(this._loading_toh);
this._loading_toh = undefined;
}
} }
BaseApplication.type = ModelType.Application; BaseApplication.type = ModelType.Application;

View File

@ -17,6 +17,7 @@
//along with this program. If not, see https://www.gnu.org/licenses/. //along with this program. If not, see https://www.gnu.org/licenses/.
namespace OS { namespace OS {
export namespace GUI { export namespace GUI {
declare var showdown: any;
/** /**
* the SubWindow class is the abstract prototype of all * the SubWindow class is the abstract prototype of all
* modal windows or dialogs definition in AntOS * modal windows or dialogs definition in AntOS
@ -44,6 +45,7 @@ namespace OS {
*/ */
parent: BaseModel | typeof GUI; parent: BaseModel | typeof GUI;
/** /**
*Creates an instance of SubWindow. *Creates an instance of SubWindow.
* @param {string} name SubWindow (class) name * @param {string} name SubWindow (class) name
@ -82,10 +84,26 @@ namespace OS {
* *
* Need to be implemented by subclasses * Need to be implemented by subclasses
* *
* @abstract *
* @memberof SubWindow * @returns {void}
* @memberof BaseDialog
*/ */
abstract init(): void; init(): void {
// show the app if it is not active
this.on("focus",() => {
if((this.pid == -1) || (PM.pidactive == this.pid))
{
return;
}
const app = PM.appByPid(this.pid);
if(app)
{
app.show();
}
});
}
/** /**
* Main entry point after rendering of the sub-window * Main entry point after rendering of the sub-window
@ -115,8 +133,8 @@ namespace OS {
* @memberof SubWindow * @memberof SubWindow
*/ */
show(): void { show(): void {
this.trigger("focus"); this.trigger("focus", undefined);
$(this.scheme).css("z-index", GUI.zindex + 2); this.trigger("focused", undefined);
if (this.dialog) { if (this.dialog) {
this.dialog.show(); this.dialog.show();
} }
@ -129,10 +147,27 @@ namespace OS {
* @memberof SubWindow * @memberof SubWindow
*/ */
hide(): void { hide(): void {
return this.trigger("hide"); this.trigger("hide", undefined);
if (this.dialog) {
this.dialog.hide();
} }
} }
/**
* blur the sub-window
*
* @returns {void}
* @memberof SubWindow
*/
blur(): void {
this.trigger("blur", undefined);
if (this.dialog) {
this.dialog.blur();
}
}
}
SubWindow.type = ModelType.SubWindow; SubWindow.type = ModelType.SubWindow;
/** /**
@ -182,6 +217,7 @@ namespace OS {
return (this.parent.dialog = undefined); return (this.parent.dialog = undefined);
} }
} }
} }
/** /**
@ -238,6 +274,7 @@ namespace OS {
* @memberof BasicDialog * @memberof BasicDialog
*/ */
init(): void { init(): void {
super.init();
//this._onenter = undefined; //this._onenter = undefined;
if (this.markup) { if (this.markup) {
if (typeof this.markup === "string") { if (typeof this.markup === "string") {
@ -264,7 +301,6 @@ namespace OS {
*/ */
main(): void { main(): void {
const win = this.scheme as tag.WindowTag; const win = this.scheme as tag.WindowTag;
$(win).attr("tabindex", 0);
$(win).on('keydown', (e) => { $(win).on('keydown', (e) => {
switch (e.which) { switch (e.which) {
case 27: case 27:
@ -532,7 +568,7 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
CalendarDialog.scheme = `\ CalendarDialog.scheme = `\
<afx-app-window width='300' height='230' apptitle = "Calendar" > <afx-app-window width='300' height='250' apptitle = "Calendar" >
<afx-vbox> <afx-vbox>
<afx-hbox> <afx-hbox>
<div data-width = "10" ></div> <div data-width = "10" ></div>
@ -672,7 +708,7 @@ namespace OS {
} }
for (let k in this.data) { for (let k in this.data) {
const v = this.data[k]; const v = this.data[k];
rows.push([{ text: k }, { text: v }]); rows.push([{ text: k }, { text: v, selectable: true }]);
} }
const grid = this.find("grid") as tag.GridViewTag; const grid = this.find("grid") as tag.GridViewTag;
grid.header = [ grid.header = [
@ -922,14 +958,25 @@ namespace OS {
if (!mt.info) { if (!mt.info) {
return; return;
} }
const rows = []; const rows = [
for (let k in mt.info) { [{ text: __("Author") }, { text: mt.info.author }],
const v = mt.info[k]; [{ text: __("Email") }, { text: mt.info.email }]
rows.push([{ text: k }, { text: v }]); ];
}
const grid = this.find("mygrid") as tag.GridViewTag; const grid = this.find("mygrid") as tag.GridViewTag;
grid.header = [{ text: "", width: 100 }, { text: "" }]; grid.header = [{ text: "", width: 100 }, { text: "" }];
grid.rows = rows; grid.rows = rows;
`pkg://${mt.pkgname?mt.pkgname:mt.app}/README.md`
.asFileHandle()
.read()
.then(async (data) => {
let _ = await API.requires("os://scripts/showdown.min.js");
const converter = new showdown.Converter();
const html = converter.makeHtml(data);
const el = this.find("read-me");
$(el).html(html);
$("img", el).css("width", "100%");
})
.catch(e => {});
(this.find("btnCancel") as tag.ButtonTag).onbtclick = ( (this.find("btnCancel") as tag.ButtonTag).onbtclick = (
_e _e
): void => { ): void => {
@ -941,19 +988,20 @@ namespace OS {
* Scheme definition * Scheme definition
*/ */
AboutDialog.scheme = `\ AboutDialog.scheme = `\
<afx-app-window data-id = 'about-window' width='300' height='200'> <afx-app-window data-id = 'about-window' width='450' height='400'>
<afx-vbox> <afx-vbox>
<div style="text-align:center; margin-top:10px;" data-height="50"> <div style="text-align:center; margin-top:10px;" data-height="50">
<h3 style = "margin:0;padding:0;"> <h3 style = "margin:0;padding:0;">
<afx-label data-id = 'mylabel'></afx-label> <afx-label data-id = 'mylabel' style="display: inline-block;"></afx-label>
</h3> </h3>
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i> <i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
</div> </div>
<afx-hbox> <afx-hbox data-height="60">
<div data-width="10"></div> <div data-width="10"></div>
<afx-grid-view data-id = 'mygrid'></afx-grid-view> <afx-grid-view data-id = 'mygrid'></afx-grid-view>
</afx-hbox> </afx-hbox>
<div data-id="read-me" style="overflow-x: hidden; overflow-y: auto;"></div>
<div data-height="10"></div>
<afx-hbox data-height="30"> <afx-hbox data-height="30">
<div ></div> <div ></div>
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" ></afx-button> <afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" ></afx-button>
@ -1035,12 +1083,6 @@ namespace OS {
return reject(d); return reject(d);
} }
FileDialog.last_opened = path; FileDialog.last_opened = path;
if (!dir.isRoot()) {
const p = dir.parent();
p.filename = "[..]";
p.type = "dir";
d.result.unshift(p);
}
return resolve(d.result); return resolve(d.result);
}) })
.catch((e: Error): void => reject(__e(e))); .catch((e: Error): void => reject(__e(e)));
@ -1052,7 +1094,7 @@ namespace OS {
__("Resource not found: {0}", path) __("Resource not found: {0}", path)
); );
} }
return (fileview.path = path); fileview.path = path;
}; };
if (!this.data || !this.data.root) { if (!this.data || !this.data.root) {
@ -1088,11 +1130,14 @@ namespace OS {
} }
}; };
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => { (this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
const f = fileview.selectedFile; let f = fileview.selectedFile;
if (!f) { if (!f) {
const sel = location.selectedItem;
if(!sel)
return this.notify( return this.notify(
__("Please select a file/fofler") __("Please select a file/fofler")
); );
f = sel.data as API.FileInfoType;
} }
if ( if (
this.data && this.data &&
@ -1150,6 +1195,18 @@ namespace OS {
if (this.data && this.data.hidden) { if (this.data && this.data.hidden) {
return (fileview.showhidden = this.data.hidden); return (fileview.showhidden = this.data.hidden);
} }
$(this.scheme).on("keyup", (evt)=>{
if(evt.which === 38)
{
const currdir = fileview.path.asFileHandle();
if (currdir.isRoot()) {
return;
}
const p = currdir.parent();
return fileview.path = p.path;
}
});
} }
} }

View File

@ -43,6 +43,7 @@ namespace OS {
*/ */
[propName: string]: any; [propName: string]: any;
} }
/** /**
* Enum definition of different model types * Enum definition of different model types
* *
@ -192,10 +193,10 @@ namespace OS {
* The HTML element ID of the virtual desktop * The HTML element ID of the virtual desktop
* *
* @protected * @protected
* @type {string} * @type {HTMLElement}
* @memberof BaseModel * @memberof BaseModel
*/ */
protected host: string; protected host: HTMLElement;
/** /**
* The process number of the current model. * The process number of the current model.
@ -293,12 +294,8 @@ namespace OS {
this._gui = GUI; this._gui = GUI;
this.systemsetting = setting; this.systemsetting = setting;
this.on("exit", () => this.quit(false)); this.on("exit", () => this.quit(false));
this.host = this._gui.workspace; this.host = this._gui.desktop();
this.dialog = undefined; this.dialog = undefined;
this.subscribe("systemlocalechange", (name) => {
this.updateLocale(name);
return this.update();
});
} }
/** /**
@ -314,11 +311,10 @@ namespace OS {
/** /**
* Update the model locale * Update the model locale
* *
* @protected
* @param {string} name * @param {string} name
* @memberof BaseModel * @memberof BaseModel
*/ */
protected updateLocale(name: string) {} updateLocale(name: string) {}
/** /**
* Render the model's UI * Render the model's UI
* *
@ -508,7 +504,6 @@ namespace OS {
/** /**
* trigger a local event * trigger a local event
* *
* @protected
* @param {string} e event name * @param {string} e event name
* @param {*} [d] event data * @param {*} [d] event data
* @returns {void} * @returns {void}
@ -524,11 +519,11 @@ namespace OS {
* *
* @protected * @protected
* @param {string} e event name * @param {string} e event name
* @param {(d: any) => void} f event callback * @param {(d: API.AnnouncementDataType<any>) => void} f event callback
* @returns {void} * @returns {void}
* @memberof BaseModel * @memberof BaseModel
*/ */
subscribe(e: string, f: (d: any) => void): void { subscribe(e: string, f: (d: API.AnnouncementDataType<any>) => void): void {
return announcer.on(e, f, this); return announcer.on(e, f, this);
} }
@ -587,41 +582,39 @@ namespace OS {
* @protected * @protected
* @param {string} t event name * @param {string} t event name
* @param {(string | FormattedString)} m event message * @param {(string | FormattedString)} m event message
* @param {Error} [e] error object if any * @param {any} u_data user data object if any
* @returns {void} * @returns {void}
* @memberof BaseModel * @memberof BaseModel
*/ */
protected publish( protected publish(
t: string, t: string,
m: string | FormattedString, m: string | FormattedString,
e?: Error u_data?: any
): void { ): void {
const mt = this.meta(); const mt = this.meta();
let icon: string = undefined; const data: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
data.icon = undefined;
if (mt && mt.icon) { if (mt && mt.icon) {
icon = `${mt.path}/${mt.icon}`; data.icon = `${mt.path}/${mt.icon}`;
} }
return announcer.trigger(t, { data.id = this.pid;
id: this.pid, data.name = this.name;
name: this.name, data.message = m;
data: { data.iconclass = mt?mt.iconclass:undefined;
m: m, data.u_data = u_data;
icon: icon, return announcer.trigger(t, data);
iconclass: mt?mt.iconclass:undefined,
e: e,
},
});
} }
/** /**
* Publish a global notification * Publish a global notification
* *
* @param {(string | FormattedString)} m notification string * @param {(string | FormattedString)} m notification string
* @param {any} u_data user data object if any
* @returns {void} * @returns {void}
* @memberof BaseModel * @memberof BaseModel
*/ */
notify(m: string | FormattedString): void { notify(m: string | FormattedString, data?: any): void {
return this.publish("notification", m); return this.publish("notification", m, data);
} }
/** /**
@ -677,7 +670,11 @@ namespace OS {
*/ */
update(): void { update(): void {
if (this.scheme) { if (this.scheme) {
return this.scheme.update(); this.scheme.update();
}
if(this.dialog)
{
this.dialog.update();
} }
} }

View File

@ -171,6 +171,13 @@ interface Date {
* @memberof Date * @memberof Date
*/ */
timestamp(): number; timestamp(): number;
/**
* Covnert to GMTString
*
* @returns {number}
* @memberof Date
*/
toGMTString(): string;
} }
/** /**
@ -252,7 +259,7 @@ namespace OS {
}; };
Ant.__e = function (e: Error): Error { Ant.__e = function (e: Error): Error {
const reason = new Error(e.toString()); const reason = new Error(e.toString().replace(/^Error: /g, ""));
reason.stack += "\nCaused By:\n" + e.stack; reason.stack += "\nCaused By:\n" + e.stack;
return reason; return reason;
}; };
@ -417,13 +424,14 @@ namespace OS {
* AntOS version number is in the following format: * AntOS version number is in the following format:
* *
* ``` * ```
* [major_number].[minor_number].[patch]-[branch] * [major_number].[minor_number].[patch]-[branch]-[build ID])
* *
* e.g.: 1.2.3-r means that: * e.g.: 1.2.3-r-b means that:
* - version major number is 1 * - version major number is 1
* - version minor number is 2 * - version minor number is 2
* - patch version is 3 * - patch version is 3
* - the current branch is release `r` * - the current branch is release `r`
* - build ID (optional)
* ``` * ```
* *
* @export * @export
@ -433,10 +441,11 @@ namespace OS {
/** /**
* The version string * The version string
* *
* @private
* @type {string} * @type {string}
* @memberof Version * @memberof Version
*/ */
string: string; private string: string;
/** /**
* The current branch * The current branch
@ -474,13 +483,40 @@ namespace OS {
*/ */
patch: number; patch: number;
/**
* Version build ID (optional): usually the current git commit hash
*
* @type {number}
* @memberof Version
*/
build_id: string;
/** /**
*Creates an instance of Version. *Creates an instance of Version.
*
* @param {string} string string represents the version * @param {string} string string represents the version
* @memberof Version * @memberof Version
*/ */
constructor(string: string) { constructor(string: string) {
this.string = string; this.version_string = string;
}
/**
* Setter/getter to set the version string to the object
*
* @memberof Version
*/
set version_string(v: string)
{
if(!v)
{
this.string = undefined;
this.major = undefined;
this.minor = undefined;
this.patch = undefined;
this.build_id = undefined;
return;
}
this.string = v;
const arr = this.string.split("-"); const arr = this.string.split("-");
const br = { const br = {
r: 3, r: 3,
@ -488,9 +524,14 @@ namespace OS {
a: 1, a: 1,
}; };
this.branch = 3; this.branch = 3;
if (arr.length === 2 && br[arr[1]]) { if (arr.length >= 2 && br[arr[1]]) {
this.branch = br[arr[1]]; this.branch = br[arr[1]];
if(arr[2])
{
this.build_id = arr[2];
} }
}
const mt = arr[0].match(/\d+/g); const mt = arr[0].match(/\d+/g);
if (!mt) { if (!mt) {
API.throwe( API.throwe(
@ -510,6 +551,10 @@ namespace OS {
this.patch = Number(mt[2]); this.patch = Number(mt[2]);
} }
} }
get version_string(): string
{
return this.string;
}
/** /**
* Compare the current version with another version. * Compare the current version with another version.
@ -754,7 +799,14 @@ namespace OS {
* Variable represents the current AntOS version, it * Variable represents the current AntOS version, it
* is an instance of [[Version]] * is an instance of [[Version]]
*/ */
export const VERSION: Version = "1.0.0-a".__v(); export const VERSION: Version = new Version(undefined);
/**
* Variable represents the current AntOS source code repository
* is an instance of [[string]]
*/
export const REPOSITORY: string = "https://github.com/lxsang/antos";
/** /**
* Register a model prototype to the system namespace. * Register a model prototype to the system namespace.
* There are two types of model to be registered, if the model * There are two types of model to be registered, if the model
@ -843,7 +895,7 @@ namespace OS {
* exits. These callbacks are useful when an application or service wants * exits. These callbacks are useful when an application or service wants
* to perform a particular task before shuting down the system * to perform a particular task before shuting down the system
*/ */
export const cleanupHandles: { [index: string]: () => void } = {}; export const cleanupHandles: { [index: string]: () => Promise<any> } = {};
/** /**
* Perform the system shutdown operation. This function calls all * Perform the system shutdown operation. This function calls all
@ -854,15 +906,16 @@ namespace OS {
*/ */
export function exit(): void { export function exit(): void {
//do clean up first //do clean up first
const promises: Promise<any>[] = [];
for (let n in cleanupHandles) { for (let n in cleanupHandles) {
const f = cleanupHandles[n]; promises.push(cleanupHandles[n]());
f();
} }
API.handle promises.push(API.handle.setting());
.setting() Promise.all(promises)
.then(function (r: any) { .then(async function (r: any) {
cleanup(); cleanup();
return API.handle.logout().then((d: any) => boot()); const d = await API.handle.logout();
return boot();
}) })
.catch((e: Error) => console.error(e)); .catch((e: Error) => console.error(e));
} }
@ -875,7 +928,7 @@ namespace OS {
* @param {() => void} f the callback handle * @param {() => void} f the callback handle
* @returns * @returns
*/ */
export function onexit(n: string, f: () => void) { export function onexit(n: string, f: () => Promise<any>) {
if (!cleanupHandles[n]) { if (!cleanupHandles[n]) {
return (cleanupHandles[n] = f); return (cleanupHandles[n] = f);
} }
@ -1215,13 +1268,13 @@ namespace OS {
o.on("change", function () { o.on("change", function () {
const files = (o[0] as HTMLInputElement).files; const files = (o[0] as HTMLInputElement).files;
const n_files = files.length; const n_files = files.length;
const tasks = [];
if (n_files > 0) if (n_files > 0)
API.loading(q, p); API.loading(q, p);
Array.from(files).forEach(file => {
const formd = new FormData(); const formd = new FormData();
formd.append("path", d); formd.append("path", d);
formd.append("upload", file); jQuery.each(files, (i, file) => {
formd.append(`upload-${i}`, file);
});
return $.ajax({ return $.ajax({
url: p, url: p,
data: formd, data: formd,
@ -1230,25 +1283,15 @@ namespace OS {
processData: false, processData: false,
}) })
.done(function (data) { .done(function (data) {
tasks.push("OK");
if (tasks.length == n_files)
{
API.loaded(q, p, "OK"); API.loaded(q, p, "OK");
resolve(data); resolve(data);
o.remove();
}
}) })
.fail(function (j, s, e) { .fail(function (j, s, e) {
tasks.push("FAIL");
if (tasks.length == n_files)
{
API.loaded(q, p, "FAIL"); API.loaded(q, p, "FAIL");
o.remove(); o.remove();
}
reject(API.throwe(s)); reject(API.throwe(s));
}); });
}); });
});
return o.trigger("click"); return o.trigger("click");
}); });
} }
@ -1284,11 +1327,12 @@ namespace OS {
* @param {string} p message string * @param {string} p message string
*/ */
export function loading(q: number, p: string): void { export function loading(q: number, p: string): void {
announcer.trigger("loading", { const data:API.AnnouncementDataType<number> = {} as API.AnnouncementDataType<number>;
id: q, data.id = q;
data: { m: `${p}`, s: true }, data.message = p;
name: "OS", data.name = p;
}); data.u_data = PM.pidactive;
announcer.trigger("loading", data);
} }
/** /**
@ -1303,11 +1347,12 @@ namespace OS {
* @param {string} m message status (`OK` of `FAIL`) * @param {string} m message status (`OK` of `FAIL`)
*/ */
export function loaded(q: number, p: string, m: string): void { export function loaded(q: number, p: string, m: string): void {
announcer.trigger("loaded", { const data:API.AnnouncementDataType<boolean> = {} as API.AnnouncementDataType<boolean>;
id: q, data.id = q;
data: { m: `${m}: ${p}`, s: false }, data.message = p;
name: "OS", data.name = "OS";
}); data.u_data = false;
announcer.trigger("loaded", data);
} }
/** /**
@ -1612,7 +1657,7 @@ namespace OS {
const d = await API.get(path, "json"); const d = await API.get(path, "json");
OS.setting.system.locale = name; OS.setting.system.locale = name;
API.lang = d; API.lang = d;
announcer.trigger("systemlocalechange", name); announcer.ostrigger("systemlocalechange", name);
return resolve(d); return resolve(d);
} catch (e) { } catch (e) {
return reject(__e(e)); return reject(__e(e));

View File

@ -119,15 +119,16 @@ namespace OS {
app: BaseModel, app: BaseModel,
parent: Element | string parent: Element | string
): void { ): void {
const scheme = $.parseHTML(html); const scheme = $.parseHTML(html)[0];
if (app.scheme) { if (app.scheme) {
$(app.scheme).remove(); $(app.scheme).remove();
} }
$(parent as GenericObject<any>).append(scheme); (parent as HTMLElement).append(scheme);
app.scheme = scheme[0] as HTMLElement; app.scheme = scheme as HTMLElement;
app.scheme.uify(app.observable, true); app.scheme.uify(app.observable, true);
app.main(); app.main();
app.show(); app.show();
app.observable.trigger("launched",undefined);
} }
/** /**
@ -182,6 +183,28 @@ namespace OS {
$("head link#ostheme").attr("href", path); $("head link#ostheme").attr("href", path);
} }
/**
* Get the system dock tag
*
* @export
* @return {*} {GUI.tag.AppDockTag}
*/
export function systemDock(): GUI.tag.AppDockTag
{
return $("#sysdock")[0] as tag.AppDockTag;
}
/**
* Get the current virtual desktop
*
* @export
* @return {*} {GUI.tag.DesktopTag}
*/
export function desktop(): GUI.tag.DesktopTag
{
return $(workspace)[0] as tag.DesktopTag;
}
/** /**
* Open a system dialog. * Open a system dialog.
* *
@ -257,10 +280,7 @@ namespace OS {
return false; return false;
}); });
} catch (e) { } catch (e) {
return announcer.osfail( return false;
__("Error find app by mimes {0}", mime),
e
);
} }
}; };
let arr: string[]; let arr: string[];
@ -380,11 +400,43 @@ namespace OS {
* @export * @export
* @param {string} app * @param {string} app
*/ */
export function unloadApp(app: string): void { export function unloadApp(app: string, save?: boolean): void {
PM.killAll(app, true); PM.killAll(app, true);
if (application[app] && application[app].style) { if (application[app] && application[app].style) {
$(application[app].style).remove(); $(application[app].style).remove();
} }
if(save)
{
// remove pinned application if any
if(OS.setting.system.startup.pinned)
OS.setting.system.startup.pinned = OS.setting.system.startup.pinned.filter((e) => e != app);
// remove service if it is the service
if(OS.setting.system.startup.services)
OS.setting.system.startup.services = OS.setting.system.startup.services.filter((e) => e != app);
// remove startup app if any
if(OS.setting.system.startup.apps)
OS.setting.system.startup.apps = OS.setting.system.startup.apps.filter((e) => e != app);
// refresh pinned list
announcer.ostrigger("app-pinned", "app-pinned", undefined);
// remove application setting
if(OS.setting.applications[app])
delete OS.setting.applications[app];
OS.API.setting()
.then((d) =>{
if(d.error)
{
announcer.oserror(
__("Error when save system setting {0}:{1}", app, d.error),
undefined);
}
})
.catch((e)=> {
announcer.oserror(
__("Error when save system setting {0}: {1}", app, e.toString()),
e);
});
}
delete application[app]; delete application[app];
} }
@ -396,19 +448,37 @@ namespace OS {
* definition in the [[application]] namespace, then update * definition in the [[application]] namespace, then update
* the system packages meta-data * the system packages meta-data
* *
* First it tries to load the package with the app name is also the
* pkgname, if its fail, it will search the app name by pkg name and load
* it
*
* @param {string} app application class name * @param {string} app application class name
* @returns {Promise<string>} * @returns {Promise<string>}
*/ */
function loadApp(app: string): Promise<string> { function loadApp(app: string): Promise<string> {
return new Promise(async function (resolve, reject) { return new Promise(async function (resolve, reject) {
let path: string; let path: string = undefined;
if (setting.system.packages[app].path) { try {
if(!setting.system.packages[app])
{
for(const key in setting.system.packages)
{
const pkg = setting.system.packages[key];
if(pkg.app == app && pkg.path)
{
path = pkg.path;
}
}
}
else if (setting.system.packages[app].path) {
path = setting.system.packages[app].path; path = setting.system.packages[app].path;
} }
if(!path)
{
throw __("Unable to locate package of {0}", app).__();
}
const js = path + "/main.js"; const js = path + "/main.js";
try {
const d = await js.asFileHandle().read("script"); const d = await js.asFileHandle().read("script");
try {
const data: API.PackageMetaType = await `${path}/package.json` const data: API.PackageMetaType = await `${path}/package.json`
.asFileHandle() .asFileHandle()
.read("json"); .read("json");
@ -422,9 +492,9 @@ namespace OS {
} }
} }
//load css file //load css file
try{
const css = `${path}/main.css`; const css = `${path}/main.css`;
try { await css.asFileHandle().onready();
const d_1 = await css.asFileHandle().onready();
const stamp = new Date().timestamp(); const stamp = new Date().timestamp();
const el = $("<link>", { const el = $("<link>", {
rel: "stylesheet", rel: "stylesheet",
@ -434,15 +504,10 @@ namespace OS {
if (application[app]) { if (application[app]) {
application[app].style = el[0]; application[app].style = el[0];
} }
} catch(e_1){}
return resolve(app); return resolve(app);
} catch (e) { } catch (e) {
return resolve(app); return reject(__e(e));
}
} catch (e_1) {
return reject(__e(e_1));
}
} catch (e_2) {
return reject(__e(e_2));
} }
}); });
} }
@ -462,36 +527,23 @@ namespace OS {
const arr = ph.split("/"); const arr = ph.split("/");
const srv = arr[1]; const srv = arr[1];
const app = arr[0]; const app = arr[0];
if (application[srv]) {
try {
const d = await OS.PM.createProcess(
srv,
application[srv]
);
return resolve(d);
} catch (e) {
return reject(__e(e));
}
} else {
try { try {
if (!application[srv]) {
await loadApp(app); await loadApp(app);
if (!application[srv]) { if (!application[srv]) {
return reject( return reject(
API.throwe(__("Service not found: {0}", ph)) API.throwe(__("Service not found: {0}", ph))
); );
} }
try { }
const d_1 = await PM.createProcess( const d = await PM.createProcess(
srv, srv,
application[srv] application[srv]
); );
return resolve(d_1); return resolve(d);
} catch (e_1) {
return reject(__e(e_1));
}
} catch (e_2) {
return reject(__e(e_2));
} }
catch (e) {
return reject(__e(e));
} }
}); });
} }
@ -532,11 +584,13 @@ namespace OS {
* @param {AppArgumentsType[]} args application arguments * @param {AppArgumentsType[]} args application arguments
*/ */
export function launch(app: string, args: AppArgumentsType[]): Promise<OS.PM.ProcessType> { export function launch(app: string, args: AppArgumentsType[]): Promise<OS.PM.ProcessType> {
return new Promise((resolve, reject) => { return new Promise(async (resolve, reject) => {
const pidactive = PM.pidactive;
try {
PM.pidactive = 0;
if (!application[app]) { if (!application[app]) {
// first load it // first load it
loadApp(app) await loadApp(app);
.then((a) => {
if (!application[app]) { if (!application[app]) {
const e = API.throwe(__("Application not found")); const e = API.throwe(__("Application not found"));
announcer.oserror( announcer.oserror(
@ -544,47 +598,31 @@ namespace OS {
e); e);
return reject(e); return reject(e);
} }
PM.createProcess( const p = await PM.createProcess(
app, app,
application[app], application[app],
args args
).catch((e) => { );
resolve(p);
} else {
// now launch it
const p = await PM.createProcess(
app,
application[app],
args
);
resolve(p);
}
}
catch (e) {
announcer.osfail( announcer.osfail(
__("Unable to launch: {0}", app), __("Unable to launch: {0}", app),
e e
); );
return reject(e); PM.pidactive = pidactive;
} return reject(__e(e));
).then((p: PM.ProcessType) => resolve(p));
}
)
.catch((e) => {
announcer.osfail(__("Unable to launch: {0}", app), e);
reject(e);
}
);
} else {
// now launch it
if (application[app]) {
PM.createProcess(
app,
application[app],
args
).catch((e: Error) => {
announcer.osfail(__("Unable to launch: {0}", app), e);
return reject(e);
} }
);
} else {
const e = API.throwe(__("Application not found"));
announcer.osfail(
__("Unable to find: {0}", app),
e
);
return reject(e);
}
}
}); });
} }
@ -846,50 +884,6 @@ namespace OS {
.css("left", left + "px"); .css("left", left + "px");
} }
/**
* Refresh the content of the virtual desktop
*
* @param {tag.FloatListTag} desktop
*/
function dkfetch(desktop: tag.FloatListTag): void {
const file = setting.desktop.path.asFileHandle();
const fn = () =>
file.read().then(function (d) {
if (d.error) {
return announcer.osfail(d.error, API.throwe(d.error));
}
const items = [];
$.each(d.result, function (i, v) {
if (
v.filename[0] === "." &&
!setting.desktop.showhidden
) {
return;
}
v.text = v.filename;
//v.text = v.text.substring(0,9) + "..." ifv.text.length > 10
v.iconclass = v.type;
return items.push(v);
});
desktop.data = items;
return desktop.calibrate();
});
file.onready()
.then(() => fn())
.catch(async function (e) {
// try to create the path
console.log(`${file.path} not found`);
const name = file.basename;
try {
const r = await file.parent().asFileHandle().mk(name);
return API.throwe("OS.VFS");
} catch (e_1) {
return announcer.osfail(e_1.toString(), e_1);
}
});
}
/** /**
* Init the virtual desktop on boot: * Init the virtual desktop on boot:
* *
@ -949,7 +943,7 @@ namespace OS {
$("#systooltip")[0].uify(undefined); $("#systooltip")[0].uify(undefined);
$("#contextmenu")[0].uify(undefined); $("#contextmenu")[0].uify(undefined);
$("#wrapper").on("contextmenu",(e) => bindContextMenu(e)); $("#wrapper").on("contextmenu", (e) => bindContextMenu(e));
// tooltip // tooltip
$(document).on("mouseover", function (e) { $(document).on("mouseover", function (e) {
const el: any = $(e.target).closest("[tooltip]"); const el: any = $(e.target).closest("[tooltip]");
@ -962,115 +956,8 @@ namespace OS {
e e
); );
}); });
const fp = setting.desktop.path.asFileHandle();
// desktop default file manager
const desktop = $(workspace)[0] as tag.FloatListTag;
desktop.onready = function (e: tag.FloatListTag) {
e.observable = OS.announcer.observable;
window.onresize = function () {
announcer.trigger("desktopresize", undefined);
return e.calibrate();
};
desktop.onlistselect = function (
d: TagEventType<tag.ListItemEventData>
) {
($("#sysdock")[0] as tag.AppDockTag).selectedApp = null;
};
desktop.onlistdbclick = function (
d: TagEventType<tag.ListItemEventData>
) {
($("#sysdock")[0] as tag.AppDockTag).selectedApp = null;
const it = desktop.selectedItem;
return openWith(it.data as AppArgumentsType);
};
//($ "#workingenv").on "click", (e) ->
// desktop[0].set "selected", -1
$(desktop).on("click", function (e) {
let el = $(e.target).parent();
if (!(el.length > 0)) {
return;
}
el = el.parent();
if (!(el.length > 0)) {
return;
}
if (el[0] !== desktop) {
return;
}
desktop.unselect();
($("#sysdock")[0] as tag.AppDockTag).selectedApp = null;
});
desktop.contextmenuHandle = function (e, m) {
if (e.target.tagName.toUpperCase() === "UL") {
desktop.unselect();
}
($("#sysdock")[0] as tag.AppDockTag).selectedApp = null;
let menu = [
{ text: __("Open"), dataid: "desktop-open" },
{ text: __("Refresh"), dataid: "desktop-refresh" },
];
menu = menu.concat(
(() => {
const result = [];
for (let k in setting.desktop.menu) {
const v = setting.desktop.menu[k];
result.push(v);
}
return result;
})()
);
m.items = menu;
m.onmenuselect = function (
evt: TagEventType<tag.MenuEventData>
) {
if (!evt.data || !evt.data.item) return;
const item = evt.data.item.data;
switch (item.dataid) {
case "desktop-open":
var it = desktop.selectedItem;
if (it) {
return openWith(
it.data as AppArgumentsType
);
}
let arg = setting.desktop.path.asFileHandle() as AppArgumentsType;
arg.mime = "dir";
arg.type = "dir";
return openWith(arg);
case "desktop-refresh":
return dkfetch(desktop);
default:
if (item.app) {
return launch(item.app, item.args);
}
}
};
return m.show(e);
};
dkfetch(desktop);
announcer.observable.on("VFS", function (d) {
if (["read", "publish", "download"].includes(d.data.m)) {
return;
}
if (
d.data.file.hash() === fp.hash() ||
d.data.file.parent().hash() === fp.hash()
) {
return dkfetch(desktop);
}
});
return announcer.ostrigger("desktoploaded", undefined);
};
// mount it // mount it
desktop.uify(undefined); desktop().uify(undefined);
} }
/** /**
@ -1079,7 +966,7 @@ namespace OS {
* @export * @export
*/ */
export function refreshDesktop(): void { export function refreshDesktop(): void {
dkfetch($(workspace)[0] as tag.FloatListTag); desktop().refresh();
} }
/** /**
@ -1090,7 +977,11 @@ namespace OS {
* @export * @export
*/ */
export function login(): void { export function login(): void {
const scheme = $.parseHTML(schemes.login); const scheme = $.parseHTML(
schemes.login
.replace("[ANTOS_BUILD_ID]", OS.VERSION.build_id)
.replace("[ANTOS_VERSION]", OS.VERSION.version_string)
);
$("#wrapper").append(scheme); $("#wrapper").append(scheme);
$("#btlogin").on("click", async function () { $("#btlogin").on("click", async function () {
const data: API.UserLoginType = { const data: API.UserLoginType = {
@ -1143,7 +1034,7 @@ namespace OS {
loadTheme(setting.appearance.theme, true); loadTheme(setting.appearance.theme, true);
wallpaper(undefined); wallpaper(undefined);
OS.announcer.observable.one("syspanelloaded", async function () { OS.announcer.observable.one("syspanelloaded", async function () {
OS.announcer.observable.on("systemlocalechange", (name) => OS.announcer.observable.on("systemlocalechange", (_) =>
$("#syspanel")[0].update() $("#syspanel")[0].update()
); );
@ -1152,9 +1043,7 @@ namespace OS {
return API.packages.fetch().then(function (r) { return API.packages.fetch().then(function (r) {
let v: API.PackageMetaType; let v: API.PackageMetaType;
if (r.result) { if (r.result) {
const result = r.result as GenericObject< const result = r.result as GenericObject<API.PackageMetaType>;
API.PackageMetaType
>;
for (let k in result) { for (let k in result) {
v = result[k]; v = result[k];
v.text = v.name; v.text = v.name;
@ -1172,11 +1061,10 @@ namespace OS {
? result ? result
: undefined; : undefined;
} }
// load services + VFSX
// GUI.refreshSystemMenu() Promise.all(
// GUI.buildSystemMenu() [
// push startup services OS.API.VFS.loadVFSX(true),
// TODO: get services list from user setting
pushServices( pushServices(
(() => { (() => {
const result = []; const result = [];
@ -1185,7 +1073,9 @@ namespace OS {
} }
return result; return result;
})() })()
).then(function () { )
])
.then(function () {
setting.system.startup.apps.map((a) => { setting.system.startup.apps.map((a) => {
launch(a, []); launch(a, []);
}); });
@ -1193,14 +1083,13 @@ namespace OS {
}); });
} }
}); });
//GUI.launch "DummyApp"
// initDM // initDM
API.setLocale(setting.system.locale).then(() => initDM()); API.setLocale(setting.system.locale).then(() => initDM());
Ant.OS.announcer.observable.on("error", function (d) { Ant.OS.announcer.observable.on("error", function (d) {
console.log(d.data.e); console.log(d.u_data);
}); });
Ant.OS.announcer.observable.on("fail", function (d) { Ant.OS.announcer.observable.on("fail", function (d) {
console.log(d.data.e); console.log(d.u_data);
}); });
} }
/** /**
@ -1217,7 +1106,7 @@ namespace OS {
<afx-sys-panel id = "syspanel"></afx-sys-panel> <afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace"> <div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock> <afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-float-list id = "desktop" dir="vertical" ></afx-float-list> <afx-desktop id = "desktop" dir="vertical" ></afx-desktop>
</div> </div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-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> <afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
@ -1231,7 +1120,8 @@ namespace OS {
<input id = "txtpass" type = "password" value = "demo" ></input> <input id = "txtpass" type = "password" value = "demo" ></input>
<button id = "btlogin">Login</button> <button id = "btlogin">Login</button>
<div id = "login_error"></div> <div id = "login_error"></div>
</div>\ </div>
<div id = "antos_build_id"><a href="${OS.REPOSITORY}/tree/[ANTOS_BUILD_ID]">AntOS v[ANTOS_VERSION]</div>\
`; `;
} }
} }

View File

@ -24,6 +24,10 @@ namespace OS {
/** /**
* All running processes is stored in this variables * All running processes is stored in this variables
*/ */
/**
* Current active process ID
*/
export var pidactive: number = 0;
export var processes: GenericObject<BaseModel[]> = {}; export var processes: GenericObject<BaseModel[]> = {};
/** /**
* Create a new process of application or service * Create a new process of application or service
@ -63,6 +67,10 @@ namespace OS {
obj.birth = new Date().getTime(); obj.birth = new Date().getTime();
PM.pidalloc++; PM.pidalloc++;
obj.pid = PM.pidalloc; obj.pid = PM.pidalloc;
obj.subscribe("systemlocalechange", (d) => {
obj.updateLocale(d.message as string);
return obj.update();
});
PM.processes[app].push(obj); PM.processes[app].push(obj);
if (metaclass.type === ModelType.Application) { if (metaclass.type === ModelType.Application) {
GUI.dock( GUI.dock(
@ -149,7 +157,25 @@ namespace OS {
if (!PM.processes[app]) { if (!PM.processes[app]) {
return; return;
} }
PM.processes[app].map((a) => a.quit(force)); const arr = PM.processes[app].map( e => e);
for(const p of arr)
{
p.quit(force);
}
}
/**
* Get the current active application
* @export
* @returns {BaseModel}
*/
export function getActiveApp():BaseModel
{
if(PM.pidactive === 0)
{
return undefined;
}
return PM.appByPid(PM.pidactive);
} }
} }
} }

View File

@ -162,6 +162,7 @@ namespace OS {
} }
this._selectedItem = el; this._selectedItem = el;
if (!el) { if (!el) {
PM.pidactive = 0;
return; return;
} }
$(el.domel).addClass("selected"); $(el.domel).addClass("selected");

272
src/core/tags/DesktopTag.ts Normal file
View File

@ -0,0 +1,272 @@
namespace OS {
export namespace GUI {
export namespace tag {
/**
* Meta tag that represents the virtual desktop environment.
* In near future, we may have multiple virtual desktop environments.
* Each desktop environment has a simple file manager and a window
* manager that render the window in a specific order.
*
* @export
* @class DesktopTag
* @extends {FloatListTag}
*/
export class DesktopTag extends FloatListTag {
/**
* internal handle to the desktop file location
*
* @private
* @type {API.VFS.BaseFileHandle}
* @memberof DesktopTag
*/
private file: API.VFS.BaseFileHandle;
/**
* local observer that detect if a new child element is
* added or removed
*
* @private
* @type {MutationObserver}
* @memberof DesktopTag
*/
private observer: MutationObserver;
/**
* Internal list of the current opened window
*
* @private
* @type {Set<WindowTag>}
* @memberof DesktopTag
*/
private window_list: Set<WindowTag>;
/**
* Creates an instance of DesktopTag.
* @memberof DesktopTag
*/
constructor() {
super();
this.observer = undefined;
this.window_list = new Set<WindowTag>();
}
/**
* Mount the virtual desktop to the DOM tree
*
* @protected
* @memberof DesktopTag
*/
protected mount(): void {
if(this.observer)
{
this.observer.disconnect();
this.observer = undefined;
}
this.observer = new MutationObserver((mutations_list) =>{
mutations_list.forEach((mutation) => {
mutation.removedNodes.forEach((removed_node) =>{
if((removed_node as HTMLElement).tagName === "AFX-APP-WINDOW")
{
this.window_list.delete(removed_node as WindowTag);
}
});
mutation.addedNodes.forEach((added_node) =>{
if((added_node as HTMLElement).tagName === "AFX-APP-WINDOW")
{
this.selectWindow(added_node as WindowTag);
}
});
});
});
this.observer.observe(this, { subtree: false, childList: true });
this.onready = (_) => {
this.observable = OS.announcer.observable;
window.onresize = () => {
announcer.trigger("desktopresize", undefined);
this.calibrate();
};
this.onlistselect = (d: TagEventType<tag.ListItemEventData>) => {
GUI.systemDock().selectedApp = null;
};
this.onlistdbclick = (d: TagEventType<tag.ListItemEventData>) => {
GUI.systemDock().selectedApp = null;
const it = this.selectedItem;
return GUI.openWith(it.data as AppArgumentsType);
};
//($ "#workingenv").on "click", (e) ->
// desktop[0].set "selected", -1
$(this).on("click", (e) => {
let el: any = $(e.target).closest("afx-app-window")[0];
if (el) {
return;
}
el = $(e.target).parent();
if (!(el.length > 0)) {
return;
}
el = el.parent();
if (!(el.length > 0)) {
return;
}
if (el[0] !== this) {
return;
}
this.unselect();
GUI.systemDock().selectedApp = null;
});
this.contextmenuHandle = (e, m) => {
if (e.target.tagName.toUpperCase() === "UL") {
this.unselect();
}
GUI.systemDock().selectedApp = null;
let menu = [
{ text: __("Open"), dataid: "desktop-open" } as GUI.BasicItemType,
{ text: __("Refresh"), dataid: "desktop-refresh" } as GUI.BasicItemType,
];
menu = menu.concat(setting.desktop.menu.map(e => e));
m.items = menu;
m.onmenuselect = (evt: TagEventType<tag.MenuEventData>) => {
if (!evt.data || !evt.data.item) return;
const item = evt.data.item.data;
switch (item.dataid) {
case "desktop-open":
var it = this.selectedItem;
if (it) {
return GUI.openWith(
it.data as AppArgumentsType
);
}
let arg = setting.desktop.path.asFileHandle() as AppArgumentsType;
arg.mime = "dir";
arg.type = "dir";
return GUI.openWith(arg);
case "desktop-refresh":
return this.refresh();
default:
if (item.app) {
return GUI.launch(item.app, item.args);
}
}
};
return m.show(e);
};
this.refresh();
announcer.observable.on("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
if (["read", "publish", "download"].includes(d.message as string)) {
return;
}
if (d.u_data.hash() === this.file.hash() || d.u_data.parent().hash() === this.file.hash()) {
return this.refresh();
}
});
return announcer.ostrigger("desktoploaded", undefined);
};
super.mount();
}
/**
* Display all files and folders in the specific desktop location
*
* @return {*} {Promise<any>}
* @memberof DesktopTag
*/
refresh(): Promise<any> {
return new Promise<any>(async (resolve, reject) => {
try {
this.file = setting.desktop.path.asFileHandle();
await this.file.onready();
const d = await this.file.read();
if (d.error) {
throw new Error(d.error);
}
const items = [];
$.each(d.result, function (i, v) {
if (v.filename[0] === "." && !setting.desktop.showhidden) {
return;
}
v.text = v.filename;
//v.text = v.text.substring(0,9) + "..." ifv.text.length > 10
v.iconclass = v.type;
return items.push(v);
});
this.data = items;
this.calibrate();
}
catch (err) {
announcer.osfail(err.toString(), err);
reject(__e(err));
}
});
}
/**
* Remove this element from its parent
*
* @memberof DesktopTag
*/
remove(): void {
if(this.observer)
{
this.observer.disconnect();
}
super.remove();
}
/**
* Active a window above all other windows
*
* @private
* @param {WindowTag} win
* @memberof DesktopTag
*/
private selectWindow(win: WindowTag)
{
if(this.window_list.has(win))
{
this.window_list.delete(win);
}
else
{
win.observable.on("focused",(_)=>{
this.selectWindow(win);
});
}
this.window_list.add(win);
this.render();
}
/**
* Render all windows in order from bottom to top
*
* @private
* @memberof DesktopTag
*/
private render(){
let zindex = 10;
for(let win of this.window_list)
{
$(win).css("z-index", zindex++);
}
}
}
define("afx-desktop", DesktopTag);
}
}
}

View File

@ -28,13 +28,13 @@ namespace OS {
private _onfileopen: TagEventCallback<API.FileInfoType>; private _onfileopen: TagEventCallback<API.FileInfoType>;
/** /**
* Reference to the currently selected file meta-data * Reference to the all selected files meta-datas
* *
* @private * @private
* @type {API.FileInfoType} * @type {API.FileInfoType[]}
* @memberof FileViewTag * @memberof FileViewTag
*/ */
private _selectedFile: API.FileInfoType; private _selectedFiles: API.FileInfoType[];
/** /**
* Data placeholder of the current working directory * Data placeholder of the current working directory
@ -58,10 +58,10 @@ namespace OS {
* Header definition of the widget grid view * Header definition of the widget grid view
* *
* @private * @private
* @type {(GenericObject<string | number>[])} * @type {(GenericObject<any>[])}
* @memberof FileViewTag * @memberof FileViewTag
*/ */
private _header: GenericObject<string | number>[]; private _header: GenericObject<any>[];
/** /**
* placeholder for the user-specified meta-data fetch function * placeholder for the user-specified meta-data fetch function
@ -92,10 +92,56 @@ namespace OS {
this.chdir = true; this.chdir = true;
this.view = "list"; this.view = "list";
this._onfileopen = this._onfileselect = (e) => { }; this._onfileopen = this._onfileselect = (e) => { };
this._selectedFiles = [];
const fn = function(r1, r2, i) {
let t1 = r1[i].text;
let t2 = r2[i].text;
if(!t1 || !t2) return 0;
if(i == 1)
{
// sort by date
t1 = new Date(t1);
t2 = new Date(t2);
}
else if(i==2)
{
// sort by size
t1 = parseInt(t1);
t2 = parseInt(t2);
}
else
{
// sort by name
t1 = t1.toString().toLowerCase();
t2 = t2.toString().toLowerCase();
}
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; }
}
return 0;
};
this._header = [ this._header = [
{ text: "__(File name)" }, {
{ text: "__(Type)" }, text: "__(File name)",
{ text: "__(Size)" }, sort: fn
},
{
text: "__(Modified)",
sort: fn
},
{
text: "__(Size)",
sort: fn
},
]; ];
} }
@ -227,6 +273,28 @@ namespace OS {
return this.hasattr("showhidden"); return this.hasattr("showhidden");
} }
/**
* Setter:
*
* Allow multiple selection on file view
*
* Getter:
*
* Check whether the multiselection is actived
*
* @memberof FileViewTag
*/
set multiselect(v: boolean) {
this.attsw(v, "multiselect");
(this.refs.listview as ListViewTag).multiselect = v;
(this.refs.gridview as GridViewTag).multiselect = v;
}
get multiselect(): boolean {
return this.hasattr("multiselect");
}
/** /**
* Get the current selected file * Get the current selected file
* *
@ -235,7 +303,21 @@ namespace OS {
* @memberof FileViewTag * @memberof FileViewTag
*/ */
get selectedFile(): API.FileInfoType { get selectedFile(): API.FileInfoType {
return this._selectedFile; if(this._selectedFiles.length == 0)
return undefined;
return this._selectedFiles[this._selectedFiles.length - 1];
}
/**
* Get all selected files
*
* @readonly
* @type {API.FileInfoType[]}
* @memberof FileViewTag
*/
get selectedFiles(): API.FileInfoType[] {
return this._selectedFiles;
} }
/** /**
@ -305,11 +387,21 @@ namespace OS {
*/ */
set ondragndrop( set ondragndrop(
v: TagEventCallback< v: TagEventCallback<
DnDEventDataType<TreeViewTag | ListViewItemTag> DnDEventDataType<TreeViewTag | ListViewItemTag | GridCellPrototype>
> >
) { ) {
(this.refs.treeview as TreeViewTag).ondragndrop = v; (this.refs.treeview as TreeViewTag).ondragndrop = v;
(this.refs.listview as ListViewTag).ondragndrop = v; (this.refs.listview as ListViewTag).ondragndrop = v;
(this.refs.gridview as GridViewTag).ondragndrop = (e) => {
const evt = {
id: this.aid,
data: {
from: e.data.from.map(x => x.data[0].domel),
to: e.data.to.data[0].domel
}
};
v(evt);
};
} }
/** /**
@ -388,12 +480,15 @@ namespace OS {
if (v.filename[0] === "." && !this.showhidden) { if (v.filename[0] === "." && !this.showhidden) {
return; return;
} }
if(!v.text)
v.text = v.filename; v.text = v.filename;
/*
if (v.text.length > 10) { if (v.text.length > 10) {
v.text = v.text.substring(0, 9) + "..."; v.text = v.text.substring(0, 9) + "...";
} }*/
v.iconclass = v.iconclass ? v.iconclass : v.type; v.iconclass = v.iconclass ? v.iconclass : v.type;
v.icon = v.icon; if(v.icon)
v.iconclass = undefined;
items.push(v); items.push(v);
}); });
(this.refs.listview as ListViewTag).data = items; (this.refs.listview as ListViewTag).data = items;
@ -412,12 +507,19 @@ namespace OS {
if (v.filename[0] === "." && !this.showhidden) { if (v.filename[0] === "." && !this.showhidden) {
return; return;
} }
v.text = v.filename;
v.iconclass = v.iconclass ? v.iconclass : v.type; v.iconclass = v.iconclass ? v.iconclass : v.type;
if(v.icon)
v.iconclass = undefined;
const row = [ const row = [
v,
{ {
text: v.mime, text: v.filename,
icon: v.icon,
iconclass: v.iconclass,
path: v.path,
data: v
},
{
text: v.mtime,
data: v, data: v,
}, },
{ {
@ -467,13 +569,15 @@ namespace OS {
if (v.filename[0] === "." && !this.showhidden) { if (v.filename[0] === "." && !this.showhidden) {
return undefined; return undefined;
} }
if(!v.text)
v.text = v.filename; v.text = v.filename;
if (v.type === "dir") { if (v.type === "dir") {
v.nodes = []; v.nodes = [];
v.open = false; v.open = false;
} }
v.iconclass = v.iconclass ? v.iconclass : v.type; v.iconclass = v.iconclass ? v.iconclass : v.type;
v.icon = v.icon; if(v.icon)
v.iconclass = undefined;
return nodes.push(v); return nodes.push(v);
}); });
return nodes; return nodes;
@ -511,7 +615,7 @@ namespace OS {
$(this.refs.listview).hide(); $(this.refs.listview).hide();
$(this.refs.gridview).hide(); $(this.refs.gridview).hide();
$(this.refs.treecontainer).hide(); $(this.refs.treecontainer).hide();
this._selectedFile = undefined; this._selectedFiles = [];
switch (this.view) { switch (this.view) {
case "icon": case "icon":
$(this.refs.listview).show(); $(this.refs.listview).show();
@ -549,7 +653,6 @@ namespace OS {
); );
} }
const evt = { id: this.aid, data: e }; const evt = { id: this.aid, data: e };
this._selectedFile = e;
this._onfileselect(evt); this._onfileselect(evt);
this.observable.trigger("fileselect", evt); this.observable.trigger("fileselect", evt);
} }
@ -609,25 +712,29 @@ namespace OS {
grid.header = this._header; grid.header = this._header;
tree.dragndrop = true; tree.dragndrop = true;
list.dragndrop = true; list.dragndrop = true;
grid.dragndrop = true;
// even handles // even handles
list.onlistselect = (e) => { list.onlistselect = (e) => {
this.fileselect(e.data.item.data as API.FileInfoType); this.fileselect(e.data.item.data as API.FileInfoType);
this._selectedFiles = e.data.items.map( x => x.data as API.FileInfoType);
}; };
grid.onrowselect = (e) => { grid.onrowselect = (e) => {
this.fileselect( this.fileselect(
$(e.data.item).children()[0] ($(e.data.item).children()[0] as GridCellPrototype)
.data as API.FileInfoType .data.data as API.FileInfoType
); );
this._selectedFiles = e.data.items.map( x => ($(x).children()[0] as GridCellPrototype).data.data as API.FileInfoType);
}; };
tree.ontreeselect = (e) => { tree.ontreeselect = (e) => {
this.fileselect(e.data.item.data as API.FileInfoType); this.fileselect(e.data.item.data as API.FileInfoType);
this._selectedFiles = [e.data.item.data as API.FileInfoType];
}; };
// dblclick // dblclick
list.onlistdbclick = (e) => { list.onlistdbclick = (e) => {
this.filedbclick(e.data.item.data as API.FileInfoType); this.filedbclick(e.data.item.data as API.FileInfoType);
}; };
grid.oncelldbclick = (e) => { grid.oncelldbclick = (e) => {
this.filedbclick(e.data.item.data as API.FileInfoType); this.filedbclick(e.data.item.data.data as API.FileInfoType);
}; };
tree.ontreedbclick = (e) => { tree.ontreedbclick = (e) => {
this.filedbclick(e.data.item.data as API.FileInfoType); this.filedbclick(e.data.item.data as API.FileInfoType);

View File

@ -19,6 +19,10 @@ interface Array<T> {
namespace OS { namespace OS {
export namespace GUI { export namespace GUI {
export namespace tag { export namespace tag {
/**
* Row item event data type
*/
export type GridRowEventData = TagEventDataType<GridRowTag>;
/** /**
* A grid Row is a simple element that * A grid Row is a simple element that
* contains a group of grid cell * contains a group of grid cell
@ -36,6 +40,15 @@ namespace OS {
*/ */
data: GenericObject<any>[]; data: GenericObject<any>[];
/**
* placeholder for the row select event callback
*
* @private
* @type {TagEventCallback<GridRowEventData>}
* @memberof ListViewItemTag
*/
private _onselect: TagEventCallback<GridRowEventData>;
/** /**
*Creates an instance of GridRowTag. *Creates an instance of GridRowTag.
* @memberof GridRowTag * @memberof GridRowTag
@ -44,6 +57,36 @@ namespace OS {
super(); super();
this.refs.yield = this; this.refs.yield = this;
this._onselect = (e) => {};
}
/**
* Set item select event handle
*
* @memberof ListViewItemTag
*/
set onrowselect(v: TagEventCallback<GridRowEventData>) {
this._onselect = v;
}
/**
* Setter: select/unselect the current item
*
* Getter: Check whether the current item is selected
*
* @memberof ListViewItemTag
*/
set selected(v: boolean) {
this.attsw(v, "selected");
$(this).removeClass();
if (!v) {
return;
}
$(this).addClass("afx-grid-row-selected");
this._onselect({ id: this.aid, data: this });
}
get selected(): boolean {
return this.hasattr("selected");
} }
/** /**
@ -303,6 +346,9 @@ namespace OS {
* @memberof SimpleGridCellTag * @memberof SimpleGridCellTag
*/ */
protected ondatachange(): void { protected ondatachange(): void {
const label = (this.refs.cell as LabelTag);
label.icon = undefined;
label.iconclass = undefined;
(this.refs.cell as LabelTag).set(this.data); (this.refs.cell as LabelTag).set(this.data);
} }
@ -420,6 +466,27 @@ namespace OS {
*/ */
private _oncelldbclick: TagEventCallback<CellEventData>; private _oncelldbclick: TagEventCallback<CellEventData>;
/**
* Event data passing between mouse event when performing
* drag and drop on the list
*
* @private
* @type {{ from: GridRowTag[]; to: GridRowTag }}
* @memberof GridViewTag
*/
private _dnd: { from: GridRowTag[]; to: GridRowTag };
/**
* placeholder of list drag and drop event handle
*
* @private
* @type {TagEventCallback<DnDEventDataType<GridRowTag>>}
* @memberof GridViewTag
*/
private _ondragndrop: TagEventCallback<
DnDEventDataType<GridRowTag>
>;
/** /**
* Creates an instance of GridViewTag. * Creates an instance of GridViewTag.
* @memberof GridViewTag * @memberof GridViewTag
@ -428,6 +495,68 @@ namespace OS {
super(); super();
} }
/**
* Set drag and drop event handle
*
* @memberof GridViewTag
*/
set ondragndrop(
v: TagEventCallback<DnDEventDataType<GridRowTag>>
) {
this._ondragndrop = v;
this.dragndrop = this.dragndrop;
}
/**
* Setter: Enable/disable drag and drop event in the list
*
* Getter: Check whether the drag and drop event is enabled
*
* @memberof GridViewTag
*/
set dragndrop(v: boolean) {
this.attsw(v, "dragndrop");
if(!v)
{
$(this.refs.container).off("mousedown", this._onmousedown);
}
else
{
$(this.refs.container).on(
"mousedown",
this._onmousedown
);
}
}
get dragndrop(): boolean {
return this.hasattr("dragndrop");
}
/**
* placeholder of drag and drop mouse down event handle
*
* @private
* @memberof GridViewTag
*/
private _onmousedown: (e: JQuery.MouseEventBase) => void;
/**
* placeholder of drag and drop mouse up event handle
*
* @private
* @memberof GridViewTag
*/
private _onmouseup: (e: JQuery.MouseEventBase) => void;
/**
* placeholder of drag and drop mouse move event handle
*
* @private
* @memberof GridViewTag
*/
private _onmousemove: (e: JQuery.MouseEventBase) => void;
/** /**
* Init the grid view before mounting. * Init the grid view before mounting.
* Reset all the placeholders to default values * Reset all the placeholders to default values
@ -444,9 +573,13 @@ namespace OS {
this._selectedRow = undefined; this._selectedRow = undefined;
this._rows = []; this._rows = [];
this.resizable = false; this.resizable = false;
this.dragndrop = false;
this._oncellselect = this._onrowselect = this._oncelldbclick = ( this._oncellselect = this._onrowselect = this._oncelldbclick = (
e: TagEventType<CellEventData> e: TagEventType<CellEventData>
): void => {}; ): void => {};
this._ondragndrop = (
e: TagEventType<DnDEventDataType<GridRowTag>>
) => {};
} }
/** /**
@ -507,7 +640,14 @@ namespace OS {
* @memberof GridViewTag * @memberof GridViewTag
*/ */
set cellitem(v: string) { set cellitem(v: string) {
const currci = this.cellitem;
$(this).attr("cellitem", v); $(this).attr("cellitem", v);
if(v != currci)
{
// force render data
$(this.refs.grid).empty();
this.rows = this.rows;
}
} }
get cellitem(): string { get cellitem(): string {
return $(this).attr("cellitem"); return $(this).attr("cellitem");
@ -539,6 +679,12 @@ namespace OS {
element.uify(this.observable); element.uify(this.observable);
element.data = item; element.data = item;
item.domel = element; item.domel = element;
element.oncellselect = (e) => {
if(element.data.sort)
{
this.sort(element.data, element.data.sort);
}
};
i++; i++;
if (this.resizable) { if (this.resizable) {
if (i != v.length) { if (i != v.length) {
@ -604,9 +750,48 @@ namespace OS {
* @memberof GridViewTag * @memberof GridViewTag
*/ */
set rows(rows: GenericObject<any>[][]) { set rows(rows: GenericObject<any>[][]) {
$(this.refs.grid).empty();
this._rows = rows; this._rows = rows;
rows.map((row) => this.push(row, false)); 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)
{
this.selectedRow.selected = false;
this._selectedRow = undefined;
this._selectedRows = [];
}
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)
{
const arr = Array.prototype.slice.call(this.refs.grid.children);
const blacklist = arr.slice(nmin, ncrows);
for(const r of blacklist)
{
this.delete(r);
}
}
// or add more rows
else if(ndrows > ncrows)
{
for(let i = nmin; i < ndrows; i++)
{
this.push(rows[i], false);
}
}
} }
get rows(): GenericObject<any>[][] { get rows(): GenericObject<any>[][] {
return this._rows; return this._rows;
@ -639,7 +824,24 @@ namespace OS {
get resizable(): boolean { get resizable(): boolean {
return this.hasattr("resizable"); 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 {
const index = this._header.indexOf(context);
const __fn = (a, b) => {
return fn.call(context,a, b, index);
}
this._rows.sort(__fn);
context.__f = ! context.__f;
this.rows = this._rows;
}
/** /**
* Delete a grid rows * Delete a grid rows
* *
@ -713,6 +915,10 @@ namespace OS {
element.oncelldbclick = (e) => this.cellselect(e, true); element.oncelldbclick = (e) => this.cellselect(e, true);
element.data = cell; element.data = cell;
} }
el.onrowselect = (e) => this.rowselect({
id: el.aid,
data: {item: el}
});
} }
/** /**
@ -751,7 +957,10 @@ namespace OS {
} else { } else {
this.observable.trigger("cellselect", e); this.observable.trigger("cellselect", e);
this._oncellselect(e); this._oncellselect(e);
return this.rowselect(e); const row = ($(
e.data.item
).parent()[0] as any) as GridRowTag;
row.selected = true;
} }
} }
@ -759,11 +968,11 @@ namespace OS {
* This function triggers the row select event, a cell select * This function triggers the row select event, a cell select
* event will also trigger this event * event will also trigger this event
* *
* @param {TagEventType<CellEventData>} e * @param {TagEventType<GridRowEventData>} e
* @returns {void} * @returns {void}
* @memberof GridViewTag * @memberof GridViewTag
*/ */
private rowselect(e: TagEventType<CellEventData>): void { private rowselect(e: TagEventType<GridRowEventData>): void {
if (!e.data.item) { if (!e.data.item) {
return; return;
} }
@ -774,39 +983,58 @@ namespace OS {
items: [], items: [],
}, },
}; };
const row = ($( const row = e.data.item as GridRowTag;
e.data.item
).parent()[0] as any) as GridRowTag;
if (this.multiselect) { if (this.multiselect) {
if (this.selectedRows.includes(row)) { if (this.selectedRows.includes(row)) {
this.selectedRows.splice( this.selectedRows.splice(
this.selectedRows.indexOf(row), this.selectedRows.indexOf(row),
1 1
); );
$(row).removeClass(); row.selected = false;
return;
} else { } else {
this.selectedRows.push(row); this.selectedRows.push(row);
$(row)
.removeClass()
.addClass("afx-grid-row-selected");
} }
evt.data.items = this.selectedRows; evt.data.items = this.selectedRows;
} else { } else {
if(this.selectedRows.length > 0)
{
for(const item of this.selectedRows)
{
if(item != row)
{
item.selected = false;
}
}
}
if (this.selectedRow === row) { if (this.selectedRow === row) {
return; return;
} }
$(this.selectedRow).removeClass(); if(this.selectedRow)
this._selectedRows = [row]; this.selectedRow.selected = false;
evt.data.item = row;
evt.data.items = [row]; evt.data.items = [row];
$(row).removeClass().addClass("afx-grid-row-selected");
this._selectedRows = [row]; this._selectedRows = [row];
} }
evt.data.item = row;
this._selectedRow = row; this._selectedRow = row;
this._onrowselect(evt); this._onrowselect(evt);
return this.observable.trigger("rowselect", evt); return this.observable.trigger("rowselect", evt);
} }
/**
* Unselect all the selected rows in the grid
*
* @returns {void}
* @memberof GridViewTag
*/
unselect(): void {
for (let v of this.selectedRows) {
v.selected = false;
}
this._selectedRows = [];
this._selectedRow = undefined;
}
/** /**
* Check whether the grid has header * Check whether the grid has header
* *
@ -904,7 +1132,6 @@ namespace OS {
*/ */
protected mount(): void { protected mount(): void {
$(this).css("overflow", "hidden"); $(this).css("overflow", "hidden");
$(this.refs.grid).css("display", "grid"); $(this.refs.grid).css("display", "grid");
$(this.refs.header).css("display", "grid"); $(this.refs.header).css("display", "grid");
this.observable.on("resize", (e) => this.calibrate()); this.observable.on("resize", (e) => this.calibrate());
@ -912,6 +1139,73 @@ namespace OS {
.css("width", "100%") .css("width", "100%")
.css("overflow-x", "hidden") .css("overflow-x", "hidden")
.css("overflow-y", "auto"); .css("overflow-y", "auto");
// drag and drop
this._dnd = {
from: undefined,
to: undefined,
};
this._onmousedown = (e) => {
if(this.multiselect || this.selectedRows == undefined || this.selectedRows.length == 0)
{
return;
}
let el: any = $(e.target).closest("afx-grid-row");
if (el.length === 0) {
return;
}
el = el[0];
if(!this.selectedRows.includes(el))
{
return;
}
this._dnd.from = this.selectedRows;
this._dnd.to = undefined;
$(window).on("mouseup", this._onmouseup);
$(window).on("mousemove", this._onmousemove);
};
this._onmouseup = (e) => {
$(window).off("mouseup", this._onmouseup);
$(window).off("mousemove", this._onmousemove);
$("#systooltip").hide();
let el: any = $(e.target).closest("afx-grid-row");
if (el.length === 0) {
return;
}
el = el[0];
if (this._dnd.from.includes(el)) {
return;
}
this._dnd.to = el;
this._ondragndrop({ id: this.aid, data: this._dnd });
this._dnd = {
from: undefined,
to: undefined,
};
};
this._onmousemove = (e) => {
if (!e) {
return;
}
if (!this._dnd.from) {
return;
}
const data = {
text: __("{0} selected elements", this._dnd.from.length).__(),
items: this._dnd.from
};
const $label = $("#systooltip");
const top = e.clientY + 5;
const left = e.clientX + 5;
$label.show();
const label = $label[0] as LabelTag;
label.set(data);
return $label
.css("top", top + "px")
.css("left", left + "px");
};
return this.calibrate(); return this.calibrate();
} }

View File

@ -33,7 +33,16 @@ namespace OS {
* @protected * @protected
* @memberof LabelTag * @memberof LabelTag
*/ */
protected mount() {} protected mount() {
$(this.refs.container)
.css("display", "flex");
$(this.refs.iclass)
.css("flex-shrink",0);
$(this.refs.i)
.css("flex-shrink",0);
$(this.refs.text)
.css("flex",1);
}
/** /**
* Refresh the text in the label * Refresh the text in the label
@ -56,6 +65,7 @@ namespace OS {
this.icon = undefined; this.icon = undefined;
this.iconclass = undefined; this.iconclass = undefined;
this.text = undefined; this.text = undefined;
this.selectable = false;
} }
/** /**
@ -121,6 +131,33 @@ namespace OS {
return this._text; return this._text;
} }
/**
* Setter: Turn on/off text selection
*
* Getter: Check whether the label is selectable
*
* @memberof LabelTag
*/
set selectable(v: boolean) {
this.attsw(v, "selectable");
if(v)
{
$(this.refs.text)
.css("user-select", "text")
.css("cursor", "text");
}
else
{
$(this.refs.text)
.css("user-select", "none")
.css("cursor", "default");
}
}
get swon(): boolean {
return this.hasattr("selectable");
}
/** /**
* Lqbel layout definition * Lqbel layout definition
* *

View File

@ -444,10 +444,10 @@ namespace OS {
* drag and drop on the list * drag and drop on the list
* *
* @private * @private
* @type {{ from: ListViewItemTag; to: ListViewItemTag }} * @type {{ from: ListViewItemTag[]; to: ListViewItemTag }}
* @memberof ListViewTag * @memberof ListViewTag
*/ */
private _dnd: { from: ListViewItemTag; to: ListViewItemTag }; private _dnd: { from: ListViewItemTag[]; to: ListViewItemTag };
/** /**
*Creates an instance of ListViewTag. *Creates an instance of ListViewTag.
@ -990,6 +990,16 @@ namespace OS {
this.selectedItems.push(e.data); this.selectedItems.push(e.data);
edata.items = this.selectedItems; edata.items = this.selectedItems;
} else { } else {
if(this.selectedItems.length > 0)
{
for(const item of this.selectedItems)
{
if(item != e.data)
{
item.selected = false;
}
}
}
if (this.selectedItem === e.data) { if (this.selectedItem === e.data) {
return; return;
} }
@ -1044,14 +1054,22 @@ namespace OS {
to: undefined, to: undefined,
}; };
this._onmousedown = (e) => { this._onmousedown = (e) => {
if(this.multiselect || this.selectedItems == undefined || this.selectedItems.length == 0)
{
return;
}
let el: any = $(e.target).closest( let el: any = $(e.target).closest(
"li[dataref='afx-list-item']" "li[dataref='afx-list-item']"
); );
if (el.length === 0) { if (el.length === 0) {
return; return;
} }
el = el.parent()[0] as ListViewItemTag; el = el.parent()[0];
this._dnd.from = el; if(!this.selectedItems.includes(el))
{
return;
}
this._dnd.from = this.selectedItems;
this._dnd.to = undefined; this._dnd.to = undefined;
$(window).on("mouseup", this._onmouseup); $(window).on("mouseup", this._onmouseup);
$(window).on("mousemove", this._onmousemove); $(window).on("mousemove", this._onmousemove);
@ -1068,7 +1086,7 @@ namespace OS {
return; return;
} }
el = el.parent()[0]; el = el.parent()[0];
if (el === this._dnd.from) { if (this._dnd.from.includes(el)) {
return; return;
} }
this._dnd.to = el; this._dnd.to = el;
@ -1086,7 +1104,18 @@ namespace OS {
if (!this._dnd.from) { if (!this._dnd.from) {
return; return;
} }
const data = this._dnd.from.data; const data = {
text: '',
items: this._dnd.from
};
if(this._dnd.from.length == 1)
{
data.text = this._dnd.from[0].data.text;
}
else
{
data.text = __("{0} selected elements", this._dnd.from.length).__();
}
const $label = $("#systooltip"); const $label = $("#systooltip");
const top = e.clientY + 5; const top = e.clientY + 5;
const left = e.clientX + 5; const left = e.clientX + 5;
@ -1097,7 +1126,7 @@ namespace OS {
.css("top", top + "px") .css("top", top + "px")
.css("left", left + "px"); .css("left", left + "px");
}; };
$(this.refs.drlabel).css("display", "inline-block");
$(this.refs.btlist).hide(); $(this.refs.btlist).hide();
this.observable.on("resize", (e) => this.calibrate()); this.observable.on("resize", (e) => this.calibrate());
return this.calibrate(); return this.calibrate();

View File

@ -386,14 +386,14 @@ namespace OS {
* @memberof SimpleMenuEntryTag * @memberof SimpleMenuEntryTag
*/ */
set icon(v: string) { set icon(v: string) {
$(this.refs.container).removeClass("fix_padding"); //$(this.refs.container).removeClass("fix_padding");
if (!v) { if (!v) {
return; return;
} }
//$(this).attr("icon", v); //$(this).attr("icon", v);
const label = this.refs.label as LabelTag; const label = this.refs.label as LabelTag;
label.icon = v; label.icon = v;
$(this.refs.container).addClass("fix_padding"); //$(this.refs.container).addClass("fix_padding");
} }
/** /**

View File

@ -536,17 +536,21 @@ namespace OS {
} }
m.show(e); m.show(e);
}; };
announcer.observable.on("app-pinned", (d) => { announcer.observable.on("app-pinned", (_) => {
this.RefreshPinnedApp(); this.RefreshPinnedApp();
}); });
announcer.observable.on("loading", (o) => { announcer.observable.on("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != 0)
{
return;
}
this._pending_task.push(o.id); this._pending_task.push(o.id);
if(!$(this.refs.panel).hasClass("loading")) if(!$(this.refs.panel).hasClass("loading"))
$(this.refs.panel).addClass("loading"); $(this.refs.panel).addClass("loading");
$(GUI.workspace).css("cursor", "wait"); $(GUI.workspace).css("cursor", "wait");
}); });
announcer.observable.on("loaded", (o) => { announcer.observable.on("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id); const i = this._pending_task.indexOf(o.id);
if (i >= 0) { if (i >= 0) {
this._pending_task.splice(i, 1); this._pending_task.splice(i, 1);

View File

@ -294,7 +294,8 @@ namespace OS {
.css("padding", 0) .css("padding", 0)
.css("margin", 0) .css("margin", 0)
.css("background-color", "transparent") .css("background-color", "transparent")
.css("width", v * 15 + "px"); .css("width", v * 15 + "px")
.css("flex-shrink", 0);
} }
/** /**
@ -375,6 +376,9 @@ namespace OS {
$(this.refs.container) $(this.refs.container)
.css("padding", 0) .css("padding", 0)
.css("margin", 0) .css("margin", 0)
.css("display","flex")
.css("flex-direction", "row")
.css("align-items", "center")
.css("white-space", "nowrap"); .css("white-space", "nowrap");
$(this.refs.itemholder).css("display", "inline-block"); $(this.refs.itemholder).css("display", "inline-block");
$(this.refs.wrapper).on("click",(e) => { $(this.refs.wrapper).on("click",(e) => {
@ -388,6 +392,7 @@ namespace OS {
$(this.refs.toggle) $(this.refs.toggle)
.css("display", "inline-block") .css("display", "inline-block")
.css("width", "15px") .css("width", "15px")
.css("flex-shrink", 0)
.addClass("afx-tree-view-item") .addClass("afx-tree-view-item")
.on("click",(e) => { .on("click",(e) => {
this.open = !this.open; this.open = !this.open;
@ -586,10 +591,10 @@ namespace OS {
* Private data object passing between dragndrop mouse event * Private data object passing between dragndrop mouse event
* *
* @private * @private
* @type {{ from: TreeViewTag; to: TreeViewTag }} * @type {{ from: TreeViewTag[]; to: TreeViewTag }}
* @memberof TreeViewTag * @memberof TreeViewTag
*/ */
private _dnd: { from: TreeViewTag; to: TreeViewTag }; private _dnd: { from: TreeViewTag[]; to: TreeViewTag };
/** /**
* Reference to parent tree of the current tree. * Reference to parent tree of the current tree.
@ -638,7 +643,7 @@ namespace OS {
* current tree. This function should return a promise on * current tree. This function should return a promise on
* a list of [[TreeViewDataType]] * a list of [[TreeViewDataType]]
* *
* @memberof TreeViewItemPrototype * @memberof TreeViewTag
*/ */
fetch: ( fetch: (
d: TreeViewItemPrototype d: TreeViewItemPrototype
@ -920,7 +925,7 @@ namespace OS {
*/ */
protected mount(): void { protected mount(): void {
this._dnd = { this._dnd = {
from: undefined, from: [],
to: undefined, to: undefined,
}; };
this._treemousedown = (e) => { this._treemousedown = (e) => {
@ -932,7 +937,7 @@ namespace OS {
if (el === this) { if (el === this) {
return; return;
} }
this._dnd.from = el; this._dnd.from = [el];
this._dnd.to = undefined; this._dnd.to = undefined;
$(window).on("mouseup", this._treemouseup); $(window).on("mouseup", this._treemouseup);
return $(window).on("mousemove", this._treemousemove); return $(window).on("mousemove", this._treemousemove);
@ -951,8 +956,8 @@ namespace OS {
el = el.parent; el = el.parent;
} }
if ( if (
el === this._dnd.from || el === this._dnd.from[0] ||
el === this._dnd.from.parent el === this._dnd.from[0].parent
) { ) {
return; return;
} }
@ -962,7 +967,7 @@ namespace OS {
data: this._dnd, data: this._dnd,
}); });
this._dnd = { this._dnd = {
from: undefined, from: [],
to: undefined, to: undefined,
}; };
}; };
@ -974,7 +979,7 @@ namespace OS {
if (!this._dnd.from) { if (!this._dnd.from) {
return; return;
} }
const data = this._dnd.from.data; const data = this._dnd.from[0].data;
const $label = $("#systooltip"); const $label = $("#systooltip");
const top = e.clientY + 5; const top = e.clientY + 5;
const left = e.clientX + 5; const left = e.clientX + 5;

View File

@ -80,6 +80,23 @@ namespace OS {
super(); super();
} }
/**
* blur overlay: If active the window overlay will be shown
* on inactive (blur event)
*
* Setter: Enable the switch
*
* Getter: Check whether the switch is enabled
*
* @memberof WindowTag
*/
set blur_overlay(v: boolean) {
this.attsw(v, "blur-overlay");
}
get blur_overlay(): boolean {
return this.hasattr("blur-overlay");
}
/** /**
* Init window tag * Init window tag
* - `shown`: false * - `shown`: false
@ -108,7 +125,7 @@ namespace OS {
* @protected * @protected
* @memberof WindowTag * @memberof WindowTag
*/ */
protected calibrate(): void {} protected calibrate(): void { }
/** /**
* Do nothing * Do nothing
@ -117,7 +134,7 @@ namespace OS {
* @param {*} [d] * @param {*} [d]
* @memberof WindowTag * @memberof WindowTag
*/ */
protected reload(d?: any): void {} protected reload(d?: any): void { }
/** /**
* Setter: Set the window width * Setter: Set the window width
@ -245,18 +262,18 @@ namespace OS {
* @memberof WindowTag * @memberof WindowTag
*/ */
protected mount(): void { protected mount(): void {
this.contextmenuHandle = function (e) {}; this.contextmenuHandle = function (e) { };
$(this.refs["minbt"]).on("click",(e) => { $(this.refs["minbt"]).on("click", (e) => {
return this.observable.trigger("hide", { return this.observable.trigger("hide", {
id: this.aid, id: this.aid,
}); });
}); });
$(this.refs["maxbt"]).on("click",(e) => { $(this.refs["maxbt"]).on("click", (e) => {
return this.toggle_window(); return this.toggle_window();
}); });
$(this.refs["closebt"]).on("click",(e) => { $(this.refs["closebt"]).on("click", (e) => {
return this.observable.trigger("exit", { return this.observable.trigger("exit", {
id: this.aid, id: this.aid,
}); });
@ -267,7 +284,7 @@ namespace OS {
.css("position", "absolute") .css("position", "absolute")
.css("left", `${left}px`) .css("left", `${left}px`)
.css("top", `${top}px`) .css("top", `${top}px`)
.css("z-index", Ant.OS.GUI.zindex++); .css("z-index", 10);
$(this).on("mousedown", (e) => { $(this).on("mousedown", (e) => {
if (this._shown) { if (this._shown) {
return; return;
@ -276,25 +293,27 @@ namespace OS {
id: this.aid, id: this.aid,
}); });
}); });
//$(this.refs.win_overlay).css("background-color", "red");
$(this.refs["dragger"]).on("dblclick",(e) => { $(this.refs["dragger"]).on("dblclick", (e) => {
return this.toggle_window(); return this.toggle_window();
}); });
this.observable.on("resize", (e) => this.resize()); this.observable.on("resize", (e) => this.resize());
this.observable.on("focus", () => { this.observable.on("focus", () => {
Ant.OS.GUI.zindex++;
$(this) $(this)
.show() .show()
.css("z-index", Ant.OS.GUI.zindex)
.removeClass("unactive"); .removeClass("unactive");
this._shown = true; this._shown = true;
$(this.refs.win_overlay).hide();
$(this).trigger("focus");
}); });
this.observable.on("blur", () => { this.observable.on("blur", () => {
this._shown = false; this._shown = false;
return $(this).addClass("unactive"); $(this).addClass("unactive");
if(this.blur_overlay)
$(this.refs.win_overlay).show();
}); });
this.observable.on("hide", () => { this.observable.on("hide", () => {
$(this).hide(); $(this).hide();
@ -312,12 +331,22 @@ namespace OS {
}); });
} }
}); });
this.observable.on("loaded", ()=>{
$(this.refs.panel).removeClass("loading");
$(this).css("cursor", "auto");
});
this.observable.on("loading", ()=>{
if(!$(this.refs.panel).hasClass("loading"))
$(this.refs.panel).addClass("loading");
$(this).css("cursor", "wait");
});
this.enable_dragging(); this.enable_dragging();
this.enable_resize(); this.enable_resize();
this.setsize({ this.setsize({
w: this.width, w: this.width,
h: this.height, h: this.height,
}); });
$(this).attr("tabindex", 0).css("outline", "none");
return this.observable.trigger("rendered", { return this.observable.trigger("rendered", {
id: this.aid, id: this.aid,
}); });
@ -408,12 +437,10 @@ namespace OS {
let w = $(this).width(); let w = $(this).width();
let h = $(this).height(); let h = $(this).height();
$(this.refs.win_overlay).show(); $(this.refs.win_overlay).show();
if(target != this.refs.grip_bottom) if (target != this.refs.grip_bottom) {
{
w += e.clientX - offset.left; w += e.clientX - offset.left;
} }
if(target != this.refs.grip_right) if (target != this.refs.grip_right) {
{
h += e.clientY - offset.top; h += e.clientY - offset.top;
} }
w = w < 100 ? 100 : w; w = w < 100 ? 100 : w;
@ -506,6 +533,7 @@ namespace OS {
children: [ children: [
{ {
el: "ul", el: "ul",
ref: 'panel',
class: "afx-window-top", class: "afx-window-top",
children: [ children: [
{ {

View File

@ -233,7 +233,7 @@ namespace OS {
* @type {T} * @type {T}
* @memberof DnDEventDataType * @memberof DnDEventDataType
*/ */
from: T; from: T[];
/** /**
* Reference to the target DOM element * Reference to the target DOM element
@ -247,11 +247,6 @@ namespace OS {
* Tag event callback type * Tag event callback type
*/ */
export type TagEventCallback<T> = (e: TagEventType<T>) => void; export type TagEventCallback<T> = (e: TagEventType<T>) => void;
/**
* Top most element z index value, start by 10
*/
export var zindex: number = 10;
/** /**
* Base abstract class for tag implementation, any AFX tag should be * Base abstract class for tag implementation, any AFX tag should be
* subclass of this class * subclass of this class

View File

@ -165,6 +165,8 @@ namespace OS {
*/ */
export namespace VFS { export namespace VFS {
declare var JSZip: any; declare var JSZip: any;
/** path for custom VFS protocol handles */
const VFSX_PATH = "pkg://vfsx/vfsx.js";
String.prototype.asFileHandle = function (): BaseFileHandle { String.prototype.asFileHandle = function (): BaseFileHandle {
const list = this.split("://"); const list = this.split("://");
@ -198,7 +200,27 @@ namespace OS {
): void { ): void {
handles[protos] = cls; handles[protos] = cls;
} }
/**
* Load custom VFS handles if the package vfsx available
*
* @export
* @param {boolean} [force] force load the file
* @return {*} {Promise<any>}
*/
export function loadVFSX(force?: boolean): Promise<any>
{
return new Promise(async (resolve, reject) => {
try {
await API.requires(VFSX_PATH, force);
resolve(true);
}
catch(e)
{
console.warn("No custom VFS protocol loaded");
resolve(true);
}
})
}
/** /**
* Looking for a attached file handle class of a string protocol * Looking for a attached file handle class of a string protocol
* *
@ -251,10 +273,11 @@ namespace OS {
/** /**
* Once read, file content will be cached in this placeholder * Once read, file content will be cached in this placeholder
* *
* @private
* @type {*} * @type {*}
* @memberof BaseFileHandle * @memberof BaseFileHandle
*/ */
cache: any; private _cache: any;
/** /**
* Flag indicated whether the file meta-data is loaded * Flag indicated whether the file meta-data is loaded
@ -333,6 +356,8 @@ namespace OS {
this.setPath(path); this.setPath(path);
} }
/** /**
* Set a file path to the current file handle * Set a file path to the current file handle
* *
@ -355,7 +380,7 @@ namespace OS {
if (re === "") { if (re === "") {
return; return;
} }
this.genealogy = re.split("/").filter(s=> s!=""); this.genealogy = re.split("/").filter(s => s != "");
this.path = `${this.protocol}://${this.genealogy.join("/")}`; this.path = `${this.protocol}://${this.genealogy.join("/")}`;
if (!this.isRoot()) { if (!this.isRoot()) {
this.basename = this.genealogy[ this.basename = this.genealogy[
@ -386,6 +411,22 @@ namespace OS {
set filename(v: string) { set filename(v: string) {
this.basename = v; this.basename = v;
} }
/**
* Getter: Get the file cache
* Setter: set the file cache
*
* @returns {any}
* @memberof BaseFileHandle
*/
get cache(): any {
return this._cache;
}
set cache(v: any) {
this._cache = v;
this._cache_changed();
}
/** /**
* Set data to the file cache * Set data to the file cache
* *
@ -548,12 +589,8 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d = await this._rd(t); const d = await this._rd(t);
return resolve(d); resolve(d);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -577,10 +614,7 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r: RequestResult = await this._wr(t); const r: RequestResult = await this._wr(t);
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "write",this);
m: "write",
file: this,
});
return resolve(r); return resolve(r);
} catch (e) { } catch (e) {
return reject(__e(e)); return reject(__e(e));
@ -601,16 +635,9 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d_1 = await this._mk(d); const d_1 = await this._mk(d);
announcer.ostrigger("VFS", { announcer.ostrigger("VFS","mk",this);
m: "mk",
file: this,
});
return resolve(d_1); return resolve(d_1);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -629,16 +656,9 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d = await this._rm(); const d = await this._rm();
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "remove",this);
m: "remove",
file: this,
});
return resolve(d); return resolve(d);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -659,16 +679,9 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d = await this._up(); const d = await this._up();
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "upload", this);
m: "upload",
file: this,
});
return resolve(d); return resolve(d);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -689,16 +702,9 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d = await this._pub(); const d = await this._pub();
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "publish",this);
m: "publish",
file: this,
});
return resolve(d); return resolve(d);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -719,16 +725,9 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d = await this._down(); const d = await this._down();
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "download",this);
m: "download",
file: this,
});
return resolve(d); return resolve(d);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -748,16 +747,10 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const data = await this._mv(d); const data = await this._mv(d);
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "move",d.asFileHandle());
m: "move",
file: d.asFileHandle(),
});
return resolve(data); return resolve(data);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -778,16 +771,9 @@ namespace OS {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
const r = await this.onready(); const r = await this.onready();
try {
const d = await this._exec(); const d = await this._exec();
announcer.ostrigger("VFS", { announcer.ostrigger("VFS", "execute", this);
m: "execute",
file: this,
});
return resolve(d); return resolve(d);
} catch (e) {
return reject(__e(e));
}
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
} }
@ -826,6 +812,17 @@ namespace OS {
}); });
} }
/**
* trigger cache changed event
*
* This function triggered when the file cached changed
*
* @protected
* @memberof BaseFileHandle
*/
protected _cache_changed():void {
}
/** /**
* Low level protocol-specific read operation * Low level protocol-specific read operation
* *
@ -1066,8 +1063,8 @@ namespace OS {
protected _wr(t: string): Promise<RequestResult> { protected _wr(t: string): Promise<RequestResult> {
// t is base64 or undefined // t is base64 or undefined
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
if (t === "base64") {
try { try {
if (t === "base64") {
const d = await API.handle.write( const d = await API.handle.write(
this.path, this.path,
this.cache this.cache
@ -1080,11 +1077,7 @@ namespace OS {
); );
} }
return resolve(d); return resolve(d);
} catch (e) {
return reject(__e(e));
}
} else { } else {
try {
const r = await this.b64(t); const r = await this.b64(t);
const result = await API.handle.write( const result = await API.handle.write(
this.path, this.path,
@ -1102,9 +1095,9 @@ namespace OS {
); );
} }
return resolve(result); return resolve(result);
} catch (e_2) {
return reject(__e(e_2));
} }
} catch (e) {
return reject(__e(e));
} }
}); });
} }
@ -1369,11 +1362,17 @@ namespace OS {
constructor(path: string) { constructor(path: string) {
super(path); super(path);
if (this.basename) { if (this.basename) {
let v: any = OS.setting.system.packages[this.basename]; let v = OS.setting.system.packages[this.basename];
v.type = "app"; this.info = {} as FileInfoType;
v.mime = "antos/app"; for(const p in v)
v.size = 0; {
this.info = v as FileInfoType; this.info[p] = v[p];
}
this.info.type = "app";
this.info.mime = "antos/app";
this.info.size = 0;
this.info.text = v.name;
} }
this.ready = true; this.ready = true;
} }
@ -1436,6 +1435,8 @@ namespace OS {
register("^app$", ApplicationHandle); register("^app$", ApplicationHandle);
var MEM_PARTITION: BufferFileHandle = undefined;
/** /**
* A buffer file handle represents a virtual file that is stored * A buffer file handle represents a virtual file that is stored
* on the system memory. Its protocol pattern is defined as: * on the system memory. Its protocol pattern is defined as:
@ -1457,18 +1458,96 @@ namespace OS {
*/ */
constructor(path: string, mime: string, data: any) { constructor(path: string, mime: string, data: any) {
super(path); super(path);
if (data) {
this.cache = data;
}
this.info = { this.info = {
mime: mime, mime: mime?mime:'application/octet-stream',
path: path, path: path,
size: data ? data.length : 0, size: data ? data.length : 0,
name: this.basename, name: this.basename,
type: "file", filename:this.basename,
ctime: (new Date()).toGMTString(),
mtime: (new Date()).toGMTString(),
type: 'file',
}; };
if (data) {
this.cache = data;
}
return this.init_file_tree();
}
/**
* init the mem file tree if necessary
*/
private init_file_tree(): BufferFileHandle
{
if(this.isRoot())
{
if(!MEM_PARTITION)
{
this.cache = {};
MEM_PARTITION = this;
}
return MEM_PARTITION;
}
let curr_level = "mem://".asFileHandle();
for(let i = 0;i<this.genealogy.length - 1;i++)
{
const segment = this.genealogy[i];
let handle = undefined;
if(segment == "..")
{
curr_level = curr_level.parent();
continue;
} }
handle = curr_level.cache[segment];
if(!handle)
{
handle = new BufferFileHandle(`${curr_level.path}/${segment}`, 'dir', {});
curr_level.cache[segment] = handle;
}
curr_level = handle;
if(!curr_level.cache)
{
curr_level.cache = {};
}
}
if(this.basename == "..")
{
return curr_level.parent() as BufferFileHandle;
}
if(!curr_level.cache[this.basename])
curr_level.cache[this.basename] = this;
return curr_level.cache[this.basename];
}
/**
* cache changed handle
*/
protected _cache_changed(): void
{
if(!this.cache)
{
return;
}
this.info.mime = (new Date()).toGMTString();
if(typeof this.cache === "string" || this.cache instanceof Blob || this.cache instanceof Uint8Array)
{
this.info.type = "file";
if(typeof this.cache === "string")
{
this.info.mime = "text/plain";
}
else
{
this.info.mime = "application/octet-stream";
}
}
else
{
this.info.type = "dir";
this.info.mime = "dir";
}
this.info.size = this.cache.length;
}
/** /**
* Read the file meta-data * Read the file meta-data
* *
@ -1477,11 +1556,39 @@ namespace OS {
*/ */
meta(): Promise<RequestResult> { meta(): Promise<RequestResult> {
return new Promise((resolve, reject) => return new Promise((resolve, reject) =>
{
const data = {};
for(const k in this.info)
{
data[k] = this.info[k];
}
resolve({ resolve({
result: this.info, result: data,
error: false, error: false,
}) })
); });
}
/**
* Load the file meta-data before performing
* any task
*
* @returns {Promise<FileInfoType>} a promise on file meta-data
* @memberof BufferFileHandle
*/
onready(): Promise<FileInfoType> {
// read meta data
return new Promise(async (resolve, reject) => {
try
{
const d = await this.meta();
resolve(d.result as FileInfoType);
}
catch(e)
{
reject(__e(e));
}
});
} }
/** /**
@ -1494,6 +1601,13 @@ namespace OS {
*/ */
protected _rd(t: string): Promise<any> { protected _rd(t: string): Promise<any> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// read dir
if (this.info.type === "dir") {
return resolve({
result: (Object.values(this.cache) as BufferFileHandle[]).map(o=>o.info),
error: false,
});
}
return resolve(this.cache); return resolve(this.cache);
}); });
} }
@ -1503,12 +1617,10 @@ namespace OS {
* *
* @protected * @protected
* @param {string} t data type, see [[write]] * @param {string} t data type, see [[write]]
* @param {*} d data
* @returns {Promise<RequestResult>} * @returns {Promise<RequestResult>}
* @memberof BufferFileHandle * @memberof BufferFileHandle
*/ */
protected _wr(t: string, d: any): Promise<RequestResult> { protected _wr(t: string): Promise<RequestResult> {
this.cache = d;
return new Promise((resolve, reject) => return new Promise((resolve, reject) =>
resolve({ resolve({
result: true, result: true,
@ -1517,6 +1629,106 @@ namespace OS {
); );
} }
/**
* Create sub directory
*
* Only work on directory file handle
*
* @protected
* @param {string} d sub directory name
* @returns {Promise<RequestResult>}
* @memberof BufferFileHandle
*/
protected _mk(d: string): Promise<RequestResult> {
return new Promise((resolve, reject) => {
if (this.info.type === "file") {
return reject(
API.throwe(
__("{0} is not a directory", this.path)
)
);
}
const handle = `${this.path}/${d}`.asFileHandle();
if(handle.info.type === "file")
{
handle.cache = {};
}
resolve({result: true, error: false});
});
}
/**
* Delete file/folder
*
* @protected
* @returns {Promise<RequestResult>}
* @memberof BufferFileHandle
*/
protected _rm(): Promise<RequestResult> {
return new Promise(async (resolve, reject) => {
try {
const parent = this.parent();
parent.cache[this.basename] = undefined;
delete parent.cache[this.basename];
return resolve({result: true, error: false});
} catch (e) {
return reject(__e(e));
}
});
}
setPath(p: string): void
{
super.setPath(p);
if(this.info)
this.info.path = this.path;
}
private updatePath(path: string)
{
this.setPath(path);
if(this.info.type == "file")
{
return;
}
for(const k in this.cache)
{
const child = this.cache[k];
child.updatePath(`${this.path}/${child.basename}`);
}
}
/**
* Move file/folder
*
* @protected
* @param {string} d
* @returns {Promise<RequestResult>}
* @memberof BufferFileHandle
*/
protected _mv(d: string): Promise<RequestResult> {
return new Promise(async (resolve, reject) => {
try {
if(d.includes(this.path))
{
return reject(API.throwe(__("Unable to move file/folder from {0} to {1}", this.path, d)));
}
const parent = this.parent()
parent.cache[this.basename] = undefined;
delete parent.cache[this.basename];
const dest = d.asFileHandle();
this.updatePath(dest.path);
dest.parent().cache[dest.basename] = this;
return resolve({result: true, error: false});
} catch (e) {
console.log(this);
return reject(__e(e));
}
});
}
/** /**
* Download the buffer file * Download the buffer file
* *
@ -1526,6 +1738,10 @@ namespace OS {
*/ */
protected _down(): Promise<void> { protected _down(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if(this.info.type == "dir")
{
return reject(API.throwe(__("{0} is a directory", this.path)));
}
const blob = new Blob([this.cache], { const blob = new Blob([this.cache], {
type: "octet/stream", type: "octet/stream",
}); });
@ -1855,11 +2071,11 @@ namespace OS {
promises.push(new Promise(async (resolve, reject) => { promises.push(new Promise(async (resolve, reject) => {
try { try {
const file = path.asFileHandle(); const file = path.asFileHandle();
const tof = `${to}/${file.basename}`.asFileHandle();
const meta = await file.onready(); const meta = await file.onready();
if (meta.type === "dir") { if (meta.type === "dir") {
const desdir = to.asFileHandle(); const desdir = to.asFileHandle();
await desdir.mk(file.basename); await desdir.mk(file.basename);
console.log(desdir, to);
const ret = await file.read(); const ret = await file.read();
const files = ret.result.map((v: API.FileInfoType) => v.path); const files = ret.result.map((v: API.FileInfoType) => v.path);
if (files.length > 0) { if (files.length > 0) {
@ -1871,6 +2087,7 @@ namespace OS {
} }
} }
else { else {
const tof = `${to}/${file.basename}`.asFileHandle();
const content = await file.read("binary"); const content = await file.read("binary");
await tof await tof
.setCache( .setCache(
@ -1947,12 +2164,10 @@ namespace OS {
const zip = new JSZip(); const zip = new JSZip();
const fhd = src.asFileHandle(); const fhd = src.asFileHandle();
const meta = await fhd.onready(); const meta = await fhd.onready();
if(meta.type === "file") if (meta.type === "file") {
{
await aradd([src], zip, "/"); await aradd([src], zip, "/");
} }
else else {
{
const ret = await fhd.read(); const ret = await fhd.read();
await aradd( await aradd(
ret.result.map((v: API.FileInfoType) => v.path), zip, "/"); ret.result.map((v: API.FileInfoType) => v.path), zip, "/");

View File

@ -1,401 +0,0 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS202: Simplify dynamic range loops
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// 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/.
// GoogleDrive File Handle
let G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
class GoogleDriveHandle extends this.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
const me = this;
this.setting = Ant.OS.setting.VFS.gdrive;
if (!this.setting) { return Ant.OS.announcer.oserror(__("Unknown API setting for {0}", "GAPI"), (Ant.OS.API.throwe("OS.VFS")), null); }
if (this.isRoot()) { this.gid = 'root'; }
this.cache = "";
}
oninit(f) {
const me = this;
if (!this.setting) { return; }
const fn = function(r) {
if (r) { return f(); }
// perform the login
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
return gapi.auth2.getAuthInstance().signIn();
};
if (Ant.OS.API.libready(this.setting.apilink)) {
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
} else {
return Ant.OS.API.require(this.setting.apilink, function() {
// avoid popup block
const q = Ant.OS.announcer.getMID();
return gapi.load("client:auth2", function() {
Ant.OS.API.loading(q, "GAPI");
return gapi.client.init({
apiKey: me.setting.API_KEY,
clientId: me.setting.CLIENT_ID,
discoveryDocs: me.setting.DISCOVERY_DOCS,
scope: me.setting.SCOPES
})
.then(function() {
Ant.OS.API.loaded(q, "OK");
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
return _GUI.openDialog("YesNoDialog", function(d) {
if (!d) { return Ant.OS.announcer.osinfo(__("User abort the authentication")); }
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
}
, __("Authentication")
, { text: __("Would you like to login to {0}?", "Google Drive") });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot init {0}: {1}", "GAPI",err.error), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
});
}
}
meta(f) {
const me = this;
return this.oninit(function() {
const q = Ant.OS.announcer.getMID();
if (G_CACHE[me.path]) { me.gid = G_CACHE[me.path].id; }
if (me.gid) {
//console.log "Gid exists ", me.gid
Ant.OS.API.loading(q, "GAPI");
return gapi.client.drive.files.get({
fileId: me.gid,
fields: me.fields()
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.result) { return; }
r.result.mime = r.result.mimeType;
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
});
} else {
//console.log "Find file in ", me.parent()
const fp = me.parent().asFileHandle();
return fp.meta(function(d) {
const file = d.result;
const q1 = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q1, "GAPI");
G_CACHE[fp.path] = { id: file.id, mime: file.mimeType };
return gapi.client.drive.files.list({
q: `name = '${me.basename}' and '${file.id}' in parents and trashed = false`,
fields: `files(${me.fields()})`
})
.then(function(r) {
//console.log r
Ant.OS.API.loaded(q1, "OK");
if (!r.result.files || !(r.result.files.length > 0)) { return; }
G_CACHE[me.path] = { id: r.result.files[0].id, mime: r.result.files[0].mimeType };
r.result.files[0].mime = r.result.files[0].mimeType;
return f({ result: r.result.files[0] });})
.catch(function(err) {
Ant.OS.API.loaded(q1, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
}
});
}
fields() {
return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size";
}
isFolder() {
return this.info.mimeType === "application/vnd.google-apps.folder";
}
save(id, m, f) {
const me = this;
const user = gapi.auth2.getAuthInstance().currentUser.get();
const oauthToken = user.getAuthResponse().access_token;
const q = Ant.OS.announcer.getMID();
const xhr = new XMLHttpRequest();
const url = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=media';
xhr.open('PATCH', url);
xhr.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
xhr.setRequestHeader('Content-Type', m);
xhr.setRequestHeader('Content-Encoding', 'base64');
xhr.setRequestHeader('Content-Transfer-Encoding', 'base64');
Ant.OS.API.loading(q, "GAPI");
const error = function(e, s) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot save : {0}", me.path), e, s);
};
xhr.onreadystatechange = function() {
if ( xhr.readyState === 4 ) {
if ( xhr.status === 200 ) {
Ant.OS.API.loaded(q, "OK");
return f({ result: JSON.parse(xhr.responseText) });
} else {
return error(xhr, xhr.status);
}
}
};
xhr.onerror = () => error(xhr, xhr.status);
if (m === "base64") { return xhr.send(me.cache.replace(/^data:[^;]+;base64,/g, "")); }
return this.sendB64(m, data => xhr.send(data.replace(/^data:[^;]+;base64,/g, "")));
}
getlink() {
if (this.ready) { return this.info.webContentLink; }
return undefined;
}
action(n, p, f) {
const me = this;
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, "GAPI");
switch (n) {
case "read":
if (!this.info.id) { return; }
if (this.isFolder()) {
return gapi.client.drive.files.list({
q: `'${me.info.id}' in parents and trashed = false`,
fields: `files(${me.fields()})`
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.result.files) { return; }
for (let file of Array.from(r.result.files)) {
file.path = me.child(file.name);
file.mime = file.mimeType;
file.filename = file.name;
file.type = "file";
file.gid = file.id;
if (file.mimeType === "application/vnd.google-apps.folder") {
file.mime = "dir";
file.type = "dir";
file.size = 0;
}
G_CACHE[file.path] = { id: file.gid, mime: file.mime };
}
return f({ result: r.result.files });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
} else {
return gapi.client.drive.files.get({
fileId: me.info.id,
alt: 'media'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (p !== "binary") { return f(r.body); }
return f(r.body.asUint8Array());}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
}
case "mk":
if (!this.isFolder()) { return f({ error: __("{0} is not a directory", this.path) }); }
var meta = {
name: p,
parents: [this.info.id],
mimeType: 'application/vnd.google-apps.folder'
};
gapi.client.drive.files.create({
resource: meta,
fields: 'id'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
//console.log r
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.child(p)] = { id: r.result.id, mime: "dir" };
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), err);
});
return;
case "write":
var gid = undefined;
if (G_CACHE[me.path]) { gid = G_CACHE[me.path].id; }
if (gid) {
Ant.OS.API.loaded(q, "OK");
return this.save(gid, p, f);
} else {
const dir = this.parent().asFileHandle();
return dir.onready(function() {
meta = {
name: me.basename,
mimeType: p,
parents: [dir.info.id]
};
return gapi.client.drive.files.create({
resource: meta,
fields: 'id'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.path] = { id: r.result.id, mime: p };
return me.save(r.result.id, p, f);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
}
case "upload":
if (!this.isFolder()) { return; }
//insert a temporal file selector
var o = ($('<input>')).attr('type', 'file').css("display", "none");
Ant.OS.API.loaded(q, "OK");
o.change(function() {
//Ant.OS.API.loading q, p
const fo = o[0].files[0];
const file = (me.child(fo.name)).asFileHandle();
file.cache = fo;
file.write(fo.type, f);
return o.remove();
});
//Ant.OS.API.loaded q, p, "OK"
//Ant.OS.API.loaded q, p, "FAIL"
return o.click();
case "remove":
if (!this.info.id) { return; }
return gapi.client.drive.files.delete({
fileId: me.info.id
})
.then(function(r) {
//console.log r
Ant.OS.API.loaded(q, "OK");
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.path] = null;
return f({ result: true });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
case "publish":
Ant.OS.API.loaded(q, "OK");
return;
case "download":
return gapi.client.drive.files.get({
fileId: me.info.id,
alt: 'media'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.body) { return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
let bytes = [];
for (let i = 0, end = r.body.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
bytes.push(r.body.charCodeAt(i));
}
bytes = new Uint8Array(bytes);
const blob = new Blob([bytes], { type: "octet/stream" });
return Ant.OS.API.saveblob(me.basename, blob);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
case "move":
var dest = p.asFileHandle().parent().asFileHandle();
return dest.onready(function() {
const previousParents = me.info.parents.join(',');
return gapi.client.drive.files.update({
fileId: me.info.id,
addParents: dest.info.id,
removeParents: previousParents,
fields: "id"
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
});
}
, err => Ant.OS.API.loaded(q, "FAIL"));
default:
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.osfail(__("VFS unknown action: {0}", n), (Ant.OS.API.throwe("OS.VFS")), n);
}
}
}
self.OS.API.VFS.register("^gdv$", GoogleDriveHandle);
// search the cache for file
self.OS.API.onsearch("Google Drive", function(t) {
const arr = [];
const term = new RegExp(t, "i");
for (let k in G_CACHE) {
const v = G_CACHE[k];
if ((k.match(term)) || (v && v.mime.match(term))) {
const file = k.asFileHandle();
file.text = file.basename;
file.mime = v.mime;
file.iconclass = "fa fa-file";
if (file.mime === "dir") { file.iconclass = "fa fa-folder"; }
file.complex = true;
file.detail = [{ text: file.path }];
arr.push(file);
}
}
return arr;
});
self.OS.onexit("cleanUpGoogleDrive", function() {
G_CACHE = { "gdv://": { id: "root", mime: 'dir' } };
if (!Ant.OS.setting.VFS.gdrive || !Ant.OS.API.libready(Ant.OS.setting.VFS.gdrive.apilink)) { return; }
const auth2 = gapi.auth2.getAuthInstance();
if (!auth2) { return; }
if (auth2.isSignedIn.get()) {
let el;
return el = $('<iframe/>', {
src: 'https://www.google.com/accounts/Logout',
frameborder: 0,
onload() {
//console.log("disconnect")
return auth2.disconnect();
}
//$(this).remove()
});
}
});
//($ "body").append(el)

View File

@ -4,7 +4,7 @@ libfiles =
cssfiles = main.css cssfiles = main.css
copyfiles = scheme.html package.json copyfiles = scheme.html package.json README.md
PKG_NAME=Files PKG_NAME=Files

View File

@ -0,0 +1,9 @@
# Files
Base AntOS file explorer application.
This application is included in the AntOS delivery as system application and
cannot be removed/uinstalled by regular user
## Change logs
- v0.1.7-b: fix - file grid view double click event hanling on diffent cells of a row

View File

@ -21,7 +21,7 @@ namespace OS {
interface FilesClipboardType { interface FilesClipboardType {
cut: boolean; cut: boolean;
file: API.VFS.BaseFileHandle; files: API.VFS.BaseFileHandle[];
} }
interface FilesViewType { interface FilesViewType {
icon: boolean; icon: boolean;
@ -71,10 +71,13 @@ namespace OS {
this.view.contextmenuHandle = (e, m) => { this.view.contextmenuHandle = (e, m) => {
const file = this.view.selectedFile; const file = this.view.selectedFile;
if (!file) {
return;
}
const apps = []; const apps = [];
let ctx_menu = [
this.mnFile(),
];
if(file)
{
ctx_menu.push(this.mnEdit());
if (file.type === "dir") { if (file.type === "dir") {
file.mime = "dir"; file.mime = "dir";
} }
@ -87,8 +90,7 @@ namespace OS {
iconclass: v.iconclass, iconclass: v.iconclass,
}); });
} }
let ctx_menu = [ ctx_menu.unshift( {
{
text: "__(Open with)", text: "__(Open with)",
nodes: apps, nodes: apps,
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => { onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
@ -98,10 +100,7 @@ namespace OS {
const it = e.data.item.data; const it = e.data.item.data;
return this._gui.launch(it.app, [file]); return this._gui.launch(it.app, [file]);
}, },
}, });
this.mnFile(),
this.mnEdit(),
];
if(file.mime === "application/zip") if(file.mime === "application/zip")
{ {
ctx_menu = ctx_menu.concat([ ctx_menu = ctx_menu.concat([
@ -170,6 +169,8 @@ namespace OS {
} }
); );
} }
}
m.items = ctx_menu; m.items = ctx_menu;
m.show(e); m.show(e);
}; };
@ -212,12 +213,6 @@ namespace OS {
if (d.error) { if (d.error) {
return reject(d.error); return reject(d.error);
} }
if (!dir.isRoot()) {
const p = dir.parent();
p.filename = "[..]";
p.type = "dir";
d.result.unshift(p);
}
this.currdir = dir; this.currdir = dir;
$(this.navinput).val(dir.path); $(this.navinput).val(dir.path);
(this.scheme as GUI.tag.WindowTag).apptitle = dir.path; (this.scheme as GUI.tag.WindowTag).apptitle = dir.path;
@ -228,43 +223,54 @@ namespace OS {
}; };
this.vfs_event_flag = true; this.vfs_event_flag = true;
this.view.ondragndrop = (e) => { this.view.ondragndrop = async (e) => {
if (!e) { if (!e) {
return; return;
} }
const src = e.data.from.data; const src = e.data.from;
const des = e.data.to.data; const des = e.data.to.data;
if (des.type === "file") { if (des.type === "file") {
return; return;
} }
const file = src.path.asFileHandle(); // ask to confirm
const r = await this.ask({
title: __("Move files"),
text: __("Move selected file to {0}?", des.text)
});
if(!r)
{
return;
}
// disable the vfs event on // disable the vfs event on
// we update it manually // we update it manually
this.vfs_event_flag = false; this.vfs_event_flag = false;
return file const promises = [];
.move(`${des.path}/${file.basename}`) for(const item of src)
.then(() => { {
if (this.view.view === "icon") { let file = item.data.path.asFileHandle();
this.view.path = this.view.path; promises.push(
} else { file.move(`${des.path}/${file.basename}`));
this.view.update(file.parent().path);
this.view.update(des.path);
} }
//reenable the vfs event try{
return (this.vfs_event_flag = true); await Promise.all(promises);
}) if (this.view.view === "tree") {
.catch((e: Error) => { this.view.update(src[0].data.path.asFileHandle().parent().path);
// reenable the vfs event this.view.update(des.path);
this.vfs_event_flag = true; } else {
return this.error( this.view.path = this.view.path;
}
}
catch(error)
{
this.error(
__( __(
"Unable to move: {0} -> {1}", "Unable to move files to: {0}",
src.path,
des.path des.path
), ),
e error
); );
}); }
this.vfs_event_flag = true;
}; };
// application setting // application setting
@ -296,16 +302,16 @@ namespace OS {
if (this.setting.view) { if (this.setting.view) {
this.view.view = this.setting.view; this.view.view = this.setting.view;
} }
this.subscribe("VFS", (d) => { this.subscribe("VFS", (d: API.AnnouncementDataType<API.VFS.BaseFileHandle>) => {
if (!this.vfs_event_flag) { if (!this.vfs_event_flag) {
return; return;
} }
if (["read", "publish", "download"].includes(d.data.m)) { if (["read", "publish", "download"].includes(d.message as string)) {
return; return;
} }
if ( if (
d.data.file.hash() === this.currdir.hash() || d.u_data.hash() === this.currdir.hash() ||
d.data.file.parent().hash() === this.currdir.hash() d.u_data.parent().hash() === this.currdir.hash()
) { ) {
return this.view.path = this.currdir.path; return this.view.path = this.currdir.path;
} }
@ -355,6 +361,32 @@ namespace OS {
this.view.view = "list"; this.view.view = "list";
this.viewType.list = true; this.viewType.list = true;
}; };
// enable or disable multi-select by CTRL key
$(this.scheme).on("keydown", (evt)=>{
if(evt.ctrlKey && evt.which == 17)
{
this.view.multiselect = true;
}
else
{
this.view.multiselect = false;
}
});
$(this.scheme).on("keyup", (evt)=>{
if(evt.which === 38)
{
if (this.currdir.isRoot()) {
return;
}
const p = this.currdir.parent();
this.favo.selected = -1;
return this.view.path = p.path;
}
if(!evt.ctrlKey)
{
this.view.multiselect = false;
}
});
this.view.path = this.currdir.path; this.view.path = this.currdir.path;
} }
@ -585,20 +617,22 @@ namespace OS {
title: "__(Delete)", title: "__(Delete)",
iconclass: "fa fa-question-circle", iconclass: "fa fa-question-circle",
text: __( text: __(
"Do you really want to delete: {0}?", "Do you really want to delete selected files?"
file.filename
), ),
}).then(async (d) => { }).then(async (d) => {
if (!d) { if (!d) {
return; return;
} }
const promises = [];
for(const f of this.view.selectedFiles)
{
promises.push(f.path.asFileHandle().remove());
}
try { try {
return file.path await Promise.all(promises);
.asFileHandle()
.remove();
} }
catch (e) { catch (e) {
return this.error(__("Fail to delete: {0}", file.path), e); return this.error(__("Fail to delete selected files"), e);
} }
}); });
break; break;
@ -609,9 +643,9 @@ namespace OS {
} }
this.clipboard = { this.clipboard = {
cut: true, cut: true,
file: file.path.asFileHandle(), files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
}; };
return this.notify(__("File {0} cut", file.filename)); return this.notify(__("{0} files cut", this.clipboard.files.length));
case `${this.name}-copy`: case `${this.name}-copy`:
if (!file) { if (!file) {
@ -619,10 +653,10 @@ namespace OS {
} }
this.clipboard = { this.clipboard = {
cut: false, cut: false,
file: file.path.asFileHandle(), files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
}; };
return this.notify( return this.notify(
__("File {0} copied", file.filename) __("{0} files copied", this.clipboard.files.length)
); );
case `${this.name}-paste`: case `${this.name}-paste`:
@ -630,29 +664,33 @@ namespace OS {
return; return;
} }
if (this.clipboard.cut) { if (this.clipboard.cut) {
this.clipboard.file const promises = [];
.move( for(const file of this.clipboard.files)
`${this.currdir.path}/${this.clipboard.file.basename}` {
) promises.push(file.move(
`${this.currdir.path}/${file.basename}`
));
}
Promise.all(promises)
.then((r) => { .then((r) => {
return (this.clipboard = undefined); return (this.clipboard = undefined);
}) })
.catch((e) => { .catch((e) => {
return this.error( return this.error(
__( __(
"Fail to paste: {0}", "Fail to paste to: {0}",
this.clipboard.file.path this.currdir.path
), ),
e e
); );
}); });
} else { } else {
API.VFS.copy([this.clipboard.file.path],this.currdir.path) API.VFS.copy(this.clipboard.files.map(x => x.path),this.currdir.path)
.then(() => { .then(() => {
return (this.clipboard = undefined); return (this.clipboard = undefined);
}) })
.catch((e) => { .catch((e) => {
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e); return this.error(__("Fail to paste to: {0}", this.currdir.path), e);
}); });
} }
break; break;
@ -727,7 +765,7 @@ namespace OS {
}); });
break; break;
case `${this.name}-download`: case `${this.name}-download`:
if (file.type !== "file") { if (!file || file.type !== "file") {
return; return;
} }
file.path file.path

View File

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

View File

@ -4,7 +4,7 @@ libfiles =
cssfiles = main.css cssfiles = main.css
copyfiles = scheme.html package.json copyfiles = scheme.html package.json README.md
PKG_NAME=MarketPlace PKG_NAME=MarketPlace

View File

@ -0,0 +1,10 @@
# Market Place
AntOS original application store.
This application is icluded in the AntOS delivery
and cannot be removed/uinstalled by regular user
## Change logs
- 0.2.7-b: only launch application
- 0.2.6-b: improve install process
- v0.2.5-b: add README.md

View File

@ -41,7 +41,7 @@ namespace OS {
main(): void { main(): void {
this.installdir = this.systemsetting.system.pkgpaths.user; this.installdir = this.systemsetting.system.pkgpaths.user;
// test repository // test repository
this.apps_meta = []; this.apps_meta = {};
this.applist = this.find("applist") as GUI.tag.ListViewTag; this.applist = this.find("applist") as GUI.tag.ListViewTag;
this.catlist = this.find("catlist") as GUI.tag.ListViewTag; this.catlist = this.find("catlist") as GUI.tag.ListViewTag;
@ -108,8 +108,8 @@ namespace OS {
return; return;
} }
const app = el.data; const app = el.data;
if (app.pkgname) { if (app.app) {
return this._gui.launch(app.pkgname, []); return this._gui.launch(app.app, []);
} }
}; };
@ -273,6 +273,7 @@ namespace OS {
if (this.apps_meta[name]) { if (this.apps_meta[name]) {
pkg.icon = this.apps_meta[name].icon; pkg.icon = this.apps_meta[name].icon;
pkg.iconclass = this.apps_meta[name].iconclass; pkg.iconclass = this.apps_meta[name].iconclass;
pkg.app = this.apps_meta[name].app;
} }
this.apps_meta[name] = pkg; this.apps_meta[name] = pkg;
} }
@ -328,16 +329,11 @@ namespace OS {
this.catlist.data = cat_list_data; this.catlist.data = cat_list_data;
this.catlist.selected = 0; this.catlist.selected = 0;
} }
private add_meta_from(k:string, v: API.PackageMetaType)
fetchApps(): Promise<GenericObject<any>> { {
return new Promise((resolve, _reject) => { const mt = {
let v: API.PackageMetaType;
this.apps_meta = {};
const pkgcache = this.systemsetting.system.packages;
for (let k in pkgcache) {
v = pkgcache[k];
this.apps_meta[`${k}@${v.version}`] = {
pkgname: v.pkgname ? v.pkgname : v.app, pkgname: v.pkgname ? v.pkgname : v.app,
app: v.app,
name: v.name, name: v.name,
text: `${v.name} ${v.version}`, text: `${v.name} ${v.version}`,
icon: v.icon, icon: v.icon,
@ -349,6 +345,17 @@ namespace OS {
dependencies: v.dependencies ? Array.from(v.dependencies) : [], dependencies: v.dependencies ? Array.from(v.dependencies) : [],
dependBy: [] dependBy: []
}; };
this.apps_meta[`${k}@${v.version}`] = mt;
return mt;
}
fetchApps(): Promise<GenericObject<any>> {
return new Promise((resolve, _reject) => {
let v: API.PackageMetaType;
this.apps_meta = {};
const pkgcache = this.systemsetting.system.packages;
for (let k in pkgcache) {
v = pkgcache[k];
this.add_meta_from(k,v);
} }
const list: string[] = [] const list: string[] = []
@ -551,7 +558,8 @@ namespace OS {
return reject(this._api.throwe(__("Unable to find package: {0}", pkgname))); return reject(this._api.throwe(__("Unable to find package: {0}", pkgname)));
} }
try { try {
const n = await this.install(meta.download + "?_=" + new Date().getTime(), meta); const mt = await this.install(meta.download + "?_=" + new Date().getTime(), meta);
meta.app = mt.app;
return resolve(meta); return resolve(meta);
} catch (e_1) { } catch (e_1) {
return reject(__e(e_1)); return reject(__e(e_1));
@ -621,13 +629,19 @@ namespace OS {
mimes: [".*/zip"], mimes: [".*/zip"],
}); });
const n = await this.install(d.file.path); const n = await this.install(d.file.path);
const name = n.pkgname?n.pkgname:n.app;
const apps = this.applist.data.map( const apps = this.applist.data.map(
(v) => v.pkgname (v) => v.pkgname
); );
const idx = apps.indexOf(n); const idx = apps.indexOf(name);
if (idx >= 0) { if (idx >= 0) {
this.applist.selected = idx; this.applist.selected = idx;
} }
else
{
const mt = this.add_meta_from(name,n);
this.appDetail(mt);
}
return resolve(n.name); return resolve(n.name);
} catch (error) { } catch (error) {
reject(__e(error)); reject(__e(error));
@ -638,7 +652,7 @@ namespace OS {
private install( private install(
zfile: string, zfile: string,
meta?: GenericObject<any> meta?: GenericObject<any>
): Promise<GenericObject<any>> { ): Promise<API.PackageMetaType> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
try { try {
let v: API.PackageMetaType; let v: API.PackageMetaType;
@ -657,22 +671,6 @@ namespace OS {
}); });
}); });
const app_meta = {
pkgname: v.pkgname ? v.pkgname : v.app,
name: v.name,
text: v.name,
icon: v.icon,
iconclass: v.iconclass,
category: v.category,
author: v.info.author,
version: v.version,
description: meta
? meta.description
: undefined,
download: meta
? meta.download
: undefined,
};
v.text = v.name; v.text = v.name;
v.filename = v.pkgname ? v.pkgname : v.app; v.filename = v.pkgname ? v.pkgname : v.app;
v.type = "app"; v.type = "app";
@ -684,11 +682,15 @@ namespace OS {
v.iconclass = v.iconclass =
"fa fa-adn"; "fa fa-adn";
} }
if(v.icon)
{
v.icon = `${pth}/${v.icon}`;
}
v.path = pth; v.path = pth;
this.systemsetting.system.packages[ this.systemsetting.system.packages[
v.pkgname ? v.pkgname : v.app v.pkgname ? v.pkgname : v.app
] = v; ] = v;
return resolve(app_meta); return resolve(v);
} catch (error) { } catch (error) {
reject(__e(error)); reject(__e(error));
} }
@ -704,20 +706,21 @@ namespace OS {
private uninstallPkg(pkgname: string): Promise<any> { private uninstallPkg(pkgname: string): Promise<any> {
return new Promise(async (resolve, reject) => { return new Promise(async (resolve, reject) => {
const meta = this.apps_meta[pkgname]; const meta = this.apps_meta[pkgname];
// got the app meta
try {
if (!meta) { if (!meta) {
return reject(this._api.throwe(__("Unable to find application meta-data: {0}", pkgname))); throw __("Unable to find application meta-data: {0}", pkgname).__();
} }
const app = this.systemsetting.system.packages[meta.pkgname]; const app = this.systemsetting.system.packages[meta.pkgname];
if (!app) { if (!app) {
return reject(this._api.throwe(__("Application {0} is not installed", pkgname))); throw __("Application {0} is not installed", pkgname).__();
} }
// got the app meta
try {
const r = await app.path const r = await app.path
.asFileHandle() .asFileHandle()
.remove(); .remove();
if (r.error) { if (r.error) {
return reject(this._api.throwe(__("Cannot uninstall package: {0}", r.error))); throw __("Cannot uninstall package: {0}", r.error).__();
} }
this.notify(__("Package uninstalled")); this.notify(__("Package uninstalled"));
// stop all the services if any // stop all the services if any
@ -727,7 +730,7 @@ namespace OS {
} }
} }
delete this.systemsetting.system.packages[meta.pkgname]; delete this.systemsetting.system.packages[meta.pkgname];
this._gui.unloadApp(meta.pkgname); this._gui.unloadApp(meta.pkgname, true);
if (meta.download) { if (meta.download) {
this.appDetail(meta); this.appDetail(meta);
} }
@ -735,6 +738,7 @@ namespace OS {
if (meta.domel) if (meta.domel)
this.applist.delete(meta.domel); this.applist.delete(meta.domel);
$(this.container).css("visibility", "hidden"); $(this.container).css("visibility", "hidden");
delete this.apps_meta[pkgname];
} }
return resolve(meta); return resolve(meta);
} }

View File

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

View File

@ -13,7 +13,7 @@
</afx-vbox> </afx-vbox>
<afx-resizer data-width = "3" ></afx-resizer> <afx-resizer data-width = "3" ></afx-resizer>
<afx-vbox data-id = "container"> <afx-vbox data-id = "container">
<afx-label data-id = "appname" data-height = "25"></afx-label> <afx-label data-id = "appname" data-height = "45"></afx-label>
<afx-hbox data-height = "50"> <afx-hbox data-height = "50">
<div style = "text-align:left;"> <div style = "text-align:left;">
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button> <afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button>

View File

@ -4,3 +4,6 @@ Defaut text editor which is included in each AntOS release.
It has very barebone features: open/edit/save text file. It has very barebone features: open/edit/save text file.
Text/Code editor with fancy features can be optionally installed via the Market Place Text/Code editor with fancy features can be optionally installed via the Market Place
## Change logs
-v0.1.1-b: update README

View File

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

View File

@ -101,6 +101,7 @@ namespace OS {
text: v.name, text: v.name,
app: k, app: k,
iconclass: v.iconclass, iconclass: v.iconclass,
icon: v.icon
}); });
} }
} }
@ -160,7 +161,7 @@ namespace OS {
} }
return result1; return result1;
})(); })();
announcer.ostrigger("app-pinned", this.applist.data); announcer.ostrigger("app-pinned", "app-pinned", this.applist.data);
} }
} }
App.AppAndServiceHandle = AppAndServiceHandle; App.AppAndServiceHandle = AppAndServiceHandle;

View File

@ -4,7 +4,7 @@ libfiles =
cssfiles = main.css cssfiles = main.css
copyfiles = scheme.html package.json copyfiles = scheme.html package.json README.md
PKG_NAME=Setting PKG_NAME=Setting

View File

@ -0,0 +1,14 @@
# Setting: AntOS system setting and configuration
GUI based system setting application for AntOS.
In-depth system settings can be found and modified in .settings.json file stored in HOME directory
of current user.
CAUTION:without using the Setting application, users can modify .settings.json with their own risk.
In case of system anormaly after the modification, the system settings can be reset to default
by simply removing the setting file
## Change logs
-v0.1.2-b: minor bug fix on UI
-v0.1.2-b: add README

View File

@ -102,6 +102,7 @@ namespace OS {
text: v.name, text: v.name,
app: k, app: k,
iconclass: v.iconclass, iconclass: v.iconclass,
icon: v.icon
}); });
} }
} }

View File

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

View File

@ -4,7 +4,7 @@ libfiles =
cssfiles = main.css cssfiles = main.css
copyfiles = package.json scheme.html copyfiles = package.json scheme.html README.md
PKG_NAME=Syslog PKG_NAME=Syslog

View File

@ -112,19 +112,19 @@ namespace OS {
* *
* @private * @private
* @param {string} s * @param {string} s
* @param {GenericObject<any>} o * @param {API.AnnouncementDataType} o
* @memberof PushNotification * @memberof PushNotification
*/ */
private addLog(s: string, o: GenericObject<any>): void { private addLog(s: string, o: API.AnnouncementDataType<any>): void {
const logtime = new Date(); const logtime = new Date();
const log = { const log = {
type: s, type: s,
name: o.name, name: o.name,
text: `${o.data.m}`, text: `${o.message}`,
id: o.id, id: o.id,
icon: o.data.icon, icon: o.icon,
iconclass: o.data.iconclass, iconclass: o.iconclass,
error: o.data.e, error: o.u_data,
time: logtime, time: logtime,
closable: true, closable: true,
tag: "afx-bug-list-item", tag: "afx-bug-list-item",
@ -141,14 +141,14 @@ namespace OS {
* *
* @private * @private
* @param {string} s * @param {string} s
* @param {GenericObject<any>} o * @param {API.AnnouncementDataType} o
* @memberof PushNotification * @memberof PushNotification
*/ */
private pushout(s: string, o: GenericObject<any>): void { private pushout(s: string, o: API.AnnouncementDataType<any>): void {
const d = { const d = {
text: `[${s}] ${o.name} (${o.id}): ${o.data.m}`, text: `[${s}] ${o.name} (${o.id}): ${o.message}`,
icon: o.data.icon, icon: o.icon,
iconclass: o.data.iconclass, iconclass: o.iconclass,
closable: true, closable: true,
}; };
if (s !== "INFO") { if (s !== "INFO") {

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

@ -13,7 +13,7 @@
"credit": "dedicated to some one here", "credit": "dedicated to some one here",
"licences": "GPLv3" "licences": "GPLv3"
}, },
"version": "0.1.1-a", "version": "0.1.2-b",
"category": "System", "category": "System",
"iconclass": "fa fa-bug", "iconclass": "fa fa-bug",
"mimes": [] "mimes": []

View File

@ -8,7 +8,7 @@ afx-file-view afx-label.status{
} }
afx-file-view afx-list-view > div.list-container > ul li{ afx-file-view afx-list-view > div.list-container > ul li{
width:70px; width:70px;
height: 60px; /*height: 60px;*/
background-color: transparent; background-color: transparent;
text-align: center; text-align: center;
margin-right: 5px; margin-right: 5px;

View File

@ -1,11 +1,11 @@
afx-menu afx-switch span{ afx-menu afx-switch span{
width: 20px; padding-top: 3px;
height: 16px;
font-size: 16px; font-size: 16px;
/*margin-top:5px;*/ height: 19px;
} }
afx-menu span.shortcut{ afx-menu span.shortcut{
text-align: right; text-align: right;
margin-left: 3px;
} }
afx-menu li:hover > a afx-switch span:before{ afx-menu li:hover > a afx-switch span:before{
color:white; color:white;
@ -21,12 +21,12 @@ afx-menu afx-menu ul {
background-color: #363636; background-color: #363636;
} }
afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{ afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{
padding:3px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
afx-menu afx-menu li{ afx-menu afx-menu li{
min-width: 150px; min-width: 150px;
width: calc(100% - 10px);
} }
afx-menu li:hover { afx-menu li:hover {
@ -55,4 +55,9 @@ afx-menu afx-menu .afx_submenu:before, afx-menu ul.context .afx_submenu:before
} }
afx-menu ul.context li{ afx-menu ul.context li{
min-width: 150px; min-width: 150px;
width: calc(100% - 10px);
}
afx-menu afx-label span {
height: 22px !important;
} }

View File

@ -7,7 +7,7 @@ afx-file-view afx-label.status{
} }
afx-file-view afx-list-view > div.list-container > ul li{ afx-file-view afx-list-view > div.list-container > ul li{
width:70px; width:70px;
height: 60px; /*height: 60px;*/
background-color: transparent; background-color: transparent;
text-align: center; text-align: center;
margin-right: 5px; margin-right: 5px;

View File

@ -1,11 +1,11 @@
afx-menu afx-switch span{ afx-menu afx-switch span{
width: 20px; padding-top: 3px;
height: 16px;
font-size: 16px; font-size: 16px;
/*margin-top:5px;*/ height: 19px;
} }
afx-menu span.shortcut{ afx-menu span.shortcut{
text-align: right; text-align: right;
margin-left: 3px;
} }
afx-menu li:hover > a afx-switch span:before{ afx-menu li:hover > a afx-switch span:before{
color:white; color:white;
@ -21,12 +21,12 @@ afx-menu afx-menu ul {
background-color: #e7e7e7; background-color: #e7e7e7;
} }
afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{ afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{
padding:3px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} }
afx-menu afx-menu li{ afx-menu afx-menu li{
min-width: 150px; min-width: 150px;
width: calc(100% - 10px);
} }
afx-menu li:hover { afx-menu li:hover {
@ -55,4 +55,9 @@ afx-menu afx-menu .afx_submenu:before, afx-menu ul.context .afx_submenu:before
} }
afx-menu ul.context li{ afx-menu ul.context li{
min-width: 150px; min-width: 150px;
width: calc(100% - 10px);
}
afx-menu afx-label span {
height: 22px !important;
} }

View File

@ -15,6 +15,21 @@ afx-app-window ul.afx-window-top{
width: 100%; width: 100%;
padding:0; padding:0;
} }
afx-app-window ul.loading::before{
background-color: orangered;
content: "";
position: absolute;
height: 2px;
width: 0%;
}
afx-app-window ul.loading::before {
right: 0;
top:0;
animation: sys-loading 1s linear infinite;
}
afx-app-window ul.afx-window-top li{ afx-app-window ul.afx-window-top li{
list-style: none; list-style: none;
} }

View File

@ -5,3 +5,6 @@ afx-calendar-view afx-grid-view afx-grid-row:nth-child(even) afx-grid-cell
afx-calendar-view afx-grid-view .grid_row_header afx-grid-cell{ afx-calendar-view afx-grid-view .grid_row_header afx-grid-cell{
border-right: 0; border-right: 0;
} }
afx-calendar-view afx-label {
display: inline-block;
}

View File

@ -7,15 +7,29 @@ afx-file-view afx-label.status{
left:0px; left:0px;
transform: translateZ(0); transform: translateZ(0);
} }
afx-file-view afx-list-view > div.list-container > ul
{
display: flex;
flex-flow: row wrap;
}
afx-file-view afx-list-view > div.list-container > ul li{ afx-file-view afx-list-view > div.list-container > ul li{
float:left; float:left;
display: block; /*display: block;*/
} }
afx-file-view afx-list-view i{ afx-file-view afx-list-view i{
display: block; display: block;
} }
afx-file-view afx-list-view i.label-text{
word-break: break-word;
}
afx-file-view afx-list-view afx-label span
{
flex-direction: column;
}
afx-file-view afx-grid-view i{ afx-file-view afx-grid-view i{
display: inline-block; display: inline-block;
} }

View File

@ -1,11 +1,11 @@
afx-float-list div.list-container > ul{ afx-desktop div.list-container > ul, afx-float-list div.list-container > ul{
padding: 0; padding: 0;
margin: 0; margin: 0;
background-color: transparent; background-color: transparent;
} }
afx-float-list div.list-container > ul li{ afx-desktop div.list-container > ul li, afx-float-list div.list-container > ul li{
padding: 0; padding: 0;
margin: 0; margin: 0;
background-color: transparent; background-color: transparent;

View File

@ -14,3 +14,7 @@ afx-grid-view .grid_row_header afx-grid-cell{
afx-grid-view { afx-grid-view {
display: block; display: block;
} }
afx-grid-view afx-grid-cell afx-label {
display: inline-block;
}

View File

@ -1,9 +1,13 @@
afx-label i.icon-style { afx-label i.icon-style {
float: left; display: inline-block;
}
afx-label span {
align-items: center;
justify-content: center;
flex-direction: row;
} }
afx-label i.label-text{ afx-label i.label-text{
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
margin-left: 3px; margin-left: 3px;
user-select:text;
} }

View File

@ -54,3 +54,7 @@ afx-list-view.dropdown > div.list-container > ul li{
display: inline-block; display: inline-block;
width:100%; width:100%;
} }
afx-list-view afx-list-item afx-label {
display: inline-block;
}

View File

@ -34,6 +34,7 @@ afx-menu ul li {
float: left; float: left;
cursor:default; cursor:default;
} }
/*
afx-menu ul li.fix_padding{ afx-menu ul li.fix_padding{
padding-top:1px; padding-top:1px;
padding-bottom: 0; padding-bottom: 0;
@ -45,7 +46,7 @@ afx-menu afx-menu ul li.fix_padding{
padding:3px; padding:3px;
padding-left: 5px; padding-left: 5px;
padding-right: 5px; padding-right: 5px;
} } */
afx-menu afx-menu { afx-menu afx-menu {
top:100%; top:100%;
left:0; left:0;

View File

@ -52,7 +52,7 @@ afx-sys-panel > div.loading::before {
100% { 100% {
right: auto; right: auto;
left: 100%; left: 100%;
width: 12.5%; width: 0%;
} }
} }
@ -97,3 +97,9 @@ afx-sys-panel afx-hbox[data-id="btlist"] afx-button button
border-radius: 0; border-radius: 0;
border: 0px; border: 0px;
} }
afx-sys-panel afx-list-view[data-id="applist"] afx-label span,
afx-sys-panel afx-list-view[data-id="catlist"] afx-label span
{
white-space: nowrap;
}

View File

@ -130,3 +130,27 @@ body
height: 1px; height: 1px;
box-sizing: border-box; box-sizing: border-box;
} }
#antos_build_id {
position: absolute;
display: block;
bottom: 0;
right: 0;
padding: 2px;
background-color: #d6d4d4;
font-family: Verdana, Geneva, Tahoma, sans-serif;
font-size: 12px;
}
#antos_build_id a:link,
#antos_build_id a:visited,
#antos_build_id a:hover
{
color:#df3154;
text-decoration: none;
}
afx-desktop > .list-container > ul > afx-list-item afx-label span {
flex-direction: column;
}
afx-desktop > .list-container > ul > afx-list-item afx-label i.label-text{
word-break: break-word;
}

View File

@ -1,9 +1,10 @@
@font-face { @font-face {
font-family: "bootstrap-icons"; font-family: "bootstrap-icons";
src: url("fonts/bootstrap-icons.woff2?231ce25e89ab5804f9a6c427b8d325c9") format("woff2"), src: url("./fonts/bootstrap-icons.woff2?a74547b2f0863226942ff8ded57db345") format("woff2"),
url("fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9") format("woff"); url("./fonts/bootstrap-icons.woff?a74547b2f0863226942ff8ded57db345") format("woff");
} }
.bi::before,
[class^="bi-"]::before, [class^="bi-"]::before,
[class*=" bi-"]::before { [class*=" bi-"]::before {
display: inline-block; display: inline-block;
@ -18,6 +19,7 @@ url("fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9") format("woff"
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.bi-123::before { content: "\f67f"; }
.bi-alarm-fill::before { content: "\f101"; } .bi-alarm-fill::before { content: "\f101"; }
.bi-alarm::before { content: "\f102"; } .bi-alarm::before { content: "\f102"; }
.bi-align-bottom::before { content: "\f103"; } .bi-align-bottom::before { content: "\f103"; }
@ -1343,3 +1345,212 @@ url("fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9") format("woff"
.bi-youtube::before { content: "\f62b"; } .bi-youtube::before { content: "\f62b"; }
.bi-zoom-in::before { content: "\f62c"; } .bi-zoom-in::before { content: "\f62c"; }
.bi-zoom-out::before { content: "\f62d"; } .bi-zoom-out::before { content: "\f62d"; }
.bi-bank::before { content: "\f62e"; }
.bi-bank2::before { content: "\f62f"; }
.bi-bell-slash-fill::before { content: "\f630"; }
.bi-bell-slash::before { content: "\f631"; }
.bi-cash-coin::before { content: "\f632"; }
.bi-check-lg::before { content: "\f633"; }
.bi-coin::before { content: "\f634"; }
.bi-currency-bitcoin::before { content: "\f635"; }
.bi-currency-dollar::before { content: "\f636"; }
.bi-currency-euro::before { content: "\f637"; }
.bi-currency-exchange::before { content: "\f638"; }
.bi-currency-pound::before { content: "\f639"; }
.bi-currency-yen::before { content: "\f63a"; }
.bi-dash-lg::before { content: "\f63b"; }
.bi-exclamation-lg::before { content: "\f63c"; }
.bi-file-earmark-pdf-fill::before { content: "\f63d"; }
.bi-file-earmark-pdf::before { content: "\f63e"; }
.bi-file-pdf-fill::before { content: "\f63f"; }
.bi-file-pdf::before { content: "\f640"; }
.bi-gender-ambiguous::before { content: "\f641"; }
.bi-gender-female::before { content: "\f642"; }
.bi-gender-male::before { content: "\f643"; }
.bi-gender-trans::before { content: "\f644"; }
.bi-headset-vr::before { content: "\f645"; }
.bi-info-lg::before { content: "\f646"; }
.bi-mastodon::before { content: "\f647"; }
.bi-messenger::before { content: "\f648"; }
.bi-piggy-bank-fill::before { content: "\f649"; }
.bi-piggy-bank::before { content: "\f64a"; }
.bi-pin-map-fill::before { content: "\f64b"; }
.bi-pin-map::before { content: "\f64c"; }
.bi-plus-lg::before { content: "\f64d"; }
.bi-question-lg::before { content: "\f64e"; }
.bi-recycle::before { content: "\f64f"; }
.bi-reddit::before { content: "\f650"; }
.bi-safe-fill::before { content: "\f651"; }
.bi-safe2-fill::before { content: "\f652"; }
.bi-safe2::before { content: "\f653"; }
.bi-sd-card-fill::before { content: "\f654"; }
.bi-sd-card::before { content: "\f655"; }
.bi-skype::before { content: "\f656"; }
.bi-slash-lg::before { content: "\f657"; }
.bi-translate::before { content: "\f658"; }
.bi-x-lg::before { content: "\f659"; }
.bi-safe::before { content: "\f65a"; }
.bi-apple::before { content: "\f65b"; }
.bi-microsoft::before { content: "\f65d"; }
.bi-windows::before { content: "\f65e"; }
.bi-behance::before { content: "\f65c"; }
.bi-dribbble::before { content: "\f65f"; }
.bi-line::before { content: "\f660"; }
.bi-medium::before { content: "\f661"; }
.bi-paypal::before { content: "\f662"; }
.bi-pinterest::before { content: "\f663"; }
.bi-signal::before { content: "\f664"; }
.bi-snapchat::before { content: "\f665"; }
.bi-spotify::before { content: "\f666"; }
.bi-stack-overflow::before { content: "\f667"; }
.bi-strava::before { content: "\f668"; }
.bi-wordpress::before { content: "\f669"; }
.bi-vimeo::before { content: "\f66a"; }
.bi-activity::before { content: "\f66b"; }
.bi-easel2-fill::before { content: "\f66c"; }
.bi-easel2::before { content: "\f66d"; }
.bi-easel3-fill::before { content: "\f66e"; }
.bi-easel3::before { content: "\f66f"; }
.bi-fan::before { content: "\f670"; }
.bi-fingerprint::before { content: "\f671"; }
.bi-graph-down-arrow::before { content: "\f672"; }
.bi-graph-up-arrow::before { content: "\f673"; }
.bi-hypnotize::before { content: "\f674"; }
.bi-magic::before { content: "\f675"; }
.bi-person-rolodex::before { content: "\f676"; }
.bi-person-video::before { content: "\f677"; }
.bi-person-video2::before { content: "\f678"; }
.bi-person-video3::before { content: "\f679"; }
.bi-person-workspace::before { content: "\f67a"; }
.bi-radioactive::before { content: "\f67b"; }
.bi-webcam-fill::before { content: "\f67c"; }
.bi-webcam::before { content: "\f67d"; }
.bi-yin-yang::before { content: "\f67e"; }
.bi-bandaid-fill::before { content: "\f680"; }
.bi-bandaid::before { content: "\f681"; }
.bi-bluetooth::before { content: "\f682"; }
.bi-body-text::before { content: "\f683"; }
.bi-boombox::before { content: "\f684"; }
.bi-boxes::before { content: "\f685"; }
.bi-dpad-fill::before { content: "\f686"; }
.bi-dpad::before { content: "\f687"; }
.bi-ear-fill::before { content: "\f688"; }
.bi-ear::before { content: "\f689"; }
.bi-envelope-check-1::before { content: "\f68a"; }
.bi-envelope-check-fill::before { content: "\f68b"; }
.bi-envelope-check::before { content: "\f68c"; }
.bi-envelope-dash-1::before { content: "\f68d"; }
.bi-envelope-dash-fill::before { content: "\f68e"; }
.bi-envelope-dash::before { content: "\f68f"; }
.bi-envelope-exclamation-1::before { content: "\f690"; }
.bi-envelope-exclamation-fill::before { content: "\f691"; }
.bi-envelope-exclamation::before { content: "\f692"; }
.bi-envelope-plus-fill::before { content: "\f693"; }
.bi-envelope-plus::before { content: "\f694"; }
.bi-envelope-slash-1::before { content: "\f695"; }
.bi-envelope-slash-fill::before { content: "\f696"; }
.bi-envelope-slash::before { content: "\f697"; }
.bi-envelope-x-1::before { content: "\f698"; }
.bi-envelope-x-fill::before { content: "\f699"; }
.bi-envelope-x::before { content: "\f69a"; }
.bi-explicit-fill::before { content: "\f69b"; }
.bi-explicit::before { content: "\f69c"; }
.bi-git::before { content: "\f69d"; }
.bi-infinity::before { content: "\f69e"; }
.bi-list-columns-reverse::before { content: "\f69f"; }
.bi-list-columns::before { content: "\f6a0"; }
.bi-meta::before { content: "\f6a1"; }
.bi-mortorboard-fill::before { content: "\f6a2"; }
.bi-mortorboard::before { content: "\f6a3"; }
.bi-nintendo-switch::before { content: "\f6a4"; }
.bi-pc-display-horizontal::before { content: "\f6a5"; }
.bi-pc-display::before { content: "\f6a6"; }
.bi-pc-horizontal::before { content: "\f6a7"; }
.bi-pc::before { content: "\f6a8"; }
.bi-playstation::before { content: "\f6a9"; }
.bi-plus-slash-minus::before { content: "\f6aa"; }
.bi-projector-fill::before { content: "\f6ab"; }
.bi-projector::before { content: "\f6ac"; }
.bi-qr-code-scan::before { content: "\f6ad"; }
.bi-qr-code::before { content: "\f6ae"; }
.bi-quora::before { content: "\f6af"; }
.bi-quote::before { content: "\f6b0"; }
.bi-robot::before { content: "\f6b1"; }
.bi-send-check-fill::before { content: "\f6b2"; }
.bi-send-check::before { content: "\f6b3"; }
.bi-send-dash-fill::before { content: "\f6b4"; }
.bi-send-dash::before { content: "\f6b5"; }
.bi-send-exclamation-1::before { content: "\f6b6"; }
.bi-send-exclamation-fill::before { content: "\f6b7"; }
.bi-send-exclamation::before { content: "\f6b8"; }
.bi-send-fill::before { content: "\f6b9"; }
.bi-send-plus-fill::before { content: "\f6ba"; }
.bi-send-plus::before { content: "\f6bb"; }
.bi-send-slash-fill::before { content: "\f6bc"; }
.bi-send-slash::before { content: "\f6bd"; }
.bi-send-x-fill::before { content: "\f6be"; }
.bi-send-x::before { content: "\f6bf"; }
.bi-send::before { content: "\f6c0"; }
.bi-steam::before { content: "\f6c1"; }
.bi-terminal-dash-1::before { content: "\f6c2"; }
.bi-terminal-dash::before { content: "\f6c3"; }
.bi-terminal-plus::before { content: "\f6c4"; }
.bi-terminal-split::before { content: "\f6c5"; }
.bi-ticket-detailed-fill::before { content: "\f6c6"; }
.bi-ticket-detailed::before { content: "\f6c7"; }
.bi-ticket-fill::before { content: "\f6c8"; }
.bi-ticket-perferated-fill::before { content: "\f6c9"; }
.bi-ticket-perferated::before { content: "\f6ca"; }
.bi-ticket::before { content: "\f6cb"; }
.bi-tiktok::before { content: "\f6cc"; }
.bi-window-dash::before { content: "\f6cd"; }
.bi-window-desktop::before { content: "\f6ce"; }
.bi-window-fullscreen::before { content: "\f6cf"; }
.bi-window-plus::before { content: "\f6d0"; }
.bi-window-split::before { content: "\f6d1"; }
.bi-window-stack::before { content: "\f6d2"; }
.bi-window-x::before { content: "\f6d3"; }
.bi-xbox::before { content: "\f6d4"; }
.bi-ethernet::before { content: "\f6d5"; }
.bi-hdmi-fill::before { content: "\f6d6"; }
.bi-hdmi::before { content: "\f6d7"; }
.bi-usb-c-fill::before { content: "\f6d8"; }
.bi-usb-c::before { content: "\f6d9"; }
.bi-usb-fill::before { content: "\f6da"; }
.bi-usb-plug-fill::before { content: "\f6db"; }
.bi-usb-plug::before { content: "\f6dc"; }
.bi-usb-symbol::before { content: "\f6dd"; }
.bi-usb::before { content: "\f6de"; }
.bi-boombox-fill::before { content: "\f6df"; }
.bi-displayport-1::before { content: "\f6e0"; }
.bi-displayport::before { content: "\f6e1"; }
.bi-gpu-card::before { content: "\f6e2"; }
.bi-memory::before { content: "\f6e3"; }
.bi-modem-fill::before { content: "\f6e4"; }
.bi-modem::before { content: "\f6e5"; }
.bi-motherboard-fill::before { content: "\f6e6"; }
.bi-motherboard::before { content: "\f6e7"; }
.bi-optical-audio-fill::before { content: "\f6e8"; }
.bi-optical-audio::before { content: "\f6e9"; }
.bi-pci-card::before { content: "\f6ea"; }
.bi-router-fill::before { content: "\f6eb"; }
.bi-router::before { content: "\f6ec"; }
.bi-ssd-fill::before { content: "\f6ed"; }
.bi-ssd::before { content: "\f6ee"; }
.bi-thunderbolt-fill::before { content: "\f6ef"; }
.bi-thunderbolt::before { content: "\f6f0"; }
.bi-usb-drive-fill::before { content: "\f6f1"; }
.bi-usb-drive::before { content: "\f6f2"; }
.bi-usb-micro-fill::before { content: "\f6f3"; }
.bi-usb-micro::before { content: "\f6f4"; }
.bi-usb-mini-fill::before { content: "\f6f5"; }
.bi-usb-mini::before { content: "\f6f6"; }
.bi-cloud-haze2::before { content: "\f6f7"; }
.bi-device-hdd-fill::before { content: "\f6f8"; }
.bi-device-hdd::before { content: "\f6f9"; }
.bi-device-ssd-fill::before { content: "\f6fa"; }
.bi-device-ssd::before { content: "\f6fb"; }
.bi-displayport-fill::before { content: "\f6fc"; }
.bi-mortarboard-fill::before { content: "\f6fd"; }
.bi-mortarboard::before { content: "\f6fe"; }
.bi-terminal-x::before { content: "\f6ff"; }