Merge pull request #45 from lxsang/1.2.1

release v1.2.1

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

62
Jenkinsfile vendored Normal file
View File

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

View File

@ -4,8 +4,13 @@ BUILDDIR?=/opt/www/htdocs/os
DOCDIR?=/opt/www/htdocs/doc/antos
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:

View File

@ -1,4 +1,4 @@
# antOS v1.2.0
# antOS v1.2.1
[![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Flxsang%2Fantos.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
@ -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

File diff suppressed because it is too large Load Diff

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

Binary file not shown.

View File

@ -1 +1 @@
1.2.0
1.2.1

View File

@ -18,6 +18,63 @@
namespace OS {
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);
}
/**

View File

@ -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;

View File

@ -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;
}
});
}
}

View File

@ -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();
}
}

View File

@ -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));

View File

@ -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>\
`;
}
}

View File

@ -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);
}
}
}

View File

@ -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
View File

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

View File

@ -28,13 +28,13 @@ namespace OS {
private _onfileopen: TagEventCallback<API.FileInfoType>;
/**
* 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);

View File

@ -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();
}

View File

@ -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
*

View File

@ -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();

View File

@ -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");
}
/**

View File

@ -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);

View File

@ -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;

View File

@ -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: [
{

View File

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

View File

@ -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, "/");

View File

@ -1,401 +0,0 @@
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* DS202: Simplify dynamic range loops
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
*/
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
// AnTOS Web desktop is is licensed under the GNU General Public
// License v3.0, see the LICENCE file for more information
// This program is free software: you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
// published by the Free Software Foundation, either version 3 of
// the License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// General Public License for more details.
// You should have received a copy of the GNU General Public License
//along with this program. If not, see https://www.gnu.org/licenses/.
// GoogleDrive File Handle
let G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
class GoogleDriveHandle extends this.OS.API.VFS.BaseFileHandle {
constructor(path) {
super(path);
const me = this;
this.setting = Ant.OS.setting.VFS.gdrive;
if (!this.setting) { return Ant.OS.announcer.oserror(__("Unknown API setting for {0}", "GAPI"), (Ant.OS.API.throwe("OS.VFS")), null); }
if (this.isRoot()) { this.gid = 'root'; }
this.cache = "";
}
oninit(f) {
const me = this;
if (!this.setting) { return; }
const fn = function(r) {
if (r) { return f(); }
// perform the login
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } };
return gapi.auth2.getAuthInstance().signIn();
};
if (Ant.OS.API.libready(this.setting.apilink)) {
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
} else {
return Ant.OS.API.require(this.setting.apilink, function() {
// avoid popup block
const q = Ant.OS.announcer.getMID();
return gapi.load("client:auth2", function() {
Ant.OS.API.loading(q, "GAPI");
return gapi.client.init({
apiKey: me.setting.API_KEY,
clientId: me.setting.CLIENT_ID,
discoveryDocs: me.setting.DISCOVERY_DOCS,
scope: me.setting.SCOPES
})
.then(function() {
Ant.OS.API.loaded(q, "OK");
gapi.auth2.getAuthInstance().isSignedIn.listen(r => fn(r));
return _GUI.openDialog("YesNoDialog", function(d) {
if (!d) { return Ant.OS.announcer.osinfo(__("User abort the authentication")); }
return fn(gapi.auth2.getAuthInstance().isSignedIn.get());
}
, __("Authentication")
, { text: __("Would you like to login to {0}?", "Google Drive") });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot init {0}: {1}", "GAPI",err.error), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
});
}
}
meta(f) {
const me = this;
return this.oninit(function() {
const q = Ant.OS.announcer.getMID();
if (G_CACHE[me.path]) { me.gid = G_CACHE[me.path].id; }
if (me.gid) {
//console.log "Gid exists ", me.gid
Ant.OS.API.loading(q, "GAPI");
return gapi.client.drive.files.get({
fileId: me.gid,
fields: me.fields()
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.result) { return; }
r.result.mime = r.result.mimeType;
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
});
} else {
//console.log "Find file in ", me.parent()
const fp = me.parent().asFileHandle();
return fp.meta(function(d) {
const file = d.result;
const q1 = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q1, "GAPI");
G_CACHE[fp.path] = { id: file.id, mime: file.mimeType };
return gapi.client.drive.files.list({
q: `name = '${me.basename}' and '${file.id}' in parents and trashed = false`,
fields: `files(${me.fields()})`
})
.then(function(r) {
//console.log r
Ant.OS.API.loaded(q1, "OK");
if (!r.result.files || !(r.result.files.length > 0)) { return; }
G_CACHE[me.path] = { id: r.result.files[0].id, mime: r.result.files[0].mimeType };
r.result.files[0].mime = r.result.files[0].mimeType;
return f({ result: r.result.files[0] });})
.catch(function(err) {
Ant.OS.API.loaded(q1, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot get meta data for {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
}
});
}
fields() {
return "webContentLink, id, name,mimeType,description, kind, parents, properties, iconLink, createdTime, modifiedTime, owners, permissions, fullFileExtension, fileExtension, size";
}
isFolder() {
return this.info.mimeType === "application/vnd.google-apps.folder";
}
save(id, m, f) {
const me = this;
const user = gapi.auth2.getAuthInstance().currentUser.get();
const oauthToken = user.getAuthResponse().access_token;
const q = Ant.OS.announcer.getMID();
const xhr = new XMLHttpRequest();
const url = 'https://www.googleapis.com/upload/drive/v3/files/' + id + '?uploadType=media';
xhr.open('PATCH', url);
xhr.setRequestHeader('Authorization', 'Bearer ' + oauthToken);
xhr.setRequestHeader('Content-Type', m);
xhr.setRequestHeader('Content-Encoding', 'base64');
xhr.setRequestHeader('Content-Transfer-Encoding', 'base64');
Ant.OS.API.loading(q, "GAPI");
const error = function(e, s) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot save : {0}", me.path), e, s);
};
xhr.onreadystatechange = function() {
if ( xhr.readyState === 4 ) {
if ( xhr.status === 200 ) {
Ant.OS.API.loaded(q, "OK");
return f({ result: JSON.parse(xhr.responseText) });
} else {
return error(xhr, xhr.status);
}
}
};
xhr.onerror = () => error(xhr, xhr.status);
if (m === "base64") { return xhr.send(me.cache.replace(/^data:[^;]+;base64,/g, "")); }
return this.sendB64(m, data => xhr.send(data.replace(/^data:[^;]+;base64,/g, "")));
}
getlink() {
if (this.ready) { return this.info.webContentLink; }
return undefined;
}
action(n, p, f) {
const me = this;
const q = Ant.OS.announcer.getMID();
Ant.OS.API.loading(q, "GAPI");
switch (n) {
case "read":
if (!this.info.id) { return; }
if (this.isFolder()) {
return gapi.client.drive.files.list({
q: `'${me.info.id}' in parents and trashed = false`,
fields: `files(${me.fields()})`
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.result.files) { return; }
for (let file of Array.from(r.result.files)) {
file.path = me.child(file.name);
file.mime = file.mimeType;
file.filename = file.name;
file.type = "file";
file.gid = file.id;
if (file.mimeType === "application/vnd.google-apps.folder") {
file.mime = "dir";
file.type = "dir";
file.size = 0;
}
G_CACHE[file.path] = { id: file.gid, mime: file.mime };
}
return f({ result: r.result.files });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
} else {
return gapi.client.drive.files.get({
fileId: me.info.id,
alt: 'media'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (p !== "binary") { return f(r.body); }
return f(r.body.asUint8Array());}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot read : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
}
case "mk":
if (!this.isFolder()) { return f({ error: __("{0} is not a directory", this.path) }); }
var meta = {
name: p,
parents: [this.info.id],
mimeType: 'application/vnd.google-apps.folder'
};
gapi.client.drive.files.create({
resource: meta,
fields: 'id'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
//console.log r
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.child(p)] = { id: r.result.id, mime: "dir" };
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot create : {0}", p), (Ant.OS.API.throwe("OS.VFS")), err);
});
return;
case "write":
var gid = undefined;
if (G_CACHE[me.path]) { gid = G_CACHE[me.path].id; }
if (gid) {
Ant.OS.API.loaded(q, "OK");
return this.save(gid, p, f);
} else {
const dir = this.parent().asFileHandle();
return dir.onready(function() {
meta = {
name: me.basename,
mimeType: p,
parents: [dir.info.id]
};
return gapi.client.drive.files.create({
resource: meta,
fields: 'id'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r || !r.result) { return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.path] = { id: r.result.id, mime: p };
return me.save(r.result.id, p, f);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot write : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
});
}
case "upload":
if (!this.isFolder()) { return; }
//insert a temporal file selector
var o = ($('<input>')).attr('type', 'file').css("display", "none");
Ant.OS.API.loaded(q, "OK");
o.change(function() {
//Ant.OS.API.loading q, p
const fo = o[0].files[0];
const file = (me.child(fo.name)).asFileHandle();
file.cache = fo;
file.write(fo.type, f);
return o.remove();
});
//Ant.OS.API.loaded q, p, "OK"
//Ant.OS.API.loaded q, p, "FAIL"
return o.click();
case "remove":
if (!this.info.id) { return; }
return gapi.client.drive.files.delete({
fileId: me.info.id
})
.then(function(r) {
//console.log r
Ant.OS.API.loaded(q, "OK");
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
G_CACHE[me.path] = null;
return f({ result: true });})
.catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot delete : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
case "publish":
Ant.OS.API.loaded(q, "OK");
return;
case "download":
return gapi.client.drive.files.get({
fileId: me.info.id,
alt: 'media'
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r.body) { return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
let bytes = [];
for (let i = 0, end = r.body.length - 1, asc = 0 <= end; asc ? i <= end : i >= end; asc ? i++ : i--) {
bytes.push(r.body.charCodeAt(i));
}
bytes = new Uint8Array(bytes);
const blob = new Blob([bytes], { type: "octet/stream" });
return Ant.OS.API.saveblob(me.basename, blob);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot download file : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), err);
});
case "move":
var dest = p.asFileHandle().parent().asFileHandle();
return dest.onready(function() {
const previousParents = me.info.parents.join(',');
return gapi.client.drive.files.update({
fileId: me.info.id,
addParents: dest.info.id,
removeParents: previousParents,
fields: "id"
})
.then(function(r) {
Ant.OS.API.loaded(q, "OK");
if (!r) { return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.path), (Ant.OS.API.throwe("OS.VFS")), r); }
return f(r);}).catch(function(err) {
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.oserror(__("VFS cannot move : {0}", me.gid), (Ant.OS.API.throwe("OS.VFS")), err);
});
}
, err => Ant.OS.API.loaded(q, "FAIL"));
default:
Ant.OS.API.loaded(q, "FAIL");
return Ant.OS.announcer.osfail(__("VFS unknown action: {0}", n), (Ant.OS.API.throwe("OS.VFS")), n);
}
}
}
self.OS.API.VFS.register("^gdv$", GoogleDriveHandle);
// search the cache for file
self.OS.API.onsearch("Google Drive", function(t) {
const arr = [];
const term = new RegExp(t, "i");
for (let k in G_CACHE) {
const v = G_CACHE[k];
if ((k.match(term)) || (v && v.mime.match(term))) {
const file = k.asFileHandle();
file.text = file.basename;
file.mime = v.mime;
file.iconclass = "fa fa-file";
if (file.mime === "dir") { file.iconclass = "fa fa-folder"; }
file.complex = true;
file.detail = [{ text: file.path }];
arr.push(file);
}
}
return arr;
});
self.OS.onexit("cleanUpGoogleDrive", function() {
G_CACHE = { "gdv://": { id: "root", mime: 'dir' } };
if (!Ant.OS.setting.VFS.gdrive || !Ant.OS.API.libready(Ant.OS.setting.VFS.gdrive.apilink)) { return; }
const auth2 = gapi.auth2.getAuthInstance();
if (!auth2) { return; }
if (auth2.isSignedIn.get()) {
let el;
return el = $('<iframe/>', {
src: 'https://www.google.com/accounts/Logout',
frameborder: 0,
onload() {
//console.log("disconnect")
return auth2.disconnect();
}
//$(this).remove()
});
}
});
//($ "body").append(el)

View File

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

View File

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

View File

@ -21,7 +21,7 @@ namespace OS {
interface FilesClipboardType {
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

View File

@ -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"],

View File

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

View File

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

View File

@ -41,7 +41,7 @@ namespace OS {
main(): void {
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);
}

View File

@ -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"],

View File

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

View File

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

View File

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

View File

@ -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;

View File

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

View File

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

View File

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

View File

@ -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"]

View File

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

View File

@ -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") {

View File

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

View File

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

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

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

View File

@ -7,15 +7,29 @@ afx-file-view afx-label.status{
left:0px;
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;
}

View File

@ -1,11 +1,11 @@
afx-float-list div.list-container > ul{
afx-desktop div.list-container > ul, afx-float-list div.list-container > ul{
padding: 0;
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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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"; }