mirror of
https://github.com/lxsang/antos-frontend.git
synced 2024-12-26 17:38:20 +01:00
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:
commit
4cd271cc51
62
Jenkinsfile
vendored
Normal file
62
Jenkinsfile
vendored
Normal 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
|
||||
//}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
45
Makefile
45
Makefile
@ -4,8 +4,13 @@ BUILDDIR?=/opt/www/htdocs/os
|
||||
DOCDIR?=/opt/www/htdocs/doc/antos
|
||||
BLUE=\033[1;34m
|
||||
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
|
||||
UNAME_S := $(shell uname -s)
|
||||
@ -34,7 +39,8 @@ tags = dist/core/tags/tag.js \
|
||||
dist/core/tags/FileViewTag.js \
|
||||
dist/core/tags/OverlayTag.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 \
|
||||
dist/core/settings.js \
|
||||
@ -58,6 +64,7 @@ packages = Syslog Files MarketPlace Setting NotePad
|
||||
|
||||
main: initd build_javascripts build_themes libs build_packages languages
|
||||
- cp src/index.html $(BUILDDIR)/
|
||||
- cp README.md $(BUILDDIR)/
|
||||
|
||||
initd:
|
||||
- mkdir -p $(BUILDDIR)
|
||||
@ -68,7 +75,7 @@ lite: build_javascripts build_themes build_packages
|
||||
|
||||
ts:
|
||||
-rm -rf dist
|
||||
tsc -p tsconfig.json
|
||||
$(TSC) -p tsconfig.json
|
||||
cat `find dist/core/ -name "*.d.ts"` > d.ts/antos.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
|
||||
@ -85,7 +92,7 @@ standalone_tags: ts
|
||||
rm "$${f}";\
|
||||
done
|
||||
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
|
||||
|
||||
@for f in src/themes/system/afx-*.css; do \
|
||||
@ -101,7 +108,7 @@ standalone_tags: ts
|
||||
(cat "$${f}"; echo) >> $(BUILDDIR)/afx.css; \
|
||||
fi;\
|
||||
done
|
||||
# uglifycss --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css
|
||||
# $(UGLIFYCSS) --output $(BUILDDIR)/afx.css $(BUILDDIR)/afx.css
|
||||
rm -r dist/core
|
||||
|
||||
build_javascripts: ts
|
||||
@ -113,6 +120,7 @@ build_javascripts: ts
|
||||
(cat "$${f}"; echo) >> dist/antos.js;\
|
||||
rm "$${f}";\
|
||||
done
|
||||
echo 'OS.VERSION.version_string = "$(VERSION)-$(BRANCH)-$(BUILDID)";' >> dist/antos.js
|
||||
cp dist/antos.js $(BUILDDIR)/scripts/
|
||||
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
|
||||
rm -r dist/core
|
||||
@ -173,27 +181,34 @@ package:
|
||||
pkgar:
|
||||
read -r -p "Enter package name: " PKG;\
|
||||
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.css && uglifycss --output $(BUILDDIR)/packages/$$PKG/main.css $(BUILDDIR)/packages/$$PKG/main.css;\
|
||||
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;\
|
||||
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
|
||||
|
||||
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
|
||||
# npm install uglifycss -g
|
||||
# npm install $(UGLIFYCSS) -g
|
||||
# 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_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/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/system/system.css $(BUILDDIR)/resources/themes/system/system.css
|
||||
#uglify each packages
|
||||
|
||||
for d in $(packages); do\
|
||||
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.css && uglifycss --output $(BUILDDIR)/packages/$$d/main.css $(BUILDDIR)/packages/$$d/main.css;\
|
||||
test -f $(BUILDDIR)/packages/$$d/main.js && \
|
||||
$(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
|
||||
|
||||
ar:
|
||||
|
@ -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)
|
||||
|
||||
@ -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)
|
||||
|
||||
## 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] 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
|
||||
|
15028
d.ts/antos.d.ts
vendored
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
BIN
release/antos-1.2.1.tar.gz
Normal file
Binary file not shown.
@ -1 +1 @@
|
||||
1.2.0
|
||||
1.2.1
|
@ -18,6 +18,63 @@
|
||||
|
||||
namespace OS {
|
||||
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
|
||||
*
|
||||
@ -251,10 +308,10 @@ namespace OS {
|
||||
*
|
||||
* @export
|
||||
* @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
|
||||
*/
|
||||
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]) {
|
||||
announcer.listeners[a.pid] = [];
|
||||
}
|
||||
@ -282,7 +339,7 @@ namespace OS {
|
||||
* @param {Error} e error to be reported
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
export function osinfo(m: string | FormattedString): void {
|
||||
announcer.ostrigger("info", { m, e: null });
|
||||
announcer.ostrigger("info", m);
|
||||
}
|
||||
|
||||
/**
|
||||
* trigger a specific global event
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @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 {
|
||||
announcer.trigger(e, { id: 0, data: d, name: "OS" });
|
||||
export function ostrigger(e: string, m: string| FormattedString, d?: any): void {
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +70,22 @@ namespace OS {
|
||||
*/
|
||||
appmenu: GUI.tag.MenuTag;
|
||||
|
||||
/**
|
||||
* Loading animation check timeout
|
||||
*
|
||||
* @private
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
private _loading_toh: any;
|
||||
/**
|
||||
* Store pending loading task
|
||||
*
|
||||
* @private
|
||||
* @type {number[]}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
private _pending_task: number[];
|
||||
|
||||
/**
|
||||
*Creates an instance of BaseApplication.
|
||||
* @param {string} name application name
|
||||
@ -83,11 +99,8 @@ namespace OS {
|
||||
}
|
||||
this.setting = setting.applications[this.name];
|
||||
this.keycomb = {};
|
||||
this.subscribe("appregistry", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.data.m);
|
||||
}
|
||||
});
|
||||
this._loading_toh = undefined;
|
||||
this._pending_task = [];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,11 +122,13 @@ namespace OS {
|
||||
this.sysdock.selectedApp = this;
|
||||
this.appmenu.pid = this.pid;
|
||||
this.appmenu.items = this.baseMenu() || [];
|
||||
OS.PM.pidactive = this.pid;
|
||||
this.appmenu.onmenuselect = (
|
||||
d: GUI.tag.MenuEventData
|
||||
): void => {
|
||||
return this.trigger("menuselect", d);
|
||||
};
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
return this.dialog.show();
|
||||
}
|
||||
@ -135,6 +150,31 @@ namespace OS {
|
||||
}
|
||||
});
|
||||
this.on("apptitlechange", () => this.sysdock.update(this));
|
||||
this.subscribe("appregistry", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.message as string);
|
||||
}
|
||||
});
|
||||
this.subscribe("loading", (o: API.AnnouncementDataType<number>) => {
|
||||
if(o.u_data != this.pid)
|
||||
{
|
||||
return;
|
||||
}
|
||||
this._pending_task.push(o.id);
|
||||
this.trigger("loading", undefined);
|
||||
});
|
||||
|
||||
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => {
|
||||
const i = this._pending_task.indexOf(o.id);
|
||||
if (i >= 0) {
|
||||
this._pending_task.splice(i, 1);
|
||||
}
|
||||
if (this._pending_task.length === 0) {
|
||||
// set time out
|
||||
if(!this._loading_toh)
|
||||
this._loading_toh = setTimeout(() => this.animation_check(),1000);
|
||||
}
|
||||
});
|
||||
this.updateLocale(this.systemsetting.system.locale);
|
||||
return this.loadScheme();
|
||||
}
|
||||
@ -225,12 +265,11 @@ namespace OS {
|
||||
* Update the application local from the system
|
||||
* locale or application specific locale configuration
|
||||
*
|
||||
* @private
|
||||
* @param {string} name locale name e.g. `en_GB`
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected updateLocale(name: string): void {
|
||||
updateLocale(name: string): void {
|
||||
const meta = this.meta();
|
||||
if (!meta || !meta.locales) {
|
||||
return;
|
||||
@ -324,7 +363,11 @@ namespace OS {
|
||||
if (this.appmenu && this.pid === this.appmenu.pid) {
|
||||
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
|
||||
*/
|
||||
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;
|
||||
|
@ -17,6 +17,7 @@
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
declare var showdown: any;
|
||||
/**
|
||||
* the SubWindow class is the abstract prototype of all
|
||||
* modal windows or dialogs definition in AntOS
|
||||
@ -44,6 +45,7 @@ namespace OS {
|
||||
*/
|
||||
parent: BaseModel | typeof GUI;
|
||||
|
||||
|
||||
/**
|
||||
*Creates an instance of SubWindow.
|
||||
* @param {string} name SubWindow (class) name
|
||||
@ -82,10 +84,26 @@ namespace OS {
|
||||
*
|
||||
* 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
|
||||
@ -115,8 +133,8 @@ namespace OS {
|
||||
* @memberof SubWindow
|
||||
*/
|
||||
show(): void {
|
||||
this.trigger("focus");
|
||||
$(this.scheme).css("z-index", GUI.zindex + 2);
|
||||
this.trigger("focus", undefined);
|
||||
this.trigger("focused", undefined);
|
||||
if (this.dialog) {
|
||||
this.dialog.show();
|
||||
}
|
||||
@ -129,10 +147,27 @@ namespace OS {
|
||||
* @memberof SubWindow
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -182,6 +217,7 @@ namespace OS {
|
||||
return (this.parent.dialog = undefined);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
@ -238,6 +274,7 @@ namespace OS {
|
||||
* @memberof BasicDialog
|
||||
*/
|
||||
init(): void {
|
||||
super.init();
|
||||
//this._onenter = undefined;
|
||||
if (this.markup) {
|
||||
if (typeof this.markup === "string") {
|
||||
@ -264,7 +301,6 @@ namespace OS {
|
||||
*/
|
||||
main(): void {
|
||||
const win = this.scheme as tag.WindowTag;
|
||||
$(win).attr("tabindex", 0);
|
||||
$(win).on('keydown', (e) => {
|
||||
switch (e.which) {
|
||||
case 27:
|
||||
@ -532,7 +568,7 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
CalendarDialog.scheme = `\
|
||||
<afx-app-window width='300' height='230' apptitle = "Calendar" >
|
||||
<afx-app-window width='300' height='250' apptitle = "Calendar" >
|
||||
<afx-vbox>
|
||||
<afx-hbox>
|
||||
<div data-width = "10" ></div>
|
||||
@ -672,7 +708,7 @@ namespace OS {
|
||||
}
|
||||
for (let k in this.data) {
|
||||
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;
|
||||
grid.header = [
|
||||
@ -922,14 +958,25 @@ namespace OS {
|
||||
if (!mt.info) {
|
||||
return;
|
||||
}
|
||||
const rows = [];
|
||||
for (let k in mt.info) {
|
||||
const v = mt.info[k];
|
||||
rows.push([{ text: k }, { text: v }]);
|
||||
}
|
||||
const rows = [
|
||||
[{ text: __("Author") }, { text: mt.info.author }],
|
||||
[{ text: __("Email") }, { text: mt.info.email }]
|
||||
];
|
||||
const grid = this.find("mygrid") as tag.GridViewTag;
|
||||
grid.header = [{ text: "", width: 100 }, { text: "" }];
|
||||
grid.rows = rows;
|
||||
`pkg://${mt.pkgname?mt.pkgname:mt.app}/README.md`
|
||||
.asFileHandle()
|
||||
.read()
|
||||
.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 = (
|
||||
_e
|
||||
): void => {
|
||||
@ -941,19 +988,20 @@ namespace OS {
|
||||
* Scheme definition
|
||||
*/
|
||||
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>
|
||||
<div style="text-align:center; margin-top:10px;" data-height="50">
|
||||
<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>
|
||||
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
|
||||
</div>
|
||||
<afx-hbox>
|
||||
<afx-hbox data-height="60">
|
||||
<div data-width="10"></div>
|
||||
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
|
||||
</afx-hbox>
|
||||
|
||||
<div data-id="read-me" style="overflow-x: hidden; overflow-y: auto;"></div>
|
||||
<div data-height="10"></div>
|
||||
<afx-hbox data-height="30">
|
||||
<div ></div>
|
||||
<afx-button data-id = "btnCancel" text = "__(Cancel)" data-width = "60" ></afx-button>
|
||||
@ -1035,12 +1083,6 @@ namespace OS {
|
||||
return reject(d);
|
||||
}
|
||||
FileDialog.last_opened = path;
|
||||
if (!dir.isRoot()) {
|
||||
const p = dir.parent();
|
||||
p.filename = "[..]";
|
||||
p.type = "dir";
|
||||
d.result.unshift(p);
|
||||
}
|
||||
return resolve(d.result);
|
||||
})
|
||||
.catch((e: Error): void => reject(__e(e)));
|
||||
@ -1052,7 +1094,7 @@ namespace OS {
|
||||
__("Resource not found: {0}", path)
|
||||
);
|
||||
}
|
||||
return (fileview.path = path);
|
||||
fileview.path = path;
|
||||
};
|
||||
|
||||
if (!this.data || !this.data.root) {
|
||||
@ -1088,11 +1130,14 @@ namespace OS {
|
||||
}
|
||||
};
|
||||
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
|
||||
const f = fileview.selectedFile;
|
||||
let f = fileview.selectedFile;
|
||||
if (!f) {
|
||||
const sel = location.selectedItem;
|
||||
if(!sel)
|
||||
return this.notify(
|
||||
__("Please select a file/fofler")
|
||||
);
|
||||
f = sel.data as API.FileInfoType;
|
||||
}
|
||||
if (
|
||||
this.data &&
|
||||
@ -1150,6 +1195,18 @@ namespace OS {
|
||||
if (this.data && 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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -43,6 +43,7 @@ namespace OS {
|
||||
*/
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum definition of different model types
|
||||
*
|
||||
@ -192,10 +193,10 @@ namespace OS {
|
||||
* The HTML element ID of the virtual desktop
|
||||
*
|
||||
* @protected
|
||||
* @type {string}
|
||||
* @type {HTMLElement}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected host: string;
|
||||
protected host: HTMLElement;
|
||||
|
||||
/**
|
||||
* The process number of the current model.
|
||||
@ -293,12 +294,8 @@ namespace OS {
|
||||
this._gui = GUI;
|
||||
this.systemsetting = setting;
|
||||
this.on("exit", () => this.quit(false));
|
||||
this.host = this._gui.workspace;
|
||||
this.host = this._gui.desktop();
|
||||
this.dialog = undefined;
|
||||
this.subscribe("systemlocalechange", (name) => {
|
||||
this.updateLocale(name);
|
||||
return this.update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -314,11 +311,10 @@ namespace OS {
|
||||
/**
|
||||
* Update the model locale
|
||||
*
|
||||
* @protected
|
||||
* @param {string} name
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected updateLocale(name: string) {}
|
||||
updateLocale(name: string) {}
|
||||
/**
|
||||
* Render the model's UI
|
||||
*
|
||||
@ -508,7 +504,6 @@ namespace OS {
|
||||
/**
|
||||
* trigger a local event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {*} [d] event data
|
||||
* @returns {void}
|
||||
@ -524,11 +519,11 @@ namespace OS {
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {(d: any) => void} f event callback
|
||||
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
|
||||
* @returns {void}
|
||||
* @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);
|
||||
}
|
||||
|
||||
@ -587,41 +582,39 @@ namespace OS {
|
||||
* @protected
|
||||
* @param {string} t event name
|
||||
* @param {(string | FormattedString)} m event message
|
||||
* @param {Error} [e] error object if any
|
||||
* @param {any} u_data user data object if any
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected publish(
|
||||
t: string,
|
||||
m: string | FormattedString,
|
||||
e?: Error
|
||||
u_data?: any
|
||||
): void {
|
||||
const mt = this.meta();
|
||||
let icon: string = undefined;
|
||||
const data: API.AnnouncementDataType<any> = {} as API.AnnouncementDataType<any>;
|
||||
data.icon = undefined;
|
||||
if (mt && mt.icon) {
|
||||
icon = `${mt.path}/${mt.icon}`;
|
||||
data.icon = `${mt.path}/${mt.icon}`;
|
||||
}
|
||||
return announcer.trigger(t, {
|
||||
id: this.pid,
|
||||
name: this.name,
|
||||
data: {
|
||||
m: m,
|
||||
icon: icon,
|
||||
iconclass: mt?mt.iconclass:undefined,
|
||||
e: e,
|
||||
},
|
||||
});
|
||||
data.id = this.pid;
|
||||
data.name = this.name;
|
||||
data.message = m;
|
||||
data.iconclass = mt?mt.iconclass:undefined;
|
||||
data.u_data = u_data;
|
||||
return announcer.trigger(t, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a global notification
|
||||
*
|
||||
* @param {(string | FormattedString)} m notification string
|
||||
* @param {any} u_data user data object if any
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
notify(m: string | FormattedString): void {
|
||||
return this.publish("notification", m);
|
||||
notify(m: string | FormattedString, data?: any): void {
|
||||
return this.publish("notification", m, data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -677,7 +670,11 @@ namespace OS {
|
||||
*/
|
||||
update(): void {
|
||||
if (this.scheme) {
|
||||
return this.scheme.update();
|
||||
this.scheme.update();
|
||||
}
|
||||
if(this.dialog)
|
||||
{
|
||||
this.dialog.update();
|
||||
}
|
||||
}
|
||||
|
||||
|
123
src/core/core.ts
123
src/core/core.ts
@ -171,6 +171,13 @@ interface Date {
|
||||
* @memberof Date
|
||||
*/
|
||||
timestamp(): number;
|
||||
/**
|
||||
* Covnert to GMTString
|
||||
*
|
||||
* @returns {number}
|
||||
* @memberof Date
|
||||
*/
|
||||
toGMTString(): string;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -252,7 +259,7 @@ namespace OS {
|
||||
};
|
||||
|
||||
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;
|
||||
return reason;
|
||||
};
|
||||
@ -417,13 +424,14 @@ namespace OS {
|
||||
* 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 minor number is 2
|
||||
* - patch version is 3
|
||||
* - the current branch is release `r`
|
||||
* - build ID (optional)
|
||||
* ```
|
||||
*
|
||||
* @export
|
||||
@ -433,10 +441,11 @@ namespace OS {
|
||||
/**
|
||||
* The version string
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof Version
|
||||
*/
|
||||
string: string;
|
||||
private string: string;
|
||||
|
||||
/**
|
||||
* The current branch
|
||||
@ -474,13 +483,40 @@ namespace OS {
|
||||
*/
|
||||
patch: number;
|
||||
|
||||
/**
|
||||
* Version build ID (optional): usually the current git commit hash
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof Version
|
||||
*/
|
||||
build_id: string;
|
||||
|
||||
/**
|
||||
*Creates an instance of Version.
|
||||
*
|
||||
* @param {string} string string represents the version
|
||||
* @memberof Version
|
||||
*/
|
||||
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 br = {
|
||||
r: 3,
|
||||
@ -488,9 +524,14 @@ namespace OS {
|
||||
a: 1,
|
||||
};
|
||||
this.branch = 3;
|
||||
if (arr.length === 2 && br[arr[1]]) {
|
||||
if (arr.length >= 2 && br[arr[1]]) {
|
||||
this.branch = br[arr[1]];
|
||||
if(arr[2])
|
||||
{
|
||||
this.build_id = arr[2];
|
||||
}
|
||||
}
|
||||
|
||||
const mt = arr[0].match(/\d+/g);
|
||||
if (!mt) {
|
||||
API.throwe(
|
||||
@ -510,6 +551,10 @@ namespace OS {
|
||||
this.patch = Number(mt[2]);
|
||||
}
|
||||
}
|
||||
get version_string(): string
|
||||
{
|
||||
return this.string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the current version with another version.
|
||||
@ -754,7 +799,14 @@ namespace OS {
|
||||
* Variable represents the current AntOS version, it
|
||||
* 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.
|
||||
* 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
|
||||
* 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
|
||||
@ -854,15 +906,16 @@ namespace OS {
|
||||
*/
|
||||
export function exit(): void {
|
||||
//do clean up first
|
||||
const promises: Promise<any>[] = [];
|
||||
for (let n in cleanupHandles) {
|
||||
const f = cleanupHandles[n];
|
||||
f();
|
||||
promises.push(cleanupHandles[n]());
|
||||
}
|
||||
API.handle
|
||||
.setting()
|
||||
.then(function (r: any) {
|
||||
promises.push(API.handle.setting());
|
||||
Promise.all(promises)
|
||||
.then(async function (r: any) {
|
||||
cleanup();
|
||||
return API.handle.logout().then((d: any) => boot());
|
||||
const d = await API.handle.logout();
|
||||
return boot();
|
||||
})
|
||||
.catch((e: Error) => console.error(e));
|
||||
}
|
||||
@ -875,7 +928,7 @@ namespace OS {
|
||||
* @param {() => void} f the callback handle
|
||||
* @returns
|
||||
*/
|
||||
export function onexit(n: string, f: () => void) {
|
||||
export function onexit(n: string, f: () => Promise<any>) {
|
||||
if (!cleanupHandles[n]) {
|
||||
return (cleanupHandles[n] = f);
|
||||
}
|
||||
@ -1215,13 +1268,13 @@ namespace OS {
|
||||
o.on("change", function () {
|
||||
const files = (o[0] as HTMLInputElement).files;
|
||||
const n_files = files.length;
|
||||
const tasks = [];
|
||||
if (n_files > 0)
|
||||
API.loading(q, p);
|
||||
Array.from(files).forEach(file => {
|
||||
const formd = new FormData();
|
||||
formd.append("path", d);
|
||||
formd.append("upload", file);
|
||||
jQuery.each(files, (i, file) => {
|
||||
formd.append(`upload-${i}`, file);
|
||||
});
|
||||
return $.ajax({
|
||||
url: p,
|
||||
data: formd,
|
||||
@ -1230,25 +1283,15 @@ namespace OS {
|
||||
processData: false,
|
||||
})
|
||||
.done(function (data) {
|
||||
tasks.push("OK");
|
||||
if (tasks.length == n_files)
|
||||
{
|
||||
API.loaded(q, p, "OK");
|
||||
resolve(data);
|
||||
o.remove();
|
||||
}
|
||||
})
|
||||
.fail(function (j, s, e) {
|
||||
tasks.push("FAIL");
|
||||
if (tasks.length == n_files)
|
||||
{
|
||||
API.loaded(q, p, "FAIL");
|
||||
o.remove();
|
||||
}
|
||||
reject(API.throwe(s));
|
||||
});
|
||||
});
|
||||
});
|
||||
return o.trigger("click");
|
||||
});
|
||||
}
|
||||
@ -1284,11 +1327,12 @@ namespace OS {
|
||||
* @param {string} p message string
|
||||
*/
|
||||
export function loading(q: number, p: string): void {
|
||||
announcer.trigger("loading", {
|
||||
id: q,
|
||||
data: { m: `${p}`, s: true },
|
||||
name: "OS",
|
||||
});
|
||||
const data:API.AnnouncementDataType<number> = {} as API.AnnouncementDataType<number>;
|
||||
data.id = q;
|
||||
data.message = p;
|
||||
data.name = p;
|
||||
data.u_data = PM.pidactive;
|
||||
announcer.trigger("loading", data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1303,11 +1347,12 @@ namespace OS {
|
||||
* @param {string} m message status (`OK` of `FAIL`)
|
||||
*/
|
||||
export function loaded(q: number, p: string, m: string): void {
|
||||
announcer.trigger("loaded", {
|
||||
id: q,
|
||||
data: { m: `${m}: ${p}`, s: false },
|
||||
name: "OS",
|
||||
});
|
||||
const data:API.AnnouncementDataType<boolean> = {} as API.AnnouncementDataType<boolean>;
|
||||
data.id = q;
|
||||
data.message = p;
|
||||
data.name = "OS";
|
||||
data.u_data = false;
|
||||
announcer.trigger("loaded", data);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1612,7 +1657,7 @@ namespace OS {
|
||||
const d = await API.get(path, "json");
|
||||
OS.setting.system.locale = name;
|
||||
API.lang = d;
|
||||
announcer.trigger("systemlocalechange", name);
|
||||
announcer.ostrigger("systemlocalechange", name);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
|
380
src/core/gui.ts
380
src/core/gui.ts
@ -119,15 +119,16 @@ namespace OS {
|
||||
app: BaseModel,
|
||||
parent: Element | string
|
||||
): void {
|
||||
const scheme = $.parseHTML(html);
|
||||
const scheme = $.parseHTML(html)[0];
|
||||
if (app.scheme) {
|
||||
$(app.scheme).remove();
|
||||
}
|
||||
$(parent as GenericObject<any>).append(scheme);
|
||||
app.scheme = scheme[0] as HTMLElement;
|
||||
(parent as HTMLElement).append(scheme);
|
||||
app.scheme = scheme as HTMLElement;
|
||||
app.scheme.uify(app.observable, true);
|
||||
app.main();
|
||||
app.show();
|
||||
app.observable.trigger("launched",undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -182,6 +183,28 @@ namespace OS {
|
||||
$("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.
|
||||
*
|
||||
@ -257,10 +280,7 @@ namespace OS {
|
||||
return false;
|
||||
});
|
||||
} catch (e) {
|
||||
return announcer.osfail(
|
||||
__("Error find app by mimes {0}", mime),
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
let arr: string[];
|
||||
@ -380,11 +400,43 @@ namespace OS {
|
||||
* @export
|
||||
* @param {string} app
|
||||
*/
|
||||
export function unloadApp(app: string): void {
|
||||
export function unloadApp(app: string, save?: boolean): void {
|
||||
PM.killAll(app, true);
|
||||
if (application[app] && application[app].style) {
|
||||
$(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];
|
||||
}
|
||||
|
||||
@ -396,19 +448,37 @@ namespace OS {
|
||||
* definition in the [[application]] namespace, then update
|
||||
* the system packages meta-data
|
||||
*
|
||||
* First it tries to load the package with the app name is also the
|
||||
* pkgname, if its fail, it will search the app name by pkg name and load
|
||||
* it
|
||||
*
|
||||
* @param {string} app application class name
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
function loadApp(app: string): Promise<string> {
|
||||
return new Promise(async function (resolve, reject) {
|
||||
let path: string;
|
||||
if (setting.system.packages[app].path) {
|
||||
let path: string = undefined;
|
||||
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;
|
||||
}
|
||||
if(!path)
|
||||
{
|
||||
throw __("Unable to locate package of {0}", app).__();
|
||||
}
|
||||
const js = path + "/main.js";
|
||||
try {
|
||||
const d = await js.asFileHandle().read("script");
|
||||
try {
|
||||
const data: API.PackageMetaType = await `${path}/package.json`
|
||||
.asFileHandle()
|
||||
.read("json");
|
||||
@ -422,9 +492,9 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
//load css file
|
||||
try{
|
||||
const css = `${path}/main.css`;
|
||||
try {
|
||||
const d_1 = await css.asFileHandle().onready();
|
||||
await css.asFileHandle().onready();
|
||||
const stamp = new Date().timestamp();
|
||||
const el = $("<link>", {
|
||||
rel: "stylesheet",
|
||||
@ -434,15 +504,10 @@ namespace OS {
|
||||
if (application[app]) {
|
||||
application[app].style = el[0];
|
||||
}
|
||||
} catch(e_1){}
|
||||
return resolve(app);
|
||||
} catch (e) {
|
||||
return resolve(app);
|
||||
}
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
} catch (e_2) {
|
||||
return reject(__e(e_2));
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -462,36 +527,23 @@ namespace OS {
|
||||
const arr = ph.split("/");
|
||||
const srv = arr[1];
|
||||
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 {
|
||||
if (!application[srv]) {
|
||||
await loadApp(app);
|
||||
if (!application[srv]) {
|
||||
return reject(
|
||||
API.throwe(__("Service not found: {0}", ph))
|
||||
);
|
||||
}
|
||||
try {
|
||||
const d_1 = await PM.createProcess(
|
||||
}
|
||||
const d = await PM.createProcess(
|
||||
srv,
|
||||
application[srv]
|
||||
);
|
||||
return resolve(d_1);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
} catch (e_2) {
|
||||
return reject(__e(e_2));
|
||||
return resolve(d);
|
||||
}
|
||||
catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -532,11 +584,13 @@ namespace OS {
|
||||
* @param {AppArgumentsType[]} args application arguments
|
||||
*/
|
||||
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]) {
|
||||
// first load it
|
||||
loadApp(app)
|
||||
.then((a) => {
|
||||
await loadApp(app);
|
||||
if (!application[app]) {
|
||||
const e = API.throwe(__("Application not found"));
|
||||
announcer.oserror(
|
||||
@ -544,47 +598,31 @@ namespace OS {
|
||||
e);
|
||||
return reject(e);
|
||||
}
|
||||
PM.createProcess(
|
||||
const p = await PM.createProcess(
|
||||
app,
|
||||
application[app],
|
||||
args
|
||||
).catch((e) => {
|
||||
);
|
||||
resolve(p);
|
||||
} else {
|
||||
// now launch it
|
||||
const p = await PM.createProcess(
|
||||
app,
|
||||
application[app],
|
||||
args
|
||||
);
|
||||
resolve(p);
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
announcer.osfail(
|
||||
__("Unable to launch: {0}", app),
|
||||
e
|
||||
);
|
||||
return reject(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);
|
||||
PM.pidactive = pidactive;
|
||||
return reject(__e(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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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:
|
||||
*
|
||||
@ -949,7 +943,7 @@ namespace OS {
|
||||
$("#systooltip")[0].uify(undefined);
|
||||
$("#contextmenu")[0].uify(undefined);
|
||||
|
||||
$("#wrapper").on("contextmenu",(e) => bindContextMenu(e));
|
||||
$("#wrapper").on("contextmenu", (e) => bindContextMenu(e));
|
||||
// tooltip
|
||||
$(document).on("mouseover", function (e) {
|
||||
const el: any = $(e.target).closest("[tooltip]");
|
||||
@ -962,115 +956,8 @@ namespace OS {
|
||||
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
|
||||
desktop.uify(undefined);
|
||||
desktop().uify(undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1079,7 +966,7 @@ namespace OS {
|
||||
* @export
|
||||
*/
|
||||
export function refreshDesktop(): void {
|
||||
dkfetch($(workspace)[0] as tag.FloatListTag);
|
||||
desktop().refresh();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1090,7 +977,11 @@ namespace OS {
|
||||
* @export
|
||||
*/
|
||||
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);
|
||||
$("#btlogin").on("click", async function () {
|
||||
const data: API.UserLoginType = {
|
||||
@ -1143,7 +1034,7 @@ namespace OS {
|
||||
loadTheme(setting.appearance.theme, true);
|
||||
wallpaper(undefined);
|
||||
OS.announcer.observable.one("syspanelloaded", async function () {
|
||||
OS.announcer.observable.on("systemlocalechange", (name) =>
|
||||
OS.announcer.observable.on("systemlocalechange", (_) =>
|
||||
$("#syspanel")[0].update()
|
||||
);
|
||||
|
||||
@ -1152,9 +1043,7 @@ namespace OS {
|
||||
return API.packages.fetch().then(function (r) {
|
||||
let v: API.PackageMetaType;
|
||||
if (r.result) {
|
||||
const result = r.result as GenericObject<
|
||||
API.PackageMetaType
|
||||
>;
|
||||
const result = r.result as GenericObject<API.PackageMetaType>;
|
||||
for (let k in result) {
|
||||
v = result[k];
|
||||
v.text = v.name;
|
||||
@ -1172,11 +1061,10 @@ namespace OS {
|
||||
? result
|
||||
: undefined;
|
||||
}
|
||||
|
||||
// GUI.refreshSystemMenu()
|
||||
// GUI.buildSystemMenu()
|
||||
// push startup services
|
||||
// TODO: get services list from user setting
|
||||
// load services + VFSX
|
||||
Promise.all(
|
||||
[
|
||||
OS.API.VFS.loadVFSX(true),
|
||||
pushServices(
|
||||
(() => {
|
||||
const result = [];
|
||||
@ -1185,7 +1073,9 @@ namespace OS {
|
||||
}
|
||||
return result;
|
||||
})()
|
||||
).then(function () {
|
||||
)
|
||||
])
|
||||
.then(function () {
|
||||
setting.system.startup.apps.map((a) => {
|
||||
launch(a, []);
|
||||
});
|
||||
@ -1193,14 +1083,13 @@ namespace OS {
|
||||
});
|
||||
}
|
||||
});
|
||||
//GUI.launch "DummyApp"
|
||||
// initDM
|
||||
API.setLocale(setting.system.locale).then(() => initDM());
|
||||
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) {
|
||||
console.log(d.data.e);
|
||||
console.log(d.u_data);
|
||||
});
|
||||
}
|
||||
/**
|
||||
@ -1217,7 +1106,7 @@ namespace OS {
|
||||
<afx-sys-panel id = "syspanel"></afx-sys-panel>
|
||||
<div id = "workspace">
|
||||
<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>
|
||||
<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>
|
||||
@ -1231,7 +1120,8 @@ namespace OS {
|
||||
<input id = "txtpass" type = "password" value = "demo" ></input>
|
||||
<button id = "btlogin">Login</button>
|
||||
<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>\
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,10 @@ namespace OS {
|
||||
/**
|
||||
* All running processes is stored in this variables
|
||||
*/
|
||||
/**
|
||||
* Current active process ID
|
||||
*/
|
||||
export var pidactive: number = 0;
|
||||
export var processes: GenericObject<BaseModel[]> = {};
|
||||
/**
|
||||
* Create a new process of application or service
|
||||
@ -63,6 +67,10 @@ namespace OS {
|
||||
obj.birth = new Date().getTime();
|
||||
PM.pidalloc++;
|
||||
obj.pid = PM.pidalloc;
|
||||
obj.subscribe("systemlocalechange", (d) => {
|
||||
obj.updateLocale(d.message as string);
|
||||
return obj.update();
|
||||
});
|
||||
PM.processes[app].push(obj);
|
||||
if (metaclass.type === ModelType.Application) {
|
||||
GUI.dock(
|
||||
@ -149,7 +157,25 @@ namespace OS {
|
||||
if (!PM.processes[app]) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -162,6 +162,7 @@ namespace OS {
|
||||
}
|
||||
this._selectedItem = el;
|
||||
if (!el) {
|
||||
PM.pidactive = 0;
|
||||
return;
|
||||
}
|
||||
$(el.domel).addClass("selected");
|
||||
|
272
src/core/tags/DesktopTag.ts
Normal file
272
src/core/tags/DesktopTag.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,13 +28,13 @@ namespace OS {
|
||||
private _onfileopen: TagEventCallback<API.FileInfoType>;
|
||||
|
||||
/**
|
||||
* Reference to the currently selected file meta-data
|
||||
* Reference to the all selected files meta-datas
|
||||
*
|
||||
* @private
|
||||
* @type {API.FileInfoType}
|
||||
* @type {API.FileInfoType[]}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _selectedFile: API.FileInfoType;
|
||||
private _selectedFiles: API.FileInfoType[];
|
||||
|
||||
/**
|
||||
* Data placeholder of the current working directory
|
||||
@ -58,10 +58,10 @@ namespace OS {
|
||||
* Header definition of the widget grid view
|
||||
*
|
||||
* @private
|
||||
* @type {(GenericObject<string | number>[])}
|
||||
* @type {(GenericObject<any>[])}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _header: GenericObject<string | number>[];
|
||||
private _header: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
* placeholder for the user-specified meta-data fetch function
|
||||
@ -92,10 +92,56 @@ namespace OS {
|
||||
this.chdir = true;
|
||||
this.view = "list";
|
||||
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 = [
|
||||
{ text: "__(File name)" },
|
||||
{ text: "__(Type)" },
|
||||
{ text: "__(Size)" },
|
||||
{
|
||||
text: "__(File name)",
|
||||
sort: fn
|
||||
},
|
||||
{
|
||||
text: "__(Modified)",
|
||||
sort: fn
|
||||
},
|
||||
{
|
||||
text: "__(Size)",
|
||||
sort: fn
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@ -227,6 +273,28 @@ namespace OS {
|
||||
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
|
||||
*
|
||||
@ -235,7 +303,21 @@ namespace OS {
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
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(
|
||||
v: TagEventCallback<
|
||||
DnDEventDataType<TreeViewTag | ListViewItemTag>
|
||||
DnDEventDataType<TreeViewTag | ListViewItemTag | GridCellPrototype>
|
||||
>
|
||||
) {
|
||||
(this.refs.treeview as TreeViewTag).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) {
|
||||
return;
|
||||
}
|
||||
if(!v.text)
|
||||
v.text = v.filename;
|
||||
/*
|
||||
if (v.text.length > 10) {
|
||||
v.text = v.text.substring(0, 9) + "...";
|
||||
}
|
||||
}*/
|
||||
v.iconclass = v.iconclass ? v.iconclass : v.type;
|
||||
v.icon = v.icon;
|
||||
if(v.icon)
|
||||
v.iconclass = undefined;
|
||||
items.push(v);
|
||||
});
|
||||
(this.refs.listview as ListViewTag).data = items;
|
||||
@ -412,12 +507,19 @@ namespace OS {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return;
|
||||
}
|
||||
v.text = v.filename;
|
||||
v.iconclass = v.iconclass ? v.iconclass : v.type;
|
||||
if(v.icon)
|
||||
v.iconclass = undefined;
|
||||
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,
|
||||
},
|
||||
{
|
||||
@ -467,13 +569,15 @@ namespace OS {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return undefined;
|
||||
}
|
||||
if(!v.text)
|
||||
v.text = v.filename;
|
||||
if (v.type === "dir") {
|
||||
v.nodes = [];
|
||||
v.open = false;
|
||||
}
|
||||
v.iconclass = v.iconclass ? v.iconclass : v.type;
|
||||
v.icon = v.icon;
|
||||
if(v.icon)
|
||||
v.iconclass = undefined;
|
||||
return nodes.push(v);
|
||||
});
|
||||
return nodes;
|
||||
@ -511,7 +615,7 @@ namespace OS {
|
||||
$(this.refs.listview).hide();
|
||||
$(this.refs.gridview).hide();
|
||||
$(this.refs.treecontainer).hide();
|
||||
this._selectedFile = undefined;
|
||||
this._selectedFiles = [];
|
||||
switch (this.view) {
|
||||
case "icon":
|
||||
$(this.refs.listview).show();
|
||||
@ -549,7 +653,6 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
const evt = { id: this.aid, data: e };
|
||||
this._selectedFile = e;
|
||||
this._onfileselect(evt);
|
||||
this.observable.trigger("fileselect", evt);
|
||||
}
|
||||
@ -609,25 +712,29 @@ namespace OS {
|
||||
grid.header = this._header;
|
||||
tree.dragndrop = true;
|
||||
list.dragndrop = true;
|
||||
grid.dragndrop = true;
|
||||
// even handles
|
||||
list.onlistselect = (e) => {
|
||||
this.fileselect(e.data.item.data as API.FileInfoType);
|
||||
this._selectedFiles = e.data.items.map( x => x.data as API.FileInfoType);
|
||||
};
|
||||
grid.onrowselect = (e) => {
|
||||
this.fileselect(
|
||||
$(e.data.item).children()[0]
|
||||
.data as API.FileInfoType
|
||||
($(e.data.item).children()[0] as GridCellPrototype)
|
||||
.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) => {
|
||||
this.fileselect(e.data.item.data as API.FileInfoType);
|
||||
this._selectedFiles = [e.data.item.data as API.FileInfoType];
|
||||
};
|
||||
// dblclick
|
||||
list.onlistdbclick = (e) => {
|
||||
this.filedbclick(e.data.item.data as API.FileInfoType);
|
||||
};
|
||||
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) => {
|
||||
this.filedbclick(e.data.item.data as API.FileInfoType);
|
||||
|
@ -19,6 +19,10 @@ interface Array<T> {
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Row item event data type
|
||||
*/
|
||||
export type GridRowEventData = TagEventDataType<GridRowTag>;
|
||||
/**
|
||||
* A grid Row is a simple element that
|
||||
* contains a group of grid cell
|
||||
@ -36,6 +40,15 @@ namespace OS {
|
||||
*/
|
||||
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.
|
||||
* @memberof GridRowTag
|
||||
@ -44,6 +57,36 @@ namespace OS {
|
||||
super();
|
||||
|
||||
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
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
const label = (this.refs.cell as LabelTag);
|
||||
label.icon = undefined;
|
||||
label.iconclass = undefined;
|
||||
(this.refs.cell as LabelTag).set(this.data);
|
||||
}
|
||||
|
||||
@ -420,6 +466,27 @@ namespace OS {
|
||||
*/
|
||||
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.
|
||||
* @memberof GridViewTag
|
||||
@ -428,6 +495,68 @@ namespace OS {
|
||||
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.
|
||||
* Reset all the placeholders to default values
|
||||
@ -444,9 +573,13 @@ namespace OS {
|
||||
this._selectedRow = undefined;
|
||||
this._rows = [];
|
||||
this.resizable = false;
|
||||
this.dragndrop = false;
|
||||
this._oncellselect = this._onrowselect = this._oncelldbclick = (
|
||||
e: TagEventType<CellEventData>
|
||||
): void => {};
|
||||
this._ondragndrop = (
|
||||
e: TagEventType<DnDEventDataType<GridRowTag>>
|
||||
) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
@ -507,7 +640,14 @@ namespace OS {
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set cellitem(v: string) {
|
||||
const currci = this.cellitem;
|
||||
$(this).attr("cellitem", v);
|
||||
if(v != currci)
|
||||
{
|
||||
// force render data
|
||||
$(this.refs.grid).empty();
|
||||
this.rows = this.rows;
|
||||
}
|
||||
}
|
||||
get cellitem(): string {
|
||||
return $(this).attr("cellitem");
|
||||
@ -539,6 +679,12 @@ namespace OS {
|
||||
element.uify(this.observable);
|
||||
element.data = item;
|
||||
item.domel = element;
|
||||
element.oncellselect = (e) => {
|
||||
if(element.data.sort)
|
||||
{
|
||||
this.sort(element.data, element.data.sort);
|
||||
}
|
||||
};
|
||||
i++;
|
||||
if (this.resizable) {
|
||||
if (i != v.length) {
|
||||
@ -604,9 +750,48 @@ namespace OS {
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set rows(rows: GenericObject<any>[][]) {
|
||||
$(this.refs.grid).empty();
|
||||
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>[][] {
|
||||
return this._rows;
|
||||
@ -639,7 +824,24 @@ namespace OS {
|
||||
get resizable(): boolean {
|
||||
return this.hasattr("resizable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort the grid using a sort function
|
||||
*
|
||||
* @param {context: any} context of the executed function
|
||||
* @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data
|
||||
* * @param {index: number} current header index
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
sort(context: any, fn: (a:GenericObject<any>[], b:GenericObject<any>[], index?: number) => number): void {
|
||||
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
|
||||
*
|
||||
@ -713,6 +915,10 @@ namespace OS {
|
||||
element.oncelldbclick = (e) => this.cellselect(e, true);
|
||||
element.data = cell;
|
||||
}
|
||||
el.onrowselect = (e) => this.rowselect({
|
||||
id: el.aid,
|
||||
data: {item: el}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -751,7 +957,10 @@ namespace OS {
|
||||
} else {
|
||||
this.observable.trigger("cellselect", 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
|
||||
* event will also trigger this event
|
||||
*
|
||||
* @param {TagEventType<CellEventData>} e
|
||||
* @param {TagEventType<GridRowEventData>} e
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private rowselect(e: TagEventType<CellEventData>): void {
|
||||
private rowselect(e: TagEventType<GridRowEventData>): void {
|
||||
if (!e.data.item) {
|
||||
return;
|
||||
}
|
||||
@ -774,39 +983,58 @@ namespace OS {
|
||||
items: [],
|
||||
},
|
||||
};
|
||||
const row = ($(
|
||||
e.data.item
|
||||
).parent()[0] as any) as GridRowTag;
|
||||
const row = e.data.item as GridRowTag;
|
||||
if (this.multiselect) {
|
||||
if (this.selectedRows.includes(row)) {
|
||||
this.selectedRows.splice(
|
||||
this.selectedRows.indexOf(row),
|
||||
1
|
||||
);
|
||||
$(row).removeClass();
|
||||
row.selected = false;
|
||||
return;
|
||||
} else {
|
||||
this.selectedRows.push(row);
|
||||
$(row)
|
||||
.removeClass()
|
||||
.addClass("afx-grid-row-selected");
|
||||
}
|
||||
evt.data.items = this.selectedRows;
|
||||
} else {
|
||||
if(this.selectedRows.length > 0)
|
||||
{
|
||||
for(const item of this.selectedRows)
|
||||
{
|
||||
if(item != row)
|
||||
{
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.selectedRow === row) {
|
||||
return;
|
||||
}
|
||||
$(this.selectedRow).removeClass();
|
||||
this._selectedRows = [row];
|
||||
evt.data.item = row;
|
||||
if(this.selectedRow)
|
||||
this.selectedRow.selected = false;
|
||||
evt.data.items = [row];
|
||||
$(row).removeClass().addClass("afx-grid-row-selected");
|
||||
this._selectedRows = [row];
|
||||
}
|
||||
evt.data.item = row;
|
||||
this._selectedRow = row;
|
||||
this._onrowselect(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
|
||||
*
|
||||
@ -904,7 +1132,6 @@ namespace OS {
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this).css("overflow", "hidden");
|
||||
|
||||
$(this.refs.grid).css("display", "grid");
|
||||
$(this.refs.header).css("display", "grid");
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
@ -912,6 +1139,73 @@ namespace OS {
|
||||
.css("width", "100%")
|
||||
.css("overflow-x", "hidden")
|
||||
.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();
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,16 @@ namespace OS {
|
||||
* @protected
|
||||
* @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
|
||||
@ -56,6 +65,7 @@ namespace OS {
|
||||
this.icon = undefined;
|
||||
this.iconclass = undefined;
|
||||
this.text = undefined;
|
||||
this.selectable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -121,6 +131,33 @@ namespace OS {
|
||||
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
|
||||
*
|
||||
|
@ -444,10 +444,10 @@ namespace OS {
|
||||
* drag and drop on the list
|
||||
*
|
||||
* @private
|
||||
* @type {{ from: ListViewItemTag; to: ListViewItemTag }}
|
||||
* @type {{ from: ListViewItemTag[]; to: ListViewItemTag }}
|
||||
* @memberof ListViewTag
|
||||
*/
|
||||
private _dnd: { from: ListViewItemTag; to: ListViewItemTag };
|
||||
private _dnd: { from: ListViewItemTag[]; to: ListViewItemTag };
|
||||
|
||||
/**
|
||||
*Creates an instance of ListViewTag.
|
||||
@ -990,6 +990,16 @@ namespace OS {
|
||||
this.selectedItems.push(e.data);
|
||||
edata.items = this.selectedItems;
|
||||
} else {
|
||||
if(this.selectedItems.length > 0)
|
||||
{
|
||||
for(const item of this.selectedItems)
|
||||
{
|
||||
if(item != e.data)
|
||||
{
|
||||
item.selected = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (this.selectedItem === e.data) {
|
||||
return;
|
||||
}
|
||||
@ -1044,14 +1054,22 @@ namespace OS {
|
||||
to: undefined,
|
||||
};
|
||||
this._onmousedown = (e) => {
|
||||
if(this.multiselect || this.selectedItems == undefined || this.selectedItems.length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let el: any = $(e.target).closest(
|
||||
"li[dataref='afx-list-item']"
|
||||
);
|
||||
if (el.length === 0) {
|
||||
return;
|
||||
}
|
||||
el = el.parent()[0] as ListViewItemTag;
|
||||
this._dnd.from = el;
|
||||
el = el.parent()[0];
|
||||
if(!this.selectedItems.includes(el))
|
||||
{
|
||||
return;
|
||||
}
|
||||
this._dnd.from = this.selectedItems;
|
||||
this._dnd.to = undefined;
|
||||
$(window).on("mouseup", this._onmouseup);
|
||||
$(window).on("mousemove", this._onmousemove);
|
||||
@ -1068,7 +1086,7 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
el = el.parent()[0];
|
||||
if (el === this._dnd.from) {
|
||||
if (this._dnd.from.includes(el)) {
|
||||
return;
|
||||
}
|
||||
this._dnd.to = el;
|
||||
@ -1086,7 +1104,18 @@ namespace OS {
|
||||
if (!this._dnd.from) {
|
||||
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 top = e.clientY + 5;
|
||||
const left = e.clientX + 5;
|
||||
@ -1097,7 +1126,7 @@ namespace OS {
|
||||
.css("top", top + "px")
|
||||
.css("left", left + "px");
|
||||
};
|
||||
|
||||
$(this.refs.drlabel).css("display", "inline-block");
|
||||
$(this.refs.btlist).hide();
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
return this.calibrate();
|
||||
|
@ -386,14 +386,14 @@ namespace OS {
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set icon(v: string) {
|
||||
$(this.refs.container).removeClass("fix_padding");
|
||||
//$(this.refs.container).removeClass("fix_padding");
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
//$(this).attr("icon", v);
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.icon = v;
|
||||
$(this.refs.container).addClass("fix_padding");
|
||||
//$(this.refs.container).addClass("fix_padding");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -536,17 +536,21 @@ namespace OS {
|
||||
}
|
||||
m.show(e);
|
||||
};
|
||||
announcer.observable.on("app-pinned", (d) => {
|
||||
announcer.observable.on("app-pinned", (_) => {
|
||||
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);
|
||||
if(!$(this.refs.panel).hasClass("loading"))
|
||||
$(this.refs.panel).addClass("loading");
|
||||
$(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);
|
||||
if (i >= 0) {
|
||||
this._pending_task.splice(i, 1);
|
||||
|
@ -294,7 +294,8 @@ namespace OS {
|
||||
.css("padding", 0)
|
||||
.css("margin", 0)
|
||||
.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)
|
||||
.css("padding", 0)
|
||||
.css("margin", 0)
|
||||
.css("display","flex")
|
||||
.css("flex-direction", "row")
|
||||
.css("align-items", "center")
|
||||
.css("white-space", "nowrap");
|
||||
$(this.refs.itemholder).css("display", "inline-block");
|
||||
$(this.refs.wrapper).on("click",(e) => {
|
||||
@ -388,6 +392,7 @@ namespace OS {
|
||||
$(this.refs.toggle)
|
||||
.css("display", "inline-block")
|
||||
.css("width", "15px")
|
||||
.css("flex-shrink", 0)
|
||||
.addClass("afx-tree-view-item")
|
||||
.on("click",(e) => {
|
||||
this.open = !this.open;
|
||||
@ -586,10 +591,10 @@ namespace OS {
|
||||
* Private data object passing between dragndrop mouse event
|
||||
*
|
||||
* @private
|
||||
* @type {{ from: TreeViewTag; to: TreeViewTag }}
|
||||
* @type {{ from: TreeViewTag[]; to: TreeViewTag }}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _dnd: { from: TreeViewTag; to: TreeViewTag };
|
||||
private _dnd: { from: TreeViewTag[]; to: TreeViewTag };
|
||||
|
||||
/**
|
||||
* Reference to parent tree of the current tree.
|
||||
@ -638,7 +643,7 @@ namespace OS {
|
||||
* current tree. This function should return a promise on
|
||||
* a list of [[TreeViewDataType]]
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
fetch: (
|
||||
d: TreeViewItemPrototype
|
||||
@ -920,7 +925,7 @@ namespace OS {
|
||||
*/
|
||||
protected mount(): void {
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
from: [],
|
||||
to: undefined,
|
||||
};
|
||||
this._treemousedown = (e) => {
|
||||
@ -932,7 +937,7 @@ namespace OS {
|
||||
if (el === this) {
|
||||
return;
|
||||
}
|
||||
this._dnd.from = el;
|
||||
this._dnd.from = [el];
|
||||
this._dnd.to = undefined;
|
||||
$(window).on("mouseup", this._treemouseup);
|
||||
return $(window).on("mousemove", this._treemousemove);
|
||||
@ -951,8 +956,8 @@ namespace OS {
|
||||
el = el.parent;
|
||||
}
|
||||
if (
|
||||
el === this._dnd.from ||
|
||||
el === this._dnd.from.parent
|
||||
el === this._dnd.from[0] ||
|
||||
el === this._dnd.from[0].parent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -962,7 +967,7 @@ namespace OS {
|
||||
data: this._dnd,
|
||||
});
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
from: [],
|
||||
to: undefined,
|
||||
};
|
||||
};
|
||||
@ -974,7 +979,7 @@ namespace OS {
|
||||
if (!this._dnd.from) {
|
||||
return;
|
||||
}
|
||||
const data = this._dnd.from.data;
|
||||
const data = this._dnd.from[0].data;
|
||||
const $label = $("#systooltip");
|
||||
const top = e.clientY + 5;
|
||||
const left = e.clientX + 5;
|
||||
|
@ -80,6 +80,23 @@ namespace OS {
|
||||
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
|
||||
* - `shown`: false
|
||||
@ -108,7 +125,7 @@ namespace OS {
|
||||
* @protected
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
protected calibrate(): void { }
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
@ -117,7 +134,7 @@ namespace OS {
|
||||
* @param {*} [d]
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
protected reload(d?: any): void { }
|
||||
|
||||
/**
|
||||
* Setter: Set the window width
|
||||
@ -245,18 +262,18 @@ namespace OS {
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this.contextmenuHandle = function (e) {};
|
||||
$(this.refs["minbt"]).on("click",(e) => {
|
||||
this.contextmenuHandle = function (e) { };
|
||||
$(this.refs["minbt"]).on("click", (e) => {
|
||||
return this.observable.trigger("hide", {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
|
||||
$(this.refs["maxbt"]).on("click",(e) => {
|
||||
$(this.refs["maxbt"]).on("click", (e) => {
|
||||
return this.toggle_window();
|
||||
});
|
||||
|
||||
$(this.refs["closebt"]).on("click",(e) => {
|
||||
$(this.refs["closebt"]).on("click", (e) => {
|
||||
return this.observable.trigger("exit", {
|
||||
id: this.aid,
|
||||
});
|
||||
@ -267,7 +284,7 @@ namespace OS {
|
||||
.css("position", "absolute")
|
||||
.css("left", `${left}px`)
|
||||
.css("top", `${top}px`)
|
||||
.css("z-index", Ant.OS.GUI.zindex++);
|
||||
.css("z-index", 10);
|
||||
$(this).on("mousedown", (e) => {
|
||||
if (this._shown) {
|
||||
return;
|
||||
@ -276,25 +293,27 @@ namespace OS {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
|
||||
$(this.refs["dragger"]).on("dblclick",(e) => {
|
||||
//$(this.refs.win_overlay).css("background-color", "red");
|
||||
$(this.refs["dragger"]).on("dblclick", (e) => {
|
||||
return this.toggle_window();
|
||||
});
|
||||
|
||||
this.observable.on("resize", (e) => this.resize());
|
||||
|
||||
this.observable.on("focus", () => {
|
||||
Ant.OS.GUI.zindex++;
|
||||
$(this)
|
||||
.show()
|
||||
.css("z-index", Ant.OS.GUI.zindex)
|
||||
.removeClass("unactive");
|
||||
this._shown = true;
|
||||
$(this.refs.win_overlay).hide();
|
||||
$(this).trigger("focus");
|
||||
});
|
||||
|
||||
this.observable.on("blur", () => {
|
||||
this._shown = false;
|
||||
return $(this).addClass("unactive");
|
||||
$(this).addClass("unactive");
|
||||
if(this.blur_overlay)
|
||||
$(this.refs.win_overlay).show();
|
||||
});
|
||||
this.observable.on("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_resize();
|
||||
this.setsize({
|
||||
w: this.width,
|
||||
h: this.height,
|
||||
});
|
||||
$(this).attr("tabindex", 0).css("outline", "none");
|
||||
return this.observable.trigger("rendered", {
|
||||
id: this.aid,
|
||||
});
|
||||
@ -408,12 +437,10 @@ namespace OS {
|
||||
let w = $(this).width();
|
||||
let h = $(this).height();
|
||||
$(this.refs.win_overlay).show();
|
||||
if(target != this.refs.grip_bottom)
|
||||
{
|
||||
if (target != this.refs.grip_bottom) {
|
||||
w += e.clientX - offset.left;
|
||||
}
|
||||
if(target != this.refs.grip_right)
|
||||
{
|
||||
if (target != this.refs.grip_right) {
|
||||
h += e.clientY - offset.top;
|
||||
}
|
||||
w = w < 100 ? 100 : w;
|
||||
@ -506,6 +533,7 @@ namespace OS {
|
||||
children: [
|
||||
{
|
||||
el: "ul",
|
||||
ref: 'panel',
|
||||
class: "afx-window-top",
|
||||
children: [
|
||||
{
|
||||
|
@ -233,7 +233,7 @@ namespace OS {
|
||||
* @type {T}
|
||||
* @memberof DnDEventDataType
|
||||
*/
|
||||
from: T;
|
||||
from: T[];
|
||||
|
||||
/**
|
||||
* Reference to the target DOM element
|
||||
@ -247,11 +247,6 @@ namespace OS {
|
||||
* Tag event callback type
|
||||
*/
|
||||
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
|
||||
* subclass of this class
|
||||
|
405
src/core/vfs.ts
405
src/core/vfs.ts
@ -165,6 +165,8 @@ namespace OS {
|
||||
*/
|
||||
export namespace VFS {
|
||||
declare var JSZip: any;
|
||||
/** path for custom VFS protocol handles */
|
||||
const VFSX_PATH = "pkg://vfsx/vfsx.js";
|
||||
|
||||
String.prototype.asFileHandle = function (): BaseFileHandle {
|
||||
const list = this.split("://");
|
||||
@ -198,7 +200,27 @@ namespace OS {
|
||||
): void {
|
||||
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
|
||||
*
|
||||
@ -251,10 +273,11 @@ namespace OS {
|
||||
/**
|
||||
* Once read, file content will be cached in this placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {*}
|
||||
* @memberof BaseFileHandle
|
||||
*/
|
||||
cache: any;
|
||||
private _cache: any;
|
||||
|
||||
/**
|
||||
* Flag indicated whether the file meta-data is loaded
|
||||
@ -333,6 +356,8 @@ namespace OS {
|
||||
this.setPath(path);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Set a file path to the current file handle
|
||||
*
|
||||
@ -355,7 +380,7 @@ namespace OS {
|
||||
if (re === "") {
|
||||
return;
|
||||
}
|
||||
this.genealogy = re.split("/").filter(s=> s!="");
|
||||
this.genealogy = re.split("/").filter(s => s != "");
|
||||
this.path = `${this.protocol}://${this.genealogy.join("/")}`;
|
||||
if (!this.isRoot()) {
|
||||
this.basename = this.genealogy[
|
||||
@ -386,6 +411,22 @@ namespace OS {
|
||||
set filename(v: string) {
|
||||
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
|
||||
*
|
||||
@ -548,12 +589,8 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._rd(t);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
resolve(d);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -577,10 +614,7 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r: RequestResult = await this._wr(t);
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "write",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "write",this);
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
@ -601,16 +635,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d_1 = await this._mk(d);
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "mk",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS","mk",this);
|
||||
return resolve(d_1);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -629,16 +656,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._rm();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "remove",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "remove",this);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -659,16 +679,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._up();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "upload",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "upload", this);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -689,16 +702,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._pub();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "publish",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "publish",this);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -719,16 +725,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._down();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "download",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "download",this);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -748,16 +747,10 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const data = await this._mv(d);
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "move",
|
||||
file: d.asFileHandle(),
|
||||
});
|
||||
announcer.ostrigger("VFS", "move",d.asFileHandle());
|
||||
return resolve(data);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
}
|
||||
@ -778,16 +771,9 @@ namespace OS {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await this.onready();
|
||||
try {
|
||||
const d = await this._exec();
|
||||
announcer.ostrigger("VFS", {
|
||||
m: "execute",
|
||||
file: this,
|
||||
});
|
||||
announcer.ostrigger("VFS", "execute", this);
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} catch (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
|
||||
*
|
||||
@ -1066,8 +1063,8 @@ namespace OS {
|
||||
protected _wr(t: string): Promise<RequestResult> {
|
||||
// t is base64 or undefined
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (t === "base64") {
|
||||
try {
|
||||
if (t === "base64") {
|
||||
const d = await API.handle.write(
|
||||
this.path,
|
||||
this.cache
|
||||
@ -1080,11 +1077,7 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
return resolve(d);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
const r = await this.b64(t);
|
||||
const result = await API.handle.write(
|
||||
this.path,
|
||||
@ -1102,9 +1095,9 @@ namespace OS {
|
||||
);
|
||||
}
|
||||
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) {
|
||||
super(path);
|
||||
if (this.basename) {
|
||||
let v: any = OS.setting.system.packages[this.basename];
|
||||
v.type = "app";
|
||||
v.mime = "antos/app";
|
||||
v.size = 0;
|
||||
this.info = v as FileInfoType;
|
||||
let v = OS.setting.system.packages[this.basename];
|
||||
this.info = {} as FileInfoType;
|
||||
for(const p in v)
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -1436,6 +1435,8 @@ namespace OS {
|
||||
|
||||
register("^app$", ApplicationHandle);
|
||||
|
||||
|
||||
var MEM_PARTITION: BufferFileHandle = undefined;
|
||||
/**
|
||||
* A buffer file handle represents a virtual file that is stored
|
||||
* on the system memory. Its protocol pattern is defined as:
|
||||
@ -1457,18 +1458,96 @@ namespace OS {
|
||||
*/
|
||||
constructor(path: string, mime: string, data: any) {
|
||||
super(path);
|
||||
if (data) {
|
||||
this.cache = data;
|
||||
}
|
||||
this.info = {
|
||||
mime: mime,
|
||||
mime: mime?mime:'application/octet-stream',
|
||||
path: path,
|
||||
size: data ? data.length : 0,
|
||||
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
|
||||
*
|
||||
@ -1477,11 +1556,39 @@ namespace OS {
|
||||
*/
|
||||
meta(): Promise<RequestResult> {
|
||||
return new Promise((resolve, reject) =>
|
||||
{
|
||||
const data = {};
|
||||
for(const k in this.info)
|
||||
{
|
||||
data[k] = this.info[k];
|
||||
}
|
||||
resolve({
|
||||
result: this.info,
|
||||
result: data,
|
||||
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> {
|
||||
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);
|
||||
});
|
||||
}
|
||||
@ -1503,12 +1617,10 @@ namespace OS {
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t data type, see [[write]]
|
||||
* @param {*} d data
|
||||
* @returns {Promise<RequestResult>}
|
||||
* @memberof BufferFileHandle
|
||||
*/
|
||||
protected _wr(t: string, d: any): Promise<RequestResult> {
|
||||
this.cache = d;
|
||||
protected _wr(t: string): Promise<RequestResult> {
|
||||
return new Promise((resolve, reject) =>
|
||||
resolve({
|
||||
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
|
||||
*
|
||||
@ -1526,6 +1738,10 @@ namespace OS {
|
||||
*/
|
||||
protected _down(): Promise<void> {
|
||||
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], {
|
||||
type: "octet/stream",
|
||||
});
|
||||
@ -1855,11 +2071,11 @@ namespace OS {
|
||||
promises.push(new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const file = path.asFileHandle();
|
||||
const tof = `${to}/${file.basename}`.asFileHandle();
|
||||
const meta = await file.onready();
|
||||
if (meta.type === "dir") {
|
||||
const desdir = to.asFileHandle();
|
||||
await desdir.mk(file.basename);
|
||||
console.log(desdir, to);
|
||||
const ret = await file.read();
|
||||
const files = ret.result.map((v: API.FileInfoType) => v.path);
|
||||
if (files.length > 0) {
|
||||
@ -1871,6 +2087,7 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
else {
|
||||
const tof = `${to}/${file.basename}`.asFileHandle();
|
||||
const content = await file.read("binary");
|
||||
await tof
|
||||
.setCache(
|
||||
@ -1947,12 +2164,10 @@ namespace OS {
|
||||
const zip = new JSZip();
|
||||
const fhd = src.asFileHandle();
|
||||
const meta = await fhd.onready();
|
||||
if(meta.type === "file")
|
||||
{
|
||||
if (meta.type === "file") {
|
||||
await aradd([src], zip, "/");
|
||||
}
|
||||
else
|
||||
{
|
||||
else {
|
||||
const ret = await fhd.read();
|
||||
await aradd(
|
||||
ret.result.map((v: API.FileInfoType) => v.path), zip, "/");
|
||||
|
@ -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)
|
||||
|
@ -4,7 +4,7 @@ libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = scheme.html package.json
|
||||
copyfiles = scheme.html package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=Files
|
||||
|
9
src/packages/Files/README.md
Normal file
9
src/packages/Files/README.md
Normal 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
|
@ -21,7 +21,7 @@ namespace OS {
|
||||
|
||||
interface FilesClipboardType {
|
||||
cut: boolean;
|
||||
file: API.VFS.BaseFileHandle;
|
||||
files: API.VFS.BaseFileHandle[];
|
||||
}
|
||||
interface FilesViewType {
|
||||
icon: boolean;
|
||||
@ -71,10 +71,13 @@ namespace OS {
|
||||
|
||||
this.view.contextmenuHandle = (e, m) => {
|
||||
const file = this.view.selectedFile;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
const apps = [];
|
||||
let ctx_menu = [
|
||||
this.mnFile(),
|
||||
];
|
||||
if(file)
|
||||
{
|
||||
ctx_menu.push(this.mnEdit());
|
||||
if (file.type === "dir") {
|
||||
file.mime = "dir";
|
||||
}
|
||||
@ -87,8 +90,7 @@ namespace OS {
|
||||
iconclass: v.iconclass,
|
||||
});
|
||||
}
|
||||
let ctx_menu = [
|
||||
{
|
||||
ctx_menu.unshift( {
|
||||
text: "__(Open with)",
|
||||
nodes: apps,
|
||||
onchildselect: (e: GUI.TagEventType<GUI.tag.MenuEventData>) => {
|
||||
@ -98,10 +100,7 @@ namespace OS {
|
||||
const it = e.data.item.data;
|
||||
return this._gui.launch(it.app, [file]);
|
||||
},
|
||||
},
|
||||
this.mnFile(),
|
||||
this.mnEdit(),
|
||||
];
|
||||
});
|
||||
if(file.mime === "application/zip")
|
||||
{
|
||||
ctx_menu = ctx_menu.concat([
|
||||
@ -170,6 +169,8 @@ namespace OS {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
m.items = ctx_menu;
|
||||
m.show(e);
|
||||
};
|
||||
@ -212,12 +213,6 @@ namespace OS {
|
||||
if (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.navinput).val(dir.path);
|
||||
(this.scheme as GUI.tag.WindowTag).apptitle = dir.path;
|
||||
@ -228,43 +223,54 @@ namespace OS {
|
||||
};
|
||||
|
||||
this.vfs_event_flag = true;
|
||||
this.view.ondragndrop = (e) => {
|
||||
this.view.ondragndrop = async (e) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
const src = e.data.from.data;
|
||||
const src = e.data.from;
|
||||
const des = e.data.to.data;
|
||||
if (des.type === "file") {
|
||||
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
|
||||
// we update it manually
|
||||
this.vfs_event_flag = false;
|
||||
return file
|
||||
.move(`${des.path}/${file.basename}`)
|
||||
.then(() => {
|
||||
if (this.view.view === "icon") {
|
||||
this.view.path = this.view.path;
|
||||
} else {
|
||||
this.view.update(file.parent().path);
|
||||
this.view.update(des.path);
|
||||
const promises = [];
|
||||
for(const item of src)
|
||||
{
|
||||
let file = item.data.path.asFileHandle();
|
||||
promises.push(
|
||||
file.move(`${des.path}/${file.basename}`));
|
||||
}
|
||||
//reenable the vfs event
|
||||
return (this.vfs_event_flag = true);
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
// reenable the vfs event
|
||||
this.vfs_event_flag = true;
|
||||
return this.error(
|
||||
try{
|
||||
await Promise.all(promises);
|
||||
if (this.view.view === "tree") {
|
||||
this.view.update(src[0].data.path.asFileHandle().parent().path);
|
||||
this.view.update(des.path);
|
||||
} else {
|
||||
this.view.path = this.view.path;
|
||||
}
|
||||
}
|
||||
catch(error)
|
||||
{
|
||||
this.error(
|
||||
__(
|
||||
"Unable to move: {0} -> {1}",
|
||||
src.path,
|
||||
"Unable to move files to: {0}",
|
||||
des.path
|
||||
),
|
||||
e
|
||||
error
|
||||
);
|
||||
});
|
||||
}
|
||||
this.vfs_event_flag = true;
|
||||
};
|
||||
|
||||
// application setting
|
||||
@ -296,16 +302,16 @@ namespace OS {
|
||||
if (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) {
|
||||
return;
|
||||
}
|
||||
if (["read", "publish", "download"].includes(d.data.m)) {
|
||||
if (["read", "publish", "download"].includes(d.message as string)) {
|
||||
return;
|
||||
}
|
||||
if (
|
||||
d.data.file.hash() === this.currdir.hash() ||
|
||||
d.data.file.parent().hash() === this.currdir.hash()
|
||||
d.u_data.hash() === this.currdir.hash() ||
|
||||
d.u_data.parent().hash() === this.currdir.hash()
|
||||
) {
|
||||
return this.view.path = this.currdir.path;
|
||||
}
|
||||
@ -355,6 +361,32 @@ namespace OS {
|
||||
this.view.view = "list";
|
||||
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;
|
||||
}
|
||||
|
||||
@ -585,20 +617,22 @@ namespace OS {
|
||||
title: "__(Delete)",
|
||||
iconclass: "fa fa-question-circle",
|
||||
text: __(
|
||||
"Do you really want to delete: {0}?",
|
||||
file.filename
|
||||
"Do you really want to delete selected files?"
|
||||
),
|
||||
}).then(async (d) => {
|
||||
if (!d) {
|
||||
return;
|
||||
}
|
||||
const promises = [];
|
||||
for(const f of this.view.selectedFiles)
|
||||
{
|
||||
promises.push(f.path.asFileHandle().remove());
|
||||
}
|
||||
try {
|
||||
return file.path
|
||||
.asFileHandle()
|
||||
.remove();
|
||||
await Promise.all(promises);
|
||||
}
|
||||
catch (e) {
|
||||
return this.error(__("Fail to delete: {0}", file.path), e);
|
||||
return this.error(__("Fail to delete selected files"), e);
|
||||
}
|
||||
});
|
||||
break;
|
||||
@ -609,9 +643,9 @@ namespace OS {
|
||||
}
|
||||
this.clipboard = {
|
||||
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`:
|
||||
if (!file) {
|
||||
@ -619,10 +653,10 @@ namespace OS {
|
||||
}
|
||||
this.clipboard = {
|
||||
cut: false,
|
||||
file: file.path.asFileHandle(),
|
||||
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
|
||||
};
|
||||
return this.notify(
|
||||
__("File {0} copied", file.filename)
|
||||
__("{0} files copied", this.clipboard.files.length)
|
||||
);
|
||||
|
||||
case `${this.name}-paste`:
|
||||
@ -630,29 +664,33 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
if (this.clipboard.cut) {
|
||||
this.clipboard.file
|
||||
.move(
|
||||
`${this.currdir.path}/${this.clipboard.file.basename}`
|
||||
)
|
||||
const promises = [];
|
||||
for(const file of this.clipboard.files)
|
||||
{
|
||||
promises.push(file.move(
|
||||
`${this.currdir.path}/${file.basename}`
|
||||
));
|
||||
}
|
||||
Promise.all(promises)
|
||||
.then((r) => {
|
||||
return (this.clipboard = undefined);
|
||||
})
|
||||
.catch((e) => {
|
||||
return this.error(
|
||||
__(
|
||||
"Fail to paste: {0}",
|
||||
this.clipboard.file.path
|
||||
"Fail to paste to: {0}",
|
||||
this.currdir.path
|
||||
),
|
||||
e
|
||||
);
|
||||
});
|
||||
} 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(() => {
|
||||
return (this.clipboard = undefined);
|
||||
})
|
||||
.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;
|
||||
@ -727,7 +765,7 @@ namespace OS {
|
||||
});
|
||||
break;
|
||||
case `${this.name}-download`:
|
||||
if (file.type !== "file") {
|
||||
if (!file || file.type !== "file") {
|
||||
return;
|
||||
}
|
||||
file.path
|
||||
|
@ -6,7 +6,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.3-a",
|
||||
"version":"0.1.7-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-hdd-o",
|
||||
"mimes":["dir"],
|
||||
|
@ -4,7 +4,7 @@ libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = scheme.html package.json
|
||||
copyfiles = scheme.html package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=MarketPlace
|
||||
|
10
src/packages/MarketPlace/README.md
Normal file
10
src/packages/MarketPlace/README.md
Normal 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
|
@ -41,7 +41,7 @@ namespace OS {
|
||||
main(): void {
|
||||
this.installdir = this.systemsetting.system.pkgpaths.user;
|
||||
// test repository
|
||||
this.apps_meta = [];
|
||||
this.apps_meta = {};
|
||||
|
||||
this.applist = this.find("applist") as GUI.tag.ListViewTag;
|
||||
this.catlist = this.find("catlist") as GUI.tag.ListViewTag;
|
||||
@ -108,8 +108,8 @@ namespace OS {
|
||||
return;
|
||||
}
|
||||
const app = el.data;
|
||||
if (app.pkgname) {
|
||||
return this._gui.launch(app.pkgname, []);
|
||||
if (app.app) {
|
||||
return this._gui.launch(app.app, []);
|
||||
}
|
||||
};
|
||||
|
||||
@ -273,6 +273,7 @@ namespace OS {
|
||||
if (this.apps_meta[name]) {
|
||||
pkg.icon = this.apps_meta[name].icon;
|
||||
pkg.iconclass = this.apps_meta[name].iconclass;
|
||||
pkg.app = this.apps_meta[name].app;
|
||||
}
|
||||
this.apps_meta[name] = pkg;
|
||||
}
|
||||
@ -328,16 +329,11 @@ namespace OS {
|
||||
this.catlist.data = cat_list_data;
|
||||
this.catlist.selected = 0;
|
||||
}
|
||||
|
||||
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.apps_meta[`${k}@${v.version}`] = {
|
||||
private add_meta_from(k:string, v: API.PackageMetaType)
|
||||
{
|
||||
const mt = {
|
||||
pkgname: v.pkgname ? v.pkgname : v.app,
|
||||
app: v.app,
|
||||
name: v.name,
|
||||
text: `${v.name} ${v.version}`,
|
||||
icon: v.icon,
|
||||
@ -349,6 +345,17 @@ namespace OS {
|
||||
dependencies: v.dependencies ? Array.from(v.dependencies) : [],
|
||||
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[] = []
|
||||
@ -551,7 +558,8 @@ namespace OS {
|
||||
return reject(this._api.throwe(__("Unable to find package: {0}", pkgname)));
|
||||
}
|
||||
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);
|
||||
} catch (e_1) {
|
||||
return reject(__e(e_1));
|
||||
@ -621,13 +629,19 @@ namespace OS {
|
||||
mimes: [".*/zip"],
|
||||
});
|
||||
const n = await this.install(d.file.path);
|
||||
const name = n.pkgname?n.pkgname:n.app;
|
||||
const apps = this.applist.data.map(
|
||||
(v) => v.pkgname
|
||||
);
|
||||
const idx = apps.indexOf(n);
|
||||
const idx = apps.indexOf(name);
|
||||
if (idx >= 0) {
|
||||
this.applist.selected = idx;
|
||||
}
|
||||
else
|
||||
{
|
||||
const mt = this.add_meta_from(name,n);
|
||||
this.appDetail(mt);
|
||||
}
|
||||
return resolve(n.name);
|
||||
} catch (error) {
|
||||
reject(__e(error));
|
||||
@ -638,7 +652,7 @@ namespace OS {
|
||||
private install(
|
||||
zfile: string,
|
||||
meta?: GenericObject<any>
|
||||
): Promise<GenericObject<any>> {
|
||||
): Promise<API.PackageMetaType> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
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.filename = v.pkgname ? v.pkgname : v.app;
|
||||
v.type = "app";
|
||||
@ -684,11 +682,15 @@ namespace OS {
|
||||
v.iconclass =
|
||||
"fa fa-adn";
|
||||
}
|
||||
if(v.icon)
|
||||
{
|
||||
v.icon = `${pth}/${v.icon}`;
|
||||
}
|
||||
v.path = pth;
|
||||
this.systemsetting.system.packages[
|
||||
v.pkgname ? v.pkgname : v.app
|
||||
] = v;
|
||||
return resolve(app_meta);
|
||||
return resolve(v);
|
||||
} catch (error) {
|
||||
reject(__e(error));
|
||||
}
|
||||
@ -704,20 +706,21 @@ namespace OS {
|
||||
private uninstallPkg(pkgname: string): Promise<any> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const meta = this.apps_meta[pkgname];
|
||||
|
||||
// got the app meta
|
||||
try {
|
||||
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];
|
||||
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
|
||||
.asFileHandle()
|
||||
.remove();
|
||||
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"));
|
||||
// stop all the services if any
|
||||
@ -727,7 +730,7 @@ namespace OS {
|
||||
}
|
||||
}
|
||||
delete this.systemsetting.system.packages[meta.pkgname];
|
||||
this._gui.unloadApp(meta.pkgname);
|
||||
this._gui.unloadApp(meta.pkgname, true);
|
||||
if (meta.download) {
|
||||
this.appDetail(meta);
|
||||
}
|
||||
@ -735,6 +738,7 @@ namespace OS {
|
||||
if (meta.domel)
|
||||
this.applist.delete(meta.domel);
|
||||
$(this.container).css("visibility", "hidden");
|
||||
delete this.apps_meta[pkgname];
|
||||
}
|
||||
return resolve(meta);
|
||||
}
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.2.4-a",
|
||||
"version":"0.2.7-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-shopping-bag",
|
||||
"mimes":["none"],
|
||||
|
@ -13,7 +13,7 @@
|
||||
</afx-vbox>
|
||||
<afx-resizer data-width = "3" ></afx-resizer>
|
||||
<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">
|
||||
<div style = "text-align:left;">
|
||||
<afx-button data-id = "bt-remove" text = "__(Uninstall)"></afx-button>
|
||||
|
@ -4,3 +4,6 @@ Defaut text editor which is included in each AntOS release.
|
||||
It has very barebone features: open/edit/save text file.
|
||||
|
||||
Text/Code editor with fancy features can be optionally installed via the Market Place
|
||||
|
||||
## Change logs
|
||||
-v0.1.1-b: update README
|
||||
|
@ -7,7 +7,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "mrsang@iohub.dev"
|
||||
},
|
||||
"version": "0.1.0-b",
|
||||
"version": "0.1.1-b",
|
||||
"category": "Utility",
|
||||
"iconclass": "bi bi-pen",
|
||||
"mimes": [
|
||||
|
@ -101,6 +101,7 @@ namespace OS {
|
||||
text: v.name,
|
||||
app: k,
|
||||
iconclass: v.iconclass,
|
||||
icon: v.icon
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -160,7 +161,7 @@ namespace OS {
|
||||
}
|
||||
return result1;
|
||||
})();
|
||||
announcer.ostrigger("app-pinned", this.applist.data);
|
||||
announcer.ostrigger("app-pinned", "app-pinned", this.applist.data);
|
||||
}
|
||||
}
|
||||
App.AppAndServiceHandle = AppAndServiceHandle;
|
||||
|
@ -4,7 +4,7 @@ libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = scheme.html package.json
|
||||
copyfiles = scheme.html package.json README.md
|
||||
|
||||
|
||||
PKG_NAME=Setting
|
||||
|
14
src/packages/Setting/README.md
Normal file
14
src/packages/Setting/README.md
Normal 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
|
@ -102,6 +102,7 @@ namespace OS {
|
||||
text: v.name,
|
||||
app: k,
|
||||
iconclass: v.iconclass,
|
||||
icon: v.icon
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
"author": "Xuan Sang LE",
|
||||
"email": "xsang.le@gmail.com"
|
||||
},
|
||||
"version":"0.1.1-a",
|
||||
"version":"0.1.3-b",
|
||||
"category":"System",
|
||||
"iconclass":"fa fa-wrench",
|
||||
"mimes":["none"]
|
||||
|
@ -4,7 +4,7 @@ libfiles =
|
||||
|
||||
cssfiles = main.css
|
||||
|
||||
copyfiles = package.json scheme.html
|
||||
copyfiles = package.json scheme.html README.md
|
||||
|
||||
|
||||
PKG_NAME=Syslog
|
||||
|
@ -112,19 +112,19 @@ namespace OS {
|
||||
*
|
||||
* @private
|
||||
* @param {string} s
|
||||
* @param {GenericObject<any>} o
|
||||
* @param {API.AnnouncementDataType} o
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
private addLog(s: string, o: GenericObject<any>): void {
|
||||
private addLog(s: string, o: API.AnnouncementDataType<any>): void {
|
||||
const logtime = new Date();
|
||||
const log = {
|
||||
type: s,
|
||||
name: o.name,
|
||||
text: `${o.data.m}`,
|
||||
text: `${o.message}`,
|
||||
id: o.id,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
error: o.data.e,
|
||||
icon: o.icon,
|
||||
iconclass: o.iconclass,
|
||||
error: o.u_data,
|
||||
time: logtime,
|
||||
closable: true,
|
||||
tag: "afx-bug-list-item",
|
||||
@ -141,14 +141,14 @@ namespace OS {
|
||||
*
|
||||
* @private
|
||||
* @param {string} s
|
||||
* @param {GenericObject<any>} o
|
||||
* @param {API.AnnouncementDataType} o
|
||||
* @memberof PushNotification
|
||||
*/
|
||||
private pushout(s: string, o: GenericObject<any>): void {
|
||||
private pushout(s: string, o: API.AnnouncementDataType<any>): void {
|
||||
const d = {
|
||||
text: `[${s}] ${o.name} (${o.id}): ${o.data.m}`,
|
||||
icon: o.data.icon,
|
||||
iconclass: o.data.iconclass,
|
||||
text: `[${s}] ${o.name} (${o.id}): ${o.message}`,
|
||||
icon: o.icon,
|
||||
iconclass: o.iconclass,
|
||||
closable: true,
|
||||
};
|
||||
if (s !== "INFO") {
|
||||
|
6
src/packages/Syslog/README.md
Normal file
6
src/packages/Syslog/README.md
Normal 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
|
@ -13,7 +13,7 @@
|
||||
"credit": "dedicated to some one here",
|
||||
"licences": "GPLv3"
|
||||
},
|
||||
"version": "0.1.1-a",
|
||||
"version": "0.1.2-b",
|
||||
"category": "System",
|
||||
"iconclass": "fa fa-bug",
|
||||
"mimes": []
|
||||
|
@ -8,7 +8,7 @@ afx-file-view afx-label.status{
|
||||
}
|
||||
afx-file-view afx-list-view > div.list-container > ul li{
|
||||
width:70px;
|
||||
height: 60px;
|
||||
/*height: 60px;*/
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
|
@ -1,11 +1,11 @@
|
||||
afx-menu afx-switch span{
|
||||
width: 20px;
|
||||
height: 16px;
|
||||
padding-top: 3px;
|
||||
font-size: 16px;
|
||||
/*margin-top:5px;*/
|
||||
height: 19px;
|
||||
}
|
||||
afx-menu span.shortcut{
|
||||
text-align: right;
|
||||
margin-left: 3px;
|
||||
}
|
||||
afx-menu li:hover > a afx-switch span:before{
|
||||
color:white;
|
||||
@ -21,12 +21,12 @@ afx-menu afx-menu ul {
|
||||
background-color: #363636;
|
||||
}
|
||||
afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{
|
||||
padding:3px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
afx-menu afx-menu li{
|
||||
min-width: 150px;
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
|
||||
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{
|
||||
min-width: 150px;
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
|
||||
afx-menu afx-label span {
|
||||
height: 22px !important;
|
||||
}
|
@ -7,7 +7,7 @@ afx-file-view afx-label.status{
|
||||
}
|
||||
afx-file-view afx-list-view > div.list-container > ul li{
|
||||
width:70px;
|
||||
height: 60px;
|
||||
/*height: 60px;*/
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
margin-right: 5px;
|
||||
|
@ -1,11 +1,11 @@
|
||||
afx-menu afx-switch span{
|
||||
width: 20px;
|
||||
height: 16px;
|
||||
padding-top: 3px;
|
||||
font-size: 16px;
|
||||
/*margin-top:5px;*/
|
||||
height: 19px;
|
||||
}
|
||||
afx-menu span.shortcut{
|
||||
text-align: right;
|
||||
margin-left: 3px;
|
||||
}
|
||||
afx-menu li:hover > a afx-switch span:before{
|
||||
color:white;
|
||||
@ -21,12 +21,12 @@ afx-menu afx-menu ul {
|
||||
background-color: #e7e7e7;
|
||||
}
|
||||
afx-menu ul li /*, afx-menu ul >afx-menu-entry > li*/{
|
||||
padding:3px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
afx-menu afx-menu li{
|
||||
min-width: 150px;
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
|
||||
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{
|
||||
min-width: 150px;
|
||||
width: calc(100% - 10px);
|
||||
}
|
||||
|
||||
afx-menu afx-label span {
|
||||
height: 22px !important;
|
||||
}
|
@ -15,6 +15,21 @@ afx-app-window ul.afx-window-top{
|
||||
width: 100%;
|
||||
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{
|
||||
list-style: none;
|
||||
}
|
||||
|
@ -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{
|
||||
border-right: 0;
|
||||
}
|
||||
afx-calendar-view afx-label {
|
||||
display: inline-block;
|
||||
}
|
@ -7,15 +7,29 @@ afx-file-view afx-label.status{
|
||||
left:0px;
|
||||
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{
|
||||
float:left;
|
||||
display: block;
|
||||
/*display: block;*/
|
||||
}
|
||||
|
||||
afx-file-view afx-list-view i{
|
||||
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{
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -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;
|
||||
margin: 0;
|
||||
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;
|
||||
margin: 0;
|
||||
background-color: transparent;
|
||||
|
@ -14,3 +14,7 @@ afx-grid-view .grid_row_header afx-grid-cell{
|
||||
afx-grid-view {
|
||||
display: block;
|
||||
}
|
||||
|
||||
afx-grid-view afx-grid-cell afx-label {
|
||||
display: inline-block;
|
||||
}
|
@ -1,9 +1,13 @@
|
||||
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{
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
margin-left: 3px;
|
||||
user-select:text;
|
||||
}
|
@ -54,3 +54,7 @@ afx-list-view.dropdown > div.list-container > ul li{
|
||||
display: inline-block;
|
||||
width:100%;
|
||||
}
|
||||
|
||||
afx-list-view afx-list-item afx-label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ afx-menu ul li {
|
||||
float: left;
|
||||
cursor:default;
|
||||
}
|
||||
/*
|
||||
afx-menu ul li.fix_padding{
|
||||
padding-top:1px;
|
||||
padding-bottom: 0;
|
||||
@ -45,7 +46,7 @@ afx-menu afx-menu ul li.fix_padding{
|
||||
padding:3px;
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
} */
|
||||
afx-menu afx-menu {
|
||||
top:100%;
|
||||
left:0;
|
||||
|
@ -52,7 +52,7 @@ afx-sys-panel > div.loading::before {
|
||||
100% {
|
||||
right: auto;
|
||||
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: 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;
|
||||
}
|
@ -130,3 +130,27 @@ body
|
||||
height: 1px;
|
||||
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;
|
||||
}
|
215
src/themes/system/bootstrap-icons.css
vendored
215
src/themes/system/bootstrap-icons.css
vendored
@ -1,9 +1,10 @@
|
||||
@font-face {
|
||||
font-family: "bootstrap-icons";
|
||||
src: url("fonts/bootstrap-icons.woff2?231ce25e89ab5804f9a6c427b8d325c9") format("woff2"),
|
||||
url("fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9") format("woff");
|
||||
src: url("./fonts/bootstrap-icons.woff2?a74547b2f0863226942ff8ded57db345") format("woff2"),
|
||||
url("./fonts/bootstrap-icons.woff?a74547b2f0863226942ff8ded57db345") format("woff");
|
||||
}
|
||||
|
||||
.bi::before,
|
||||
[class^="bi-"]::before,
|
||||
[class*=" bi-"]::before {
|
||||
display: inline-block;
|
||||
@ -18,6 +19,7 @@ url("fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9") format("woff"
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.bi-123::before { content: "\f67f"; }
|
||||
.bi-alarm-fill::before { content: "\f101"; }
|
||||
.bi-alarm::before { content: "\f102"; }
|
||||
.bi-align-bottom::before { content: "\f103"; }
|
||||
@ -1343,3 +1345,212 @@ url("fonts/bootstrap-icons.woff?231ce25e89ab5804f9a6c427b8d325c9") format("woff"
|
||||
.bi-youtube::before { content: "\f62b"; }
|
||||
.bi-zoom-in::before { content: "\f62c"; }
|
||||
.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"; }
|
||||
|
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user