mirror of
https://github.com/lxsang/antos-frontend.git
synced 2025-02-20 17:22:46 +01:00
commit
7a619ac3a8
7
.gitignore
vendored
7
.gitignore
vendored
@ -1,3 +1,8 @@
|
||||
build
|
||||
node_modules
|
||||
.DS_Store
|
||||
.DS_Store
|
||||
package-lock.json
|
||||
dist
|
||||
docs
|
||||
coffees
|
||||
.vscode
|
158
Makefile
158
Makefile
@ -1,6 +1,7 @@
|
||||
|
||||
|
||||
BUILDDIR?=build/htdocs/os
|
||||
BUILDDIR?=/opt/www/htdocs/os
|
||||
DOCDIR?=/opt/www/htdocs/doc/antos/api
|
||||
BLUE=\033[1;34m
|
||||
NC=\033[0m
|
||||
|
||||
@ -11,88 +12,109 @@ ifeq ($(UNAME_S),Darwin)
|
||||
endif
|
||||
|
||||
|
||||
coffees= src/core/core.coffee\
|
||||
src/core/api.coffee\
|
||||
src/core/settings.coffee\
|
||||
src/core/handlers/RemoteHandler.coffee\
|
||||
src/core/vfs.coffee\
|
||||
src/core/vfs/GoogleDriveHandler.coffee\
|
||||
src/core/db.coffee\
|
||||
src/core/gui.coffee\
|
||||
src/core/BaseModel.coffee\
|
||||
src/core/BaseApplication.coffee\
|
||||
src/core/BaseService.coffee\
|
||||
src/core/BaseEvent.coffee\
|
||||
src/core/BaseDialog.coffee\
|
||||
src/antos.coffee
|
||||
javascripts= dist/core/core.js \
|
||||
dist/core/settings.js \
|
||||
dist/core/handles/RemoteHandle.js \
|
||||
dist/core/Announcerment.js \
|
||||
dist/core/vfs.js \
|
||||
dist/core/db.js \
|
||||
dist/core/BaseModel.js \
|
||||
dist/core/BaseApplication.js \
|
||||
dist/core/BaseService.js \
|
||||
dist/core/BaseEvent.js \
|
||||
dist/core/BaseDialog.js \
|
||||
dist/core/tags/tag.js \
|
||||
dist/core/tags/WindowTag.js \
|
||||
dist/core/tags/TileLayoutTags.js \
|
||||
dist/core/tags/ResizerTag.js \
|
||||
dist/core/tags/LabelTag.js \
|
||||
dist/core/tags/ButtonTag.js \
|
||||
dist/core/tags/ListViewTag.js \
|
||||
dist/core/tags/SwitchTag.js \
|
||||
dist/core/tags/NSpinnerTag.js \
|
||||
dist/core/tags/MenuTag.js \
|
||||
dist/core/tags/GridViewTag.js \
|
||||
dist/core/tags/TabBarTag.js \
|
||||
dist/core/tags/TabContainerTag.js \
|
||||
dist/core/tags/TreeViewTag.js \
|
||||
dist/core/tags/SliderTag.js \
|
||||
dist/core/tags/FloatListTag.js \
|
||||
dist/core/tags/CalendarTag.js \
|
||||
dist/core/tags/ColorPickerTag.js \
|
||||
dist/core/tags/FileViewTag.js \
|
||||
dist/core/tags/OverlayTag.js \
|
||||
dist/core/tags/AppDockTag.js \
|
||||
dist/core/tags/SystemPanelTag.js \
|
||||
dist/core/gui.js \
|
||||
dist/core/pm.js \
|
||||
dist/bootstrap.js
|
||||
|
||||
packages = Syslog CodePad Files MarketPlace Setting
|
||||
|
||||
|
||||
|
||||
packages = CoreServices NotePad wTerm ActivityMonitor Files MarkOn MarketPlace Preview Setting
|
||||
|
||||
main: initd build_coffees build_tags build_themes schemes libs build_packages languages
|
||||
main: initd build_javascripts build_themes libs build_packages languages
|
||||
- cp src/index.html $(BUILDDIR)/
|
||||
|
||||
initd:
|
||||
- mkdir -p $(BUILDDIR)
|
||||
|
||||
lite: build_coffees build_tags build_themes schemes build_packages
|
||||
lite: build_javascripts build_themes build_packages
|
||||
#%.js: %.coffee
|
||||
# coffee --compile $<
|
||||
|
||||
build_coffees:
|
||||
@echo "$(BLUE)Building coffee files$(NC)"
|
||||
build_javascripts:
|
||||
-rm -rf dist
|
||||
tsc
|
||||
@echo "$(BLUE)Bundling javascript files$(NC)"
|
||||
- mkdir $(BUILDDIR)/scripts
|
||||
- rm $(BUILDDIR)/scripts/antos.js
|
||||
- rm $(BUILDDIR)/scripts/antos.coffee
|
||||
for f in $(coffees); do (cat "$${f}"; echo) >> $(BUILDDIR)/scripts/antos.coffee; done
|
||||
coffee --compile $(BUILDDIR)/scripts/antos.coffee
|
||||
- rm $(BUILDDIR)/scripts/antos.coffee
|
||||
|
||||
|
||||
#echo "(function() {" > $(BUILDDIR)/scripts/antos.js
|
||||
for f in $(javascripts); do \
|
||||
(cat "$${f}"; echo) >> dist/antos.js;\
|
||||
rm "$${f}";\
|
||||
done
|
||||
cp dist/antos.js $(BUILDDIR)/scripts/
|
||||
echo "if(exports){ exports.__esModule = true;exports.OS = OS; }" >> dist/antos.js
|
||||
rm -r dist/core
|
||||
libs:
|
||||
@echo "$(BLUE)Copy lib files$(NC)"
|
||||
cp -rf src/libs/* $(BUILDDIR)/scripts/
|
||||
|
||||
schemes:
|
||||
@echo "$(BLUE)Copy schemes files$(NC)"
|
||||
- mkdir -p $(BUILDDIR)/resources/schemes
|
||||
cp src/core/schemes/* $(BUILDDIR)/resources/schemes/
|
||||
|
||||
testdata:
|
||||
@echo "$(BLUE)Copy JSON test files$(NC)"
|
||||
- mkdir -p $(BUILDDIR)/resources/jsons
|
||||
cp src/core/handlers/jsons/* $(BUILDDIR)/resources/jsons
|
||||
build_tags:
|
||||
@echo "$(BLUE)Building tag files$(NC)"
|
||||
-mkdir $(BUILDDIR)/resources
|
||||
-rm $(BUILDDIR)/resources/antos_tags.js
|
||||
for f in src/core/tags/*; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/antos_tags.js; done
|
||||
|
||||
languages:
|
||||
-mkdir $(BUILDDIR)/resources
|
||||
-mkdir $(BUILDDIR)/resources/languages
|
||||
-mkdir -p $(BUILDDIR)/resources
|
||||
-mkdir -p $(BUILDDIR)/resources/languages
|
||||
cp src/core/languages/*.json $(BUILDDIR)/resources/languages/
|
||||
|
||||
genlang:
|
||||
read -r -p "Enter locale: " LOCAL;\
|
||||
./src/core/languages/gen.sh ./src ./src/core/languages/$$LOCAL.json
|
||||
build_themes: antos_themes_build
|
||||
cp -r src/themes/system $(BUILDDIR)/resources/themes/
|
||||
build_themes: antos_light antos_dark
|
||||
-rm -rf $(BUILDDIR)/resources/themes/system/*
|
||||
-mkdir -p $(BUILDDIR)/resources/themes/system
|
||||
cp -r src/themes/system/fonts $(BUILDDIR)/resources/themes/system
|
||||
cp -r src/themes/system/wp $(BUILDDIR)/resources/themes/system
|
||||
for f in src/themes/system/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/system/system.css;done
|
||||
|
||||
antos_themes_build:
|
||||
@echo "$(BLUE)Building themes name: antos$(NC)"
|
||||
-rm -rf $(BUILDDIR)/resources/themes/antos/*
|
||||
-mkdir -p $(BUILDDIR)/resources/themes/antos
|
||||
for f in src/themes/antos/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/antos/antos.css;done
|
||||
-mkdir -p $(BUILDDIR)/resources/themes/antos/fonts
|
||||
cp -rf src/themes/antos/fonts/* $(BUILDDIR)/resources/themes/antos/fonts
|
||||
cp src/themes/antos/wp* $(BUILDDIR)/resources/themes/antos/
|
||||
antos_light:
|
||||
@echo "$(BLUE)Building themes name: antos-light$(NC)"
|
||||
-rm -rf $(BUILDDIR)/resources/themes/antos_light/*
|
||||
-mkdir -p $(BUILDDIR)/resources/themes/antos_light
|
||||
for f in src/themes/antos_light/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/antos_light/antos_light.css;done
|
||||
|
||||
|
||||
antos_dark:
|
||||
@echo "$(BLUE)Building themes name: antos-dark$(NC)"
|
||||
-rm -rf $(BUILDDIR)/resources/themes/antos_dark/*
|
||||
-mkdir -p $(BUILDDIR)/resources/themes/antos_dark
|
||||
for f in src/themes/antos_dark/*.css; do (cat "$${f}"; echo) >> $(BUILDDIR)/resources/themes/antos_dark/antos_dark.css;done
|
||||
|
||||
|
||||
build_packages:
|
||||
- mkdir $(BUILDDIR)/packages
|
||||
- mkdir -p $(BUILDDIR)/packages
|
||||
- for d in $(packages); do ( test -d $(BUILDDIR)/packages/$$d && rm -rf $(BUILDDIR)/packages/$$d/* ); done
|
||||
for d in $(packages); do (cd src/packages/$$d; make);done
|
||||
for d in $(packages); do ( test -d $(BUILDDIR)/packages/$$d || mkdir -p $(BUILDDIR)/packages/$$d && cp -rf src/packages/$$d/build/* $(BUILDDIR)/packages/$$d/);done
|
||||
@ -104,42 +126,42 @@ package:
|
||||
cd src/packages/$$PKG && make;\
|
||||
cd ../../../;\
|
||||
test -d $(BUILDDIR)/packages/$$PKG || mkdir -p $(BUILDDIR)/packages/$$PKG;\
|
||||
cp -rf src/packages/$$PKG/build/* $(BUILDDIR)/packages/$$PKG/;\
|
||||
cp -rfv src/packages/$$PKG/build/* $(BUILDDIR)/packages/$$PKG/;\
|
||||
test -d src/packages/$$PKG/build && rm -r src/packages/$$PKG/build;
|
||||
|
||||
pkgar:
|
||||
read -r -p "Enter package name: " PKG;\
|
||||
echo $$PKG | make package &&\
|
||||
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.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;\
|
||||
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:
|
||||
# uglify antos.js
|
||||
# npm install uglify-es -g
|
||||
# npm install uglify-js -g
|
||||
uglifyjs $(BUILDDIR)/scripts/antos.js --compress --mangle --output $(BUILDDIR)/scripts/antos.js
|
||||
# sudo npm install terser -g
|
||||
#
|
||||
terser $(BUILDDIR)/scripts/antos.js --compress --mangle --output $(BUILDDIR)/scripts/antos.js
|
||||
# uglify tags
|
||||
# npm install riot-cli -g
|
||||
riot --ext js $(BUILDDIR)/resources/antos_tags.js $(BUILDDIR)/resources/antos_tags.js
|
||||
uglifyjs $(BUILDDIR)/resources/antos_tags.js --compress --mangle --output $(BUILDDIR)/resources/antos_tags.js
|
||||
$(GSED) -i 's/resources\/antos_tags.js/scripts\/riot.min.js/g' $(BUILDDIR)/index.html
|
||||
$(GSED) -i 's/scripts\/riot.compiler.min.js/resources\/antos_tags.js/g' $(BUILDDIR)/index.html
|
||||
$(GSED) -i 's/type=\"riot\/tag\"/ /g' "$(BUILDDIR)/index.html"
|
||||
# npm install uglifycss -g
|
||||
# uglify the css
|
||||
uglifycss --output $(BUILDDIR)/resources/themes/antos/antos.css $(BUILDDIR)/resources/themes/antos/antos.css
|
||||
uglifycss --output $(BUILDDIR)/resources/themes/system/font-awesome.css $(BUILDDIR)/resources/themes/system/font-awesome.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 && uglifyjs $(BUILDDIR)/packages/$$d/main.js --compress --mangle --output $(BUILDDIR)/packages/$$d/main.js;\
|
||||
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;\
|
||||
done
|
||||
|
||||
release: main uglify
|
||||
|
||||
doc:
|
||||
./node_modules/.bin/typedoc --mode file --excludeNotExported --hideGenerator --name "AntOS API" --out $(DOCDIR)
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
rm -rf $(BUILDDIR)/resources
|
||||
rm -rf $(BUILDDIR)/scripts
|
||||
rm -rf $(BUILDDIR)/packages
|
||||
rm -rf $(BUILDDIR)/index.html
|
||||
|
30
README.md
30
README.md
@ -1,34 +1,34 @@
|
||||
# antOS
|
||||
|
||||
**This version 1.0.0a removes the dependencies on Riot.js by reimplementing the major API for GUI and Announcement system. The entire core API is also rewritten in TypeScript**
|
||||
|
||||
[](https://travis-ci.org/lxsang/antos)
|
||||
[](https://app.fossa.io/projects/git%2Bgithub.com%2Flxsang%2Fantos?ref=badge_shield)
|
||||
|
||||
Server or Embedded Linux are often headless, so accessing the resource on these systems is not always obvious. The aim of this project is to develop a client core API that enables desktop like experience to remote server's resource accessing using web technologies. AntOS is based on jQuery and Riot, it is designed to be used along with our [**antd**](https://github.com/lxsang/ant-http) server and Lua based server side app, but it can also be adapted to any server side languages (PHP, etc) and server, by implementing all the system calls API defined in core/handlers/RemoteHandler.coffee. Basically, application design for the web os relies on these system calls to communicating with the server. The API defines the core UI, system calls (to server), Virtual File system, virtual database and the necessary libraries for easing the development of webOS's applications. Applications can be developped with coffee/javascript/css without the need of a server side script.
|
||||
AntOS is a front-end API that mimics the traditional desktop environment on the web browser. The front-end can connect to a remote server and acts as a virtual desktop environment (VDE). The original purpose of AntOS is to provide visual tools to access and control resource on remote server
|
||||
and embedded linux environment. With its application API and the provided SDK, AntOS facilitates the
|
||||
development and deployment of user specific applications.
|
||||
|
||||
This repository contains only the front-end API. To have fully functional VDE, AntOS need to connect
|
||||
to the corresponding server side API.
|
||||
|
||||

|
||||
|
||||
Note that, the development of the project is in early alpha state, so bugs are very welcome :)
|
||||
The WebOS is tested on recent Firefox, Chrome and Safari, however i did not test it on IE or Edge since i have no Windows device :)
|
||||
|
||||
## Demo
|
||||
A demo of the web desktop is available at my page [https://os.lxsang.me](https://os.lxsang.me) using username: demo and password: demo
|
||||
A demo of the VDE is available at my page [https://os.iohub.dev](https://os.iohub.dev) using username: demo and password: demo
|
||||
|
||||

|
||||
|
||||
## AntOS applications
|
||||
[https://github.com/lxsang/antosdk-apps](https://github.com/lxsang/antosdk-apps)
|
||||
|
||||
## Build
|
||||
|
||||
Note that this is only the client API, to make it work for your application, you need to implement all the system calls in core/handlers/RemoteHandler.coffee using a server side scripting language (e.g. PHP). I'm planning to release an API documentation which describes what need to be sent and what will be returned for each system call in near future (i'm kind of very busy right now :) ).
|
||||
## Documentation
|
||||
|
||||
I'm a big fan of the Make system, so i use it as a build system for all of my projects. So, to build AntOS:
|
||||
1. You need to have *make* installed. Then since most of the API is written in Coffee script, you will need it to be installed too.
|
||||
2. Edit the BUILDDIR variable in the Makefile file to point to where you want to put the built API
|
||||
3. Type `make` then you are good to go.
|
||||
|
||||
It you have any problem, please contact me or open an issue, i'll try to response ASAP.
|
||||
- API documentation: [https://doc.iohub.dev/antos/api/](https://doc.iohub.dev/antos/api/)
|
||||
|
||||
## Licence
|
||||
|
||||
Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
Copyright 2017-2020 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
AnTOS is is licensed under the GNU General Public License v3.0, see the LICENCE file for more information
|
||||
|
||||
|
8
jest.config.js
Normal file
8
jest.config.js
Normal file
@ -0,0 +1,8 @@
|
||||
module.exports = {
|
||||
roots: ['<rootDir>'],
|
||||
transform : {
|
||||
'^.+\\.ts$': 'ts-jest'
|
||||
},
|
||||
testRegex: '(/tests/test.*|(\\.|/)(test|spec))\\.[tj]s?$',
|
||||
moduleFileExtensions: ['js', 'ts'],
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
_GUI = self.OS.GUI
|
||||
_API = self.OS.API
|
||||
_PM = self.OS.PM
|
||||
_OS = self.OS
|
||||
_courrier = self.OS.courrier
|
||||
this.onload = () ->
|
||||
($ document).on 'webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange', ()->
|
||||
_GUI.fullscreen = not _GUI.fullscreen
|
||||
self.OS.boot()
|
25
src/bootstrap.ts
Normal file
25
src/bootstrap.ts
Normal file
@ -0,0 +1,25 @@
|
||||
// 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/.
|
||||
|
||||
Ant.onload = function () {
|
||||
$(document).on(
|
||||
"webkitfullscreenchange mozfullscreenchange fullscreenchange MSFullscreenChange",
|
||||
() => (Ant.OS.GUI.fullscreen = !Ant.OS.GUI.fullscreen)
|
||||
);
|
||||
return Ant.OS.boot();
|
||||
};
|
353
src/core/Announcerment.ts
Normal file
353
src/core/Announcerment.ts
Normal file
@ -0,0 +1,353 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
export namespace API {
|
||||
/**
|
||||
* Observable entry type definition
|
||||
*
|
||||
* @export
|
||||
* @interface ObservableEntryType
|
||||
*/
|
||||
export interface ObservableEntryType {
|
||||
/**
|
||||
* A Set of callbacks that should be called only once.
|
||||
* These callbacks will be removed after the first
|
||||
* occurrence of the corresponding event
|
||||
*
|
||||
* @memberof ObservableEntryType
|
||||
*/
|
||||
one: Set<(d: any) => void>;
|
||||
|
||||
/**
|
||||
* A Set of callbacks that should be called
|
||||
* every time the corresponding event is triggered
|
||||
*
|
||||
* @memberof ObservableEntryType
|
||||
*/
|
||||
many: Set<(d: any) => void>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Announcement listener type definition
|
||||
*
|
||||
* @export
|
||||
* @interface AnnouncerListenerType
|
||||
*/
|
||||
export interface AnnouncerListenerType {
|
||||
[index: number]: {
|
||||
/**
|
||||
* The event name
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
e: string;
|
||||
|
||||
/**
|
||||
* The event callback
|
||||
*
|
||||
*/
|
||||
f: (d: any) => void;
|
||||
}[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is the based class used in AntOS event
|
||||
* announcement system.
|
||||
* It implements the observer pattern using simple
|
||||
* subscribe/publish mechanism
|
||||
* @export
|
||||
* @class Announcer
|
||||
*/
|
||||
export class Announcer {
|
||||
/**
|
||||
* The observable object that stores event name
|
||||
* and its corresponding callback in [[ObservableEntryType]]
|
||||
*
|
||||
* @type {GenericObject<ObservableEntryType>}
|
||||
* @memberof Announcer
|
||||
*/
|
||||
observable: GenericObject<ObservableEntryType>;
|
||||
|
||||
/**
|
||||
* Enable/disable the announcer
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof Announcer
|
||||
*/
|
||||
enable: boolean;
|
||||
|
||||
/**
|
||||
*Creates an instance of Announcer.
|
||||
* @memberof Announcer
|
||||
*/
|
||||
constructor() {
|
||||
this.observable = {};
|
||||
this.enable = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the announcer, when this function is called
|
||||
* all events and their callbacks will be removed
|
||||
*
|
||||
* @returns
|
||||
* @memberof Announcer
|
||||
*/
|
||||
disable() {
|
||||
this.off("*");
|
||||
return (this.enable = false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event, the callback will be called
|
||||
* every time the corresponding event is trigged
|
||||
*
|
||||
* @param {string} evtName event name
|
||||
* @param {(d: any) => void} callback The corresponding callback
|
||||
* @returns {void}
|
||||
* @memberof Announcer
|
||||
*/
|
||||
on(evtName: string, callback: (d: any) => void): void {
|
||||
if (!this.enable) {
|
||||
return;
|
||||
}
|
||||
if (!this.observable[evtName]) {
|
||||
this.observable[evtName] = {
|
||||
one: new Set(),
|
||||
many: new Set(),
|
||||
};
|
||||
}
|
||||
this.observable[evtName].many.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event, the callback will
|
||||
* be called only once and then removed from the announcer
|
||||
*
|
||||
* @param {string} evtName event name
|
||||
* @param {(d: any) => void} callback the corresponding callback
|
||||
* @returns {void}
|
||||
* @memberof Announcer
|
||||
*/
|
||||
one(evtName: string, callback: (d: any) => void): void {
|
||||
if (!this.enable) {
|
||||
return;
|
||||
}
|
||||
if (!this.observable[evtName]) {
|
||||
this.observable[evtName] = {
|
||||
one: new Set(),
|
||||
many: new Set(),
|
||||
};
|
||||
}
|
||||
this.observable[evtName].one.add(callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe the callback from an event
|
||||
*
|
||||
* @param {string} evtName event name
|
||||
* @param {(d: any) => void} [callback] the callback to be unsubscribed.
|
||||
* When the `callback` is `*`, all callbacks related to `evtName` will be
|
||||
* removed
|
||||
* @memberof Announcer
|
||||
*/
|
||||
off(evtName: string, callback?: (d: any) => void): void {
|
||||
const fn = (evt: string, cb: (d: any) => void) => {
|
||||
if (!this.observable[evt]) {
|
||||
return;
|
||||
}
|
||||
if (cb) {
|
||||
this.observable[evt].one.delete(cb);
|
||||
return this.observable[evt].many.delete(cb);
|
||||
} else {
|
||||
if (this.observable[evt]) {
|
||||
return delete this.observable[evt];
|
||||
}
|
||||
}
|
||||
};
|
||||
if (evtName === "*") {
|
||||
for (let k in this.observable) {
|
||||
fn(k, callback);
|
||||
}
|
||||
} else {
|
||||
fn(evtName, callback);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger an event
|
||||
*
|
||||
* @param {string} evtName event name
|
||||
* @param {*} data data object that will be send to all related callback
|
||||
* @returns {void}
|
||||
* @memberof Announcer
|
||||
*/
|
||||
trigger(evtName: string, data: any): void {
|
||||
const trig = (name: string, d: any) => {
|
||||
const names = [name, "*"];
|
||||
for (let evt of names) {
|
||||
if (!this.observable[evt]) {
|
||||
continue;
|
||||
}
|
||||
this.observable[evt].one.forEach((f) => f(d));
|
||||
this.observable[evt].one = new Set();
|
||||
this.observable[evt].many.forEach((f) => f(d));
|
||||
}
|
||||
};
|
||||
if (evtName === "*") {
|
||||
for (let k in this.observable) {
|
||||
const v = this.observable[k];
|
||||
if (k !== "*") {
|
||||
trig(k, data);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return trig(evtName, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This namespace defines every thing related to the system announcement.
|
||||
*
|
||||
* The system announcement provides a global way to communicate between
|
||||
* processes (applications/services) using the subscribe/publish
|
||||
* mechanism
|
||||
*/
|
||||
export namespace announcer {
|
||||
/**
|
||||
* The global announcer object that manages global events
|
||||
* and callbacks
|
||||
*/
|
||||
export var observable: API.Announcer = new API.Announcer();
|
||||
/**
|
||||
* This variable is used to allocate the `id` of all messages
|
||||
* passing between publishers and subscribers in the
|
||||
* system announcement
|
||||
*/
|
||||
export var quota: 0;
|
||||
/**
|
||||
* Placeholder of all global events listeners
|
||||
*/
|
||||
export var listeners: API.AnnouncerListenerType = {};
|
||||
|
||||
/**
|
||||
* Subscribe to a global event
|
||||
*
|
||||
* @export
|
||||
* @param {string} e event name
|
||||
* @param {(d: 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 {
|
||||
if (!announcer.listeners[a.pid]) {
|
||||
announcer.listeners[a.pid] = [];
|
||||
}
|
||||
announcer.listeners[a.pid].push({ e, f });
|
||||
announcer.observable.on(e, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a global event
|
||||
*
|
||||
* @export
|
||||
* @param {string} e event name
|
||||
* @param {*} d data passing to all related callback
|
||||
*/
|
||||
export function trigger(e: string, d: any): void {
|
||||
announcer.observable.trigger(e, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report system fail. This will trigger the global `fail`
|
||||
* event
|
||||
*
|
||||
* @export
|
||||
* @param {(string | FormattedString)} m message string
|
||||
* @param {Error} e error to be reported
|
||||
*/
|
||||
export function osfail(m: string | FormattedString, e: Error): void {
|
||||
announcer.ostrigger("fail", { m, e });
|
||||
}
|
||||
|
||||
/**
|
||||
* Report system error. This will trigger the global `error`
|
||||
* event
|
||||
*
|
||||
* @export
|
||||
* @param {(string | FormattedString)} m message string
|
||||
* @param {Error} e error to be reported
|
||||
*/
|
||||
export function oserror(m: string | FormattedString, e: Error): void {
|
||||
announcer.ostrigger("error", { m, e });
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger system notification (`info` event)
|
||||
*
|
||||
* @export
|
||||
* @param {(string | FormattedString)} m notification message
|
||||
*/
|
||||
export function osinfo(m: string | FormattedString): void {
|
||||
announcer.ostrigger("info", { m, e: null });
|
||||
}
|
||||
|
||||
/**
|
||||
* trigger a specific global event
|
||||
*
|
||||
* @export
|
||||
* @param {string} e event name
|
||||
* @param {*} d event data
|
||||
*/
|
||||
export function ostrigger(e: string, d: any): void {
|
||||
announcer.trigger(e, { id: 0, data: d, name: "OS" });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a process (application/service) from
|
||||
* the global announcement system
|
||||
*
|
||||
* @export
|
||||
* @param {GUI.BaseModel} app reference to the process
|
||||
* @returns {void}
|
||||
*/
|
||||
export function unregister(app: BaseModel): void {
|
||||
if (
|
||||
!announcer.listeners[app.pid] ||
|
||||
!(announcer.listeners[app.pid].length > 0)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
for (let i of announcer.listeners[app.pid]) {
|
||||
announcer.observable.off(i.e, i.f);
|
||||
}
|
||||
delete announcer.listeners[app.pid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate message id
|
||||
*
|
||||
* @export
|
||||
* @returns {number}
|
||||
*/
|
||||
export function getMID(): number {
|
||||
quota += 1;
|
||||
return quota;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,132 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
class BaseApplication extends this.OS.GUI.BaseModel
|
||||
constructor: (name, args) ->
|
||||
super name, args
|
||||
if (not _OS.setting.applications[@name]) or (Array.isArray OS.setting.applications[@name])
|
||||
_OS.setting.applications[@name] = {}
|
||||
@setting = _OS.setting.applications[@name]
|
||||
@keycomb =
|
||||
ALT: {}
|
||||
CTRL: {}
|
||||
SHIFT: {}
|
||||
META: {}
|
||||
me = @
|
||||
init: ->
|
||||
me = @
|
||||
@off "*"
|
||||
@on "exit", () -> me.quit()
|
||||
# first register some base event to the app
|
||||
@on "focus", () ->
|
||||
me.sysdock.set "selectedApp", me
|
||||
me.appmenu.pid = me.pid
|
||||
me.appmenu.set "items", (me.baseMenu() || [])
|
||||
me.appmenu.set "onmenuselect", (d) ->
|
||||
me.trigger("menuselect", d)
|
||||
me.dialog.show() if me.dialog
|
||||
@on "hide", () ->
|
||||
me.sysdock.set "selectedApp", null
|
||||
me.appmenu.set "items", []
|
||||
me.dialog.hide() if me.dialog
|
||||
@on "menuselect", (d) ->
|
||||
switch d.e.item.data.dataid
|
||||
when "#{me.name}-about" then me.openDialog "AboutDialog", ()->
|
||||
when "#{me.name}-exit" then me.trigger "exit"
|
||||
@on "apptitlechange", () -> me.sysdock.update()
|
||||
@loadScheme()
|
||||
|
||||
loadScheme: () ->
|
||||
#now load the scheme
|
||||
path = "#{@meta().path}/scheme.html"
|
||||
@.render path
|
||||
|
||||
bindKey: (k, f) ->
|
||||
arr = k.split "-"
|
||||
return unless arr.length is 2
|
||||
fnk = arr[0].toUpperCase()
|
||||
c = arr[1].toUpperCase()
|
||||
return unless @keycomb[fnk]
|
||||
@keycomb[fnk][c] = f
|
||||
|
||||
shortcut: (fnk, c, e) ->
|
||||
return true unless @keycomb[fnk]
|
||||
return true unless @keycomb[fnk][c]
|
||||
@keycomb[fnk][c](e)
|
||||
return false
|
||||
|
||||
applySetting: (k) ->
|
||||
applyAllSetting: () ->
|
||||
@applySetting k for k, v of @setting
|
||||
registry: (k, v) ->
|
||||
@setting[k] = v
|
||||
@publish "appregistry", k
|
||||
|
||||
show: () ->
|
||||
@trigger "focus"
|
||||
|
||||
blur: () ->
|
||||
@.appmenu.set "items", [] if @.appmenu and @.pid == @.appmenu.pid
|
||||
@trigger "blur"
|
||||
|
||||
hide: () ->
|
||||
@trigger "hide"
|
||||
|
||||
toggle: () ->
|
||||
@trigger "toggle"
|
||||
|
||||
title: () ->
|
||||
@scheme.get "apptitle"
|
||||
|
||||
onexit: (evt) ->
|
||||
@cleanup(evt)
|
||||
if not evt.prevent
|
||||
@.appmenu.set "items", [] if @.pid == @.appmenu.pid
|
||||
($ @scheme).remove()
|
||||
meta: () -> _OS.APP[@name].meta
|
||||
baseMenu: ->
|
||||
mn =
|
||||
[{
|
||||
text: _OS.APP[@name].meta.name,
|
||||
child: [
|
||||
{ text: "__(About)", dataid: "#{@name}-about" },
|
||||
{ text: "__(Exit)", dataid: "#{@name}-exit" }
|
||||
]
|
||||
}]
|
||||
mn = mn.concat @menu() || []
|
||||
mn
|
||||
|
||||
main: ->
|
||||
#main program
|
||||
# implement by subclasses
|
||||
menu: ->
|
||||
# implement by subclasses
|
||||
# to add menu to application
|
||||
[]
|
||||
open:->
|
||||
#implement by subclasses
|
||||
data:->
|
||||
#implement by subclasses
|
||||
# to return app data
|
||||
|
||||
cleanup: (e) ->
|
||||
#implement by subclasses
|
||||
# to handle the exit event
|
||||
# use e.preventDefault() to
|
||||
# discard the quit command
|
||||
BaseApplication.type = 1
|
||||
this.OS.GUI.BaseApplication = BaseApplication
|
443
src/core/BaseApplication.ts
Normal file
443
src/core/BaseApplication.ts
Normal file
@ -0,0 +1,443 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
/**
|
||||
* This namespace is dedicated to application and service definition.
|
||||
* When an application is loaded, its prototype definition will be
|
||||
* inserted to this namespace for reuse lately
|
||||
*/
|
||||
export namespace application {
|
||||
/**
|
||||
* Abstract prototype of all AntOS applications.
|
||||
* Any new application definition should extend
|
||||
* this prototype
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class BaseApplication
|
||||
* @extends {BaseModel}
|
||||
*/
|
||||
export abstract class BaseApplication extends BaseModel {
|
||||
/**
|
||||
* Placeholder of all settings specific to the application.
|
||||
* The settings stored in this object will be saved to system
|
||||
* setting when logout and can be reused in the next login session
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
setting: GenericObject<any>;
|
||||
|
||||
/**
|
||||
* Hotkeys (shortcuts) defined for this application
|
||||
*
|
||||
* @protected
|
||||
* @type {GUI.ShortcutType}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected keycomb: GUI.ShortcutType;
|
||||
|
||||
/**
|
||||
* Reference to the system dock
|
||||
*
|
||||
* @type {GUI.tag.AppDockTag}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
sysdock: GUI.tag.AppDockTag;
|
||||
|
||||
/**
|
||||
* Reference to the system application menu located
|
||||
* on the system panel
|
||||
*
|
||||
* @type {GUI.tag.MenuTag}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
appmenu: GUI.tag.MenuTag;
|
||||
|
||||
/**
|
||||
*Creates an instance of BaseApplication.
|
||||
* @param {string} name application name
|
||||
* @param {AppArgumentsType[]} args application arguments
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
constructor(name: string, args: AppArgumentsType[]) {
|
||||
super(name, args);
|
||||
if (!setting.applications[this.name]) {
|
||||
setting.applications[this.name] = {};
|
||||
}
|
||||
this.setting = setting.applications[this.name];
|
||||
this.keycomb = {
|
||||
ALT: {},
|
||||
CTRL: {},
|
||||
SHIFT: {},
|
||||
META: {},
|
||||
};
|
||||
this.subscribe("appregistry", (m) => {
|
||||
if (m.name === this.name) {
|
||||
this.applySetting(m.data.m);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the application, this function is called when the
|
||||
* application process is created and docked in the application
|
||||
* dock.
|
||||
*
|
||||
* The application UI will be rendered after the execution
|
||||
* of this function.
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
init(): void {
|
||||
this.off("*");
|
||||
this.on("exit", () => this.quit(false));
|
||||
// first register some base event to the app
|
||||
this.on("focus", () => {
|
||||
this.sysdock.selectedApp = this;
|
||||
this.appmenu.pid = this.pid;
|
||||
this.appmenu.items = this.baseMenu() || [];
|
||||
this.appmenu.onmenuselect = (
|
||||
d: GUI.tag.MenuEventData
|
||||
): void => {
|
||||
return this.trigger("menuselect", d);
|
||||
};
|
||||
if (this.dialog) {
|
||||
return this.dialog.show();
|
||||
}
|
||||
});
|
||||
this.on("hide", () => {
|
||||
this.sysdock.selectedApp = null;
|
||||
this.appmenu.items = [];
|
||||
this.appmenu.pid = -1;
|
||||
if (this.dialog) {
|
||||
return this.dialog.hide();
|
||||
}
|
||||
});
|
||||
this.on("menuselect", (d) => {
|
||||
switch (d.data.item.data.dataid) {
|
||||
case `${this.name}-about`:
|
||||
return this.openDialog("AboutDialog");
|
||||
case `${this.name}-exit`:
|
||||
return this.trigger("exit", undefined);
|
||||
}
|
||||
});
|
||||
this.on("apptitlechange", () => this.sysdock.update(undefined));
|
||||
this.updateLocale(this.systemsetting.system.locale);
|
||||
return this.loadScheme();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the application UI by first loading its scheme
|
||||
* and then mount this scheme to the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected loadScheme(): void {
|
||||
//now load the scheme
|
||||
const path = `${this.meta().path}/scheme.html`;
|
||||
return this.render(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* API function to perform an heavy task.
|
||||
* This function will trigger the global `loading`
|
||||
* event at the beginning of the task, and the `loaded`
|
||||
* event after finishing the task
|
||||
*
|
||||
* @protected
|
||||
* @param {Promise<any>} promise the promise on a task to be performed
|
||||
* @returns {Promise<any>}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected load(promise: Promise<any>): Promise<any> {
|
||||
const q = this._api.mid();
|
||||
return new Promise(async (resolve, reject) => {
|
||||
this._api.loading(q, this.name);
|
||||
try {
|
||||
await promise;
|
||||
this._api.loaded(q, this.name, "OK");
|
||||
return resolve();
|
||||
} catch (e) {
|
||||
this._api.loaded(q, this.name, "FAIL");
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a hotkey to the application, this function
|
||||
* is used to define application keyboard shortcut
|
||||
*
|
||||
* @protected
|
||||
* @param {string} k the hotkey to bind, should be in the following
|
||||
* format: `[ALT|SHIFT|CTRL|META]-KEY`, e.g. `CTRL-S`
|
||||
* @param {(e: JQuery.KeyboardEventBase) => void} f the callback function
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected bindKey(
|
||||
k: string,
|
||||
f: (e: JQuery.KeyboardEventBase) => void
|
||||
): void {
|
||||
const arr = k.split("-");
|
||||
if (arr.length !== 2) {
|
||||
return;
|
||||
}
|
||||
const fnk = arr[0].toUpperCase();
|
||||
const c = arr[1].toUpperCase();
|
||||
if (!this.keycomb[fnk]) {
|
||||
return;
|
||||
}
|
||||
this.keycomb[fnk][c] = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
const meta = this.meta();
|
||||
if (!meta || !meta.locales) {
|
||||
return;
|
||||
}
|
||||
if (!meta.locales[name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = [];
|
||||
for (let k in meta.locales[name]) {
|
||||
const v = meta.locales[name][k];
|
||||
result.push((this._api.lang[k] = v));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback subscribed to a
|
||||
* keyboard shortcut
|
||||
*
|
||||
* @param {string} fnk meta or modifier key e.g. `CTRL`, `ALT`, `SHIFT` or `META`
|
||||
* @param {string} c a regular key
|
||||
* @param {JQuery.KeyboardEventBase} e JQuery keyboard event
|
||||
* @returns {boolean} return whether the shortcut is executed
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
shortcut(fnk: string, c: string, e: JQuery.KeyDownEvent): boolean {
|
||||
if (!this.keycomb[fnk]) {
|
||||
return true;
|
||||
}
|
||||
if (!this.keycomb[fnk][c]) {
|
||||
return true;
|
||||
}
|
||||
this.keycomb[fnk][c](e);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a setting to the application
|
||||
*
|
||||
* @protected
|
||||
* @param {string} k the setting name
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected applySetting(k: string): void {}
|
||||
|
||||
/**
|
||||
* Apply all settings to the application
|
||||
*
|
||||
* @protected
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected applyAllSetting(): void {
|
||||
for (let k in this.setting) {
|
||||
const v = this.setting[k];
|
||||
this.applySetting(k);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a setting value to the application setting
|
||||
* registry
|
||||
*
|
||||
* @protected
|
||||
* @param {string} k setting name
|
||||
* @param {*} v setting value
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected registry(k: string, v: any): void {
|
||||
this.setting[k] = v;
|
||||
return this.publish("appregistry", k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the appliation
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
show(): void {
|
||||
return this.trigger("focus", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Blur the application
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
blur(): void {
|
||||
if (this.appmenu && this.pid === this.appmenu.pid) {
|
||||
this.appmenu.items = [];
|
||||
}
|
||||
return this.trigger("blur", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the application
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
hide(): void {
|
||||
return this.trigger("hide", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximize or restore the application window size
|
||||
* and its position
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
toggle(): void {
|
||||
return this.trigger("toggle", undefined);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application title
|
||||
*
|
||||
* @returns {(string| FormattedString)}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
title(): string | FormattedString {
|
||||
return (this.scheme as GUI.tag.WindowTag).apptitle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function called when the application exit.
|
||||
* If the input exit event is prevented, the application
|
||||
* process will not be killed
|
||||
*
|
||||
*
|
||||
* @protected
|
||||
* @param {BaseEvent} evt exit event
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected onexit(evt: BaseEvent): void {
|
||||
this.cleanup(evt);
|
||||
if (!evt.prevent) {
|
||||
if (this.pid === this.appmenu.pid) {
|
||||
this.appmenu.items = [];
|
||||
}
|
||||
$(this.scheme).remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the application meta-data
|
||||
*
|
||||
* @returns {API.PackageMetaType}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
meta(): API.PackageMetaType {
|
||||
return application[this.name].meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base menu definition. This function
|
||||
* returns the based menu definition of all applications.
|
||||
* Other application specific menu entries
|
||||
* should be defined in [[menu]] function
|
||||
*
|
||||
* @protected
|
||||
* @returns {GUI.BasicItemType[]}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected baseMenu(): GUI.BasicItemType[] {
|
||||
let mn: GUI.BasicItemType[] = [
|
||||
{
|
||||
text: application[this.name].meta.name,
|
||||
nodes: [
|
||||
{ text: "__(About)", dataid: `${this.name}-about` },
|
||||
{ text: "__(Exit)", dataid: `${this.name}-exit` },
|
||||
],
|
||||
},
|
||||
];
|
||||
mn = mn.concat(this.menu() || []);
|
||||
return mn;
|
||||
}
|
||||
|
||||
/**
|
||||
* The main application entry that is called after
|
||||
* the application UI is rendered. This application
|
||||
* must be implemented by all subclasses
|
||||
*
|
||||
* @abstract
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
abstract main(): void;
|
||||
|
||||
/**
|
||||
* Application specific menu definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {GUI.BasicItemType[]}
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected menu(): GUI.BasicItemType[] {
|
||||
// implement by subclasses
|
||||
// to add menu to application
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The cleanup function that is called by [[onexit]] function.
|
||||
* Application need to override this function to perform some
|
||||
* specific task before exiting or to prevent the application
|
||||
* to be exited
|
||||
*
|
||||
* @protected
|
||||
* @param {BaseEvent} e
|
||||
* @memberof BaseApplication
|
||||
*/
|
||||
protected cleanup(e: BaseEvent): void {}
|
||||
}
|
||||
|
||||
BaseApplication.type = ModelType.Application;
|
||||
}
|
||||
}
|
@ -1,339 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
class SubWindow extends this.OS.GUI.BaseModel
|
||||
constructor: (name) ->
|
||||
super name, null
|
||||
@parent = undefined
|
||||
@modal = false
|
||||
|
||||
quit: () ->
|
||||
evt = new _GUI.BaseEvent("exit")
|
||||
@onexit(evt)
|
||||
if not evt.prevent
|
||||
delete @.observable
|
||||
($ @scheme).remove() if @scheme
|
||||
@dialog.quit() if @dialog
|
||||
init: () ->
|
||||
main: () ->
|
||||
meta: () ->
|
||||
@parent.meta()
|
||||
show: () ->
|
||||
@trigger 'focus'
|
||||
($ @scheme).css "z-index", window._zindex + 2
|
||||
hide: () ->
|
||||
@trigger 'hide'
|
||||
|
||||
SubWindow.type = 3
|
||||
this.OS.GUI.SubWindow = SubWindow
|
||||
|
||||
class BaseDialog extends SubWindow
|
||||
constructor: (name) ->
|
||||
super name
|
||||
@handler = undefined
|
||||
|
||||
onexit: (e) ->
|
||||
@parent.dialog = undefined if @parent
|
||||
|
||||
this.OS.GUI.BaseDialog = BaseDialog
|
||||
###
|
||||
this dialog rende a tag as main content
|
||||
and a list of buttons, the behaviour of
|
||||
the button is specified by user. The conf
|
||||
object is in the follow form
|
||||
{
|
||||
tag: <tag_name>,
|
||||
buttons:[
|
||||
{
|
||||
label: 'buton label',
|
||||
onclick: function(d){...}
|
||||
}, ...
|
||||
]
|
||||
}
|
||||
###
|
||||
class BasicDialog extends BaseDialog
|
||||
constructor: ( name, @conf, @title) ->
|
||||
super name
|
||||
|
||||
init: () ->
|
||||
@title = @name if not @title
|
||||
html = "<afx-app-window data-id = '#{@name}' width='#{@conf.width}' height='#{@conf.height}'>"
|
||||
html += "<afx-hbox><div data-width='7'></div><afx-vbox><div data-height='5'></div>"
|
||||
html += "<#{v.tag} #{v.att} data-id = 'content#{k}'></#{v.tag}>" for k,v of @conf.tags
|
||||
html += "<div data-height = '35' style=' text-align:right;padding-top:3px;'>"
|
||||
html += "<afx-button data-id = 'bt#{k}' text = '#{v.label}' style='margin-left:5px;'></afx-button>" for k,v of @conf.buttons
|
||||
html += "</div><div data-height='5'></div></afx-vbox><div data-width='7'></div></afx-hbox></afx-app-window>"
|
||||
#render the html
|
||||
_GUI.htmlToScheme html, @, @host
|
||||
|
||||
main: () ->
|
||||
@scheme.set "apptitle", @title
|
||||
@scheme.set "minimizable", false
|
||||
@scheme.set "resizable", @conf.resizable if @conf.resizable isnt undefined
|
||||
me = @
|
||||
f = (_v) -> () -> _v.onclick me
|
||||
# bind action to button
|
||||
( (me.find "bt#{k}").set "onbtclick", f(v) ) for k, v of @conf.buttons
|
||||
@conf.filldata @ if @conf.filldata
|
||||
@conf.xtra @ if @conf.xtra
|
||||
|
||||
this.OS.GUI.BasicDialog = BasicDialog
|
||||
|
||||
class PromptDialog extends BasicDialog
|
||||
constructor: () ->
|
||||
super "PromptDialog", {
|
||||
tags: [
|
||||
{ tag: "afx-label" },
|
||||
{ tag: "input", att: "type = 'text' data-height='25'" }
|
||||
],
|
||||
width: 200,
|
||||
height: 120,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
label: "__(Ok)",
|
||||
onclick: (d) ->
|
||||
txt = (d.find "content1").value
|
||||
return d.quit() if txt is ""
|
||||
d.handler txt if d.handler
|
||||
d.quit()
|
||||
},
|
||||
{
|
||||
label: "__(Cancel)",
|
||||
onclick: (d) -> d.quit()
|
||||
}
|
||||
],
|
||||
filldata: (d) ->
|
||||
return unless d.data
|
||||
(d.find "content0").set "text", d.data.label
|
||||
(d.find "content1").value = d.data.value if d.data.value
|
||||
$(d.find "content1").attr("type", d.data.type) if d.data.type
|
||||
xtra: (d) ->
|
||||
$( d.find "content1" ).keyup (e) ->
|
||||
(d.find "bt0").trigger() if e.which is 13
|
||||
}
|
||||
|
||||
this.OS.register "PromptDialog", PromptDialog
|
||||
|
||||
class CalendarDialog extends BasicDialog
|
||||
constructor: () ->
|
||||
super "CalendarDialog", {
|
||||
tags: [{ tag: 'afx-calendar-view' }],
|
||||
width: 300,
|
||||
height: 230,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
label: "__(Ok)",
|
||||
onclick: (d) ->
|
||||
date = (d.find "content0").get "selectedDate"
|
||||
if date
|
||||
d.handler date if d.handler
|
||||
d.quit()
|
||||
else
|
||||
d.notify __("Please select a date")
|
||||
},
|
||||
{
|
||||
label: "__(Cancel)",
|
||||
onclick: (d) -> d.quit()
|
||||
}
|
||||
]
|
||||
}
|
||||
this.OS.register "CalendarDialog", CalendarDialog
|
||||
|
||||
class ColorPickerDialog extends BasicDialog
|
||||
constructor: () ->
|
||||
super "ColorPickerDialog", {
|
||||
tags: [{ tag: 'afx-color-picker' }, {tag:'div', att: 'data-height="5"' }],
|
||||
width: 313,
|
||||
height: 250,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
label: "__(Ok)",
|
||||
onclick: (d) ->
|
||||
c = (d.find "content0").get "selectedColor"
|
||||
if c
|
||||
d.handler c if d.handler
|
||||
d.quit()
|
||||
else
|
||||
d.notify "Please select a color"
|
||||
},
|
||||
{
|
||||
label: "__(Cancel)",
|
||||
onclick: (d) -> d.quit()
|
||||
}
|
||||
]
|
||||
}
|
||||
this.OS.register "ColorPickerDialog", ColorPickerDialog
|
||||
|
||||
class InfoDialog extends BasicDialog
|
||||
constructor: () ->
|
||||
super "InfoDialog", {
|
||||
tags: [{ tag: 'afx-grid-view' }],
|
||||
width: 250,
|
||||
height: 300,
|
||||
resizable: true,
|
||||
buttons: [ { label: "__(Cancel)", onclick: (d) -> d.quit() } ],
|
||||
filldata: (d) ->
|
||||
return unless d.data
|
||||
rows = []
|
||||
rows.push [ { value: k }, { value: v } ] for k, v of d.data
|
||||
(d.find "content0").set "rows", rows
|
||||
}
|
||||
this.OS.register "InfoDialog", InfoDialog
|
||||
|
||||
|
||||
class YesNoDialog extends BasicDialog
|
||||
constructor: () ->
|
||||
super "YesNoDialog", {
|
||||
tags: [{ tag: "afx-label" }],
|
||||
width: 300,
|
||||
height: 100,
|
||||
resizable: true,
|
||||
buttons: [
|
||||
{
|
||||
label: "__(Yes)", onclick: (d) ->
|
||||
d.handler true if d.handler
|
||||
d.quit()
|
||||
},
|
||||
{
|
||||
label: "__(No)", onclick: (d) ->
|
||||
d.handler false if d.handler
|
||||
d.quit()
|
||||
}
|
||||
],
|
||||
filldata: (d) ->
|
||||
return unless d.data
|
||||
l = d.find "content0"
|
||||
for k, v of d.data
|
||||
l.set k, v
|
||||
}
|
||||
this.OS.register "YesNoDialog", YesNoDialog
|
||||
|
||||
class SelectionDialog extends BasicDialog
|
||||
constructor: () ->
|
||||
super "SelectionDialog", {
|
||||
tags: [{ tag: "afx-list-view" }],
|
||||
width: 250,
|
||||
height: 300,
|
||||
resizable: false,
|
||||
buttons: [
|
||||
{
|
||||
label: "__(Ok)", onclick: (d) ->
|
||||
el = d.find "content0"
|
||||
it = el.get "selected"
|
||||
return unless it
|
||||
d.handler it if d.handler
|
||||
d.quit()
|
||||
},
|
||||
{ label: "__(Cancel)", onclick: (d) -> d.quit() }
|
||||
],
|
||||
filldata: (d) ->
|
||||
return unless d.data
|
||||
(d.find "content0").set "items", d.data
|
||||
xtra: (d) ->
|
||||
( d.find "content0" ).set "onlistdbclick", (e) ->
|
||||
(d.find "bt0").trigger()
|
||||
|
||||
}
|
||||
this.OS.register "SelectionDialog", SelectionDialog
|
||||
|
||||
class AboutDialog extends BaseDialog
|
||||
constructor: () ->
|
||||
super "AboutDialog"
|
||||
|
||||
init: () ->
|
||||
@render "os://resources/schemes/about.html"
|
||||
|
||||
main: () ->
|
||||
mt = @meta()
|
||||
@scheme.set "apptitle", __("About: {0}",mt.name)
|
||||
(@find "mylabel").set "*", {icon:mt.icon, iconclass:mt.iconclass, text:"#{mt.name}(v#{mt.version})"}
|
||||
($ @find "mydesc").html mt.description
|
||||
# grid data for author info
|
||||
return unless mt.info
|
||||
rows = []
|
||||
rows.push [ { value: k }, { value: v } ] for k, v of mt.info
|
||||
(@find "mygrid").set "rows", rows
|
||||
|
||||
this.OS.register "AboutDialog", AboutDialog
|
||||
|
||||
class FileDiaLog extends BaseDialog
|
||||
constructor: () ->
|
||||
super "FileDiaLog"
|
||||
|
||||
init: () ->
|
||||
@render "os://resources/schemes/filedialog.html"
|
||||
|
||||
main: () ->
|
||||
fileview = @find "fileview"
|
||||
location = @find "location"
|
||||
filename = @find "filename"
|
||||
me = @
|
||||
@scheme.set "apptitle", @title
|
||||
fileview.set "fetch", (e, f) ->
|
||||
return unless e.child
|
||||
e.child.path.asFileHandler().read (d) ->
|
||||
return me.error __("Resource not found: {0}", e.child.path) if d.error
|
||||
f d.result
|
||||
setroot = (path) ->
|
||||
path.asFileHandler().read (d) ->
|
||||
if(d.error)
|
||||
return me.error __("Resource not found: {0}", path)
|
||||
fileview.set "path", path
|
||||
fileview.set "data", d.result
|
||||
if not @data or not @data.root
|
||||
location.set "onlistselect", (e) ->
|
||||
return unless e and e.data.path
|
||||
setroot e.data.path
|
||||
location.set "items", ( i for i in @systemsetting.VFS.mountpoints when i.type isnt "app" )
|
||||
location.set "selected", 0 unless location.get "selected"
|
||||
else
|
||||
$(location).hide()
|
||||
@trigger "calibrate"
|
||||
setroot @data.root
|
||||
fileview.set "onfileselect", (f) ->
|
||||
($ filename).val f.filename if f.type is "file"
|
||||
(@find "bt-ok").set "onbtclick", (e) ->
|
||||
f = fileview.get "selectedFile"
|
||||
return me.notify __("Please select a file/fofler") unless f
|
||||
return me.notify __("Please select {0} only", me.data.type) if me.data and me.data.type and me.data.type isnt f.type
|
||||
if me.data and me.data.mimes
|
||||
#verify the mime
|
||||
m = false
|
||||
if f.mime
|
||||
for v in me.data.mimes
|
||||
if f.mime.match (new RegExp v, "g")
|
||||
m = true
|
||||
break
|
||||
return me.notify __("Only {0} could be selected", me.data.mimes.join(",")) unless m
|
||||
d = f.path
|
||||
d = f.path.asFileHandler().parent() if f.type is "file"
|
||||
me.handler d, ($ filename).val(), f.path, f if me.handler
|
||||
#sel = if me.data and me.data.selection then me.data.selection else "file"
|
||||
#me.handler f, ($ filename).val() if me.handler and ((f.type is sel) or (sel is "*"))
|
||||
me.quit()
|
||||
|
||||
(@find "bt-cancel").set "onbtclick", (e) ->
|
||||
me.quit()
|
||||
if @data and @data.file
|
||||
($ filename).css("display", "block").val @data.file.basename or "Untitled"
|
||||
@trigger "resize"
|
||||
fileview.set "showhidden", @data.hidden if @data and @data.hidden
|
||||
|
||||
this.OS.register "FileDiaLog", FileDiaLog
|
1126
src/core/BaseDialog.ts
Normal file
1126
src/core/BaseDialog.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,24 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
class BaseEvent
|
||||
constructor: (@name, @force) ->
|
||||
@prevent = false
|
||||
preventDefault: () ->
|
||||
@prevent = true if not @force
|
||||
|
||||
this.OS.GUI.BaseEvent = BaseEvent
|
@ -1,121 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class BaseModel
|
||||
constructor: (@name, @args) ->
|
||||
me = @
|
||||
@observable = riot.observable()
|
||||
@_api = self.OS.API
|
||||
@_gui = self.OS.GUI
|
||||
@systemsetting = self.OS.setting
|
||||
me = @
|
||||
@on "exit", () -> me.quit()
|
||||
@host = "#desktop"
|
||||
@dialog = undefined
|
||||
render: (p) ->
|
||||
_GUI.loadScheme p, @, @host
|
||||
|
||||
quit: (force) ->
|
||||
evt = new _GUI.BaseEvent("exit", force)
|
||||
@onexit(evt)
|
||||
if not evt.prevent
|
||||
@observable.off "*"
|
||||
delete @.observable
|
||||
@dialog.quit() if @dialog
|
||||
_PM.kill @
|
||||
|
||||
path: () ->
|
||||
mt = @meta()
|
||||
return mt.path if mt and mt.path
|
||||
return null
|
||||
|
||||
# call a server side script
|
||||
call: (cmd, func) ->
|
||||
@_api.apigateway cmd, false, func
|
||||
|
||||
# get a stream
|
||||
stream: () ->
|
||||
return @_api.apigateway null, true, null
|
||||
|
||||
init: ->
|
||||
#implement by sub class
|
||||
onexit: (e) ->
|
||||
#implement by subclass
|
||||
|
||||
one: (e, f) -> @observable.one e, f
|
||||
on: (e, f) -> @observable.on e, f
|
||||
off: (e, f) ->
|
||||
return @observable.off e unless f
|
||||
@observable.off e, f
|
||||
trigger: (e, d) -> @observable.trigger e, d
|
||||
|
||||
subscribe: (e, f) ->
|
||||
_courrier.on e, f, @
|
||||
|
||||
openDialog: (d, f, title, data) ->
|
||||
if @dialog
|
||||
@dialog.show()
|
||||
return
|
||||
if typeof d is "string"
|
||||
if not _GUI.subwindows[d]
|
||||
@error __("Dialog {0} not found", d)
|
||||
return
|
||||
@dialog = new _GUI.subwindows[d]()
|
||||
else
|
||||
@dialog = d
|
||||
#@dialog.observable = riot.observable() unless @dialog
|
||||
@dialog.parent = @
|
||||
@dialog.handler = f
|
||||
@dialog.pid = @pid
|
||||
@dialog.data = data
|
||||
@dialog.title = title
|
||||
@dialog.init()
|
||||
|
||||
ask: (t, m, f) ->
|
||||
@._gui.openDialog "YesNoDialog", (d) ->
|
||||
f() if d
|
||||
, t, { text: m }
|
||||
|
||||
publish: (t, m, e) ->
|
||||
mt = @meta()
|
||||
icon = undefined
|
||||
icon = "#{mt.path}/#{mt.icon}" if mt.icon
|
||||
_courrier.trigger t, { id: @pid, name: @name, data: { m: m, icon: icon, iconclass: mt.iconclass }, error: e }
|
||||
|
||||
notify: (m) ->
|
||||
@publish "notification", m
|
||||
|
||||
warn: (m) ->
|
||||
@publish "warning", m
|
||||
|
||||
error: (m) ->
|
||||
@publish "error", m, (@_api.throwe @name)
|
||||
|
||||
fail: (m) ->
|
||||
@publish "fail", m
|
||||
|
||||
throwe: () ->
|
||||
@_api.throwe @name
|
||||
|
||||
update:->
|
||||
@scheme.update() if @scheme
|
||||
|
||||
find: (id) -> ($ "[data-id='#{id}']", @scheme)[0] if @scheme
|
||||
|
||||
select: (sel) -> $ sel, @scheme if @scheme
|
||||
this.OS.GUI.BaseModel = BaseModel
|
712
src/core/BaseModel.ts
Normal file
712
src/core/BaseModel.ts
Normal file
@ -0,0 +1,712 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
namespace OS {
|
||||
/**
|
||||
* Application argument type definition
|
||||
*
|
||||
* @export
|
||||
* @interface AppArgumentsType
|
||||
*/
|
||||
export interface AppArgumentsType {
|
||||
/**
|
||||
* File type to be open by the app
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AppArgumentsType
|
||||
*/
|
||||
type?: string;
|
||||
|
||||
/**
|
||||
* File path to be opened
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AppArgumentsType
|
||||
*/
|
||||
path: string;
|
||||
/**
|
||||
* Any other object
|
||||
*/
|
||||
[propName: string]: any;
|
||||
}
|
||||
/**
|
||||
* Enum definition of different model types
|
||||
*
|
||||
* @export
|
||||
* @enum {number}
|
||||
*/
|
||||
export enum ModelType {
|
||||
/**
|
||||
* Applications
|
||||
*/
|
||||
Application,
|
||||
|
||||
/**
|
||||
* Services
|
||||
*/
|
||||
Service,
|
||||
|
||||
/**
|
||||
* Sub-window such as dialogs
|
||||
*/
|
||||
SubWindow,
|
||||
}
|
||||
/**
|
||||
* Base AntOS event definition
|
||||
*
|
||||
* @export
|
||||
* @class BaseEvent
|
||||
*/
|
||||
export class BaseEvent {
|
||||
/**
|
||||
* The event name placeholder
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof BaseEvent
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Placeholder indicates whether the event is forced to
|
||||
* be happen
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
* @memberof BaseEvent
|
||||
*/
|
||||
private force: boolean;
|
||||
|
||||
/**
|
||||
* Placeholder indicates whether the event is prevented.
|
||||
* This value has not effect if `force` is set to `true`
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof BaseEvent
|
||||
*/
|
||||
prevent: boolean;
|
||||
|
||||
/**
|
||||
*Creates an instance of BaseEvent.
|
||||
* @param {string} name event name
|
||||
* @param {boolean} force indicates whether the event is forced
|
||||
* @memberof BaseEvent
|
||||
*/
|
||||
constructor(name: string, force: boolean) {
|
||||
this.name = name;
|
||||
this.force = force;
|
||||
this.prevent = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the current event. This function
|
||||
* has no effect if `force` is set to true
|
||||
*
|
||||
* @memberof BaseEvent
|
||||
*/
|
||||
preventDefault(): void {
|
||||
if (!this.force) {
|
||||
this.prevent = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The root model of all applications, dialogs or services
|
||||
* in the system
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class BaseModel
|
||||
*/
|
||||
export abstract class BaseModel {
|
||||
/**
|
||||
* The class name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* The argument of the model
|
||||
*
|
||||
* @type {AppArgumentsType[]}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
args: AppArgumentsType[];
|
||||
|
||||
/**
|
||||
* Each model has its own local announcement system
|
||||
* to handle all local events inside that model.
|
||||
*
|
||||
* This observable object is propagate to all the
|
||||
* UI elements ([[AFXTag]]) inside the model
|
||||
*
|
||||
* @protected
|
||||
* @type {API.Announcer}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected _observable: API.Announcer;
|
||||
|
||||
/**
|
||||
* Reference to the core API namespace
|
||||
*
|
||||
* @protected
|
||||
* @type {typeof API}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected _api: typeof API;
|
||||
|
||||
/**
|
||||
* Reference to the core GUI namespace
|
||||
*
|
||||
* @protected
|
||||
* @type {typeof GUI}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected _gui: typeof GUI;
|
||||
|
||||
/**
|
||||
* Reference to the model's dialog
|
||||
*
|
||||
* @type {GUI.BaseDialog}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
dialog: GUI.BaseDialog;
|
||||
|
||||
/**
|
||||
* The HTML element ID of the virtual desktop
|
||||
*
|
||||
* @protected
|
||||
* @type {string}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected host: string;
|
||||
|
||||
/**
|
||||
* The process number of the current model.
|
||||
* For sub-window this number is the number
|
||||
* of the parent window
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
pid: number;
|
||||
|
||||
/**
|
||||
* Reference the DOM element of the UI scheme belong to
|
||||
* this model
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
scheme: HTMLElement;
|
||||
|
||||
/**
|
||||
* Reference to the system setting
|
||||
*
|
||||
* @protected
|
||||
* @type {typeof setting}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected systemsetting: typeof setting;
|
||||
|
||||
/**
|
||||
* Placeholder for the process creation timestamp
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
birth: number;
|
||||
|
||||
/**
|
||||
* Different model type
|
||||
*
|
||||
* @static
|
||||
* @type {ModelType}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
static type: ModelType;
|
||||
|
||||
/**
|
||||
* Allow singleton on this model
|
||||
*
|
||||
* @static
|
||||
* @type {boolean}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
static singleton: boolean;
|
||||
|
||||
/**
|
||||
* The javascript or css files that the model depends on. All dependencies
|
||||
* will be loaded before the model is rendered
|
||||
*
|
||||
* @static
|
||||
* @type {string[]} list of VFS paths of dependencies
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
static dependencies: string[];
|
||||
|
||||
/**
|
||||
* Reference to the CSS Element of the model
|
||||
*
|
||||
* @static
|
||||
* @type {(HTMLElement | string)}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
static style: HTMLElement | string;
|
||||
|
||||
/**
|
||||
* Place holder for model meta-data
|
||||
*
|
||||
* @static
|
||||
* @type {API.PackageMetaType}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
static meta: API.PackageMetaType;
|
||||
|
||||
/**
|
||||
*Creates an instance of BaseModel.
|
||||
* @param {string} name class name
|
||||
* @param {AppArgumentsType[]} args arguments
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
constructor(name: string, args: AppArgumentsType[]) {
|
||||
this.name = name;
|
||||
this.args = args;
|
||||
this._observable = new API.Announcer();
|
||||
this._api = API;
|
||||
this._gui = GUI;
|
||||
this.systemsetting = setting;
|
||||
this.on("exit", () => this.quit(false));
|
||||
this.host = this._gui.workspace;
|
||||
this.dialog = undefined;
|
||||
this.subscribe("systemlocalechange", (name) => {
|
||||
this.updateLocale(name);
|
||||
return this.update();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter: get the local announcer object
|
||||
*
|
||||
* @readonly
|
||||
* @type {API.Announcer}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
get observable(): API.Announcer {
|
||||
return this._observable;
|
||||
}
|
||||
/**
|
||||
* Update the model locale
|
||||
*
|
||||
* @protected
|
||||
* @param {string} name
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected updateLocale(name: string) {}
|
||||
/**
|
||||
* Render the model's UI
|
||||
*
|
||||
* @protected
|
||||
* @param {string} p VFS path to the UI scheme definition
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected render(p: string): void {
|
||||
return GUI.loadScheme(p, this, this.host);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exit the model
|
||||
*
|
||||
* @param {boolean} force set this value to `true` will bypass the prevented exit event by user
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
quit(force: boolean): void {
|
||||
const evt = new BaseEvent("exit", force);
|
||||
this.onexit(evt);
|
||||
if (!evt.prevent) {
|
||||
this.observable.off("*");
|
||||
delete this._observable;
|
||||
if (this.dialog) {
|
||||
this.dialog.quit();
|
||||
}
|
||||
return PM.kill(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Model meta data, need to be implemented by
|
||||
* subclasses
|
||||
*
|
||||
* @abstract
|
||||
* @returns {API.PackageMetaType}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
abstract meta(): API.PackageMetaType;
|
||||
|
||||
/**
|
||||
* VFS path to the model asset
|
||||
*
|
||||
* @returns {string}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
path(): string {
|
||||
const mt = this.meta();
|
||||
if (mt && mt.path) {
|
||||
return mt.path;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a server side script and get back the result
|
||||
*
|
||||
* @protected
|
||||
* @param {GenericObject<any>} cmd execution indication, should be:
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* path?: string, // VFS path to the server side script
|
||||
* code: string, // or server side code to be executed
|
||||
* parameters: any // the parameters of the server side execution
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns {Promise<any>}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected call(cmd: GenericObject<any>): Promise<any> {
|
||||
return this._api.apigateway(cmd, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the server side api using a websocket connection
|
||||
*
|
||||
* Server side script can be execute inside the stream by writing
|
||||
* data in JSON format with the following interface
|
||||
*
|
||||
* ```
|
||||
* {
|
||||
* path?: string, // VFS path to the server side script
|
||||
* code: string, // or server side code to be executed
|
||||
* parameters: any // the parameters of the server side execution
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @protected
|
||||
* @returns {Promise<WebSocket>}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected stream(): Promise<WebSocket> {
|
||||
return this._api.apigateway(null, true) as Promise<WebSocket>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the model before UI rendering
|
||||
*
|
||||
* @abstract
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
abstract init(): void;
|
||||
|
||||
/**
|
||||
* Main entry point after UI rendering
|
||||
*
|
||||
* @abstract
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
abstract main(): void;
|
||||
|
||||
/**
|
||||
* Show the model
|
||||
*
|
||||
* @abstract
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
abstract show(): void;
|
||||
|
||||
/**
|
||||
* Hide the model
|
||||
*
|
||||
* @abstract
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
abstract hide(): void;
|
||||
|
||||
/**
|
||||
* Function called when the model exits
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @param {BaseEvent} e exit event
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected abstract onexit(e: BaseEvent): void;
|
||||
|
||||
/**
|
||||
* subscribe once to a local event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e name of the event
|
||||
* @param {(d: any) => void} f event callback
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected one(e: string, f: (d: any) => void): void {
|
||||
return this.observable.one(e, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to a local event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {(d: any) => void} f event callback
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected on(e: string, f: (d: any) => void): void {
|
||||
return this.observable.on(e, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe an event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name or `*` (all events)
|
||||
* @param {(d: any) => void} [f] callback to be unsubscribed, can be `undefined`
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected off(e: string, f?: (d: any) => void): void {
|
||||
if (!f) {
|
||||
return this.observable.off(e);
|
||||
}
|
||||
return this.observable.off(e, f);
|
||||
}
|
||||
|
||||
/**
|
||||
* trigger a local event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {*} [d] event data
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
trigger(e: string, d?: any): void {
|
||||
if (!this.observable) return;
|
||||
this.observable.trigger(e, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* subscribe to an event on the global announcement system
|
||||
*
|
||||
* @protected
|
||||
* @param {string} e event name
|
||||
* @param {(d: any) => void} f event callback
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
subscribe(e: string, f: (d: any) => void): void {
|
||||
return announcer.on(e, f, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a dialog
|
||||
*
|
||||
* @param {(GUI.BaseDialog | string)} d a dialog object or a dialog class name
|
||||
* @param {GenericObject<any>} [data] input data of the dialog, refer to each
|
||||
* dialog definition for the format of the input data
|
||||
* @returns {Promise<any>} A promise on the callback data of the dialog, refer
|
||||
* to each dialog definition for the format of the callback data
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
openDialog(
|
||||
d: GUI.BaseDialog | string,
|
||||
data?: GenericObject<any>
|
||||
): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.dialog) {
|
||||
this.dialog.show();
|
||||
return;
|
||||
}
|
||||
if (typeof d === "string") {
|
||||
if (!GUI.dialogs[d]) {
|
||||
this.error(__("Dialog {0} not found", d));
|
||||
return;
|
||||
}
|
||||
this.dialog = new OS.GUI.dialogs[d as string]();
|
||||
} else {
|
||||
this.dialog = d;
|
||||
}
|
||||
//@dialog.observable = riot.observable() unless @dialog
|
||||
this.dialog.parent = this;
|
||||
this.dialog.handle = resolve;
|
||||
this.dialog.pid = this.pid;
|
||||
this.dialog.data = data;
|
||||
|
||||
return this.dialog.init();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a [[YesNoDialog]] to confirm a task
|
||||
*
|
||||
* @protected
|
||||
* @param {GenericObject<any>} data [[YesNoDialog]] input data
|
||||
* @returns {Promise<boolean>}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected ask(data: GenericObject<any>): Promise<boolean> {
|
||||
return this._gui.openDialog("YesNoDialog", data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger a global event
|
||||
*
|
||||
* @protected
|
||||
* @param {string} t event name
|
||||
* @param {(string | FormattedString)} m event message
|
||||
* @param {Error} [e] error object if any
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected publish(
|
||||
t: string,
|
||||
m: string | FormattedString,
|
||||
e?: Error
|
||||
): void {
|
||||
const mt = this.meta();
|
||||
let icon: string = undefined;
|
||||
if (mt.icon) {
|
||||
icon = `${mt.path}/${mt.icon}`;
|
||||
}
|
||||
return announcer.trigger(t, {
|
||||
id: this.pid,
|
||||
name: this.name,
|
||||
data: {
|
||||
m: m,
|
||||
icon: icon,
|
||||
iconclass: mt.iconclass,
|
||||
e: e,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a global notification
|
||||
*
|
||||
* @param {(string | FormattedString)} m notification string
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
notify(m: string | FormattedString): void {
|
||||
return this.publish("notification", m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a global warning
|
||||
*
|
||||
* @param {(string | FormattedString)} m warning string
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
warn(m: string | FormattedString): void {
|
||||
return this.publish("warning", m);
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a global error
|
||||
*
|
||||
* @param {(string | FormattedString)} m error message
|
||||
* @param {Error} [e] error object if any
|
||||
* @returns
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
error(m: string | FormattedString, e?: Error) {
|
||||
return this.publish("error", m, e ? e : this._api.throwe(m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Report a global fail event
|
||||
*
|
||||
* @param {string} m fail message
|
||||
* @param {Error} [e] error object if any
|
||||
* @returns
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
fail(m: string, e?: Error) {
|
||||
return this.publish("fail", m, e ? e : this._api.throwe(m));
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw an error inside the model
|
||||
*
|
||||
* @returns {Error}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
throwe(): Error {
|
||||
return this._api.throwe(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the model, this will update all its UI elements
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
update(): void {
|
||||
if (this.scheme) {
|
||||
return this.scheme.update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a HTMLElement in the UI of the model
|
||||
* using the `data-id` attribute of the element
|
||||
*
|
||||
* @protected
|
||||
* @param {string} id
|
||||
* @returns {HTMLElement}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected find(id: string): HTMLElement {
|
||||
if (this.scheme) {
|
||||
return $(`[data-id='${id}']`, this.scheme)[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select all DOM Element inside the UI of the model
|
||||
* using JQuery selector
|
||||
*
|
||||
* @protected
|
||||
* @param {string} sel
|
||||
* @returns {HTMLElement}
|
||||
* @memberof BaseModel
|
||||
*/
|
||||
protected select(sel: string): JQuery<HTMLElement> {
|
||||
if (this.scheme) {
|
||||
return $(sel, this.scheme);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class BaseService extends this.OS.GUI.BaseModel
|
||||
constructor: (name, args) ->
|
||||
super name, args
|
||||
@icon = undefined
|
||||
@iconclass = "fa-paper-plane-o"
|
||||
@text = ""
|
||||
@timer = undefined
|
||||
@holder = undefined
|
||||
|
||||
init: ()->
|
||||
#implement by user
|
||||
# event registe, etc
|
||||
# scheme loader
|
||||
meta: () ->
|
||||
_OS.APP[@name].meta
|
||||
attach: (h) ->
|
||||
@holder = h
|
||||
|
||||
update: () ->
|
||||
@holder.update() if @holder
|
||||
@scheme.update() if @scheme
|
||||
|
||||
watch: ( t, f) ->
|
||||
me = @
|
||||
func = () ->
|
||||
f()
|
||||
me.timer = setTimeout (() -> func()), t
|
||||
func()
|
||||
onexit: (evt) ->
|
||||
console.log "clean timer" if @timer
|
||||
clearTimeout @timer if @timer
|
||||
@cleanup(evt)
|
||||
($ @scheme).remove() if @scheme
|
||||
|
||||
main: () ->
|
||||
show: () ->
|
||||
awake: (e) ->
|
||||
#implement by user to tart the service
|
||||
cleanup: (evt) ->
|
||||
#implemeted by user
|
||||
BaseService.type = 2
|
||||
BaseService.singleton = true
|
||||
this.OS.GUI.BaseService = BaseService
|
252
src/core/BaseService.ts
Normal file
252
src/core/BaseService.ts
Normal file
@ -0,0 +1,252 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
namespace OS {
|
||||
export namespace application {
|
||||
/**
|
||||
* Services are processes that run in the background and
|
||||
* are waken up in certain circumstances such as by global
|
||||
* events or user interactions.
|
||||
*
|
||||
* Each service takes an entry in the system tray menu
|
||||
* located on the system panel. This menu entry is used
|
||||
* to access to service visual contents such as: options,
|
||||
* task performing based on user interaction, etc.
|
||||
*
|
||||
* Services are singleton processes, there is only
|
||||
* one process of a service at a time
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class BaseService
|
||||
* @extends {BaseModel}
|
||||
*/
|
||||
export abstract class BaseService extends BaseModel {
|
||||
/**
|
||||
* The service icon shown in the system tray
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
icon: string;
|
||||
|
||||
/**
|
||||
* CSS class of the service icon shown in the system tray
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
iconclass: string;
|
||||
|
||||
/**
|
||||
* Text of the service shown in the system tray
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* Reference to the menu entry DOM element attached
|
||||
* to the service
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
domel: HTMLElement;
|
||||
|
||||
/**
|
||||
* Reference to the timer that periodically executes the callback
|
||||
* defined in [[watch]].
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
private timer: number;
|
||||
|
||||
/**
|
||||
* Reference to the system tray menu
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
holder: HTMLElement;
|
||||
|
||||
/**
|
||||
* Placeholder for service select callback
|
||||
*
|
||||
* @memberof BaseService
|
||||
*/
|
||||
onmenuselect: (
|
||||
d: OS.GUI.TagEventType<GUI.tag.MenuEventData>
|
||||
) => void;
|
||||
|
||||
/**
|
||||
*Creates an instance of BaseService.
|
||||
* @param {string} name service class name
|
||||
* @param {AppArgumentsType[]} args service arguments
|
||||
* @memberof BaseService
|
||||
*/
|
||||
constructor(name: string, args: AppArgumentsType[]) {
|
||||
super(name, args);
|
||||
this.icon = undefined;
|
||||
this.iconclass = "fa-paper-plane-o";
|
||||
this.text = "";
|
||||
this.timer = undefined;
|
||||
this.holder = undefined;
|
||||
this.onmenuselect = (d) => {
|
||||
return this.awake(d);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @memberof BaseService
|
||||
*/
|
||||
hide(): void {}
|
||||
|
||||
/**
|
||||
* Init the service before attaching it to
|
||||
* the system tray: event subscribe, scheme
|
||||
* loading.
|
||||
*
|
||||
* Should be implemented by all subclasses
|
||||
*
|
||||
* @abstract
|
||||
* @memberof BaseService
|
||||
*/
|
||||
abstract init(): void;
|
||||
|
||||
/**
|
||||
* Refresh the service menu entry in the
|
||||
* system tray
|
||||
*
|
||||
* @memberof BaseService
|
||||
*/
|
||||
update(): void {
|
||||
(this.domel as GUI.tag.MenuEntryTag).data = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the service meta-data
|
||||
*
|
||||
* @returns {API.PackageMetaType}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
meta(): API.PackageMetaType {
|
||||
return application[this.name].meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach the service to a menu element
|
||||
* such as the system tray menu
|
||||
*
|
||||
* @param {HTMLElement} h
|
||||
* @memberof BaseService
|
||||
*/
|
||||
attach(h: HTMLElement): void {
|
||||
this.holder = h;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the callback that will be called periodically
|
||||
* after a period of time.
|
||||
*
|
||||
* Each service should only have at most one watcher
|
||||
*
|
||||
* @protected
|
||||
* @param {number} t period time in seconds
|
||||
* @param {() => void} f callback function
|
||||
* @returns {number}
|
||||
* @memberof BaseService
|
||||
*/
|
||||
protected watch(t: number, f: () => void): number {
|
||||
var func = () => {
|
||||
f();
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
return (this.timer = setTimeout(() => func(), t));
|
||||
};
|
||||
return func();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function is called when the service
|
||||
* is exited
|
||||
*
|
||||
* @protected
|
||||
* @param {BaseEvent} evt exit event
|
||||
* @returns
|
||||
* @memberof BaseService
|
||||
*/
|
||||
protected onexit(evt: BaseEvent) {
|
||||
if (this.timer) {
|
||||
console.log("clean timer");
|
||||
}
|
||||
if (this.timer) {
|
||||
clearTimeout(this.timer);
|
||||
}
|
||||
this.cleanup(evt);
|
||||
if (this.scheme) {
|
||||
return $(this.scheme).remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @memberof BaseService
|
||||
*/
|
||||
main(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @memberof BaseService
|
||||
*/
|
||||
show(): void {}
|
||||
|
||||
/**
|
||||
* Awake the service, this function is usually called when
|
||||
* the system tray menu entry attached to the service is
|
||||
* selected.
|
||||
*
|
||||
* This function should be implemented by all subclasses
|
||||
*
|
||||
* @abstract
|
||||
* @param {GUI.TagEventType} e
|
||||
* @memberof BaseService
|
||||
*/
|
||||
abstract awake(e: GUI.TagEventType<GUI.tag.MenuEventData>): void;
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {BaseEvent} evt
|
||||
* @memberof BaseService
|
||||
*/
|
||||
protected cleanup(evt: BaseEvent) {}
|
||||
}
|
||||
|
||||
BaseService.type = ModelType.Service;
|
||||
BaseService.singleton = true;
|
||||
}
|
||||
}
|
@ -1,403 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
class FormatedString
|
||||
constructor: (@fs, args) ->
|
||||
@values = []
|
||||
return unless args
|
||||
@values[i] = args[i] for i in [0..args.length - 1]
|
||||
toString: () ->
|
||||
@__()
|
||||
__: () ->
|
||||
me = @
|
||||
return @fs.l().replace /{(\d+)}/g, (match, number) ->
|
||||
return if typeof me.values[number] != 'undefined' then me.values[number].__() else match
|
||||
hash: () ->
|
||||
@__().hash()
|
||||
|
||||
asBase64: () ->
|
||||
@__().asBase64()
|
||||
|
||||
unescape: () ->
|
||||
@__().unescape()
|
||||
|
||||
asUint8Array: () ->
|
||||
@__().asUint8Array()
|
||||
|
||||
format: () ->
|
||||
args = arguments
|
||||
@values[i] = args[i] for i in [0..args.length - 1]
|
||||
|
||||
class Version
|
||||
constructor:(@string) ->
|
||||
arr = @string.split "-"
|
||||
br =
|
||||
"r": 3,
|
||||
"b": 2,
|
||||
"a": 1
|
||||
@branch = 3
|
||||
@branch = br[arr[1]] if arr.length is 2 and br[arr[1]]
|
||||
mt = arr[0].match /\d+/g
|
||||
throw new Error __("Version string is in invalid format: {0}", @string) if not mt
|
||||
@major = 0
|
||||
@minor = 0
|
||||
@patch = 0
|
||||
@major = Number mt[0] if mt.length >= 1
|
||||
@minor = Number mt[1] if mt.length >= 2
|
||||
@patch = Number mt[2] if mt.length >= 3
|
||||
|
||||
# this function return
|
||||
# 0 if the version is unchanged
|
||||
# 1 if the current version is newer
|
||||
# -1 if the current version is older
|
||||
compare: (o) ->
|
||||
other = o.__v()
|
||||
return 1 if @branch > other.branch
|
||||
return -1 if @branch < other.branch
|
||||
return 0 if @major is other.major and @minor is other.minor and @patch is other.patch
|
||||
return 1 if @major > other.major
|
||||
return -1 if @major < other.major
|
||||
return 1 if @minor > other.minor
|
||||
return -1 if @minor < other.minor
|
||||
return 1 if @patch > other.patch
|
||||
return -1
|
||||
nt: (o) ->
|
||||
return (@compare o) is 1
|
||||
ot: (o) ->
|
||||
return (@compare o) is -1
|
||||
__v: () -> @
|
||||
toString: () -> @string
|
||||
|
||||
Object.defineProperty Object.prototype, '__',
|
||||
value: () ->
|
||||
return @toString()
|
||||
enumerable: false
|
||||
writable: true
|
||||
|
||||
String.prototype.hash = () ->
|
||||
hash = 5381
|
||||
i = this.length
|
||||
hash = (hash * 33) ^ this.charCodeAt(--i) while i
|
||||
hash >>> 0
|
||||
String.prototype.__v = () ->
|
||||
return new Version @
|
||||
String.prototype.asBase64 = () ->
|
||||
tmp = encodeURIComponent this
|
||||
return btoa ( tmp.replace /%([0-9A-F]{2})/g, (match, p1) ->
|
||||
return String.fromCharCode (parseInt p1, 16)
|
||||
)
|
||||
String.prototype.unescape = () ->
|
||||
d = @
|
||||
d = d.replace /\\\\/g, "\\"
|
||||
d = d.replace /\\"/g, '"'
|
||||
d = d.replace /\\n/g, "\n"
|
||||
d = d.replace /\\t/g, "\t"
|
||||
d = d.replace /\\b/g, "\b"
|
||||
d = d.replace /\\f/g, "\f"
|
||||
d = d.replace /\\r/g, "\r"
|
||||
d
|
||||
String.prototype.asUint8Array = () ->
|
||||
bytes = []
|
||||
for i in [0..(@length - 1)]
|
||||
bytes.push @charCodeAt i
|
||||
bytes = new Uint8Array(bytes)
|
||||
return bytes
|
||||
|
||||
if not String.prototype.format
|
||||
String.prototype.format = () ->
|
||||
args = arguments
|
||||
return @replace /{(\d+)}/g, (match, number) ->
|
||||
return if typeof args[number] != 'undefined' then args[number].__() else match
|
||||
|
||||
String.prototype.f = () ->
|
||||
args = arguments
|
||||
return new FormatedString(@, args)
|
||||
|
||||
String.prototype.__ = () ->
|
||||
match = @match(/^__\((.*)\)$/)
|
||||
return match[1].l() if match
|
||||
return @
|
||||
String.prototype.l = () ->
|
||||
_API = window.OS.API
|
||||
_API.lang[@] = @ unless _API.lang[@]
|
||||
return _API.lang[@]
|
||||
# language directive
|
||||
|
||||
this.__ = () ->
|
||||
_API = window.OS.API
|
||||
args = arguments
|
||||
return "Undefined" unless args.length > 0
|
||||
d = args[0]
|
||||
d.l()
|
||||
return new FormatedString d, (args[i] for i in [1 .. args.length - 1])
|
||||
|
||||
Date.prototype.toString = () ->
|
||||
dd = @getDate()
|
||||
mm = @getMonth() + 1
|
||||
yyyy = @getFullYear()
|
||||
hh = @getHours()
|
||||
mi = @getMinutes()
|
||||
se = @getSeconds()
|
||||
|
||||
dd = "0#{dd}" if dd < 10
|
||||
mm = "0#{mm}" if mm < 10
|
||||
hh = "0#{hh}" if hh < 10
|
||||
mi = "0#{mi}" if mi < 10
|
||||
se = "0#{se}" if se < 10
|
||||
return "#{dd}/#{mm}/#{yyyy} #{hh}:#{mi}:#{se}"
|
||||
|
||||
Date.prototype.timestamp = () ->
|
||||
return @getTime() / 1000 | 0
|
||||
|
||||
self.OS.API =
|
||||
# the handler object could be a any remote or local handle to
|
||||
# fetch user data, used by the API to make requests
|
||||
# handlers are defined in /src/handlers
|
||||
handler: {}
|
||||
shared: {} # shared libraries
|
||||
searchHandler:{}
|
||||
lang:{}
|
||||
#request a user data
|
||||
mid: () ->
|
||||
return _courrier.getMID()
|
||||
post: (p, d, c, f) ->
|
||||
q = _courrier.getMID()
|
||||
_API.loading q, p
|
||||
|
||||
$.ajax {
|
||||
type: 'POST',
|
||||
url: p,
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify d,
|
||||
dataType: 'json',
|
||||
success: null
|
||||
}
|
||||
#$.getJSON p, d
|
||||
.done (data) ->
|
||||
_API.loaded q, p, "OK"
|
||||
c(data)
|
||||
.fail (e, s) ->
|
||||
_API.loaded q, p, "FAIL"
|
||||
f(e, s)
|
||||
|
||||
blob: (p, c, f) ->
|
||||
q = _courrier.getMID()
|
||||
r = new XMLHttpRequest()
|
||||
r.open "GET", p, true
|
||||
r.responseType = "arraybuffer"
|
||||
|
||||
r.onload = (e) ->
|
||||
if @status is 200 and @readyState is 4
|
||||
c @response
|
||||
_API.loaded q, p, "OK"
|
||||
else
|
||||
f e, @
|
||||
_API.loaded q, p, "FAIL"
|
||||
|
||||
_API.loading q, p
|
||||
r.send()
|
||||
|
||||
upload: (p, d, c, f) ->
|
||||
q = _courrier.getMID()
|
||||
#insert a temporal file selector
|
||||
o = ($ '<input>').attr('type', 'file').css("display", "none")
|
||||
o.change () ->
|
||||
_API.loading q, p
|
||||
formd = new FormData()
|
||||
formd.append 'path', d
|
||||
# TODO: only one file is selected at this time
|
||||
formd.append 'upload', o[0].files[0]
|
||||
|
||||
$.ajax {
|
||||
url: p,
|
||||
data: formd,
|
||||
type: 'POST',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
}
|
||||
.done (data) ->
|
||||
_API.loaded q, p, "OK"
|
||||
c(data)
|
||||
o.remove()
|
||||
.fail (e, s) ->
|
||||
_API.loaded q, p, "FAIL"
|
||||
f(e, s)
|
||||
o.remove()
|
||||
|
||||
o.click()
|
||||
|
||||
saveblob: (name, b) ->
|
||||
url = window.URL.createObjectURL b
|
||||
o = ($ '<a>')
|
||||
.attr("href", url)
|
||||
.attr("download", name)
|
||||
.css("display", "none")
|
||||
.appendTo("body")
|
||||
o[0].click()
|
||||
window.URL.revokeObjectURL(url)
|
||||
o.remove()
|
||||
|
||||
systemConfig: ->
|
||||
_API.request 'config', (result) ->
|
||||
console.log result
|
||||
loading: (q, p) ->
|
||||
_courrier.trigger "loading", { id: q, data: { m: "#{p}", s: true }, name: "OS" }
|
||||
loaded: (q, p, m ) ->
|
||||
_courrier.trigger "loaded", { id: q, data: { m: "#{m}: #{p}", s: false }, name: "OS" }
|
||||
get: (p, c, f, t) ->
|
||||
conf =
|
||||
type: 'GET',
|
||||
url: p,
|
||||
conf.dataType = t if t
|
||||
|
||||
q = _courrier.getMID()
|
||||
_API.loading q, p
|
||||
$.ajax conf
|
||||
.done (data) ->
|
||||
_API.loaded q, p, "OK"
|
||||
c(data)
|
||||
.fail (e, s) ->
|
||||
_API.loaded q, p, "FAIL"
|
||||
f(e, s)
|
||||
script: (p, c, f) ->
|
||||
q = _courrier.getMID()
|
||||
_API.loading q, p
|
||||
$.getScript p
|
||||
.done (data) ->
|
||||
_API.loaded q, p, "OK"
|
||||
c(data)
|
||||
.fail (e, s) ->
|
||||
_API.loaded q, p, "FAIL"
|
||||
f(e, s)
|
||||
resource: (r, c, f) ->
|
||||
path = "resources/#{r}"
|
||||
_API.get path, c, f
|
||||
|
||||
libready: (l) ->
|
||||
return _API.shared[l] || false
|
||||
|
||||
require: (l,f) ->
|
||||
if not _API.shared[l]
|
||||
if l.match /^(https?:\/\/[^\s]+)/g
|
||||
_API.script l, () ->
|
||||
_API.shared[l] = true
|
||||
_courrier.trigger "sharedlibraryloaded", l
|
||||
f() if f
|
||||
, (e, s) ->
|
||||
_courrier.oserror __("Cannot load 3rd library at: {0}", l), e, r
|
||||
else
|
||||
path = "os://scripts/"
|
||||
js = "#{path}#{l}.js"
|
||||
js.asFileHandler().onready (d) ->
|
||||
_API.shared[l] = true
|
||||
el = $ '<script>', { src: "#{_API.handler.get}/#{js}" }
|
||||
.appendTo 'head'
|
||||
#load css file
|
||||
css = "#{path}#{l}.css"
|
||||
css.asFileHandler().onready (d) ->
|
||||
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{_API.handler.get}/#{css}" }
|
||||
.appendTo 'head'
|
||||
, () ->
|
||||
console.log "loaded", l
|
||||
_courrier.trigger "sharedlibraryloaded", l
|
||||
f() if f
|
||||
else
|
||||
console.log l, "Library exist, no need to load"
|
||||
f() if f
|
||||
_courrier.trigger "sharedlibraryloaded", l
|
||||
|
||||
requires:(libs, f) ->
|
||||
return f() unless libs.length > 0
|
||||
_courrier.observable.one "sharedlibraryloaded", (l) ->
|
||||
libs.splice 0, 1
|
||||
_API.requires libs, f
|
||||
_API.require libs[0], null
|
||||
packages:
|
||||
fetch: (f) ->
|
||||
_API.handler.packages {
|
||||
command: "list", args: { paths: (v for k, v of _OS.setting.system.pkgpaths) }
|
||||
}, f
|
||||
cache: (f) ->
|
||||
_API.handler.packages {
|
||||
command: "cache", args: { paths: (v for k, v of _OS.setting.system.pkgpaths) }
|
||||
}, f
|
||||
setting: (f) ->
|
||||
_API.handler.setting f
|
||||
apigateway: (d, ws, c) ->
|
||||
return _API.handler.apigateway d, ws, c
|
||||
search: (text) ->
|
||||
r = []
|
||||
|
||||
for k, v of _API.searchHandler
|
||||
ret = _API.searchHandler[k](text)
|
||||
if ret.length > 0
|
||||
ret.unshift { text: k, class: "search-header", dataid: "header" }
|
||||
r = r.concat ret
|
||||
return r
|
||||
|
||||
onsearch: (name, fn) ->
|
||||
self.OS.API.searchHandler[name] = fn unless self.OS.API.searchHandler[name]
|
||||
|
||||
setLocale: (name, f) ->
|
||||
path = "resources/languages/#{name}.json"
|
||||
_API.get path, (d) ->
|
||||
_OS.setting.system.locale = name
|
||||
_API.lang = d
|
||||
if f then f() else _courrier.trigger "systemlocalechange", name
|
||||
, (e, s) ->
|
||||
#_OS.setting.system.locale = "en_GB"
|
||||
_courrier.oserror __("Language file {0} not found", path), e, s
|
||||
f() if f
|
||||
, "json"
|
||||
|
||||
throwe: (n) ->
|
||||
err = undefined
|
||||
try
|
||||
throw new Error(n)
|
||||
catch e
|
||||
err = e
|
||||
return "" if not err
|
||||
return err
|
||||
# utilities functioncs
|
||||
switcher: () ->
|
||||
o = {}
|
||||
p = {}
|
||||
p[arguments[i]] = false for i in [0..arguments.length - 1 ]
|
||||
Object.defineProperty o, "__p", {
|
||||
enumerable: false,
|
||||
value: p
|
||||
}
|
||||
fn = (o, v) ->
|
||||
Object.defineProperty o, v, {
|
||||
enumerable: true,
|
||||
set: (value) ->
|
||||
for k,l of @__p
|
||||
@__p[k] = false
|
||||
o.__p[v] = value
|
||||
, get: () ->
|
||||
return o.__p[v]
|
||||
}
|
||||
for k, v of o.__p
|
||||
fn o, k
|
||||
Object.defineProperty o, "selected", {
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
get: () ->
|
||||
for k,v of o.__p
|
||||
return k if v
|
||||
}
|
||||
return o
|
@ -1,152 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
'use strict'
|
||||
#define the OS object
|
||||
self = this
|
||||
self.OS or=
|
||||
|
||||
API: {}
|
||||
GUI: {}
|
||||
APP: {}
|
||||
setting:
|
||||
user: {}
|
||||
applications: {}
|
||||
desktop: {}
|
||||
appearance: {}
|
||||
VFS: {}
|
||||
system: {}
|
||||
courrier:
|
||||
observable: riot.observable()
|
||||
quota: 0
|
||||
listeners: {}
|
||||
on: (e, f, a) ->
|
||||
_courrier.listeners[a.pid] = [] unless _courrier.listeners[a.pid]
|
||||
_courrier.listeners[a.pid].push { e: e, f: f }
|
||||
_courrier.observable.on e, f
|
||||
trigger: (e, d) -> _courrier.observable.trigger e, d
|
||||
osfail: (m, e, s) ->
|
||||
_courrier.ostrigger "fail", { m: m, e: e, s: s }
|
||||
oserror: (m, e, s) ->
|
||||
_courrier.ostrigger "error", { m: m, e: e, s: s }
|
||||
osinfo: (m) ->
|
||||
_courrier.ostrigger "info", { m: m, e: null, s: null }
|
||||
ostrigger: (e, d) ->
|
||||
_courrier.trigger e, { id: 0, data: d, name: "OS" }
|
||||
unregister: (app) ->
|
||||
return unless _courrier.listeners[app.pid] and _courrier.listeners[app.pid].length > 0
|
||||
_courrier.observable.off i.e, i.f for i in _courrier.listeners[app.pid]
|
||||
delete _courrier.listeners[app.pid]
|
||||
# _courrier.listeners[app.pid]
|
||||
getMID: () ->
|
||||
_courrier.quota += 1
|
||||
_courrier.quota
|
||||
register: (name, x) ->
|
||||
if x.type is 3 then self.OS.GUI.subwindows[name] = x else _OS.APP[name] = x
|
||||
|
||||
PM:
|
||||
pidalloc: 0
|
||||
processes: {}
|
||||
createProcess: (app, cls, args) ->
|
||||
f = () ->
|
||||
#if it is single ton
|
||||
# and a process is existing
|
||||
# just return it
|
||||
if cls.singleton and _PM.processes[app] and _PM.processes[app].length == 1
|
||||
_PM.processes[app][0].show()
|
||||
else
|
||||
_PM.processes[app] = [] if not _PM.processes[app]
|
||||
obj = new cls(args)
|
||||
obj.birth = (new Date).getTime()
|
||||
_PM.pidalloc++
|
||||
obj.pid = _PM.pidalloc
|
||||
_PM.processes[app].push obj
|
||||
if cls.type is 1 then _GUI.dock obj, cls.meta else _GUI.attachservice obj
|
||||
if cls.type is 2
|
||||
_courrier.trigger "srvroutineready", app
|
||||
if cls.dependencies
|
||||
libs = (v for v in cls.dependencies)
|
||||
_API.requires libs, f
|
||||
else
|
||||
f()
|
||||
appByPid: (pid) ->
|
||||
app = undefined
|
||||
find = (l) ->
|
||||
return a for a in l when a.pid is pid
|
||||
for k, v of _PM.processes
|
||||
app = find v
|
||||
break if app
|
||||
app
|
||||
|
||||
kill: (app) ->
|
||||
return if not app.name or not _PM.processes[app.name]
|
||||
|
||||
i = _PM.processes[app.name].indexOf app
|
||||
if i >= 0
|
||||
if _OS.APP[app.name].type == 1 then _GUI.undock app else _GUI.detachservice app
|
||||
_courrier.unregister app
|
||||
delete _PM.processes[app.name][i]
|
||||
_PM.processes[app.name].splice i, 1
|
||||
|
||||
killAll: (app, force) ->
|
||||
return unless _PM.processes[app]
|
||||
a.quit(force) for a in _PM.processes[app]
|
||||
|
||||
cleanup: ->
|
||||
console.log "Clean up system"
|
||||
_PM.killAll a, true for a, v of _PM.processes
|
||||
_courrier.observable.off("*") if _courrier.observable
|
||||
$(window).off('keydown')
|
||||
($ "#workspace").off("mouseover")
|
||||
delete _courrier.observable
|
||||
($ "#wrapper").empty()
|
||||
_GUI.clearTheme()
|
||||
_courrier.observable = riot.observable()
|
||||
_courrier.quota = 0
|
||||
_OS.APP = {}
|
||||
_OS.setting =
|
||||
user: {}
|
||||
applications: {}
|
||||
desktop: {}
|
||||
appearance: {}
|
||||
VFS: {}
|
||||
system: {}
|
||||
_PM.processes = {}
|
||||
_PM.pidalloc = 0
|
||||
|
||||
boot: ->
|
||||
#first login
|
||||
console.log "Booting sytem"
|
||||
_API.handler.auth (d) ->
|
||||
# in case someone call it more than once :)
|
||||
if d.error
|
||||
# show login screen
|
||||
_GUI.login()
|
||||
else
|
||||
# startX :)
|
||||
_GUI.startAntOS d.result
|
||||
|
||||
cleanupHandlers: {}
|
||||
exit: ->
|
||||
#do clean up first
|
||||
f() for n, f of _OS.cleanupHandlers
|
||||
_API.handler.setting (r) ->
|
||||
_OS.cleanup()
|
||||
_API.handler.logout()
|
||||
onexit: (n, f) ->
|
||||
self.OS.cleanupHandlers[n] = f unless self.OS.cleanupHandlers[n]
|
1722
src/core/core.ts
Normal file
1722
src/core/core.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
class DB
|
||||
constructor: (@table) ->
|
||||
|
||||
save: (d, f) ->
|
||||
_API.handler.dbquery "save", { table: @table, data: d }, f
|
||||
delete: (c, f) ->
|
||||
rq = { table: @table }
|
||||
return ( _courrier.oserror __("VDB Unknown condition for delete command"),
|
||||
(_API.throwe "OS.DB"), c ) unless c and c isnt ""
|
||||
if isNaN c
|
||||
rq.cond = c
|
||||
else
|
||||
rq.id = c
|
||||
_API.handler.dbquery "delete", rq, f
|
||||
get: (id, f) ->
|
||||
_API.handler.dbquery "get", { table: @table, id: id }, f
|
||||
find: (cond, f) ->
|
||||
_API.handler.dbquery "select", { table: @table, cond: cond }, f
|
||||
|
||||
self.OS.API.DB = DB
|
202
src/core/db.ts
Normal file
202
src/core/db.ts
Normal file
@ -0,0 +1,202 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
export namespace API {
|
||||
/**
|
||||
* Simple Virtual Database (VDB) application API.
|
||||
*
|
||||
* This API abstracts and provides a standard way to
|
||||
* connect to a server-side relational database (e.g. sqlite).
|
||||
*
|
||||
* Each user when connected has their own database previously
|
||||
* created. All VDB operations related to that user will be
|
||||
* performed on this database.
|
||||
*
|
||||
* The creation of user database need to be managed by the server-side API.
|
||||
* The VDB API assumes that the database already exist. All operations
|
||||
* is performed in tables level
|
||||
*
|
||||
* @export
|
||||
* @class DB
|
||||
*/
|
||||
export class DB {
|
||||
/**
|
||||
* A table name on the user's database
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof DB
|
||||
*/
|
||||
private table: string;
|
||||
|
||||
/**
|
||||
*Creates an instance of DB.
|
||||
* @param {string} table table name
|
||||
* @memberof DB
|
||||
*/
|
||||
constructor(table: string) {
|
||||
this.table = table;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save data to the current table. The input
|
||||
* data must conform to the table record format.
|
||||
*
|
||||
* On the server side, if the table doest not
|
||||
* exist yet, it should be created automatically
|
||||
* by inferring the data structure of the input
|
||||
* object
|
||||
*
|
||||
* @param {GenericObject<any>} d data object represents a current table record
|
||||
* @returns {Promise<API.RequestResult>}
|
||||
* @memberof DB
|
||||
*/
|
||||
save(d: GenericObject<any>): Promise<API.RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.dbquery("save", {
|
||||
table: this.table,
|
||||
data: d,
|
||||
});
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* delete record(s) from the current table by
|
||||
* a conditional object
|
||||
*
|
||||
* @param {*} c conditional object, c can be:
|
||||
*
|
||||
* * a `number`: the operation will delete the record with `id = c`
|
||||
* * a `string`: The SQL string condition that selects record to delete
|
||||
* * a conditional object represents a SQL condition statement as an object,
|
||||
* example: `pid = 10 AND cid = 2` is represented by:
|
||||
*
|
||||
* ```typescript
|
||||
* {
|
||||
* exp: {
|
||||
* "and": {
|
||||
* pid: 10,
|
||||
* cid: 2
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @returns {Promise<API.RequestResult>}
|
||||
* @memberof DB
|
||||
*/
|
||||
delete(
|
||||
c: GenericObject<any> | number | string
|
||||
): Promise<API.RequestResult> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const rq: any = { table: this.table };
|
||||
if (!c || c === "") {
|
||||
reject(API.throwe("OS.DB: unknown condition"));
|
||||
}
|
||||
if (isNaN(c as number)) {
|
||||
rq.cond = c;
|
||||
} else {
|
||||
rq.id = c;
|
||||
}
|
||||
try {
|
||||
const r = await API.handle.dbquery("delete", rq);
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a record in the table by its primary key
|
||||
*
|
||||
* @param {number} id the primary key value
|
||||
* @returns {Promise<GenericObject<any>>} Promise on returned record data
|
||||
* @memberof DB
|
||||
*/
|
||||
get(id: number): Promise<GenericObject<any>> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.dbquery("get", {
|
||||
table: this.table,
|
||||
id: id,
|
||||
});
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r.result as GenericObject<any>);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Find records by a condition
|
||||
*
|
||||
* @param {GenericObject<any>} cond conditional object
|
||||
*
|
||||
* a conditional object represents a SQL condition statement as an object,
|
||||
* example: `pid = 10 AND cid = 2 ORDER BY date DESC` is represented by:
|
||||
*
|
||||
* ```typescript
|
||||
* {
|
||||
* exp: {
|
||||
* "and": {
|
||||
* pid: 10,
|
||||
* cid: 2
|
||||
* }
|
||||
* },
|
||||
* order: {
|
||||
* date: "DESC"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
* @returns {Promise<GenericObject<any>[]>}
|
||||
* @memberof DB
|
||||
*/
|
||||
find(cond: GenericObject<any>): Promise<GenericObject<any>[]> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const r = await API.handle.dbquery("select", {
|
||||
table: this.table,
|
||||
cond,
|
||||
});
|
||||
if (r.error) {
|
||||
return reject(API.throwe(r.error.toString()));
|
||||
}
|
||||
return resolve(r.result as GenericObject<any>[]);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,491 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
self.OS.GUI =
|
||||
subwindows: new Object()
|
||||
dialog: undefined
|
||||
fullscreen: false
|
||||
shortcut:
|
||||
ALT: {}
|
||||
CTRL: {}
|
||||
SHIFT: {}
|
||||
META: {}
|
||||
SYS_MENU: [
|
||||
{
|
||||
text: "",
|
||||
iconclass: "fa fa-eercast",
|
||||
dataid: "sys-menu-root",
|
||||
child: [
|
||||
{
|
||||
text: "__(Applications)",
|
||||
child: [],
|
||||
dataid: "sys-apps"
|
||||
iconclass: "fa fa-adn",
|
||||
onmenuselect: (d) ->
|
||||
_GUI.launch d.item.data.app
|
||||
}
|
||||
],
|
||||
onmenuselect: (d) ->
|
||||
return _OS.exit() if d.item.data.dataid is "sys-logout"
|
||||
return _GUI.toggleFullscreen() if d.item.data.dataid is "os-fullsize"
|
||||
_GUI.launch d.item.data.app unless d.item.data.dataid
|
||||
}
|
||||
]
|
||||
htmlToScheme: (html, app, parent) ->
|
||||
scheme = $.parseHTML html
|
||||
|
||||
$(app.scheme).remove() if app.scheme
|
||||
($ parent).append scheme
|
||||
app.scheme = scheme[0]
|
||||
riot.mount ($ scheme), { observable: app.observable }
|
||||
app.main()
|
||||
app.show()
|
||||
loadScheme: (path, app, parent) ->
|
||||
path.asFileHandler().read (x) ->
|
||||
return null unless x
|
||||
_GUI.htmlToScheme x, app, parent
|
||||
#, (e, s) ->
|
||||
# _courrier.osfail "Cannot load scheme file: #{path} for #{app.name} (#{app.pid})", e, s
|
||||
|
||||
clearTheme: () ->
|
||||
$ "head link#ostheme"
|
||||
.attr "href", ""
|
||||
|
||||
loadTheme: (name, force) ->
|
||||
_GUI.clearTheme() if force
|
||||
path = "resources/themes/#{name}/#{name}.css"
|
||||
$ "head link#ostheme"
|
||||
.attr "href", path
|
||||
|
||||
pushServices: (srvs) ->
|
||||
return unless srvs.length > 0
|
||||
_courrier.observable.one "srvroutineready", () ->
|
||||
srvs.splice 0, 1
|
||||
_GUI.pushServices srvs
|
||||
_GUI.pushService srvs[0]
|
||||
|
||||
openDialog: (d, f, title, data) ->
|
||||
if _GUI.dialog
|
||||
_GUI.dialog.show()
|
||||
return
|
||||
if not _GUI.subwindows[d]
|
||||
ex = _API.throwe "Dialog"
|
||||
return _courrier.oserror __("Dialog {0} not found", d), ex, null
|
||||
_GUI.dialog = new _GUI.subwindows[d]()
|
||||
_GUI.dialog.parent = _GUI
|
||||
_GUI.dialog.handler = f
|
||||
_GUI.dialog.pid = -1
|
||||
_GUI.dialog.data = data
|
||||
_GUI.dialog.title = title
|
||||
_GUI.dialog.init()
|
||||
|
||||
pushService: (ph) ->
|
||||
arr = ph.split "/"
|
||||
srv = arr[1]
|
||||
app = arr[0]
|
||||
return _PM.createProcess srv, _OS.APP[srv] if _OS.APP[srv]
|
||||
_GUI.loadApp app,
|
||||
(a) ->
|
||||
return _PM.createProcess srv, _OS.APP[srv] if _OS.APP[srv]
|
||||
(e, s) ->
|
||||
_courrier.trigger "srvroutineready", srv
|
||||
_courrier.osfail __("Cannot read service script: {0}", srv), e, s
|
||||
|
||||
appsByMime: (mime) ->
|
||||
metas = ( v for k, v of _OS.setting.system.packages when v and v.app )
|
||||
mimes = ( m.mimes for m in metas when m)
|
||||
apps = []
|
||||
# search app by mimes
|
||||
f = ( arr, idx ) ->
|
||||
try
|
||||
arr.filter (m, i) ->
|
||||
if mime.match (new RegExp m, "g")
|
||||
return false if (apps.indexOf metas[idx]) >= 0
|
||||
apps.push metas[idx]
|
||||
return false
|
||||
return false
|
||||
catch e
|
||||
_courrier.osfail __("Error find app by mimes {0}", mime), e, mime
|
||||
|
||||
( f m, i if m ) for m, i in mimes
|
||||
return apps
|
||||
|
||||
appsWithServices: () ->
|
||||
o = {}
|
||||
o[k] = v for k, v of _OS.setting.system.packages when v and v.services and v.services.length > 0
|
||||
o
|
||||
|
||||
openWith: (it) ->
|
||||
return unless it
|
||||
return _GUI.launch it.app if it.type is "app" and it.app
|
||||
return _courrier.osinfo __("Application {0} is not executable", it.text) if it.type is "app"
|
||||
apps = _GUI.appsByMime ( if it.type is "dir" then "dir" else it.mime )
|
||||
return _courrier.osinfo __("No application available to open {0}", it.filename) if apps.length is 0
|
||||
return _GUI.launch apps[0].app, [it.path] if apps.length is 1
|
||||
list = ( { text: e.app, icon: e.icon, iconclass: e.iconclass } for e in apps )
|
||||
_GUI.openDialog "SelectionDialog", ( d ) ->
|
||||
_GUI.launch d.text, [it.path]
|
||||
, __("Open with"), list
|
||||
|
||||
forceLaunch: (app, args) ->
|
||||
console.warn "This method is used for developing only, please use the launch method instead"
|
||||
_GUI.unloadApp app
|
||||
_GUI.launch app, args
|
||||
|
||||
unloadApp: (app) ->
|
||||
_PM.killAll app, true
|
||||
($ _OS.APP[app].style).remove() if _OS.APP[app] and _OS.APP[app].style
|
||||
delete _OS.APP[app]
|
||||
|
||||
loadApp: (app, ok, err) ->
|
||||
path = "os://packages/#{app}"
|
||||
path = _OS.setting.system.packages[app].path if _OS.setting.system.packages[app].path
|
||||
js = path + "/main.js"
|
||||
|
||||
js.asFileHandler().read (d) ->
|
||||
# load app meta data
|
||||
"#{path}/package.json".asFileHandler().read (data) ->
|
||||
data.path = path
|
||||
_OS.APP[app].meta = data if _OS.APP[app]
|
||||
_OS.APP[v].meta = data for v in data.services if data.services
|
||||
#load css file
|
||||
css = "#{path}/main.css"
|
||||
css.asFileHandler().onready (d) ->
|
||||
stamp = (new Date).timestamp()
|
||||
el = $ '<link>', { rel: 'stylesheet', type: 'text/css', 'href': "#{_API.handler.get}/#{css}?stamp=#{stamp}" }
|
||||
.appendTo 'head'
|
||||
_OS.APP[app].style = el[0] if _OS.APP[app]
|
||||
ok app
|
||||
, () ->
|
||||
#launch
|
||||
ok app
|
||||
, "json"
|
||||
#ok app
|
||||
, "script"
|
||||
launch: (app, args) ->
|
||||
if not _OS.APP[app]
|
||||
# first load it
|
||||
_GUI.loadApp app,
|
||||
(a)->
|
||||
_PM.createProcess a, _OS.APP[a], args
|
||||
, (e, s) ->
|
||||
else
|
||||
# now launch it
|
||||
if _OS.APP[app]
|
||||
_PM.createProcess app, _OS.APP[app], args
|
||||
|
||||
dock: (app, meta) ->
|
||||
# dock an application to a dock
|
||||
# create a data object
|
||||
data =
|
||||
icon: null
|
||||
iconclass: meta.iconclass || ""
|
||||
app: app
|
||||
onbtclick: () -> app.toggle()
|
||||
# TODO: this path is not good, need to create a blob of it
|
||||
data.icon = "#{meta.path}/#{meta.icon}" if meta.icon
|
||||
# TODO: add default app icon class in system setting
|
||||
# so that it can be themed
|
||||
data.iconclass = "fa fa-cogs" if (not meta.icon) and (not meta.iconclass)
|
||||
dock = $ "#sysdock"
|
||||
app.init()
|
||||
app.one "rendered", () ->
|
||||
dock.get(0).newapp data
|
||||
app.sysdock = dock.get(0)
|
||||
app.appmenu = ($ "[data-id = 'appmenu']", "#syspanel")[0]
|
||||
app.subscribe "systemlocalechange", (name) -> app.update()
|
||||
app.subscribe "appregistry", ( m ) ->
|
||||
app.applySetting m.data.m if (m.name is app.name)
|
||||
|
||||
toggleFullscreen: () ->
|
||||
el = ($ "body")[0]
|
||||
if _GUI.fullscreen
|
||||
return document.exitFullscreen() if document.exitFullscreen
|
||||
return document.mozCancelFullScreen() if document.mozCancelFullScreen
|
||||
return document.webkitExitFullscreen() if document.webkitExitFullscreen
|
||||
return document.cancelFullScreen() if document.cancelFullScreen
|
||||
else
|
||||
return el.requestFullscreen() if el.requestFullscreen
|
||||
return el.mozRequestFullScreen() if el.mozRequestFullScreen
|
||||
return el.webkitRequestFullscreen() if el.webkitRequestFullscreen
|
||||
return el.msRequestFullscreen() if el.msRequestFullscreen
|
||||
|
||||
undock: (app) ->
|
||||
($ "#sysdock").get(0).removeapp app
|
||||
|
||||
attachservice: (srv) ->
|
||||
($ "#syspanel")[0].attachservice srv
|
||||
srv.init()
|
||||
srv.subscribe "systemlocalechange", (name) -> srv.update()
|
||||
detachservice: (srv) ->
|
||||
($ "#syspanel")[0].detachservice srv
|
||||
bindContextMenu: (event) ->
|
||||
handler = (e) ->
|
||||
if e.contextmenuHandler
|
||||
e.contextmenuHandler event, ($ "#contextmenu")[0]
|
||||
else
|
||||
p = $(e).parent().get(0)
|
||||
handler p if p isnt ($ "#workspace").get(0)
|
||||
handler event.target
|
||||
event.preventDefault()
|
||||
|
||||
bindKey: (k, f) ->
|
||||
arr = k.split "-"
|
||||
return unless arr.length is 2
|
||||
fnk = arr[0].toUpperCase()
|
||||
c = arr[1].toUpperCase()
|
||||
return unless _GUI.shortcut[fnk]
|
||||
_GUI.shortcut[fnk][c] = f
|
||||
|
||||
wallpaper: (obj) ->
|
||||
if obj
|
||||
_OS.setting.appearance.wp = obj
|
||||
wp = _OS.setting.appearance.wp
|
||||
$("body").css("background-image", "url(#{_API.handler.get}/#{wp.url})" )
|
||||
.css("background-size", wp.size)
|
||||
.css("background-repeat", wp.repeat)
|
||||
|
||||
showTooltip: (el, text, e) ->
|
||||
el = el[0]
|
||||
label = ($ "#systooltip")[0]
|
||||
$("#workspace").on "mousemove", (ev) ->
|
||||
if $(ev.target).closest(el).length is 0
|
||||
$(label).hide()
|
||||
$("#workspace").off "mousemove"
|
||||
arr = text.split /:(.+)/
|
||||
tip = text
|
||||
tip = arr[1] if arr.length > 1
|
||||
offset = $(el).offset()
|
||||
w = $(el).width()
|
||||
h = $(el).height()
|
||||
label.set "text", tip
|
||||
$(label).show()
|
||||
switch arr[0]
|
||||
when "cr" # center right of the element
|
||||
left = offset.left + w + 5
|
||||
top = offset.top + h / 2 - $(label).height() / 2
|
||||
$(label).css "top", top + "px"
|
||||
.css "left", left + "px"
|
||||
else
|
||||
return unless e
|
||||
$(label).css "top", e.clientY + 5 + "px"
|
||||
.css "left", e.clientX + 5 + "px"
|
||||
|
||||
initDM: ->
|
||||
# check login first
|
||||
_API.resource "schemes/dm.html", (x) ->
|
||||
return null unless x
|
||||
scheme = $.parseHTML x
|
||||
($ "#wrapper").append scheme
|
||||
|
||||
_courrier.observable.one "sysdockloaded", () ->
|
||||
($ window).bind 'keydown', (event) ->
|
||||
dock = ($ "#sysdock")[0]
|
||||
return unless dock
|
||||
app = dock.get "selectedApp"
|
||||
#return true unless app
|
||||
c = String.fromCharCode(event.which).toUpperCase()
|
||||
fnk = undefined
|
||||
if event.ctrlKey
|
||||
fnk = "CTRL"
|
||||
else if event.metaKey
|
||||
fnk = "META"
|
||||
else if event.shiftKey
|
||||
fnk = "SHIFT"
|
||||
else if event.altKey
|
||||
fnk = "ALT"
|
||||
|
||||
return unless fnk
|
||||
r = if app then app.shortcut fnk, c, event else true
|
||||
return event.preventDefault() if not r
|
||||
return unless _GUI.shortcut[fnk]
|
||||
return unless _GUI.shortcut[fnk][c]
|
||||
_GUI.shortcut[fnk][c](event)
|
||||
event.preventDefault()
|
||||
# system menu and dock
|
||||
riot.mount ($ "#syspanel", $ "#wrapper")
|
||||
riot.mount ($ "#sysdock", $ "#workspace"), { items: [] }
|
||||
riot.mount ($ "#systooltip", $ "#wrapper")
|
||||
# context menu
|
||||
riot.mount ($ "#contextmenu", $ "#wrapper")
|
||||
($ "#workspace").contextmenu (e) -> _GUI.bindContextMenu e
|
||||
# tooltip
|
||||
($ "#workspace").mouseover (e) ->
|
||||
el = $(e.target).closest "[tooltip]"
|
||||
return unless el.length > 0
|
||||
_GUI.showTooltip el, ($(el).attr "tooltip"), e
|
||||
|
||||
# desktop default file manager
|
||||
desktop = $ "#desktop"
|
||||
fp = _OS.setting.desktop.path.asFileHandler()
|
||||
desktop[0].fetch = () ->
|
||||
fn = () ->
|
||||
fp = _OS.setting.desktop.path.asFileHandler()
|
||||
fp.read (d) ->
|
||||
return _courrier.osfail d.error, (_API.throwe "OS.VFS"), d.error if d.error
|
||||
items = []
|
||||
$.each d.result, (i, v) ->
|
||||
return if v.filename[0] is '.' and not _OS.setting.desktop.showhidden
|
||||
v.text = v.filename
|
||||
#v.text = v.text.substring(0,9) + "..." ifv.text.length > 10
|
||||
v.iconclass = v.type
|
||||
items.push(v)
|
||||
desktop[0].set "items", items
|
||||
desktop[0].refresh()
|
||||
|
||||
fp.onready () ->
|
||||
fn()
|
||||
, ( e ) -> # try to create the path
|
||||
console.log "#{fp.path} not found"
|
||||
name = fp.basename
|
||||
fp.parent().asFileHandler().mk name, (r) ->
|
||||
ex = _API.throwe "OS.VFS"
|
||||
if r.error then _courrier.osfail d.error, ex, d.error else fn()
|
||||
|
||||
desktop[0].ready = (e) ->
|
||||
e.observable = _courrier
|
||||
window.onresize = () ->
|
||||
_courrier.trigger "desktopresize"
|
||||
e.refresh()
|
||||
|
||||
desktop[0].set "onlistselect", (d) ->
|
||||
($ "#sysdock").get(0).set "selectedApp", null
|
||||
|
||||
desktop[0].set "onlistdbclick", ( d ) ->
|
||||
($ "#sysdock").get(0).set "selectedApp", null
|
||||
it = desktop[0].get "selected"
|
||||
_GUI.openWith it
|
||||
|
||||
#($ "#workingenv").on "click", (e) ->
|
||||
# desktop[0].set "selected", -1
|
||||
|
||||
desktop.on "click", (e) ->
|
||||
return unless e.target is desktop[0]
|
||||
desktop[0].set "selected", -1
|
||||
($ "#sysdock").get(0).set "selectedApp", null
|
||||
#console.log "desktop clicked"
|
||||
|
||||
desktop[0].contextmenuHandler = (e, m) ->
|
||||
desktop[0].set "selected", -1 if e.target is desktop[0]
|
||||
($ "#sysdock").get(0).set "selectedApp", null
|
||||
menu = [
|
||||
{ text: __("Open"), dataid: "desktop-open" },
|
||||
{ text: __("Refresh"), dataid: "desktop-refresh" }
|
||||
]
|
||||
menu = menu.concat ( v for k, v of _OS.setting.desktop.menu)
|
||||
m.set "items", menu
|
||||
m.set "onmenuselect", (evt) ->
|
||||
switch evt.item.data.dataid
|
||||
when "desktop-open"
|
||||
it = desktop[0].get "selected"
|
||||
return _GUI.openWith it if it
|
||||
it = _OS.setting.desktop.path.asFileHandler()
|
||||
it.mime = "dir"
|
||||
_GUI.openWith it
|
||||
when "desktop-refresh"
|
||||
desktop[0].fetch()
|
||||
else
|
||||
_GUI.launch evt.item.data.app, evt.item.data.args if evt.item.data.app
|
||||
m.show(e)
|
||||
|
||||
desktop[0].fetch()
|
||||
_courrier.observable.on "VFS", (d) ->
|
||||
desktop[0].fetch() if d.data.file.hash() is fp.hash() or d.data.file.parent().hash() is fp.hash()
|
||||
_courrier.ostrigger "desktoploaded"
|
||||
# mount it
|
||||
riot.mount desktop
|
||||
, (e, s) ->
|
||||
alert __("System fail: Cannot init desktop manager")
|
||||
console.log s, e
|
||||
refreshDesktop: () ->
|
||||
($ "#desktop")[0].fetch()
|
||||
|
||||
refreshSystemMenu: () ->
|
||||
_GUI.SYS_MENU[0].child.length = 1
|
||||
_GUI.SYS_MENU[0].child[0].child.length = 0
|
||||
_GUI.SYS_MENU[0].child[0].child.push v for k, v of _OS.setting.system.packages when (v and v.app)
|
||||
_GUI.SYS_MENU[0].child.push v for k, v of _OS.setting.system.menu
|
||||
_GUI.SYS_MENU[0].child.push
|
||||
text: "__(Toggle Full screen)",
|
||||
dataid: "os-fullsize",
|
||||
iconclass: "fa fa-tv"
|
||||
_GUI.SYS_MENU[0].child.push
|
||||
text: "__(Log out)",
|
||||
dataid: "sys-logout",
|
||||
iconclass: "fa fa-user-times"
|
||||
($ "[data-id = 'os_menu']", "#syspanel")[0].update()
|
||||
buildSystemMenu: () ->
|
||||
($ "[data-id = 'os_menu']", "#syspanel")[0].set "items", _GUI.SYS_MENU
|
||||
|
||||
#console.log menu
|
||||
|
||||
mkdialog: (conf) ->
|
||||
return new _GUI.BasicDialog conf.name, conf.layout
|
||||
|
||||
login: () ->
|
||||
_API.resource "schemes/login.html", (x) ->
|
||||
return null unless x
|
||||
scheme = $.parseHTML x
|
||||
($ "#wrapper").append scheme
|
||||
($ "#btlogin").click () ->
|
||||
data =
|
||||
username: ($ "#txtuser").val(),
|
||||
password: ($ "#txtpass").val()
|
||||
_API.handler.login data, (d) ->
|
||||
if d.error then ($ "#login_error").html d.error else _GUI.startAntOS d.result
|
||||
($ "#txtpass").keyup (e) ->
|
||||
($ "#btlogin").click() if e.which is 13
|
||||
, (e, s) ->
|
||||
alert __("System fail: Cannot init login screen")
|
||||
|
||||
startAntOS: (conf) ->
|
||||
# clean up things
|
||||
_OS.cleanup()
|
||||
# get setting from conf
|
||||
_OS.systemSetting conf
|
||||
#console.log _OS.setting
|
||||
# load theme
|
||||
_GUI.loadTheme _OS.setting.appearance.theme
|
||||
_GUI.wallpaper()
|
||||
_courrier.observable.one "syspanelloaded", () ->
|
||||
# TODO load packages list then build system menu
|
||||
_courrier.observable.on "systemlocalechange", (name) ->
|
||||
($ "#syspanel")[0].update()
|
||||
|
||||
_API.packages.cache (ret) ->
|
||||
if ret.result
|
||||
_API.packages.fetch (r) ->
|
||||
if r.result
|
||||
for k, v of r.result
|
||||
v.text = v.name
|
||||
v.filename = k
|
||||
v.type = "app"
|
||||
v.mime = "antos/app"
|
||||
v.icon = "#{v.path}/#{v.icon}" if v.icon
|
||||
v.iconclass = "fa fa-adn" unless v.iconclass or v.icon
|
||||
_OS.setting.system.packages = if r.result then r.result else
|
||||
_GUI.refreshSystemMenu()
|
||||
_GUI.buildSystemMenu()
|
||||
# push startup services
|
||||
# TODO: get services list from user setting
|
||||
_GUI.pushServices (v for v in _OS.setting.system.startup.services)
|
||||
(_GUI.launch a) for a in _OS.setting.system.startup.apps
|
||||
#_GUI.launch "DummyApp"
|
||||
# initDM
|
||||
_API.setLocale _OS.setting.system.locale, () ->
|
||||
_GUI.initDM()
|
1227
src/core/gui.ts
Normal file
1227
src/core/gui.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,125 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
self.OS.API.HOST = self.location.hostname+ (if self.location.port then":#{self.location.port}" else "")
|
||||
self.OS.API.REST = "#{self.location.protocol}//#{self.OS.API.HOST}"
|
||||
|
||||
_REST = self.OS.API.REST
|
||||
_HOST = self.OS.API.HOST
|
||||
self.OS.API.handler =
|
||||
# get file, require authentification
|
||||
get: "#{_REST}/VFS/get"
|
||||
# get shared file with publish
|
||||
shared: "#{_REST}/VFS/shared"
|
||||
scandir: (p, c ) ->
|
||||
path = "#{_REST}/VFS/scandir"
|
||||
_API.post path, { path: p }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to scan directory: {0}", p), e, s
|
||||
mkdir: (p, c ) ->
|
||||
path = "#{_REST}/VFS/mkdir"
|
||||
_API.post path, { path: p }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to create directory: {0}", p), e, s
|
||||
sharefile: (p, pub , c) ->
|
||||
path = "#{_REST}/VFS/publish"
|
||||
_API.post path, { path: p , publish: pub }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to publish file: {0}", p), e, s
|
||||
|
||||
fileinfo: (p, c) ->
|
||||
path = "#{_REST}/VFS/fileinfo"
|
||||
_API.post path, { path: p }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to get file meta data: {0}", p), e, s
|
||||
|
||||
readfile: (p, c, t) ->
|
||||
path = "#{_REST}/VFS/get/"
|
||||
_API.get path + p, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to read file: {0}", p), e, s
|
||||
, t
|
||||
|
||||
move: (s, d, c) ->
|
||||
path = "#{_REST}/VFS/move"
|
||||
_API.post path, { src: s, dest: d }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to move file: {0} -> {1}", s, d), e, s
|
||||
|
||||
delete: (p , c) ->
|
||||
path = "#{_REST}/VFS/delete"
|
||||
_API.post path, { path: p }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to delete: {0}", p), e, s
|
||||
|
||||
fileblob: (p, c) ->
|
||||
path = "#{_REST}/VFS/get/"
|
||||
_API.blob path + p, c, (e, s) ->
|
||||
_courrier.osfail "Fail to read file: #{p}", e, s
|
||||
|
||||
packages: (d, c) ->
|
||||
path = "#{_REST}/system/packages"
|
||||
_API.post path, d, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to {0} package", d.command), e, s
|
||||
|
||||
upload: (d, c) ->
|
||||
path = "#{_REST}/VFS/upload"
|
||||
_API.upload path, d, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to upload file to: {0}", d), e, s
|
||||
|
||||
write: (p, d , c) ->
|
||||
path = "#{_REST}/VFS/write"
|
||||
_API.post path, { path: p, data: d }, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to write to file: {0}", p), e, s
|
||||
|
||||
scanapp: (p, c ) ->
|
||||
path = "#{_REST}/system/application"
|
||||
|
||||
apigateway: (d, ws, c) ->
|
||||
if ws
|
||||
path = "#{_HOST}/system/apigateway?ws=1"
|
||||
proto = if window.location.protocol is "https:" then "wss://" else "ws://"
|
||||
socket = new WebSocket proto + path
|
||||
if c then c(socket)
|
||||
return socket
|
||||
else
|
||||
path = "#{_REST}/system/apigateway?ws=0"
|
||||
_API.post path, d, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to invoke gateway api"), e, s
|
||||
|
||||
auth: (c) ->
|
||||
p = "#{_REST}/user/auth"
|
||||
_API.post p, {}, c, (e, s) ->
|
||||
console.log e, s
|
||||
alert __("Resource not found: {0}", p)
|
||||
login: (d, c) ->
|
||||
p = "#{_REST}/user/login"
|
||||
_API.post p, d, c, () ->
|
||||
alert __("Resource not found: {0}", p)
|
||||
logout: () ->
|
||||
p = "#{_REST}/user/logout"
|
||||
_API.post p, {}, (d) ->
|
||||
_OS.boot()
|
||||
, () ->
|
||||
alert __("Resource not found: {0}", p)
|
||||
setting: (f) ->
|
||||
p = "#{_REST}/system/settings"
|
||||
_API.post p, _OS.setting, (d) ->
|
||||
_courrier.oserror __("Cannot save system setting"), d.error if d.error
|
||||
f(d) if f
|
||||
, (e, s) ->
|
||||
m = __("Fail to make request: {0}", p)
|
||||
_courrier.osfail m , e, s
|
||||
f({ error: m }) if f
|
||||
|
||||
dbquery: (cmd, d, c) ->
|
||||
path = "#{_REST}/VDB/#{cmd}"
|
||||
_API.post path, d, c, (e, s) ->
|
||||
_courrier.osfail __("Fail to query data from database: {0}", path), e, s
|
@ -1,23 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
self.OS.API.handler =
|
||||
scandir: (p, c ) ->
|
||||
path = 'resources/jsons/scandir.json'
|
||||
_API.get path , c, (e, s) ->
|
||||
_courrier.osfail "System fall: Cannot read #{path}", e, s
|
||||
|
465
src/core/handles/RemoteHandle.ts
Normal file
465
src/core/handles/RemoteHandle.ts
Normal file
@ -0,0 +1,465 @@
|
||||
// Copyright 2017-2020 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
export namespace API {
|
||||
/**
|
||||
* Interface for user login data
|
||||
*
|
||||
* @export
|
||||
* @interface UserLoginType
|
||||
*/
|
||||
export interface UserLoginType {
|
||||
/**
|
||||
* The user credential
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UserLoginType
|
||||
*/
|
||||
username: string;
|
||||
|
||||
/**
|
||||
* The user password
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UserLoginType
|
||||
*/
|
||||
password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a command sent to
|
||||
* server side package manage, it contains two field:
|
||||
*
|
||||
* @export
|
||||
* @interface PackageCommandType
|
||||
*/
|
||||
export interface PackageCommandType {
|
||||
/**
|
||||
* Command name, should be: `init`, `cache`, `install`,
|
||||
* `uninstall` or `list`
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof PackageCommandType
|
||||
*/
|
||||
command: string;
|
||||
|
||||
/**
|
||||
* Parameter object of each command
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof PackageCommandType
|
||||
*/
|
||||
args: GenericObject<any>;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Interface for basic request result returned
|
||||
* from the server-side. A valid server-side response should
|
||||
* be in the following format
|
||||
* ```json
|
||||
* {
|
||||
* "error": boolean or string_err,
|
||||
* "result": JSON result object
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @export
|
||||
* @interface RequestResult
|
||||
*/
|
||||
export interface RequestResult {
|
||||
/**
|
||||
* Indicate whether the response is error
|
||||
*
|
||||
* @type {(boolean | string)}
|
||||
* @memberof RequestResult
|
||||
*/
|
||||
error: boolean | string;
|
||||
|
||||
/**
|
||||
* The response result, this value must be
|
||||
* set when `error` is false
|
||||
*
|
||||
* @type {(string
|
||||
* | boolean
|
||||
* | GenericObject<any>
|
||||
* | any[]
|
||||
* | FileInfoType
|
||||
* | FileInfoType[]
|
||||
* | setting.UserSettingType)}
|
||||
* @memberof RequestResult
|
||||
*/
|
||||
result:
|
||||
| string
|
||||
| boolean
|
||||
| GenericObject<any>
|
||||
| any[]
|
||||
| FileInfoType
|
||||
| FileInfoType[]
|
||||
| setting.UserSettingType;
|
||||
}
|
||||
|
||||
let loc: any = { hostname: "localhost", port: "80", protocol: "http" };
|
||||
|
||||
if (Ant.location) loc = Ant.location;
|
||||
/**
|
||||
* The host name of the server-side
|
||||
*/
|
||||
export var HOST: string =
|
||||
loc.hostname + (loc.port ? `:${loc.port}` : "");
|
||||
/**
|
||||
* The base REST URI of the server-side API
|
||||
*/
|
||||
export var REST: string = `${loc.protocol}//${HOST}`;
|
||||
|
||||
/**
|
||||
* The namespace `handle` contains some low level API to
|
||||
* communicate with the server side API. It is the only
|
||||
* API layer that communicate directly with the server.
|
||||
* To make AntOS compatible with any server side API,
|
||||
* all exported variable unctions defined in the `handle`
|
||||
* namespace should be re-implemented
|
||||
*/
|
||||
export namespace handle {
|
||||
/**
|
||||
* Base URI for reading content of VFS file
|
||||
*/
|
||||
export var get: string = `${REST}/VFS/get`;
|
||||
/**
|
||||
* Base URI for VFS file sharing
|
||||
*/
|
||||
export var shared: string = `${REST}/VFS/shared`;
|
||||
|
||||
/**
|
||||
* Send a request to the server-side API for a directory scanning
|
||||
* operation
|
||||
*
|
||||
* @export
|
||||
* @param {string} p a VFS file path e.g. home://test/
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* which contains an error or a list of FileInfoType
|
||||
*/
|
||||
export function scandir(p: string): Promise<RequestResult> {
|
||||
const path = `${REST}/VFS/scandir`;
|
||||
return API.post(path, { path: p });
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Send a request to the server-side API for directory creation
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS path of the directory to be created
|
||||
* @returns {Promise<RequestResult>} A promise on a RequestResult
|
||||
* which contains an error or true on success
|
||||
*/
|
||||
export function mkdir(p: string): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/mkdir`;
|
||||
return API.post(path, { path: p });
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the server-side API for sharing/unsharing a VFS file,
|
||||
* once shared a VFS file will be publicly visible by everyone
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS file path to be shared
|
||||
* @param {boolean} pub flag: share (true) or unshare (false)
|
||||
* @returns {Promise<RequestResult>} A promise on a RequestResult
|
||||
* which contains an error or true on success
|
||||
*/
|
||||
export function sharefile(
|
||||
p: string,
|
||||
pub: boolean
|
||||
): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/publish`;
|
||||
return API.post(path, { path: p, publish: pub });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get VFS file meta-data
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS file path
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* which contains an error or an object of FileInfoType
|
||||
*/
|
||||
export function fileinfo(p: string): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/fileinfo`;
|
||||
return API.post(path, { path: p });
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a VFS file content. There are many ways a VFS file can be read:
|
||||
* - Read as a raw text content
|
||||
* - Read as a javascript file, in this case the content of the
|
||||
* file will be executed
|
||||
* - Read as JSON object
|
||||
*
|
||||
* @export
|
||||
* @param {string} p path of the VFS file
|
||||
* @param {string} t return data type:
|
||||
* - jsonp: the response is an json object
|
||||
* - script: the response is a javascript code
|
||||
* - xml, html: the response is a XML/HTML object
|
||||
* - text: plain text
|
||||
*
|
||||
* @returns {Promise<any>} A promise on a [[RequestResult]]
|
||||
* which contains an error or an object of [[FileInfoType]]
|
||||
*/
|
||||
export function readfile(p: string, t: string): Promise<any> {
|
||||
const path = `${API.REST}/VFS/get/`;
|
||||
return API.get(path + p, t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a file to another location on server-side
|
||||
*
|
||||
* @export
|
||||
* @param {string} s VFS source file path
|
||||
* @param {string} d VFS destination file path
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* which contains an error or a success response
|
||||
*/
|
||||
export function move(s: string, d: string): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/move`;
|
||||
return API.post(path, { src: s, dest: d });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a VFS file on the server-side
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS file path
|
||||
* @returns {Promise<RequestResult>} A promise on a [[RequestResult]]
|
||||
* which contains an error or a success response
|
||||
*/
|
||||
export function remove(p: string): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/delete`;
|
||||
return API.post(path, { path: p });
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the file as binary data
|
||||
*
|
||||
* @export
|
||||
* @param {string} p VFS file to be read
|
||||
* @returns {Promise<ArrayBuffer>} a Promise on an array buffer
|
||||
*/
|
||||
export function fileblob(p: string): Promise<ArrayBuffer> {
|
||||
const path = `${API.REST}/VFS/get/`;
|
||||
return API.blob(path + p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a command to the serverside package manager
|
||||
*
|
||||
* @export
|
||||
* @param {PackageCommandType} d a package command of type PackageCommandType
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
*/
|
||||
export function packages(
|
||||
d: PackageCommandType
|
||||
): Promise<RequestResult> {
|
||||
const path = `${API.REST}/system/packages`;
|
||||
return API.post(path, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload file to the server via VFS interface
|
||||
*
|
||||
* @export
|
||||
* @param {string} d VFS destination directory path
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
*/
|
||||
export function upload(d: string): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/upload`;
|
||||
return API.upload(path, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write Base 64 encoded data to a VFS file
|
||||
*
|
||||
* @export
|
||||
* @param {string} p path to the VFS file
|
||||
* @param {string} d file data encoded in Base 64
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
*/
|
||||
export function write(
|
||||
p: string,
|
||||
d: string
|
||||
): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VFS/write`;
|
||||
return API.post(path, { path: p, data: d });
|
||||
}
|
||||
|
||||
/**
|
||||
* An apigateway allows client side to execute a custom server-side
|
||||
* script and get back the result. This gateway is particularly
|
||||
* useful in case of performing a task that is not provided by the core
|
||||
* API
|
||||
*
|
||||
* @export
|
||||
* @param {GenericObject<any>} d execution indication, provided only when ws is `false`
|
||||
* otherwise, `d` should be written directly to the websocket stream as JSON object.
|
||||
* Two possible formats of `d`:
|
||||
* ```text
|
||||
* execute an server-side script file:
|
||||
*
|
||||
* {
|
||||
* path: [VFS path],
|
||||
* parameters: [parameters of the server-side script]
|
||||
* }
|
||||
*
|
||||
* or, execute directly a snippet of server-side script:
|
||||
*
|
||||
* { code: [server-side script code snippet as string] }
|
||||
*
|
||||
* ```
|
||||
*
|
||||
* @param {boolean} ws flag indicate whether to use websocket for the connection
|
||||
* to the gateway API. In case of streaming data, the websocket is preferred
|
||||
* @returns {Promise<any>} a promise on the result object (any)
|
||||
*/
|
||||
export function apigateway(
|
||||
d: GenericObject<any>,
|
||||
ws: boolean
|
||||
): Promise<any> {
|
||||
if (ws) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
try {
|
||||
const path = `${API.HOST}/system/apigateway?ws=1`;
|
||||
const proto =
|
||||
window.location.protocol === "https:"
|
||||
? "wss://"
|
||||
: "ws://";
|
||||
const socket = new WebSocket(proto + path);
|
||||
return resolve(socket);
|
||||
} catch (e) {
|
||||
return reject(__e(e));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const path = `${API.REST}/system/apigateway?ws=0`;
|
||||
return API.post(path, d);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is logged in
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
|
||||
* contains an error or a [[UserSettingType]] object
|
||||
*/
|
||||
export function auth(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/user/auth`;
|
||||
return API.post(p, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a login operation
|
||||
*
|
||||
* @export
|
||||
* @param {UserLoginType} d user data [[UserLoginType]]
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]] that
|
||||
* contains an error or a [[UserSettingType]] object
|
||||
*/
|
||||
export function login(d: UserLoginType): Promise<RequestResult> {
|
||||
const p = `${API.REST}/user/login`;
|
||||
return API.post(p, d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a logout operation
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
*/
|
||||
export function logout(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/user/logout`;
|
||||
return API.post(p, {});
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current user settings
|
||||
*
|
||||
* @export
|
||||
* @returns {Promise<RequestResult>} a promise on a [[RequestResult]]
|
||||
*/
|
||||
export function setting(): Promise<RequestResult> {
|
||||
const p = `${API.REST}/system/settings`;
|
||||
return API.post(p, OS.setting);
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the low level function of AntOS VDB API.
|
||||
* It requests the server API to perform some simple
|
||||
* SQL query.
|
||||
*
|
||||
* @export
|
||||
* @param {string} cmd action to perform: save, delete, get, select
|
||||
* @param {GenericObject<any>} d data object of the request based on each action:
|
||||
* - save:
|
||||
* ```
|
||||
* { table: "table name", data: [record data object]}
|
||||
* ```
|
||||
* - get:
|
||||
* ```
|
||||
* { table: "table name", id: [record id]}
|
||||
* ```
|
||||
* - delete:
|
||||
* ```
|
||||
* { table: "table name", id: [record id]}
|
||||
* or
|
||||
* { table: "table name", cond: [conditional object]}
|
||||
* ```
|
||||
* - select:
|
||||
* ```
|
||||
* { table: "table name", cond: [conditional object]}
|
||||
* ```
|
||||
* @returns {Promise<RequestResult>} a promise of [[RequestResult]] on the
|
||||
* query data
|
||||
*
|
||||
* A conditional object represents a SQL condition statement as an object,
|
||||
* example: `pid = 10 AND cid = 2 ORDER BY date DESC`
|
||||
* ```
|
||||
* {
|
||||
* exp: {
|
||||
* "and": {
|
||||
* pid: 10,
|
||||
* cid: 2
|
||||
* }
|
||||
* },
|
||||
* order: {
|
||||
* date: "DESC"
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function dbquery(
|
||||
cmd: string,
|
||||
d: GenericObject<any>
|
||||
): Promise<RequestResult> {
|
||||
const path = `${API.REST}/VDB/${cmd}`;
|
||||
return API.post(path, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
29
src/core/handles/TestHandle.js
Normal file
29
src/core/handles/TestHandle.js
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* decaffeinate suggestions:
|
||||
* DS102: Remove unnecessary code created because of implicit returns
|
||||
* 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/.
|
||||
Ant.OS.API.handle = {
|
||||
scandir(p, c ) {
|
||||
const path = 'resources/jsons/scandir.json';
|
||||
return Ant.OS.API.get(path , c, (e, s) => Ant.OS.announcer.osfail(`System fall: Cannot read ${path}`, e, s));
|
||||
}
|
||||
};
|
||||
|
4
src/core/languages/gen.sh
Normal file → Executable file
4
src/core/languages/gen.sh
Normal file → Executable file
@ -20,7 +20,7 @@
|
||||
ord() {
|
||||
LC_CTYPE=C printf '%d' "'$1"
|
||||
}
|
||||
grep --include=\*.{coffee,tag} -roh "$1" -e '__("[^"]*"' | while read -r line ; do
|
||||
grep --include=\*.coffee -roh "$1" -e '__("[^"]*"' | while read -r line ; do
|
||||
SUBSTRING=$(echo $line| cut -d'"' -f 2)
|
||||
if test -f "$2" && [ ! -z "$(grep -F "\"$SUBSTRING\":" "$2")" ]
|
||||
then
|
||||
@ -29,7 +29,7 @@ grep --include=\*.{coffee,tag} -roh "$1" -e '__("[^"]*"' | while read -r line ;
|
||||
echo -e "\t\"$SUBSTRING\":\"$SUBSTRING\"," >> "tmp.json"
|
||||
fi
|
||||
done
|
||||
grep --include=\*.{coffee,html,tag} -roh "$1" -e '\"__([^\"]*)\"' | while read -r line; do
|
||||
grep --include=\*.{coffee,html} -roh "$1" -e '\"__([^\"]*)\"' | while read -r line; do
|
||||
len=$(( ${#line} - 6 ))
|
||||
#echo $len
|
||||
#echo $line
|
||||
|
@ -318,4 +318,79 @@
|
||||
"Welcome to AntOSDK":"Welcome to AntOSDK",
|
||||
"Your application version is older ({0} < {1})":"Your application version is older ({0} < {1})",
|
||||
"zip file generated in release folder":"zip file generated in release folder"
|
||||
|
||||
,
|
||||
"Cancels":"Cancels",
|
||||
"Command Palette":"Command Palette",
|
||||
"Mount Points":"Mount Points",
|
||||
"New CodePad extension at":"New CodePad extension at",
|
||||
"Report":"Report",
|
||||
"Select extension archive":"Select extension archive",
|
||||
"System error log":"System error log"
|
||||
|
||||
,
|
||||
"[^":"[^",
|
||||
"{0} is not a file":"{0} is not a file",
|
||||
"Action {0} is unsupported on: {1}":"Action {0} is unsupported on: {1}",
|
||||
"Application meta data isnt found":"Application meta data isnt found",
|
||||
"Cannot load extension meta data":"Cannot load extension meta data",
|
||||
"Cannot load scheme: {0}":"Cannot load scheme: {0}",
|
||||
"Cannot read folder: {0}":"Cannot read folder: {0}",
|
||||
"Change language mode":"Change language mode",
|
||||
"Change theme":"Change theme",
|
||||
"Command palete":"Command palete",
|
||||
"Command palette":"Command palette",
|
||||
"ct:Logout":"ct:Logout",
|
||||
"ct:Toggle fullscreen":"ct:Toggle fullscreen",
|
||||
"ct:User: {0}":"ct:User: {0}",
|
||||
"Current folder is not found":"Current folder is not found",
|
||||
"Error reported":"Error reported",
|
||||
"Error saving file {0}: {1}":"Error saving file {0}: {1}",
|
||||
"Example action":"Example action",
|
||||
"Extension installed":"Extension installed",
|
||||
"ExtensionName":"ExtensionName",
|
||||
"Fail to create: {0}":"Fail to create: {0}",
|
||||
"Fail to download: {0}":"Fail to download: {0}",
|
||||
"Fail to publish: {0}":"Fail to publish: {0}",
|
||||
"Fail to read: {0}":"Fail to read: {0}",
|
||||
"Fail to rename: {0}":"Fail to rename: {0}",
|
||||
"Fail to upload: {0}":"Fail to upload: {0}",
|
||||
"Install extension":"Install extension",
|
||||
"Invalid library: {0}":"Invalid library: {0}",
|
||||
"New Extension":"New Extension",
|
||||
"New project from current folder":"New project from current folder",
|
||||
"New Project":"New Project",
|
||||
"No meta-data found":"No meta-data found",
|
||||
"Open folder":"Open folder",
|
||||
"Open Folder":"Open Folder",
|
||||
"Package is generated in release folder":"Package is generated in release folder",
|
||||
"Please select a day":"Please select a day",
|
||||
"Please select an item":"Please select an item",
|
||||
"Please select color":"Please select color",
|
||||
"Start":"Start",
|
||||
"The folder is not empty: {0}":"The folder is not empty: {0}",
|
||||
"Unable to build extension":"Unable to build extension",
|
||||
"Unable to build project":"Unable to build project",
|
||||
"Unable to create archive":"Unable to create archive",
|
||||
"Unable to create extension directories":"Unable to create extension directories",
|
||||
"Unable to create extension template":"Unable to create extension template",
|
||||
"Unable to create package archive":"Unable to create package archive",
|
||||
"Unable to create project directory":"Unable to create project directory",
|
||||
"Unable to create template files":"Unable to create template files",
|
||||
"Unable to find action: {0}":"Unable to find action: {0}",
|
||||
"Unable to find extension: {0}":"Unable to find extension: {0}",
|
||||
"Unable to install extension":"Unable to install extension",
|
||||
"unable to load extension: {0}":"unable to load extension: {0}",
|
||||
"Unable to load libraries":"Unable to load libraries",
|
||||
"Unable to open: {0}":"Unable to open: {0}",
|
||||
"Unable to preload extension":"Unable to preload extension",
|
||||
"Unable to read: {0}":"Unable to read: {0}",
|
||||
"Unable to read meta-data":"Unable to read meta-data",
|
||||
"Unable to report error: {0}":"Unable to report error: {0}",
|
||||
"Unable to run extension":"Unable to run extension",
|
||||
"Unable to run project":"Unable to run project",
|
||||
"Unable to save file: {0}":"Unable to save file: {0}",
|
||||
"Value":"Value",
|
||||
"Verifying: {0}":"Verifying: {0}",
|
||||
"VFS unknown handle: {0}":"VFS unknown handle: {0}"
|
||||
}
|
||||
|
@ -267,4 +267,64 @@
|
||||
,
|
||||
"Graph editor":"Graph editor",
|
||||
"Render":"Render"
|
||||
|
||||
,
|
||||
"add {0} to zip":"add {0} to zip",
|
||||
"Add files to build target":"Add files to build target",
|
||||
"and unsaved project":"and unsaved project",
|
||||
"Build and Run":"Build and Run",
|
||||
"Build":"Build",
|
||||
"Build done":"Build done",
|
||||
"Build Options":"Build Options",
|
||||
"Build release":"Build release",
|
||||
"Cannot create file: {0}":"Cannot create file: {0}",
|
||||
"Cannot save project: {0}":"Cannot save project: {0}",
|
||||
"Cannot save the zip file {0} : {1}":"Cannot save the zip file {0} : {1}",
|
||||
"Coffees":"Coffees",
|
||||
"Compiled successful":"Compiled successful",
|
||||
"Copied {0} -> {1}":"Copied {0} -> {1}",
|
||||
"Copied files":"Copied files",
|
||||
"Created directory: {0}":"Created directory: {0}",
|
||||
"Created file: {0}":"Created file: {0}",
|
||||
"Css":"Css",
|
||||
"Error when create directory: {0}":"Error when create directory: {0}",
|
||||
"Generated {0}":"Generated {0}",
|
||||
"Hide":"Hide",
|
||||
"Ignore: {0} unsaved files {1}?":"Ignore: {0} unsaved files {1}?",
|
||||
"Ignore unsaved project ?":"Ignore unsaved project ?",
|
||||
"Installing...":"Installing...",
|
||||
"Javascripts":"Javascripts",
|
||||
"Metadata found...":"Metadata found...",
|
||||
"New Project at":"New Project at",
|
||||
"New project":"New project",
|
||||
"Opening {0}":"Opening {0}",
|
||||
"Open project":"Open project",
|
||||
"Open Project":"Open Project",
|
||||
"Output":"Output",
|
||||
"Please select {0} only":"Please select {0} only",
|
||||
"Please select a file/fofler":"Please select a file/fofler",
|
||||
"Preparing for release":"Preparing for release",
|
||||
"ProjectName":"ProjectName",
|
||||
"Project":"Project",
|
||||
"project saved":"project saved",
|
||||
"Running {0}...":"Running {0}...",
|
||||
"Select a file":"Select a file",
|
||||
"Show":"Show",
|
||||
"Uninstall: {0}?":"Uninstall: {0}?",
|
||||
"Unsaved project":"Unsaved project",
|
||||
"Update":"Update",
|
||||
"Verifying {0}":"Verifying {0}",
|
||||
"Version string is in invalid format: {0}":"Version string is in invalid format: {0}",
|
||||
"Welcome to AntOSDK":"Welcome to AntOSDK",
|
||||
"Your application version is older ({0} < {1})":"Your application version is older ({0} < {1})",
|
||||
"zip file generated in release folder":"zip file generated in release folder"
|
||||
|
||||
,
|
||||
"Cancels":"Cancels",
|
||||
"Command Palette":"Command Palette",
|
||||
"Mount Points":"Mount Points",
|
||||
"New CodePad extension at":"New CodePad extension at",
|
||||
"Report":"Report",
|
||||
"Select extension archive":"Select extension archive",
|
||||
"System error log":"System error log"
|
||||
}
|
||||
|
155
src/core/pm.ts
Normal file
155
src/core/pm.ts
Normal file
@ -0,0 +1,155 @@
|
||||
namespace OS {
|
||||
/**
|
||||
* This namespace dedicated to all operations related to system
|
||||
* process management
|
||||
*/
|
||||
export namespace PM {
|
||||
/**
|
||||
* A process is either an instance of an application or a service
|
||||
*/
|
||||
export type ProcessType =
|
||||
| application.BaseApplication
|
||||
| application.BaseService;
|
||||
/**
|
||||
* Alias to all classes that extends [[BaseModel]]
|
||||
*/
|
||||
export type ModelTypeClass = {
|
||||
new <T extends BaseModel>(args: AppArgumentsType[]): T;
|
||||
};
|
||||
/**
|
||||
* Process id allocator, when a new process is created, the value of
|
||||
* this variable is increased
|
||||
*/
|
||||
export var pidalloc: number = 0;
|
||||
/**
|
||||
* All running processes is stored in this variables
|
||||
*/
|
||||
export var processes: GenericObject<BaseModel[]> = {};
|
||||
/**
|
||||
* Create a new process of application or service
|
||||
*
|
||||
* @export
|
||||
* @param {string} app class name string
|
||||
* @param {ProcessTypeClass} cls prototype class
|
||||
* @param {GUI.AppArgumentsType[]} [args] process arguments
|
||||
* @returns {Promise<ProcessType>} a promise on the created process
|
||||
*/
|
||||
export function createProcess(
|
||||
app: string,
|
||||
cls: ModelTypeClass,
|
||||
args?: AppArgumentsType[]
|
||||
): Promise<ProcessType> {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let metaclass = (cls as any) as typeof BaseModel;
|
||||
const f = function () {
|
||||
//if it is single ton
|
||||
// and a process is existing
|
||||
// just return it
|
||||
let obj: ProcessType;
|
||||
if (
|
||||
metaclass.singleton &&
|
||||
PM.processes[app] &&
|
||||
PM.processes[app].length === 1
|
||||
) {
|
||||
obj = PM.processes[
|
||||
app
|
||||
][0] as application.BaseApplication;
|
||||
obj.show();
|
||||
} else {
|
||||
if (!PM.processes[app]) {
|
||||
PM.processes[app] = [];
|
||||
}
|
||||
obj = new cls(args);
|
||||
obj.birth = new Date().getTime();
|
||||
PM.pidalloc++;
|
||||
obj.pid = PM.pidalloc;
|
||||
PM.processes[app].push(obj);
|
||||
if (metaclass.type === ModelType.Application) {
|
||||
GUI.dock(
|
||||
obj as application.BaseApplication,
|
||||
metaclass.meta
|
||||
);
|
||||
} else {
|
||||
GUI.attachservice(obj as application.BaseService);
|
||||
}
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
if (metaclass.dependencies) {
|
||||
const libs = metaclass.dependencies;
|
||||
return API.require(libs)
|
||||
.then(() => resolve(f()))
|
||||
.catch((e: Error) => reject(__e(e)));
|
||||
} else {
|
||||
return resolve(f());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference to a process using its id
|
||||
*
|
||||
* @export
|
||||
* @param {number} pid
|
||||
* @returns {BaseModel}
|
||||
*/
|
||||
export function appByPid(pid: number): BaseModel {
|
||||
let app = undefined;
|
||||
const find = function (l: Array<any>) {
|
||||
for (let a of l) {
|
||||
if (a.pid === pid) {
|
||||
return a;
|
||||
}
|
||||
}
|
||||
};
|
||||
for (let k in PM.processes) {
|
||||
const v = PM.processes[k];
|
||||
app = find(v);
|
||||
if (app) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill a process
|
||||
*
|
||||
* @export
|
||||
* @param {OS.GUI.BaseModel} app reference to the process
|
||||
* @returns {void}
|
||||
*/
|
||||
export function kill(app: BaseModel): void {
|
||||
if (!app.name || !PM.processes[app.name]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const i = PM.processes[app.name].indexOf(app);
|
||||
if (i >= 0) {
|
||||
if (application[app.name].type === ModelType.Application) {
|
||||
GUI.undock(app as application.BaseApplication);
|
||||
} else {
|
||||
GUI.detachservice(app as application.BaseService);
|
||||
}
|
||||
announcer.unregister(app);
|
||||
delete PM.processes[app.name][i];
|
||||
PM.processes[app.name].splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill all process of an application or service
|
||||
*
|
||||
* @export
|
||||
* @param {string} app process class name
|
||||
* @param {boolean} force force exit all process
|
||||
* @returns {void}
|
||||
*/
|
||||
export function killAll(app: string, force: boolean): void {
|
||||
if (!PM.processes[app]) {
|
||||
return;
|
||||
}
|
||||
PM.processes[app].map((a) => a.quit(force));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
<afx-app-window data-id = 'about-window' width='300' height='200'>
|
||||
<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>
|
||||
</h3>
|
||||
<i><p style = "margin:0; padding:0" data-id = 'mydesc'></p></i>
|
||||
</div>
|
||||
<afx-grid-view data-id = 'mygrid'></afx-grid-view>
|
||||
</afx-vbox>
|
||||
</afx-app-window>
|
@ -1,10 +0,0 @@
|
||||
|
||||
<afx-sys-panel id = "syspanel"></afx-sys-panel>
|
||||
<!--div class = "afx-clear"></div-->
|
||||
<div id = "workspace">
|
||||
<afx-apps-dock id="sysdock"></afx-apps-dock>
|
||||
<afx-float-list id = "desktop" ></afx-float-list>
|
||||
<!--div id = "workingenv"></div-->
|
||||
</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>
|
@ -1,13 +0,0 @@
|
||||
<afx-app-window data-id = 'file-dialog-window' width='400' height='300'>
|
||||
<afx-hbox>
|
||||
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
|
||||
<afx-vbox>
|
||||
<afx-file-view data-id = "fileview" view='tree' status = false></afx-file-view>
|
||||
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" />
|
||||
<div data-height = '30' style=' text-align:right;padding:3px;'>
|
||||
<afx-button data-id = "bt-ok" text = "__(Ok)"></afx-button>
|
||||
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
|
||||
</div>
|
||||
</afx-vbox>
|
||||
</afx-hbox>
|
||||
</afx-app-window>
|
@ -1,78 +0,0 @@
|
||||
<!--
|
||||
Since nothing is loaded, the login form is a standalone element,
|
||||
it deserves it own unique style
|
||||
For now, it can not be themed !!!
|
||||
-->
|
||||
<style>
|
||||
body, html{
|
||||
background-color: rgba(215,215,215,0.7);
|
||||
}
|
||||
#login_form{
|
||||
width:300px;
|
||||
height: 200px;
|
||||
display: block;
|
||||
border:1px solid #a6a6a6;
|
||||
border-radius: 6px;
|
||||
box-shadow: 1px 1px 1px #9f9F9F;
|
||||
position: absolute;
|
||||
margin: auto;
|
||||
top:0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
font-family:Verdana, Geneva, Tahoma, sans-serif;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
color: #414339;
|
||||
}
|
||||
|
||||
#login_form p{
|
||||
display: inline-block;
|
||||
background-color:#dfdfdf;
|
||||
border-bottom: 1px solid #a6a6a6;
|
||||
padding:10px;
|
||||
width: calc(100% - 20px);
|
||||
border-top-left-radius: 6px;
|
||||
border-top-right-radius: 6px;
|
||||
font-weight: bold;
|
||||
margin:0;
|
||||
|
||||
}
|
||||
#login_form input {
|
||||
width: 250px;
|
||||
outline: none;
|
||||
margin-top:10px;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
font-size: 13px;
|
||||
padding: 5px;
|
||||
border: 1px solid #a6a6a6;
|
||||
background-color: white;
|
||||
color: #414339;
|
||||
}
|
||||
#login_form button{
|
||||
margin-top:10px;
|
||||
width: 250px;
|
||||
height: 30px;
|
||||
background-color: #2786F3;
|
||||
color: white;
|
||||
border: 1px solid #dedede;
|
||||
border-radius: 6px;
|
||||
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||
font-size: 13px;
|
||||
padding:5px;
|
||||
}
|
||||
#login_error{
|
||||
padding:3px;
|
||||
color:red;
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
<div id = "login_form">
|
||||
<p>Welcome to AntOS, please identify</p>
|
||||
<input id = "txtuser" type = "text" value = "demo" />
|
||||
<input id = "txtpass" type = "password" value = "demo" />
|
||||
<button id = "btlogin">Login</button>
|
||||
<span id = "login_error"></span>
|
||||
</div>
|
@ -1,91 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
self.OS.systemSetting = (conf) ->
|
||||
_OS.setting.desktop = conf.desktop if conf.desktop
|
||||
_OS.setting.applications = conf.applications if conf.applications
|
||||
_OS.setting.appearance = conf.appearance if conf.appearance
|
||||
_OS.setting.appearance.wp = {
|
||||
url: "os://resources/themes/system/wp/wp2.jpg",
|
||||
size: "cover",
|
||||
repeat: "repeat"
|
||||
} unless _OS.setting.appearance.wp
|
||||
_OS.setting.appearance.wps = [] unless _OS.setting.appearance.wps
|
||||
_OS.setting.applications = {} unless _OS.setting.applications
|
||||
_OS.setting.user = conf.user
|
||||
_OS.setting.VFS = conf.VFS if conf.VFS
|
||||
_OS.setting.desktop.path = "home://.desktop" unless _OS.setting.desktop.path
|
||||
_OS.setting.desktop.menu = {} unless _OS.setting.desktop.menu
|
||||
_OS.setting.VFS.mountpoints = [
|
||||
#TODO: multi app try to write to this object, it neet to be cloned
|
||||
{ text: "__(Applications)", path: 'app://', iconclass: "fa fa-adn", type: "app" },
|
||||
{ text: "__(Home)", path: 'home://', iconclass: "fa fa-home", type: "fs" },
|
||||
{ text: "__(Desktop)", path: _OS.setting.desktop.path , iconclass: "fa fa-desktop", type: "fs" },
|
||||
{ text: "__(OS)", path: 'os://', iconclass: "fa fa-inbox", type: "fs" },
|
||||
{ text: "__(Google Drive)", path: 'gdv://', iconclass: "fa fa-inbox", type: "fs" },
|
||||
{ text: "__(Shared)", path: 'shared://' , iconclass: "fa fa-share-square", type: "fs" }
|
||||
] if not _OS.setting.VFS.mountpoints
|
||||
|
||||
_OS.setting.system = conf.system if conf.system
|
||||
_OS.setting.system.startup = {
|
||||
services: [
|
||||
"CoreServices/PushNotification",
|
||||
"CoreServices/UserService",
|
||||
"CoreServices/Calendar",
|
||||
"CoreServices/Spotlight"
|
||||
],
|
||||
apps: []
|
||||
} if not _OS.setting.system.startup
|
||||
|
||||
_OS.setting.system.pkgpaths = {
|
||||
user: "home://.packages",
|
||||
system: "os://packages"
|
||||
} unless _OS.setting.system.pkgpaths
|
||||
_OS.setting.system.locale = "en_GB" unless _OS.setting.system.locale
|
||||
_OS.setting.system.menu = {} unless _OS.setting.system.menu
|
||||
_OS.setting.system.repositories = [] unless _OS.setting.system.repositories
|
||||
_OS.setting.appearance.theme = "antos" unless _OS.setting.appearance.theme
|
||||
|
||||
_OS.setting.VFS.gdrive = {
|
||||
CLIENT_ID: ""
|
||||
API_KEY: ""
|
||||
apilink: "https://apis.google.com/js/api.js"
|
||||
DISCOVERY_DOCS: ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"]
|
||||
SCOPES: 'https://www.googleapis.com/auth/drive'
|
||||
} unless _OS.setting.VFS.gdrive
|
||||
|
||||
#search for app
|
||||
_API.onsearch "__(Applications)", (t) ->
|
||||
ar = []
|
||||
term = new RegExp t, "i"
|
||||
for k, v of _OS.setting.system.packages when v.app
|
||||
if (v.name.match term) or (v.description and v.description.match term)
|
||||
v1 = {}
|
||||
v1[k1] = e for k1, e of v when k1 isnt "selected"
|
||||
v1.detail = [{ text: v1.path }]
|
||||
v1.complex = true
|
||||
ar.push v1
|
||||
else if v.mimes
|
||||
for m in v.mimes
|
||||
if t.match (new RegExp m, "g")
|
||||
v1 = {}
|
||||
v1[k1] = v[k1] for k1, e of v when k1 isnt "selected"
|
||||
v1.detail = [{ text: v1.path }]
|
||||
v1.complex = true
|
||||
ar.push v1
|
||||
break
|
||||
return ar
|
569
src/core/settings.ts
Normal file
569
src/core/settings.ts
Normal file
@ -0,0 +1,569 @@
|
||||
// Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
// AnTOS Web desktop is is licensed under the GNU General Public
|
||||
// License v3.0, see the LICENCE file for more information
|
||||
|
||||
// This program is free software: you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of
|
||||
// the License, or (at your option) any later version.
|
||||
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
// General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
//along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
namespace OS {
|
||||
/**
|
||||
* This namespace is dedicated to everything related to the
|
||||
* global system settings
|
||||
*/
|
||||
export namespace setting {
|
||||
/**
|
||||
* User setting type definition
|
||||
*
|
||||
* @export
|
||||
* @interface UserSettingType
|
||||
*/
|
||||
export interface UserSettingType {
|
||||
/**
|
||||
* User full name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UserSettingType
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* User name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UserSettingType
|
||||
*/
|
||||
username: string;
|
||||
|
||||
/**
|
||||
* User id
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof UserSettingType
|
||||
*/
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* User groups
|
||||
*
|
||||
* @type {{ [index: number]: string }}
|
||||
* @memberof UserSettingType
|
||||
*/
|
||||
group?: { [index: number]: string };
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual desktop setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface DesktopSettingType
|
||||
*/
|
||||
export interface DesktopSettingType {
|
||||
/**
|
||||
* Desktop VFS path
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof DesktopSettingType
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* Desktop menu, can be added automatically by applications
|
||||
*
|
||||
* @type {GUI.BasicItemType[]}
|
||||
* @memberof DesktopSettingType
|
||||
*/
|
||||
menu: GUI.BasicItemType[];
|
||||
|
||||
/**
|
||||
* Show desktop hidden files
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof DesktopSettingType
|
||||
*/
|
||||
showhidden: boolean;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wallpaper setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface WPSettingType
|
||||
*/
|
||||
export interface WPSettingType {
|
||||
/**
|
||||
* Repeat wallpaper:
|
||||
* - `repeat`
|
||||
* - `repeat-x`
|
||||
* - `repeat-y`
|
||||
* - `no-repeat`
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof WPSettingType
|
||||
*/
|
||||
repeat: string;
|
||||
|
||||
/**
|
||||
* Wallpaper size
|
||||
* - `contain`
|
||||
* - `cover`
|
||||
* - `auto`
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof WPSettingType
|
||||
*/
|
||||
size: string;
|
||||
|
||||
/**
|
||||
* VFS path to the wallpaper image
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof WPSettingType
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Theme setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface ThemeSettingType
|
||||
*/
|
||||
export interface ThemeSettingType {
|
||||
/**
|
||||
* Theme name, this value is used for looking
|
||||
* theme file in system asset
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ThemeSettingType
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* Theme user-friendly text
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ThemeSettingType
|
||||
*/
|
||||
text: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appearance setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface AppearanceSettingType
|
||||
*/
|
||||
export interface AppearanceSettingType {
|
||||
/**
|
||||
* Current theme name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof AppearanceSettingType
|
||||
*/
|
||||
theme: string;
|
||||
|
||||
/**
|
||||
* All themes available in the system
|
||||
*
|
||||
* @type {ThemeSettingType[]}
|
||||
* @memberof AppearanceSettingType
|
||||
*/
|
||||
themes: ThemeSettingType[];
|
||||
|
||||
/**
|
||||
* Current wallpaper setting
|
||||
*
|
||||
* @type {WPSettingType}
|
||||
* @memberof AppearanceSettingType
|
||||
*/
|
||||
wp: WPSettingType;
|
||||
|
||||
/**
|
||||
* All wallpapers available in the system
|
||||
*
|
||||
* @type {string[]}
|
||||
* @memberof AppearanceSettingType
|
||||
*/
|
||||
wps: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* VFS Mount points setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface VFSMountPointSettingType
|
||||
*/
|
||||
export interface VFSMountPointSettingType {
|
||||
/**
|
||||
* Path to the mount point
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof VFSMountPointSettingType
|
||||
*/
|
||||
path: string;
|
||||
|
||||
/**
|
||||
* User friendly mount point name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof VFSMountPointSettingType
|
||||
*/
|
||||
text: string;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* VFS setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface VFSSettingType
|
||||
*/
|
||||
export interface VFSSettingType {
|
||||
/**
|
||||
* mount points setting
|
||||
*
|
||||
* @type {VFSMountPointSettingType[]}
|
||||
* @memberof VFSSettingType
|
||||
*/
|
||||
mountpoints: VFSMountPointSettingType[];
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global system setting data type
|
||||
*
|
||||
* @export
|
||||
* @interface SystemSettingType
|
||||
*/
|
||||
export interface SystemSettingType {
|
||||
/**
|
||||
* System error report URL
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SystemSettingType
|
||||
*/
|
||||
error_report: string;
|
||||
|
||||
/**
|
||||
* Current system locale e.g. `en_GB`
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof SystemSettingType
|
||||
*/
|
||||
locale: string;
|
||||
|
||||
/**
|
||||
* System menus
|
||||
*
|
||||
* @type {API.PackageMetaType[]}
|
||||
* @memberof API.PackageMetaType
|
||||
*/
|
||||
menu: API.PackageMetaType[];
|
||||
|
||||
/**
|
||||
* Packages meta-data
|
||||
*
|
||||
* @type {{ [index: string]: API.PackageMetaType }}
|
||||
* @memberof SystemSettingType
|
||||
*/
|
||||
packages: { [index: string]: API.PackageMetaType };
|
||||
|
||||
/**
|
||||
* Path to the installed packages
|
||||
*
|
||||
* @type {{
|
||||
* user: string;
|
||||
* system: string;
|
||||
* }}
|
||||
* @memberof SystemSettingType
|
||||
*/
|
||||
pkgpaths: {
|
||||
/**
|
||||
* User specific packages install location
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
user: string;
|
||||
|
||||
/**
|
||||
* System packages install location
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
system: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Package repositories setting.
|
||||
* This configuration is used by [[MarketPlace]]
|
||||
* for package management
|
||||
*
|
||||
* @type {{
|
||||
* text: string;
|
||||
* url: string;
|
||||
* }[]}
|
||||
* @memberof SystemSettingType
|
||||
*/
|
||||
repositories: {
|
||||
/**
|
||||
* Repository name
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
text: string;
|
||||
|
||||
/**
|
||||
* Repository uri
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
url: string;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* Startup applications and services
|
||||
*
|
||||
* @type {{
|
||||
* apps: string[];
|
||||
* services: string[];
|
||||
* }}
|
||||
* @memberof SystemSettingType
|
||||
*/
|
||||
startup: {
|
||||
/**
|
||||
* List of application names
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
apps: string[];
|
||||
|
||||
/**
|
||||
* List of service names
|
||||
*
|
||||
* @type {string[]}
|
||||
*/
|
||||
services: string[];
|
||||
};
|
||||
}
|
||||
/**
|
||||
* User settings
|
||||
*/
|
||||
export var user: UserSettingType;
|
||||
/**
|
||||
* Application settings
|
||||
*/
|
||||
export var applications: GenericObject<any> = {};
|
||||
/**
|
||||
* Desktop settings
|
||||
*/
|
||||
export var desktop: DesktopSettingType;
|
||||
/**
|
||||
* Appearance settings
|
||||
*/
|
||||
export var appearance: AppearanceSettingType;
|
||||
/**
|
||||
* VFS settings
|
||||
*/
|
||||
export var VFS: VFSSettingType;
|
||||
/**
|
||||
* System settings
|
||||
*/
|
||||
export var system: SystemSettingType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the system settings to default values
|
||||
*
|
||||
* @export
|
||||
*/
|
||||
export function resetSetting(): void {
|
||||
setting.desktop = {
|
||||
path: "home://.desktop",
|
||||
menu: [],
|
||||
showhidden: false,
|
||||
};
|
||||
setting.user = {
|
||||
name: undefined,
|
||||
username: undefined,
|
||||
id: 0,
|
||||
};
|
||||
|
||||
setting.appearance = {
|
||||
theme: "antos_dark",
|
||||
themes: [
|
||||
{
|
||||
text: "AntOS light",
|
||||
name: "antos_light",
|
||||
},
|
||||
{
|
||||
text: "AntOS dark",
|
||||
name: "antos_dark",
|
||||
},
|
||||
],
|
||||
wp: {
|
||||
url: "os://resources/themes/system/wp/wp3.jpg",
|
||||
size: "cover",
|
||||
repeat: "repeat",
|
||||
},
|
||||
wps: [],
|
||||
};
|
||||
|
||||
setting.VFS = {
|
||||
mountpoints: [
|
||||
//TODO: multi app try to write to this object, it neet to be cloned
|
||||
{
|
||||
text: "__(Applications)",
|
||||
path: "app://",
|
||||
iconclass: "fa fa-adn",
|
||||
type: "app",
|
||||
},
|
||||
{
|
||||
text: "__(Home)",
|
||||
path: "home://",
|
||||
iconclass: "fa fa-home",
|
||||
type: "fs",
|
||||
},
|
||||
{
|
||||
text: "__(Desktop)",
|
||||
path: setting.desktop.path,
|
||||
iconclass: "fa fa-desktop",
|
||||
type: "fs",
|
||||
},
|
||||
{
|
||||
text: "__(OS)",
|
||||
path: "os://",
|
||||
iconclass: "fa fa-inbox",
|
||||
type: "fs",
|
||||
},
|
||||
{
|
||||
text: "__(Google Drive)",
|
||||
path: "gdv://",
|
||||
iconclass: "fa fa-inbox",
|
||||
type: "fs",
|
||||
},
|
||||
{
|
||||
text: "__(Shared)",
|
||||
path: "shared://",
|
||||
iconclass: "fa fa-share-square",
|
||||
type: "fs",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
OS.setting.system = {
|
||||
error_report: "https://os.iohub.dev/report",
|
||||
locale: "en_GB",
|
||||
menu: [],
|
||||
packages: {},
|
||||
pkgpaths: {
|
||||
user: "home://.packages",
|
||||
system: "os://packages",
|
||||
},
|
||||
repositories: [],
|
||||
startup: {
|
||||
apps: [],
|
||||
services: ["Syslog/PushNotification", "Syslog/Calendar"],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the input parameter object to system settings.
|
||||
* This object could be an object loaded from
|
||||
* setting JSON file saved on the server.
|
||||
*
|
||||
* @export
|
||||
* @param {*} conf
|
||||
*/
|
||||
export function systemSetting(conf: any) {
|
||||
resetSetting();
|
||||
if (conf.desktop) {
|
||||
setting.desktop = conf.desktop;
|
||||
}
|
||||
if (conf.applications) {
|
||||
setting.applications = conf.applications;
|
||||
}
|
||||
if (conf.appearance) {
|
||||
setting.appearance = conf.appearance;
|
||||
}
|
||||
if (conf.user) {
|
||||
setting.user = conf.user;
|
||||
}
|
||||
if (conf.VFS) {
|
||||
setting.VFS = conf.VFS;
|
||||
}
|
||||
|
||||
if (conf.system) {
|
||||
setting.system = conf.system;
|
||||
}
|
||||
|
||||
if (!setting.VFS.gdrive) {
|
||||
setting.VFS.gdrive = {
|
||||
CLIENT_ID: "",
|
||||
API_KEY: "",
|
||||
apilink: "https://apis.google.com/js/api.js",
|
||||
DISCOVERY_DOCS: [
|
||||
"https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
|
||||
],
|
||||
SCOPES: "https://www.googleapis.com/auth/drive",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Register handle for application search
|
||||
API.onsearch("__(Applications)", function (t) {
|
||||
const ar = [];
|
||||
const term = new RegExp(t, "i");
|
||||
for (let k in setting.system.packages) {
|
||||
const v = setting.system.packages[k];
|
||||
if (v.app) {
|
||||
var e, k1, v1;
|
||||
if (
|
||||
v.name.match(term) ||
|
||||
(v.description && v.description.match(term))
|
||||
) {
|
||||
v1 = {};
|
||||
for (k1 in v) {
|
||||
e = v[k1];
|
||||
if (k1 !== "selected") {
|
||||
v1[k1] = e;
|
||||
}
|
||||
}
|
||||
v1.detail = [{ text: v1.path }];
|
||||
v1.complex = true;
|
||||
ar.push(v1);
|
||||
} else if (v.mimes) {
|
||||
for (let m of v.mimes) {
|
||||
if (t.match(new RegExp(m, "g"))) {
|
||||
v1 = {};
|
||||
for (k1 in v) {
|
||||
e = v[k1];
|
||||
if (k1 !== "selected") {
|
||||
v1[k1] = v[k1];
|
||||
}
|
||||
}
|
||||
v1.detail = [{ text: v1.path }];
|
||||
v1.complex = true;
|
||||
ar.push(v1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ar;
|
||||
});
|
||||
}
|
245
src/core/tags/AppDockTag.ts
Normal file
245
src/core/tags/AppDockTag.ts
Normal file
@ -0,0 +1,245 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
/**
|
||||
*
|
||||
* Interface for an application dock item
|
||||
*
|
||||
* @export
|
||||
* @interface AppDockItemType
|
||||
*/
|
||||
export interface AppDockItemType {
|
||||
/**
|
||||
* Reference to the application process represented
|
||||
* by the dock item
|
||||
*
|
||||
* @type {application.BaseApplication}
|
||||
* @memberof AppDockItemType
|
||||
*/
|
||||
app: application.BaseApplication;
|
||||
|
||||
/**
|
||||
* Reference to the DOM element of
|
||||
* the owner dock item
|
||||
*
|
||||
* @type {AFXTag}
|
||||
* @memberof AppDockItemType
|
||||
*/
|
||||
domel?: AFXTag;
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
export namespace tag {
|
||||
/**
|
||||
* This class define the AntOS system application dock tag
|
||||
*
|
||||
* @export
|
||||
* @class AppDockTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class AppDockTag extends AFXTag {
|
||||
/**
|
||||
* variable holds the application select event
|
||||
* callback handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<any>}
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
private _onappselect: TagEventCallback<any>;
|
||||
|
||||
/**
|
||||
* Items data of the dock
|
||||
*
|
||||
* @private
|
||||
* @type {AppDockItemType[]}
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
private _items: AppDockItemType[];
|
||||
|
||||
/**
|
||||
* Reference to the currently select application
|
||||
* process in the dock
|
||||
*
|
||||
* @private
|
||||
* @type {application.BaseApplication}
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
private _selectedApp: application.BaseApplication;
|
||||
|
||||
/**
|
||||
*Creates an instance of AppDockTag.
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._onappselect = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation of the abstract function: Update the current tag.
|
||||
* It do nothing for this tag
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this._items = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* The tag layout, it is empty on creation but elements will
|
||||
* be added automatically to it in operation
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* getter to get the dock items
|
||||
*
|
||||
* @readonly
|
||||
* @type {AppDockItemType[]}
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
get items(): AppDockItemType[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* set the selected application in the dock
|
||||
* this will trigger two event:
|
||||
* - `focus`: on the selected application
|
||||
* - `blur`: on all other applications on the dock
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the current selected application
|
||||
* on the dock
|
||||
*
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
set selectedApp(v: application.BaseApplication) {
|
||||
this._selectedApp = v;
|
||||
let el = undefined;
|
||||
for (let it of this.items) {
|
||||
it.app.blur();
|
||||
$(it.domel).removeClass();
|
||||
if (v && v === it.app) {
|
||||
el = it.domel;
|
||||
}
|
||||
}
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
$(el).addClass("selected");
|
||||
($(Ant.OS.GUI.workspace)[0] as FloatListTag).unselect();
|
||||
}
|
||||
|
||||
get selectedApp(): application.BaseApplication {
|
||||
return this._selectedApp;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a new application process is created, this function
|
||||
* will be called to add new application entry to the dock.
|
||||
* The added application will becomes the current selected
|
||||
* application
|
||||
*
|
||||
* @param {AppDockItemType} item an application dock item entry
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
newapp(item: AppDockItemType): void {
|
||||
this.items.push(item);
|
||||
const el = $("<afx-button>");
|
||||
const bt = el[0] as ButtonTag;
|
||||
el.appendTo(this);
|
||||
el[0].uify(this.observable);
|
||||
bt.set(item);
|
||||
bt.data = item.app;
|
||||
el.attr("tooltip", `cr:${item.app.title()}`);
|
||||
item.domel = bt;
|
||||
bt.onbtclick = (e) => {
|
||||
e.id = this.aid;
|
||||
//e.data.item = item;
|
||||
this._onappselect(e);
|
||||
item.app.show();
|
||||
};
|
||||
this.selectedApp = item.app;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete and application entry from the dock.
|
||||
* This function will be called when an application
|
||||
* is exit
|
||||
*
|
||||
* @param {BaseApplication} a the application to be removed from the dock
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
removeapp(a: application.BaseApplication): void {
|
||||
let i = -1;
|
||||
const iterable = this.items;
|
||||
for (let k = 0; k < iterable.length; k++) {
|
||||
const v = iterable[k];
|
||||
if (v.app.pid === a.pid) {
|
||||
i = k;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i !== -1) {
|
||||
delete this.items[i].app;
|
||||
this.items.splice(i, 1);
|
||||
$($(this).children()[i]).remove();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the current dock tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof AppDockTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this.contextmenuHandle = (e, m) => {
|
||||
if (e.target === this) {
|
||||
return;
|
||||
}
|
||||
const bt = ($(e.target).closest(
|
||||
"afx-button"
|
||||
)[0] as any) as ButtonTag;
|
||||
const app = bt.data;
|
||||
m.items = [
|
||||
{ text: "__(Show)", dataid: "show" },
|
||||
{ text: "__(Hide)", dataid: "hide" },
|
||||
{ text: "__(Close)", dataid: "quit" },
|
||||
];
|
||||
m.onmenuselect = function (evt) {
|
||||
const item = evt.data.item.data;
|
||||
if (app[item.dataid]) {
|
||||
return app[item.dataid]();
|
||||
}
|
||||
};
|
||||
return m.show(e);
|
||||
};
|
||||
announcer.trigger("sysdockloaded", undefined);
|
||||
}
|
||||
}
|
||||
define("afx-apps-dock", AppDockTag);
|
||||
}
|
||||
}
|
||||
}
|
198
src/core/tags/ButtonTag.ts
Normal file
198
src/core/tags/ButtonTag.ts
Normal file
@ -0,0 +1,198 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* This tag define a basic button and its behavior
|
||||
*
|
||||
* @export
|
||||
* @class ButtonTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class ButtonTag extends AFXTag {
|
||||
/**
|
||||
* Variable hold the button click callback handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<JQuery.MouseEventBase>}
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
private _onbtclick: TagEventCallback<JQuery.MouseEventBase>;
|
||||
|
||||
/**
|
||||
* Custom user data
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
data: GenericObject<any>;
|
||||
|
||||
/**
|
||||
*Creates an instance of ButtonTag.
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the click callback handle for the target button
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set onbtclick(v: TagEventCallback<JQuery.MouseEventBase>) {
|
||||
this._onbtclick = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the path to the button icon, the path should be
|
||||
* a VFS file path
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set icon(v: string) {
|
||||
$(this).attr("icon", v);
|
||||
(this.refs.label as LabelTag).icon = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the icon class to the button, this property
|
||||
* allows to style the button icon using CSS
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set iconclass(v: string) {
|
||||
$(this).attr("iconclass", v);
|
||||
(this.refs.label as LabelTag).iconclass = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the text of the button
|
||||
*
|
||||
* Getter: Get the current button test
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set text(v: string | FormattedString) {
|
||||
(this.refs.label as LabelTag).text = v;
|
||||
}
|
||||
|
||||
get text(): string | FormattedString {
|
||||
return (this.refs.label as LabelTag).text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Enable or disable the button
|
||||
*
|
||||
* Getter: Get the `enable` property of the button
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set enable(v: boolean) {
|
||||
$(this.refs.button).prop("disabled", !v);
|
||||
}
|
||||
get enable(): boolean {
|
||||
return !$(this.refs.button).prop("disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set or remove the attribute `selected` of the button
|
||||
*
|
||||
* Getter: check whether the attribute `selected` of the button is set
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set selected(v: boolean) {
|
||||
$(this.refs.button).removeClass();
|
||||
this.attsw(v, "selected");
|
||||
if (v) {
|
||||
$(this.refs.button).addClass("selected");
|
||||
}
|
||||
}
|
||||
get selected(): boolean {
|
||||
return this.hasattr("selected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: activate or deactivate the toggle mode of the button
|
||||
*
|
||||
* Getter: Check whether the button is in toggle mode
|
||||
*
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
set toggle(v: boolean) {
|
||||
this.attsw(v, "toggle");
|
||||
}
|
||||
get toggle(): boolean {
|
||||
return this.hasattr("toggle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
protected mount() {
|
||||
$(this.refs.button).click((e) => {
|
||||
const evt: TagEventType<JQuery.MouseEventBase> = {
|
||||
id: this.aid,
|
||||
data: e,
|
||||
};
|
||||
this._onbtclick(evt);
|
||||
this.observable.trigger("btclick", evt);
|
||||
if (this.toggle) {
|
||||
return (this.selected = !this.selected);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.enable = true;
|
||||
this.toggle = false;
|
||||
this._onbtclick = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-calibrate the button, do nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Update the current tag, do nothing in this tag
|
||||
*
|
||||
* @param {*} [d]
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Button layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof ButtonTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "Button",
|
||||
ref: "button",
|
||||
children: [{ el: "afx-label", ref: "label" }],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-button", ButtonTag);
|
||||
}
|
||||
}
|
||||
}
|
335
src/core/tags/CalendarTag.ts
Normal file
335
src/core/tags/CalendarTag.ts
Normal file
@ -0,0 +1,335 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Tag that define system calendar widget
|
||||
*
|
||||
* @export
|
||||
* @class CalendarTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class CalendarTag extends AFXTag {
|
||||
/**
|
||||
* The current selected day
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private _day: number;
|
||||
|
||||
/**
|
||||
* The current selected month
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private _month: number;
|
||||
|
||||
/**
|
||||
* The current selected year
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private _year: number;
|
||||
|
||||
/**
|
||||
* The current selected date object
|
||||
*
|
||||
* @private
|
||||
* @type {Date}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private _selectedDate: Date;
|
||||
|
||||
/**
|
||||
* placeholder for date select event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<Date>}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private _ondateselect: TagEventCallback<Date>;
|
||||
|
||||
/**
|
||||
*Creates an instance of CalendarTag.
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._day = 0;
|
||||
this._month = 0;
|
||||
this._year = 0;
|
||||
this._ondateselect = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
protected init(): void {
|
||||
$(this).css("height", "100%");
|
||||
$(this.refs.grid).css("width", "100%");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current tag, doing nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d] any data object
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Get the current selected date in the widget
|
||||
*
|
||||
* @readonly
|
||||
* @type {Date}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
get selectedDate(): Date {
|
||||
return this._selectedDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the date select event callback handle for the widget
|
||||
*
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
set ondateselect(v: TagEventCallback<Date>) {
|
||||
this._ondateselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the current widget to the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.prev).click((e) => this.prevmonth());
|
||||
$(this.refs.next).click((e) => this.nextmonth());
|
||||
const grid = this.refs.grid as GridViewTag;
|
||||
grid.header = [
|
||||
{ text: "__(Sun)" },
|
||||
{ text: "__(Mon)" },
|
||||
{ text: "__(Tue)" },
|
||||
{ text: "__(Wed)" },
|
||||
{ text: "__(Thu)" },
|
||||
{ text: "__(Fri)" },
|
||||
{ text: "__(Sat)" },
|
||||
];
|
||||
grid.oncellselect = (e) => {
|
||||
this.dateselect(e);
|
||||
};
|
||||
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
this.calibrate();
|
||||
this.calendar(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the date select event
|
||||
*
|
||||
* @private
|
||||
* @param {TagEventType} e AFX tag event data [[TagEventType]]
|
||||
* @returns {void}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private dateselect(
|
||||
e: TagEventType<TagEventDataType<tag.GridCellPrototype>>
|
||||
): void {
|
||||
if (!e.data.item) {
|
||||
return;
|
||||
}
|
||||
const value = e.data.item.data.text;
|
||||
if (value === "") {
|
||||
return;
|
||||
}
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: new Date(
|
||||
this._year,
|
||||
this._month,
|
||||
parseInt(value)
|
||||
),
|
||||
};
|
||||
this._ondateselect(evt);
|
||||
this._selectedDate = evt.data;
|
||||
return this.observable.trigger("dateselect", evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the layout of the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
protected calibrate(): void {
|
||||
$(this.refs.grid).css(
|
||||
"height",
|
||||
`${$(this).height() - $(this.refs.ctrl).height()}px`
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the previous month of the current month
|
||||
*
|
||||
* @private
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private prevmonth(): void {
|
||||
this._selectedDate = undefined;
|
||||
this._month--;
|
||||
if (this._month < 0) {
|
||||
this._month = 11;
|
||||
this._year--;
|
||||
}
|
||||
this.calendar(new Date(this._year, this._month, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the next month of the current month
|
||||
*
|
||||
* @private
|
||||
* @returns
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private nextmonth() {
|
||||
this._selectedDate = undefined;
|
||||
this._month++;
|
||||
if (this._month > 11) {
|
||||
this._month = 0;
|
||||
this._year++;
|
||||
}
|
||||
return this.calendar(new Date(this._year, this._month, 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Visualize the calendar base on input date
|
||||
*
|
||||
* @private
|
||||
* @param {Date} date
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
private calendar(date: Date) {
|
||||
let week_day: number;
|
||||
let asc: any, end: any;
|
||||
if (!date) {
|
||||
date = new Date();
|
||||
}
|
||||
this._day = date.getDate();
|
||||
this._month = date.getMonth();
|
||||
this._year = date.getFullYear();
|
||||
|
||||
const now = {
|
||||
d: new Date().getDate(),
|
||||
m: new Date().getMonth(),
|
||||
y: new Date().getFullYear(),
|
||||
};
|
||||
const months = [
|
||||
__("January"),
|
||||
__("February"),
|
||||
__("March"),
|
||||
__("April"),
|
||||
__("May"),
|
||||
__("June"),
|
||||
__("July"),
|
||||
__("August"),
|
||||
__("September"),
|
||||
__("October"),
|
||||
__("November"),
|
||||
__("December"),
|
||||
];
|
||||
const this_month = new Date(this._year, this._month, 1);
|
||||
const next_month = new Date(this._year, this._month + 1, 1);
|
||||
// Find out when this month starts and ends.
|
||||
const first_week_day = this_month.getDay();
|
||||
const days_in_this_month = Math.round(
|
||||
(next_month.getTime() - this_month.getTime()) /
|
||||
(1000 * 60 * 60 * 24)
|
||||
);
|
||||
//self.mtext = months[self.month]
|
||||
const rows = [];
|
||||
let row = [];
|
||||
// Fill the first week of the month with the appropriate number of blanks.
|
||||
for (
|
||||
week_day = 0, end = first_week_day - 1, asc = 0 <= end;
|
||||
asc ? week_day <= end : week_day >= end;
|
||||
asc ? week_day++ : week_day--
|
||||
) {
|
||||
row.push({ text: "" });
|
||||
}
|
||||
week_day = first_week_day;
|
||||
for (
|
||||
let day_counter = 1,
|
||||
end1 = days_in_this_month,
|
||||
asc1 = 1 <= end1;
|
||||
asc1 ? day_counter <= end1 : day_counter >= end1;
|
||||
asc1 ? day_counter++ : day_counter--
|
||||
) {
|
||||
week_day %= 7;
|
||||
if (week_day === 0) {
|
||||
rows.push(row);
|
||||
row = [];
|
||||
}
|
||||
// Do something different for the current day.
|
||||
if (
|
||||
now.d === day_counter &&
|
||||
this._month === now.m &&
|
||||
this._year === now.y
|
||||
) {
|
||||
row.push({ text: day_counter, selected: true });
|
||||
} else {
|
||||
row.push({ text: day_counter });
|
||||
}
|
||||
week_day++;
|
||||
}
|
||||
for (
|
||||
let i = 0, end2 = 7 - row.length, asc2 = 0 <= end2;
|
||||
asc2 ? i <= end2 : i >= end2;
|
||||
asc2 ? i++ : i--
|
||||
) {
|
||||
row.push({ text: "" });
|
||||
}
|
||||
rows.push(row);
|
||||
const grid = this.refs.grid as GridViewTag;
|
||||
grid.rows = rows;
|
||||
(this.refs.mlbl as LabelTag).text = `${
|
||||
months[this._month]
|
||||
} ${this._year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the widget
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof CalendarTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
ref: "ctrl",
|
||||
children: [
|
||||
{ el: "i", class: "prevmonth", ref: "prev" },
|
||||
{ el: "afx-label", ref: "mlbl" },
|
||||
{ el: "i", class: "nextmonth", ref: "next" },
|
||||
],
|
||||
},
|
||||
{ el: "afx-grid-view", ref: "grid" },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-calendar-view", CalendarTag);
|
||||
}
|
||||
}
|
||||
}
|
327
src/core/tags/ColorPickerTag.ts
Normal file
327
src/core/tags/ColorPickerTag.ts
Normal file
@ -0,0 +1,327 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
/**
|
||||
* Color type used by AFX API
|
||||
*
|
||||
* @export
|
||||
* @interface ColorType
|
||||
*/
|
||||
export interface ColorType {
|
||||
/**
|
||||
* Red chanel
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ColorType
|
||||
*/
|
||||
r: number;
|
||||
|
||||
/**
|
||||
* Green chanel
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ColorType
|
||||
*/
|
||||
g: number;
|
||||
|
||||
/**
|
||||
* Blue chanel
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ColorType
|
||||
*/
|
||||
b: number;
|
||||
|
||||
/**
|
||||
* Alpha chanel
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof ColorType
|
||||
*/
|
||||
a?: number;
|
||||
|
||||
/**
|
||||
* color text in CSS format
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ColorType
|
||||
*/
|
||||
text?: string;
|
||||
|
||||
/**
|
||||
* Color in hex format
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof ColorType
|
||||
*/
|
||||
hex?: string;
|
||||
}
|
||||
export namespace tag {
|
||||
/**
|
||||
* Class definition of Color picker widget
|
||||
*
|
||||
* @export
|
||||
* @class ColorPickerTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class ColorPickerTag extends AFXTag {
|
||||
/**
|
||||
* The current selected color object
|
||||
*
|
||||
* @private
|
||||
* @type {ColorType}
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
private _selectedColor: ColorType;
|
||||
|
||||
/**
|
||||
* placeholder for the color select event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<ColorType>}
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
private _oncolorselect: TagEventCallback<ColorType>;
|
||||
|
||||
/**
|
||||
* Creates an instance of ColorPickerTag.
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._oncolorselect = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init tag before mounting, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
protected init(): void {}
|
||||
|
||||
/**
|
||||
* Reload tag, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Get selected color value
|
||||
*
|
||||
* @readonly
|
||||
* @type {ColorType}
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
get selectedColor(): ColorType {
|
||||
return this._selectedColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the color select event handle
|
||||
*
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
set oncolorselect(v: TagEventCallback<ColorType>) {
|
||||
this._oncolorselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the widget to DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.wrapper)
|
||||
.css("width", "310px")
|
||||
.css("height", "190px")
|
||||
.css("display", "block")
|
||||
.css("padding", "3px");
|
||||
$(this.refs.palette)
|
||||
.css("width", "284px")
|
||||
.css("height", "155px")
|
||||
.css("float", "left");
|
||||
$(this.refs.colorval)
|
||||
.css("width", "15px")
|
||||
.css("height", "155px")
|
||||
.css("text-align", "center")
|
||||
.css("margin-left", "3px")
|
||||
.css("display", "block")
|
||||
.css("float", "left");
|
||||
|
||||
$(this.refs.inputwrp).css("margin-top", "3px");
|
||||
|
||||
$(this.refs.hextext)
|
||||
.css("width", "70px")
|
||||
.css("margin-left", "3px")
|
||||
.css("margin-right", "5px");
|
||||
|
||||
this.build_palette();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the color palette
|
||||
*
|
||||
* @private
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
private build_palette(): void {
|
||||
const colorctx = ($(this.refs.palette).get(
|
||||
0
|
||||
) as HTMLCanvasElement).getContext("2d");
|
||||
let gradient = colorctx.createLinearGradient(
|
||||
0,
|
||||
0,
|
||||
$(this.refs.palette).width(),
|
||||
0
|
||||
);
|
||||
// fill color
|
||||
gradient.addColorStop(0, "rgb(255, 0, 0)");
|
||||
gradient.addColorStop(0.15, "rgb(255, 0, 255)");
|
||||
gradient.addColorStop(0.33, "rgb(0, 0, 255)");
|
||||
gradient.addColorStop(0.49, "rgb(0, 255, 255)");
|
||||
gradient.addColorStop(0.67, "rgb(0, 255, 0)");
|
||||
gradient.addColorStop(0.84, "rgb(255, 255, 0)");
|
||||
gradient.addColorStop(1, "rgb(255, 0, 0)");
|
||||
gradient.addColorStop(0, "rgb(0, 0, 0)");
|
||||
// Apply gradient to canvas
|
||||
colorctx.fillStyle = gradient;
|
||||
colorctx.fillRect(
|
||||
0,
|
||||
0,
|
||||
colorctx.canvas.width,
|
||||
colorctx.canvas.height
|
||||
);
|
||||
|
||||
// Create semi transparent gradient (white -> trans. -> black)
|
||||
gradient = colorctx.createLinearGradient(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
$(this.refs.palette).width()
|
||||
);
|
||||
gradient.addColorStop(0, "rgba(255, 255, 255, 1)");
|
||||
gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)");
|
||||
gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)");
|
||||
gradient.addColorStop(1, "rgba(0, 0, 0, 1)");
|
||||
// Apply gradient to canvas
|
||||
colorctx.fillStyle = gradient;
|
||||
colorctx.fillRect(
|
||||
0,
|
||||
0,
|
||||
colorctx.canvas.width,
|
||||
colorctx.canvas.height
|
||||
);
|
||||
// now add mouse move event
|
||||
const getHex = function (c) {
|
||||
let s = c.toString(16);
|
||||
if (s.length === 1) {
|
||||
s = "0" + s;
|
||||
}
|
||||
return s;
|
||||
};
|
||||
|
||||
const pick_color = (e) => {
|
||||
$(this.refs.palette).css("cursor", "crosshair");
|
||||
const offset = $(this.refs.palette).offset();
|
||||
const x = e.pageX - offset.left;
|
||||
const y = e.pageY - offset.top;
|
||||
const color = colorctx.getImageData(x, y, 1, 1);
|
||||
const data: ColorType = {
|
||||
r: color.data[0],
|
||||
g: color.data[1],
|
||||
b: color.data[2],
|
||||
text:
|
||||
"rgb(" +
|
||||
color.data[0] +
|
||||
", " +
|
||||
color.data[1] +
|
||||
", " +
|
||||
color.data[2] +
|
||||
")",
|
||||
hex:
|
||||
"#" +
|
||||
getHex(color.data[0]) +
|
||||
getHex(color.data[1]) +
|
||||
getHex(color.data[2]),
|
||||
};
|
||||
return data;
|
||||
};
|
||||
|
||||
const mouse_move_h = (e) => {
|
||||
const data = pick_color(e);
|
||||
return $(this.refs.colorval).css(
|
||||
"background-color",
|
||||
data.text
|
||||
);
|
||||
};
|
||||
|
||||
$(this.refs.palette).mouseenter((e) => {
|
||||
return $(this.refs.palette).on(
|
||||
"mousemove",
|
||||
mouse_move_h
|
||||
);
|
||||
});
|
||||
|
||||
$(this.refs.palette).mouseout((e) => {
|
||||
$(this.refs.palette).unbind("mousemove", mouse_move_h);
|
||||
if (this.selectedColor) {
|
||||
return $(this.refs.colorval).css(
|
||||
"background-color",
|
||||
this.selectedColor.text
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
$(this.refs.palette).on("click", (e) => {
|
||||
const data = pick_color(e);
|
||||
$(this.refs.rgbtext).html(data.text);
|
||||
$(this.refs.hextext).val(data.hex);
|
||||
this._selectedColor = data;
|
||||
const evt = { id: this.aid, data };
|
||||
this._oncolorselect(evt);
|
||||
return this.observable.trigger("colorselect", data);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* layout definition of the widget
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof ColorPickerTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
ref: "wrapper",
|
||||
children: [
|
||||
{
|
||||
el: "canvas",
|
||||
class: "color-palette",
|
||||
ref: "palette",
|
||||
},
|
||||
{ el: "color-sample", ref: "colorval" },
|
||||
{ el: "div", class: "afx-clear" },
|
||||
{
|
||||
el: "div",
|
||||
ref: "inputwrp",
|
||||
children: [
|
||||
{ el: "input", ref: "hextext" },
|
||||
{ el: "span", ref: "rgbtext" },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-color-picker", ColorPickerTag);
|
||||
}
|
||||
}
|
||||
}
|
641
src/core/tags/FileViewTag.ts
Normal file
641
src/core/tags/FileViewTag.ts
Normal file
@ -0,0 +1,641 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Definition of system file view widget
|
||||
*
|
||||
* @export
|
||||
* @class FileViewTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class FileViewTag extends AFXTag {
|
||||
/**
|
||||
* placeholder for file select event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<API.FileInfoType>}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _onfileselect: TagEventCallback<API.FileInfoType>;
|
||||
|
||||
/**
|
||||
* placeholder for file open event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<API.FileInfoType>}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _onfileopen: TagEventCallback<API.FileInfoType>;
|
||||
|
||||
/**
|
||||
* Reference to the currently selected file meta-data
|
||||
*
|
||||
* @private
|
||||
* @type {API.FileInfoType}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _selectedFile: API.FileInfoType;
|
||||
|
||||
/**
|
||||
* Data placeholder of the current working directory
|
||||
*
|
||||
* @private
|
||||
* @type {API.FileInfoType[]}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _data: API.FileInfoType[];
|
||||
|
||||
/**
|
||||
* The path of the current working directory
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _path: string;
|
||||
|
||||
/**
|
||||
* Header definition of the widget grid view
|
||||
*
|
||||
* @private
|
||||
* @type {(GenericObject<string | number>[])}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _header: GenericObject<string | number>[];
|
||||
|
||||
/**
|
||||
* placeholder for the user-specified meta-data fetch function
|
||||
*
|
||||
* @private
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private _fetch: (p: string) => Promise<API.FileInfoType[]>;
|
||||
|
||||
/**
|
||||
*Creates an instance of FileViewTag.
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the widget before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.data = [];
|
||||
this.status = true;
|
||||
this.showhidden = false;
|
||||
this.chdir = true;
|
||||
this.view = "list";
|
||||
this._onfileopen = this._onfileselect = (e) => {};
|
||||
this._header = [
|
||||
{ text: "__(File name)" },
|
||||
{ text: "__(Type)", width: 150 },
|
||||
{ text: "__(Size)", width: 70 },
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current widget, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* set the function that allows to fetch file entries.
|
||||
* This handle function should return a promise on
|
||||
* an arry of [[API.FileInfoType]]
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set fetch(v: (p: string) => Promise<API.FileInfoType[]>) {
|
||||
this._fetch = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the callback handle for the file select event.
|
||||
* The parameter of the callback should be an object
|
||||
* of type [[TagEventType]]<T> with the data type `T` is [[API.FileInfoType]]
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set onfileselect(e: TagEventCallback<API.FileInfoType>) {
|
||||
this._onfileselect = e;
|
||||
}
|
||||
|
||||
/**
|
||||
set the callback handle for the file open event.
|
||||
* The parameter of the callback should be an object
|
||||
* of type [[TagEventType]]<T> with the data type `T` is [[API.FileInfoType]]
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set onfileopen(e: TagEventCallback<API.FileInfoType>) {
|
||||
this._onfileopen = e;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* chang the view of the widget, there are three different views
|
||||
* - `icon`
|
||||
* - `list`
|
||||
* - `tree`
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the current view setting of the widget
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set view(v: string) {
|
||||
$(this).attr("view", v);
|
||||
this.switchView();
|
||||
}
|
||||
get view(): string {
|
||||
return $(this).attr("view");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Turn on/off the changing current working directory feature
|
||||
* of the widget when a directory is double clicked. If enabled,
|
||||
* the widget will use the configured [[fetch]] function to query
|
||||
* the content of the selected directory
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* check whether changing current working directory feature
|
||||
* is enabled
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set chdir(v: boolean) {
|
||||
this.attsw(v, "chdir");
|
||||
}
|
||||
get chdir(): boolean {
|
||||
return this.hasattr("chdir");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter : Enable or disable the status bar of the widget
|
||||
*
|
||||
* Getter: Check whether the status bar is enabled
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set status(v: boolean) {
|
||||
this.attsw(v, "status");
|
||||
if (v) {
|
||||
$(this.refs.status).show();
|
||||
return;
|
||||
}
|
||||
$(this.refs.status).hide();
|
||||
}
|
||||
get status(): boolean {
|
||||
return this.hasattr("status");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Allow the widget to show or hide hidden file
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the hidden file should be shown in
|
||||
* the widget
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set showhidden(v: boolean) {
|
||||
this.attsw(v, "showhidden");
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
this.switchView();
|
||||
}
|
||||
get showhidden(): boolean {
|
||||
return this.hasattr("showhidden");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current selected file
|
||||
*
|
||||
* @readonly
|
||||
* @type {API.FileInfoType}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
get selectedFile(): API.FileInfoType {
|
||||
return this._selectedFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the path of the current working directory.
|
||||
* When called the widget will refresh the current
|
||||
* working directory using the configured [[fetch]]
|
||||
* function
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the path of the current working directory
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set path(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this._path = v;
|
||||
if (!this._fetch) {
|
||||
return;
|
||||
}
|
||||
this._fetch(v)
|
||||
.then((data: API.FileInfoType[]) => {
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
this.data = data;
|
||||
if (this.status) {
|
||||
(this.refs.status as LabelTag).text = " ";
|
||||
}
|
||||
})
|
||||
.catch((e: Error) =>
|
||||
announcer.oserror(e.toString(), e)
|
||||
);
|
||||
}
|
||||
get path(): string {
|
||||
return this._path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the data of the current working directory
|
||||
*
|
||||
* Getter: Get the data of the current working directory
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set data(v: API.FileInfoType[]) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this._data = v;
|
||||
this.refreshData();
|
||||
}
|
||||
get data(): API.FileInfoType[] {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file drag and drop event handle. This allows application
|
||||
* to define custom behavior of the event
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
set ondragndrop(
|
||||
v: TagEventCallback<
|
||||
DnDEventDataType<TreeViewTag | ListViewItemTag>
|
||||
>
|
||||
) {
|
||||
(this.refs.treeview as TreeViewTag).ondragndrop = v;
|
||||
(this.refs.listview as ListViewTag).ondragndrop = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* sort file by it type
|
||||
*
|
||||
* @private
|
||||
* @param {API.FileInfoType} a first file meta-data
|
||||
* @param {API.FileInfoType} b second file meta-data
|
||||
* @returns {(0|-1|1)}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private sortByType(
|
||||
a: API.FileInfoType,
|
||||
b: API.FileInfoType
|
||||
): 0 | -1 | 1 {
|
||||
if (a.type < b.type) {
|
||||
return -1;
|
||||
} else if (a.type > b.type) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* calibrate the widget layout
|
||||
*
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
let h = $(this).outerHeight();
|
||||
const w = $(this).width();
|
||||
if (this.status) {
|
||||
h -= $(this.refs.status).height() + 10;
|
||||
}
|
||||
$(this.refs.listview).css("height", h + "px");
|
||||
$(this.refs.gridview).css("height", h + "px");
|
||||
$(this.refs.treecontainer).css("height", h + "px");
|
||||
$(this.refs.listview).css("width", w + "px");
|
||||
$(this.refs.gridview).css("width", w + "px");
|
||||
$(this.refs.treecontainer).css("width", w + "px");
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the list view of the widget. This function
|
||||
* is called when the view of the widget changed to `icon`
|
||||
*
|
||||
* @private
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private refreshList(): void {
|
||||
const items = [];
|
||||
$.each(this.data, (i, v) => {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
items.push(v);
|
||||
});
|
||||
(this.refs.listview as ListViewTag).data = items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the grid view of the widget, this function is called
|
||||
* when the view of the widget set to `list`
|
||||
*
|
||||
* @private
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private refreshGrid(): void {
|
||||
const rows = [];
|
||||
$.each(this.data, (i, v) => {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return;
|
||||
}
|
||||
v.text = v.filename;
|
||||
v.iconclass = v.iconclass ? v.iconclass : v.type;
|
||||
const row = [
|
||||
v,
|
||||
{
|
||||
text: v.mime,
|
||||
data: v,
|
||||
},
|
||||
{
|
||||
text: v.size,
|
||||
data: v,
|
||||
},
|
||||
];
|
||||
return rows.push(row);
|
||||
});
|
||||
(this.refs.gridview as GridViewTag).rows = rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the Treeview of the widget, this function is called
|
||||
* when the view of the widget set to `tree`
|
||||
*
|
||||
* @private
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private refreshTree(): void {
|
||||
//@treeview.root.set("selectedItem", null)
|
||||
const tdata: TreeViewDataType = {
|
||||
text: this.path,
|
||||
path: this.path,
|
||||
open: true,
|
||||
nodes: this.getTreeData(this.data),
|
||||
};
|
||||
(this.refs.treeview as TreeViewTag).data = tdata;
|
||||
// (this.refs.treeview as TreeViewTag).expandAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the tree data from the list of input
|
||||
* file meta-data
|
||||
*
|
||||
* @private
|
||||
* @param {API.FileInfoType[]} data list of file meta-data
|
||||
* @returns {TreeViewDataType[]}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private getTreeData(
|
||||
data: API.FileInfoType[]
|
||||
): TreeViewDataType[] {
|
||||
const nodes = [];
|
||||
const me = this;
|
||||
$.each(data, (i, v) => {
|
||||
if (v.filename[0] === "." && !this.showhidden) {
|
||||
return undefined;
|
||||
}
|
||||
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;
|
||||
return nodes.push(v);
|
||||
});
|
||||
return nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh data of the current widget view
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private refreshData(): void {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
this.data.sort(this.sortByType);
|
||||
switch (this.view) {
|
||||
case "icon":
|
||||
return this.refreshList();
|
||||
case "list":
|
||||
return this.refreshGrid();
|
||||
default:
|
||||
return this.refreshTree();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch between three view options
|
||||
*
|
||||
* @private
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private switchView(): void {
|
||||
$(this.refs.listview).hide();
|
||||
$(this.refs.gridview).hide();
|
||||
$(this.refs.treecontainer).hide();
|
||||
this._selectedFile = undefined;
|
||||
switch (this.view) {
|
||||
case "icon":
|
||||
$(this.refs.listview).show();
|
||||
break;
|
||||
case "list":
|
||||
$(this.refs.gridview).show();
|
||||
break;
|
||||
default:
|
||||
$(this.refs.treecontainer).show();
|
||||
}
|
||||
this.refreshData();
|
||||
this.calibrate();
|
||||
if (this.status) {
|
||||
(this.refs.status as LabelTag).text = " ";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the file select event
|
||||
*
|
||||
* @private
|
||||
* @param {API.FileInfoType} e selected file meta-data
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private fileselect(e: API.FileInfoType): void {
|
||||
if (e.path === this.path) {
|
||||
e.type = "dir";
|
||||
e.mime = "dir";
|
||||
}
|
||||
if (this.status) {
|
||||
(this.refs.status as LabelTag).text = __(
|
||||
"Selected: {0} ({1} bytes)",
|
||||
e.filename,
|
||||
e.size ? e.size : "0"
|
||||
);
|
||||
}
|
||||
const evt = { id: this.aid, data: e };
|
||||
this._selectedFile = e;
|
||||
this._onfileselect(evt);
|
||||
this.observable.trigger("fileselect", evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the file open event
|
||||
*
|
||||
* @private
|
||||
* @param {API.FileInfoType} e selected file meta-data
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
private filedbclick(e: API.FileInfoType): void {
|
||||
if (e.path === this.path) {
|
||||
e.type = "dir";
|
||||
e.mime = "dir";
|
||||
}
|
||||
if (e.type === "dir" && this.chdir) {
|
||||
this.path = e.path;
|
||||
} else {
|
||||
const evt = { id: this.aid, data: e };
|
||||
this._onfileopen(evt);
|
||||
this.observable.trigger("fileopen", evt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the widget in the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
const tree = this.refs.treeview as TreeViewTag;
|
||||
tree.fetch = (v) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this._fetch) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
if (!v.data.path) {
|
||||
return resolve(undefined);
|
||||
}
|
||||
return this._fetch(v.data.path)
|
||||
.then((d: API.FileInfoType[]) =>
|
||||
resolve(
|
||||
this.getTreeData(
|
||||
d.sort(this.sortByType)
|
||||
)
|
||||
)
|
||||
)
|
||||
.catch((e: Error) => reject(__e(e)));
|
||||
});
|
||||
};
|
||||
const grid = this.refs.gridview as GridViewTag;
|
||||
const list = this.refs.listview as ListViewTag;
|
||||
grid.header = this._header;
|
||||
tree.dragndrop = true;
|
||||
list.dragndrop = true;
|
||||
// even handles
|
||||
list.onlistselect = (e) => {
|
||||
this.fileselect(e.data.item.data as API.FileInfoType);
|
||||
};
|
||||
grid.onrowselect = (e) => {
|
||||
this.fileselect(
|
||||
$(e.data.item).children()[0]
|
||||
.data as API.FileInfoType
|
||||
);
|
||||
};
|
||||
tree.ontreeselect = (e) => {
|
||||
this.fileselect(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);
|
||||
};
|
||||
tree.ontreedbclick = (e) => {
|
||||
this.filedbclick(e.data.item.data as API.FileInfoType);
|
||||
};
|
||||
this.switchView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the widget
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof FileViewTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{ el: "afx-list-view", ref: "listview" },
|
||||
{
|
||||
el: "div",
|
||||
class: "treecontainer",
|
||||
ref: "treecontainer",
|
||||
children: [
|
||||
{ el: "afx-tree-view", ref: "treeview" },
|
||||
],
|
||||
},
|
||||
{ el: "afx-grid-view", ref: "gridview" },
|
||||
{ el: "afx-label", class: "status", ref: "status" },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-file-view", FileViewTag);
|
||||
}
|
||||
}
|
||||
}
|
238
src/core/tags/FloatListTag.ts
Normal file
238
src/core/tags/FloatListTag.ts
Normal file
@ -0,0 +1,238 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A float list is a list of items in which each
|
||||
* item can be moved (drag and drop) freely
|
||||
*
|
||||
* @export
|
||||
* @class FloatListTag
|
||||
* @extends {ListViewTag}
|
||||
*/
|
||||
export class FloatListTag extends ListViewTag {
|
||||
/**
|
||||
* Update the current tag, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Variable that hold the onready callback of
|
||||
* the tag. This callback will be called after
|
||||
* the tag is mounted
|
||||
*
|
||||
* @private
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
private _onready: (e: FloatListTag) => void;
|
||||
|
||||
/**
|
||||
*Creates an instance of FloatListTag.
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* set the onready callback function to the tag.
|
||||
* This callback will be called after
|
||||
* the tag is mounted
|
||||
*
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
set onready(v: (e: FloatListTag) => void) {
|
||||
this._onready = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the direction of the list item layout.
|
||||
* Two directions are available:
|
||||
* - `vertical`
|
||||
* - `horizontal`
|
||||
*
|
||||
* This setter acts as a DOM attribute
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the currently set direction of list
|
||||
* item layout
|
||||
*
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
set dir(v: string) {
|
||||
$(this).attr("dir", v);
|
||||
this.calibrate();
|
||||
}
|
||||
get dir(): string {
|
||||
return $(this).attr("dir");
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the dropdown option in this list
|
||||
*
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
set dropdown(v: boolean) {}
|
||||
|
||||
/**
|
||||
* Disable the list buttons configuration in this
|
||||
* list
|
||||
*
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
set buttons(v: GenericObject<any>[]) {}
|
||||
|
||||
/**
|
||||
* Disable the `showlist` behavior in this list
|
||||
*
|
||||
* @protected
|
||||
* @param {*} e
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
protected showlist(e: any) {}
|
||||
|
||||
/**
|
||||
* Disable the `dropoff` behavior in this list
|
||||
*
|
||||
* @protected
|
||||
* @param {*} e
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
protected dropoff(e: any) {}
|
||||
|
||||
/**
|
||||
* Function called when the data of the list
|
||||
* is changed
|
||||
*
|
||||
* @protected
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the list to the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.container)
|
||||
.css("width", "100%")
|
||||
.css("height", "100%");
|
||||
$(this.refs.mlist)
|
||||
.css("position", "absolute")
|
||||
.css("display", "block")
|
||||
.css("width", "100%");
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
if (this._onready) {
|
||||
return this._onready(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Push an element to the list
|
||||
*
|
||||
* @param {GenericObject<any>} v an element data
|
||||
* @returns
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
push(v: GenericObject<any>) {
|
||||
const el = super.push(v);
|
||||
this.enable_drag(el);
|
||||
return el;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable drag and drop on the list
|
||||
*
|
||||
* @private
|
||||
* @param {ListViewItemTag} el the list item DOM element
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
private enable_drag(el: ListViewItemTag): void {
|
||||
$(el)
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default")
|
||||
.css("display", "block")
|
||||
.css("position", "absolute")
|
||||
.on("mousedown", (evt) => {
|
||||
const globalof = $(this.refs.mlist).offset();
|
||||
evt.preventDefault();
|
||||
const offset = $(el).offset();
|
||||
offset.top = evt.clientY - offset.top;
|
||||
offset.left = evt.clientX - offset.left;
|
||||
const mouse_move = function (
|
||||
e: JQuery.MouseEventBase
|
||||
) {
|
||||
let top = e.clientY - offset.top - globalof.top;
|
||||
let left =
|
||||
e.clientX - globalof.left - offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
top = top < 0 ? 0 : top;
|
||||
return $(el)
|
||||
.css("top", `${top}px`)
|
||||
.css("left", `${left}px`);
|
||||
};
|
||||
|
||||
var mouse_up = function (e: JQuery.MouseEventBase) {
|
||||
$(window).unbind("mousemove", mouse_move);
|
||||
return $(window).unbind("mouseup", mouse_up);
|
||||
};
|
||||
$(window).on("mousemove", mouse_move);
|
||||
return $(window).on("mouseup", mouse_up);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the view of the list
|
||||
*
|
||||
* @memberof FloatListTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
let ctop = 20;
|
||||
let cleft = 20;
|
||||
$(this.refs.mlist).css(
|
||||
"height",
|
||||
`${$(this.refs.container).height()}px`
|
||||
);
|
||||
const gw = $(this.refs.mlist).width();
|
||||
const gh = $(this.refs.mlist).height();
|
||||
|
||||
$(this.refs.mlist)
|
||||
.children()
|
||||
.each((i, e) => {
|
||||
$(e)
|
||||
.css("top", `${ctop}px`)
|
||||
.css("left", `${cleft}px`);
|
||||
const w = $(e).width();
|
||||
const h = $(e).height();
|
||||
if (this.dir === "vertical") {
|
||||
ctop += h + 20;
|
||||
if (ctop > gh) {
|
||||
ctop = 20;
|
||||
cleft += w + 20;
|
||||
}
|
||||
} else {
|
||||
cleft += w + 20;
|
||||
if (cleft > gw) {
|
||||
cleft = 20;
|
||||
ctop += h + 20;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-float-list", FloatListTag);
|
||||
}
|
||||
}
|
||||
}
|
896
src/core/tags/GridViewTag.ts
Normal file
896
src/core/tags/GridViewTag.ts
Normal file
@ -0,0 +1,896 @@
|
||||
/**
|
||||
* Extend the Array interface with some
|
||||
* property needed by AFX API
|
||||
*
|
||||
* @interface Array
|
||||
* @template T
|
||||
*/
|
||||
interface Array<T> {
|
||||
/**
|
||||
* Reference to a DOM element created by AFX API,
|
||||
* this property is used by some AFX tags to refer
|
||||
* to its child element in it data object
|
||||
*
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof Array
|
||||
*/
|
||||
domel?: GenericObject<any>;
|
||||
}
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A grid Row is a simple element that
|
||||
* contains a group of grid cell
|
||||
*
|
||||
* @export
|
||||
* @class GridRowTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class GridRowTag extends AFXTag {
|
||||
/**
|
||||
* Data placeholder for a collection of cell data
|
||||
*
|
||||
* @type {GenericObject<any>[]}
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
data: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
*Creates an instance of GridRowTag.
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.refs.yield = this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag, do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected mount(): void {}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting: reset the data placeholder
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.data = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty layout
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* This function does nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof GridRowTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event data used by grid cell
|
||||
*/
|
||||
export type CellEventData = TagEventDataType<GridCellPrototype>;
|
||||
|
||||
/**
|
||||
* Prototype of any grid cell, custom grid cell
|
||||
* definition should extend and implement this
|
||||
* abstract prototype
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class GridCellPrototype
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export abstract class GridCellPrototype extends AFXTag {
|
||||
/**
|
||||
* placeholder for cell selected event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<CellEventData>}
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
private _oncellselect: TagEventCallback<CellEventData>;
|
||||
|
||||
/**
|
||||
* placeholder for cell double click event callback
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<CellEventData>}
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
private _oncelldbclick: TagEventCallback<CellEventData>;
|
||||
|
||||
/**
|
||||
* Data placeholder of the current cell
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
private _data: GenericObject<any>;
|
||||
|
||||
/**
|
||||
*Creates an instance of GridCellPrototype.
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cell selected event callback
|
||||
*
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
set oncellselect(v: TagEventCallback<CellEventData>) {
|
||||
this._oncellselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the cell double click event callback
|
||||
*
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
set oncelldbclick(v: TagEventCallback<CellEventData>) {
|
||||
this._oncelldbclick = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the data of the cell, this will trigger
|
||||
* the [[ondatachange]] function
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the current cell data placeholder
|
||||
*
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
set data(v: GenericObject<any>) {
|
||||
if (!v) return;
|
||||
this._data = v;
|
||||
this.ondatachange();
|
||||
if (!v.selected) {
|
||||
return;
|
||||
}
|
||||
this.selected = v.selected;
|
||||
}
|
||||
get data(): GenericObject<any> {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set/unset the current cell as selected.
|
||||
* This will trigger the [[cellselect]]
|
||||
* event
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the current cell is selected
|
||||
*
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
set selected(v: boolean) {
|
||||
this.attsw(v, "selected");
|
||||
if (this._data) this._data.selected = v;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.cellselect({ id: this.aid, data: this }, false);
|
||||
}
|
||||
get selected(): boolean {
|
||||
return this.hasattr("selected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current cell. This will
|
||||
* reset the cell data
|
||||
*
|
||||
* @protected
|
||||
* @param {*} d
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
protected reload(d: any): void {
|
||||
this.data = this.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the current cell to the grid
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this).attr("class", "afx-grid-cell");
|
||||
this.oncelldbclick = this.oncellselect = (
|
||||
e: TagEventType<GridCellPrototype>
|
||||
): void => {};
|
||||
this.selected = false;
|
||||
$(this).css("display", "block");
|
||||
$(this).click((e) => {
|
||||
let evt = { id: this.aid, data: this };
|
||||
return this.cellselect(evt, false);
|
||||
});
|
||||
$(this).dblclick((e) => {
|
||||
let evt = { id: this.aid, data: this };
|
||||
return this.cellselect(evt, true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the cell select
|
||||
* event
|
||||
*
|
||||
* @private
|
||||
* @param {TagEventType<GridCellPrototype>} e
|
||||
* @param {boolean} flag
|
||||
* @returns {void}
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
private cellselect(
|
||||
e: TagEventType<GridCellPrototype>,
|
||||
flag: boolean
|
||||
): void {
|
||||
const evt = { id: this.aid, data: { item: e.data } };
|
||||
if (!flag) {
|
||||
return this._oncellselect(evt);
|
||||
}
|
||||
return this._oncelldbclick(evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract function called when the cell data changed.
|
||||
* This should be implemented by subclasses
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @memberof GridCellPrototype
|
||||
*/
|
||||
protected abstract ondatachange(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple grid cell defines a grid cell with
|
||||
* an [[LabelTag]] as it cell layout
|
||||
*
|
||||
* @export
|
||||
* @class SimpleGridCellTag
|
||||
* @extends {GridCellPrototype}
|
||||
*/
|
||||
export class SimpleGridCellTag extends GridCellPrototype {
|
||||
/**
|
||||
*Creates an instance of SimpleGridCellTag.
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the label of the cell with its data
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
(this.refs.cell as LabelTag).set(this.data);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function do nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
protected init(): void {}
|
||||
|
||||
/**
|
||||
* This function do nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* The layout of the cell with a simple [[LabelTag]]
|
||||
*
|
||||
* @returns
|
||||
* @memberof SimpleGridCellTag
|
||||
*/
|
||||
layout() {
|
||||
return [
|
||||
{
|
||||
el: "afx-label",
|
||||
ref: "cell",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A Grid contains a header and a collection grid rows
|
||||
* which has the same number of cells as the number of
|
||||
* the header elements
|
||||
*
|
||||
* @export
|
||||
* @class GridViewTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class GridViewTag extends AFXTag {
|
||||
/**
|
||||
* Grid header definition
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>[]}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _header: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
* Grid rows data placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>[][]}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _rows: GenericObject<any>[][];
|
||||
|
||||
/**
|
||||
* Reference to the current selected row DOM element
|
||||
*
|
||||
* @private
|
||||
* @type {GridRowTag}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _selectedRow: GridRowTag;
|
||||
|
||||
/**
|
||||
* A collection of selected grid rows DOM element
|
||||
*
|
||||
* @private
|
||||
* @type {GridRowTag[]}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _selectedRows: GridRowTag[];
|
||||
|
||||
/**
|
||||
* Reference to the current selected cell
|
||||
*
|
||||
* @private
|
||||
* @type {GridCellPrototype}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _selectedCell: GridCellPrototype;
|
||||
|
||||
/**
|
||||
* Cell select event callback placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<CellEventData>}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _oncellselect: TagEventCallback<CellEventData>;
|
||||
|
||||
/**
|
||||
* Row select event callback placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<CellEventData>}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _onrowselect: TagEventCallback<CellEventData>;
|
||||
|
||||
/**
|
||||
* Cell double click event callback placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<CellEventData>}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private _oncelldbclick: TagEventCallback<CellEventData>;
|
||||
|
||||
/**
|
||||
* Creates an instance of GridViewTag.
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the grid view before mounting.
|
||||
* Reset all the placeholders to default values
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this._header = [];
|
||||
this.headeritem = "afx-grid-cell";
|
||||
this.cellitem = "afx-grid-cell";
|
||||
this._selectedCell = undefined;
|
||||
this._selectedRows = [];
|
||||
this._selectedRow = undefined;
|
||||
this._rows = [];
|
||||
this._oncellselect = this._onrowselect = this._oncelldbclick = (
|
||||
e: TagEventType<CellEventData>
|
||||
): void => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function does nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* set the cell select event callback
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set oncellselect(v: TagEventCallback<CellEventData>) {
|
||||
this._oncellselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the row select event callback
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set onrowselect(v: TagEventCallback<CellEventData>) {
|
||||
this._onrowselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* set the cell double click event callback
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set oncelldbclick(v: TagEventCallback<CellEventData>) {
|
||||
this._oncelldbclick = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set the tag name of the header cells
|
||||
*
|
||||
* Getter: get the grid header tag name
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set headeritem(v: string) {
|
||||
$(this).attr("headeritem", v);
|
||||
}
|
||||
get headeritem(): string {
|
||||
return $(this).attr("headeritem");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set the tag name of the grid cell
|
||||
*
|
||||
* Getter: get the tag name of the grid cell
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set cellitem(v: string) {
|
||||
$(this).attr("cellitem", v);
|
||||
}
|
||||
get cellitem(): string {
|
||||
return $(this).attr("cellitem");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set the header data
|
||||
*
|
||||
* Getter: get the header data placeholder
|
||||
*
|
||||
* @type {GenericObject<any>[]}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
get header(): GenericObject<any>[] {
|
||||
return this._header;
|
||||
}
|
||||
set header(v: GenericObject<any>[]) {
|
||||
this._header = v;
|
||||
if (!v || v.length === 0) {
|
||||
$(this.refs.header).hide();
|
||||
return;
|
||||
}
|
||||
$(this.refs.header).empty();
|
||||
for (let item of v) {
|
||||
const el = $(`<${this.headeritem}>`).appendTo(
|
||||
this.refs.header
|
||||
);
|
||||
const element = el[0] as GridCellPrototype;
|
||||
element.uify(this.observable);
|
||||
element.data = item;
|
||||
item.domel = element;
|
||||
}
|
||||
this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the selected rows
|
||||
*
|
||||
* @readonly
|
||||
* @type {GridRowTag[]}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
get selectedRows(): GridRowTag[] {
|
||||
return this._selectedRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latest selected row
|
||||
*
|
||||
* @readonly
|
||||
* @type {GridRowTag}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
get selectedRow(): GridRowTag {
|
||||
return this._selectedRow;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current selected cell
|
||||
*
|
||||
* @readonly
|
||||
* @type {GridCellPrototype}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
get selectedCell(): GridCellPrototype {
|
||||
return this._selectedCell;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set the rows data
|
||||
*
|
||||
* Getter: get the rows data
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set rows(rows: GenericObject<any>[][]) {
|
||||
$(this.refs.grid).empty();
|
||||
this._rows = rows;
|
||||
rows.map((row) => this.push(row, false));
|
||||
}
|
||||
get rows(): GenericObject<any>[][] {
|
||||
return this._rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: activate deactivate multi-select
|
||||
*
|
||||
* Getter: check whether the `multiselect` option is activated
|
||||
*
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
set multiselect(v: boolean) {
|
||||
this.attsw(v, "multiselect");
|
||||
}
|
||||
get multiselect(): boolean {
|
||||
return this.hasattr("multiselect");
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a grid rows
|
||||
*
|
||||
* @param {GridRowTag} row row DOM element
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
delete(row: GridRowTag): void {
|
||||
if (!row) {
|
||||
return;
|
||||
}
|
||||
const rowdata = row.data;
|
||||
const data = this.rows;
|
||||
if (this.selectedRow === row) {
|
||||
this._selectedRow = undefined;
|
||||
}
|
||||
let parentRow: any = $(this.selectedCell).parent()[0];
|
||||
if ((parentRow as GridRowTag) === row) {
|
||||
this._selectedCell = undefined;
|
||||
}
|
||||
const list = this.selectedRows;
|
||||
if (list.includes(row)) {
|
||||
list.splice(list.indexOf(row), 1);
|
||||
}
|
||||
if (data.includes(rowdata)) {
|
||||
data.splice(data.indexOf(rowdata), 1);
|
||||
}
|
||||
$(row).remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a row to the grid
|
||||
*
|
||||
* @param {GenericObject<any>[]} row list of cell data
|
||||
* @param {boolean} flag indicates where the row is add to beginning or end
|
||||
* of the row
|
||||
* @memberof GridViewTags
|
||||
*/
|
||||
push(row: GenericObject<any>[], flag: boolean): void {
|
||||
const rowel = $("<afx-grid-row>").css(
|
||||
"display",
|
||||
"contents"
|
||||
);
|
||||
if (flag) {
|
||||
$(this.refs.grid).prepend(rowel[0]);
|
||||
if (!this.rows.includes(row)) {
|
||||
this.rows.unshift(row);
|
||||
}
|
||||
} else {
|
||||
rowel.appendTo(this.refs.grid);
|
||||
if (!this.rows.includes(row)) {
|
||||
this.rows.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
const el = rowel[0] as GridRowTag;
|
||||
rowel[0].uify(this.observable);
|
||||
el.data = row;
|
||||
row.domel = rowel[0];
|
||||
|
||||
for (let cell of row) {
|
||||
let tag = this.cellitem;
|
||||
if (cell.tag) {
|
||||
({ tag } = cell);
|
||||
}
|
||||
const el = $(`<${tag}>`).appendTo(rowel);
|
||||
cell.domel = el[0];
|
||||
const element = el[0] as GridCellPrototype;
|
||||
element.uify(this.observable);
|
||||
element.oncellselect = (e) => this.cellselect(e, false);
|
||||
element.oncelldbclick = (e) => this.cellselect(e, true);
|
||||
element.data = cell;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unshift a row to the grid
|
||||
*
|
||||
* @param {GenericObject<any>[]} row list of cell data in the row
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
unshift(row: GenericObject<any>[]): void {
|
||||
this.push(row, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the cell select event
|
||||
*
|
||||
* @private
|
||||
* @param {TagEventType<CellEventData>} e event contains cell event data
|
||||
* @param {boolean} flag indicates whether the event is double clicked
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private cellselect(
|
||||
e: TagEventType<CellEventData>,
|
||||
flag: boolean
|
||||
): void {
|
||||
e.id = this.aid;
|
||||
// return if e.data.item is selectedCell and not flag
|
||||
if (this.selectedCell) {
|
||||
$(this.selectedCell).attr("class", "afx-grid-cell");
|
||||
}
|
||||
this._selectedCell = e.data.item;
|
||||
$(e.data.item).addClass("afx-grid-cell-selected");
|
||||
if (flag) {
|
||||
this.observable.trigger("celldbclick", e);
|
||||
return this._oncelldbclick(e);
|
||||
} else {
|
||||
this.observable.trigger("cellselect", e);
|
||||
this._oncellselect(e);
|
||||
return this.rowselect(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the row select event, a cell select
|
||||
* event will also trigger this event
|
||||
*
|
||||
* @param {TagEventType<CellEventData>} e
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private rowselect(e: TagEventType<CellEventData>): void {
|
||||
if (!e.data.item) {
|
||||
return;
|
||||
}
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: {
|
||||
item: undefined,
|
||||
items: [],
|
||||
},
|
||||
};
|
||||
const row = ($(
|
||||
e.data.item
|
||||
).parent()[0] as any) as GridRowTag;
|
||||
if (this.multiselect) {
|
||||
if (this.selectedRows.includes(row)) {
|
||||
this.selectedRows.splice(
|
||||
this.selectedRows.indexOf(row),
|
||||
1
|
||||
);
|
||||
$(row).removeClass();
|
||||
} else {
|
||||
this.selectedRows.push(row);
|
||||
$(row)
|
||||
.removeClass()
|
||||
.addClass("afx-grid-row-selected");
|
||||
}
|
||||
evt.data.items = this.selectedRows;
|
||||
} else {
|
||||
if (this.selectedRow === row) {
|
||||
return;
|
||||
}
|
||||
$(this.selectedRow).removeClass();
|
||||
this._selectedRows = [row];
|
||||
evt.data.item = row;
|
||||
evt.data.items = [row];
|
||||
$(row).removeClass().addClass("afx-grid-row-selected");
|
||||
this._selectedRows = [row];
|
||||
}
|
||||
this._selectedRow = row;
|
||||
this._onrowselect(evt);
|
||||
return this.observable.trigger("rowselect", evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the grid has header
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private has_header(): boolean {
|
||||
const h = this._header;
|
||||
return h && h.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the grid
|
||||
*
|
||||
* @protected
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
protected calibrate(): void {
|
||||
this.calibrate_header();
|
||||
if (this.has_header()) {
|
||||
$(this.refs.container).css(
|
||||
"height",
|
||||
$(this).height() -
|
||||
$(this.refs.header).height() +
|
||||
"px"
|
||||
);
|
||||
} else {
|
||||
$(this.refs.container).css(
|
||||
"height",
|
||||
$(this).height() + "px"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate the size of each header cell, changing
|
||||
* in header cell size will also resize the entire
|
||||
* related column
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
private calibrate_header(): void {
|
||||
if (!this.has_header()) {
|
||||
return;
|
||||
}
|
||||
const colssize = [];
|
||||
let ocw = 0;
|
||||
let nauto = 0;
|
||||
const totalw = $(this).parent().width();
|
||||
$.each(this._header, function (i, item) {
|
||||
if (item.width) {
|
||||
colssize.push(item.width);
|
||||
return (ocw += item.width);
|
||||
} else {
|
||||
colssize.push(-1);
|
||||
return nauto++;
|
||||
}
|
||||
});
|
||||
if (nauto > 0) {
|
||||
const cellw = Math.round((totalw - ocw) / nauto);
|
||||
$.each(colssize, function (i, e) {
|
||||
if (e !== -1) {
|
||||
return;
|
||||
}
|
||||
return (colssize[i] = cellw);
|
||||
});
|
||||
}
|
||||
let template = "";
|
||||
for (let v of colssize) {
|
||||
template += `${v}px `;
|
||||
}
|
||||
$(this.refs.grid).css("grid-template-columns", template);
|
||||
$(this.refs.header).css("grid-template-columns", template);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the grid view tag
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
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());
|
||||
$(this.refs.container)
|
||||
.css("width", "100%")
|
||||
.css("overflow-x", "hidden")
|
||||
.css("overflow-y", "auto");
|
||||
return this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the grid view
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof GridViewTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{ el: "div", ref: "header", class: "grid_row_header" },
|
||||
{
|
||||
el: "div",
|
||||
ref: "container",
|
||||
children: [{ el: "div", ref: "grid" }],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
define("afx-grid-view", GridViewTag);
|
||||
define("afx-grid-cell", SimpleGridCellTag);
|
||||
define("afx-grid-row", GridRowTag);
|
||||
}
|
||||
}
|
||||
}
|
149
src/core/tags/LabelTag.ts
Normal file
149
src/core/tags/LabelTag.ts
Normal file
@ -0,0 +1,149 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* This class defines basic AFX label tag.
|
||||
* A label contains a text and an icon (optional)
|
||||
*
|
||||
* @export
|
||||
* @class LabelTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class LabelTag extends AFXTag {
|
||||
/**
|
||||
* placeholder of the text to be displayed
|
||||
*
|
||||
* @private
|
||||
* @type {(string | FormattedString)}
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
private _text: string | FormattedString;
|
||||
|
||||
/**
|
||||
*Creates an instance of LabelTag.
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* this implementation does nothing in this tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
protected mount() {}
|
||||
|
||||
/**
|
||||
* Refresh the text in the label
|
||||
*
|
||||
* @protected
|
||||
* @param {*} d
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
protected reload(d: any): void {
|
||||
this.text = this.text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset to default some property value
|
||||
*
|
||||
* @protected
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.icon = undefined;
|
||||
this.iconclass = undefined;
|
||||
this.text = undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation of the function does nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Set the VFS path of the label icon
|
||||
*
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
set icon(v: string) {
|
||||
$(this.refs.i).attr("style", "");
|
||||
$(this).attr("icon", v);
|
||||
if (v) {
|
||||
$(this.refs.i)
|
||||
.css("background", `url(${API.handle.get}/${v})`)
|
||||
.css("background-size", "100% 100%")
|
||||
.css("background-repeat", "no-repeat");
|
||||
$(this.refs.i).show();
|
||||
} else {
|
||||
$(this.refs.i).hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the CSS class of the label icon
|
||||
*
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
set iconclass(v: string) {
|
||||
$(this).attr("iconclass", v);
|
||||
$(this.refs.iclass).removeClass();
|
||||
if (v) {
|
||||
$(this.refs.iclass).addClass(v);
|
||||
$(this.refs.iclass).show();
|
||||
} else {
|
||||
$(this.refs.iclass).hide();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the text of the label
|
||||
*
|
||||
* Getter: Get the text displayed on the label
|
||||
*
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
set text(v: string | FormattedString) {
|
||||
this._text = v;
|
||||
if (v && v !== "") {
|
||||
$(this.refs.text).show();
|
||||
$(this.refs.text).html(v.__());
|
||||
} else {
|
||||
$(this.refs.text).hide();
|
||||
}
|
||||
}
|
||||
get text(): string | FormattedString {
|
||||
return this._text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lqbel layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof LabelTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "span",
|
||||
ref: "container",
|
||||
children: [
|
||||
{ el: "i", ref: "iclass" },
|
||||
{ el: "i", ref: "i", class: "icon-style" },
|
||||
{ el: "i", ref: "text", class: "label-text" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-label", LabelTag);
|
||||
}
|
||||
}
|
||||
}
|
1220
src/core/tags/ListViewTag.ts
Normal file
1220
src/core/tags/ListViewTag.ts
Normal file
File diff suppressed because it is too large
Load Diff
826
src/core/tags/MenuTag.ts
Normal file
826
src/core/tags/MenuTag.ts
Normal file
@ -0,0 +1,826 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Menu event data interface definition
|
||||
*/
|
||||
export type MenuEventData = TagEventDataType<MenuEntryTag>;
|
||||
|
||||
/**
|
||||
* This class defines the abstract prototype of an menu entry.
|
||||
* Any implementation of menu entry tag should extend this class
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class MenuEntryTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export abstract class MenuEntryTag extends AFXTag {
|
||||
/**
|
||||
* Data placeholder of the menu entry
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private _data: GenericObject<any>;
|
||||
|
||||
/**
|
||||
* placeholder of `menu entry select` event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<MenuEventData>}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private _onmenuselect: TagEventCallback<MenuEventData>;
|
||||
|
||||
/**
|
||||
* placeholder of `sub-menu entry select event` handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<MenuEventData>}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private _onchildselect: TagEventCallback<MenuEventData>;
|
||||
|
||||
/**
|
||||
* Reference to the parent menu entry of current one
|
||||
*
|
||||
* @type {MenuEntryTag}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
parent: MenuEntryTag;
|
||||
|
||||
/**
|
||||
* Reference to the root menu entry
|
||||
*
|
||||
* @type {MenuTag}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
root: MenuTag;
|
||||
|
||||
/**
|
||||
*Creates an instance of MenuEntryTag.
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._onmenuselect = this._onchildselect = (
|
||||
e: TagEventType<MenuEventData>
|
||||
): void => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag before mounting
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.nodes = undefined;
|
||||
}
|
||||
/**
|
||||
* Set the `menu entry select` event handle
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set onmenuselect(v: TagEventCallback<MenuEventData>) {
|
||||
this._onmenuselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the `sub menu entry select` event handle
|
||||
*
|
||||
* Getter: get the current `sub menu entry select` event handle
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set onchildselect(v: TagEventCallback<MenuEventData>) {
|
||||
this._onchildselect = v;
|
||||
}
|
||||
get onchildselect(): TagEventCallback<MenuEventData> {
|
||||
return this._onchildselect;
|
||||
}
|
||||
/**
|
||||
* Setter: Set data to the entry
|
||||
*
|
||||
* Getter: Get data of the current menu entry
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set data(data: GenericObject<any>) {
|
||||
this._data = data;
|
||||
this.set(data);
|
||||
}
|
||||
get data(): GenericObject<any> {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current menu entry has sub-menu
|
||||
*
|
||||
* @protected
|
||||
* @returns {boolean}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected has_nodes(): boolean {
|
||||
const ch = this.nodes;
|
||||
return ch && ch.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current menu entry is the root entry
|
||||
*
|
||||
* @protected
|
||||
* @returns
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected is_root() {
|
||||
if (this.parent) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the menu entry
|
||||
* This function define the outer layout of the menu entry.
|
||||
* Custom inner layout of each item implementation should
|
||||
* be defined in [[itemlayout]]
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "li",
|
||||
ref: "container",
|
||||
children: [
|
||||
{
|
||||
el: "a",
|
||||
ref: "entry",
|
||||
children: this.itemlayout(),
|
||||
},
|
||||
{ el: "afx-menu", ref: "submenu" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the sub-menu data
|
||||
*
|
||||
* Getter: Get the sub-menu data
|
||||
*
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
set nodes(v: GenericObject<any>[]) {
|
||||
$(this.refs.container).removeClass("afx_submenu");
|
||||
if (!v || !(v.length > 0)) {
|
||||
$(this.refs.submenu).hide();
|
||||
return;
|
||||
}
|
||||
$(this.refs.container).addClass("afx_submenu");
|
||||
$(this.refs.submenu).show().attr("style", "");
|
||||
const element = this.refs.submenu as MenuTag;
|
||||
element.parent = this;
|
||||
element.root = this.root;
|
||||
element.items = v;
|
||||
// ensure that the data is in sync
|
||||
this._data.nodes = v;
|
||||
if (this.is_root()) {
|
||||
$(this.refs.container).mouseleave((e) => {
|
||||
return $(this.refs.submenu).attr("style", "");
|
||||
});
|
||||
}
|
||||
}
|
||||
get nodes(): GenericObject<any>[] {
|
||||
if (this.data && this.data.nodes) {
|
||||
return this.data.nodes;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
/**
|
||||
* Bind some base event to the menu entry
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.entry).click((e) => this.select(e));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the sub-menu of the current menu entry
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
private submenuoff(): void {
|
||||
const p = this.parent;
|
||||
if (!p) {
|
||||
$(this.refs.submenu).attr("style", "");
|
||||
return;
|
||||
}
|
||||
return p.submenuoff();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function trigger two event:
|
||||
* - the `onmenuselect` event on the current entry
|
||||
* - the `onchildselect` event on the parent of the current entry
|
||||
*
|
||||
* @protected
|
||||
* @param {JQuery.ClickEvent} e
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected select(e: JQuery.ClickEvent): void {
|
||||
const evt = {
|
||||
id: this.aid,
|
||||
data: { item: this, event: e },
|
||||
};
|
||||
e.preventDefault();
|
||||
if (this.is_root() && this.has_nodes()) {
|
||||
$(this.refs.submenu).show();
|
||||
} else {
|
||||
this.submenuoff();
|
||||
}
|
||||
this._onmenuselect(evt);
|
||||
if (this.parent) {
|
||||
this.parent.onchildselect(evt);
|
||||
}
|
||||
if (this.root) {
|
||||
this.root.onmenuitemselect(evt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* custom inner layout of a menu entry
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof MenuEntryTag
|
||||
*/
|
||||
protected abstract itemlayout(): TagLayoutType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* This class extends the [[MenuEntryTag]] prototype. It inner layout is
|
||||
* defined with the following elements:
|
||||
* - a [[SwitchTag]] acts as checker or radio
|
||||
* - a [[LabelTag]] to display the content of the menu entry
|
||||
* - a `span` element that display the keyboard shortcut of the entry
|
||||
*
|
||||
* @class SimpleMenuEntryTag
|
||||
* @extends {MenuEntryTag}
|
||||
*/
|
||||
export class SimpleMenuEntryTag extends MenuEntryTag {
|
||||
/**
|
||||
*Creates an instance of SimpleMenuEntryTag.
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some properties to default value
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected init(): void {
|
||||
super.init();
|
||||
this.switch = false;
|
||||
this.radio = false;
|
||||
this.checked = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off the checker feature of the menu entry
|
||||
*
|
||||
* Getter: Check whether the checker feature is enabled on this menu entry
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set switch(v: boolean) {
|
||||
this.attsw(v, "switch");
|
||||
if (this.radio || v) {
|
||||
$(this.refs.switch).show();
|
||||
} else {
|
||||
$(this.refs.switch).hide();
|
||||
}
|
||||
}
|
||||
get switch(): boolean {
|
||||
return this.hasattr("switch");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off the radio feature of the menu entry
|
||||
*
|
||||
* Getter: Check whether the radio feature is enabled
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set radio(v: boolean) {
|
||||
this.attsw(v, "radio");
|
||||
if (this.switch || v) {
|
||||
$(this.refs.switch).show();
|
||||
} else {
|
||||
$(this.refs.switch).hide();
|
||||
}
|
||||
}
|
||||
get radio(): boolean {
|
||||
return this.hasattr("radio");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Toggle the switch on the menu entry, this setter
|
||||
* only works when the `checker` or `radio` feature is
|
||||
* enabled
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the switch is turned on
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set checked(v: boolean) {
|
||||
this.attsw(v, "checked");
|
||||
if (this.data) this.data.checked = v;
|
||||
if (!this.radio && !this.switch) {
|
||||
return;
|
||||
}
|
||||
(this.refs.switch as SwitchTag).swon = v;
|
||||
}
|
||||
get checked(): boolean {
|
||||
return this.hasattr("checked");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label icon using a VFS path
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set icon(v: string) {
|
||||
$(this.refs.container).removeClass("fix_padding");
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
//$(this).attr("icon", v);
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.icon = v;
|
||||
$(this.refs.container).addClass("fix_padding");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label CSS icon class
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set iconclass(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.iconclass = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the label text
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set text(v: string) {
|
||||
if (v === undefined) {
|
||||
return;
|
||||
}
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.text = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the keyboard shortcut text
|
||||
*
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
set shortcut(v: string) {
|
||||
$(this.refs.shortcut).hide();
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.shortcut).show();
|
||||
$(this.refs.shortcut).text(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uncheck all sub-menu items of the current menu entry
|
||||
* that have the radio feature enabled
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected reset_radio(): void {
|
||||
if (!this.has_nodes()) {
|
||||
return;
|
||||
}
|
||||
for (let v of this.nodes) {
|
||||
if (!v.domel.radio) {
|
||||
continue;
|
||||
}
|
||||
v.domel.checked = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the current tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
super.mount();
|
||||
(this.refs.switch as SwitchTag).enable = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger the onmenuselect and onchildselect events
|
||||
*
|
||||
* @protected
|
||||
* @param {JQuery.ClickEvent} e Mouse click event
|
||||
* @returns {void}
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
protected select(e: JQuery.ClickEvent): void {
|
||||
if (this.switch) {
|
||||
this.checked = !this.checked;
|
||||
} else if (this.radio) {
|
||||
const p = this.parent as SimpleMenuEntryTag;
|
||||
if (p) {
|
||||
p.reset_radio();
|
||||
}
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
return super.select(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner item layout of the menu entry
|
||||
*
|
||||
* @returns
|
||||
* @memberof SimpleMenuEntryTag
|
||||
*/
|
||||
itemlayout() {
|
||||
return [
|
||||
{ el: "afx-switch", ref: "switch" },
|
||||
{ el: "afx-label", ref: "label" },
|
||||
{ el: "span", class: "shortcut", ref: "shortcut" },
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A menu tag contains a collection of menu entries in which each
|
||||
* entry maybe a leaf entry or may contain a submenu
|
||||
*
|
||||
* @export
|
||||
* @class MenuTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class MenuTag extends AFXTag {
|
||||
/**
|
||||
* Reference to the parent menu entry of the current value.
|
||||
* This value is `undefined` in case of the current menu is
|
||||
* the root menu
|
||||
*
|
||||
* @type {MenuEntryTag}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
parent: MenuEntryTag;
|
||||
|
||||
/**
|
||||
* Reference to the root menu
|
||||
*
|
||||
* @type {MenuTag}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
root: MenuTag;
|
||||
|
||||
/**
|
||||
* The `pid` of the application that attached to this menu.
|
||||
* This value is optional
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
pid?: number;
|
||||
|
||||
/**
|
||||
* placeholder for menu select event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<MenuEventData>}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private _onmenuselect: TagEventCallback<MenuEventData>;
|
||||
|
||||
/**
|
||||
* Menu data placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>[]}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private _items: GenericObject<any>[];
|
||||
|
||||
/**
|
||||
*Creates an instance of MenuTag.
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset some properties to default value
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.contentag = "afx-menu-entry";
|
||||
this.context = false;
|
||||
this._items = [];
|
||||
this._onmenuselect = (
|
||||
e: TagEventType<MenuEventData>
|
||||
): void => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Set the menu items data
|
||||
*
|
||||
* Getter: Get menu items data
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set items(data: GenericObject<any>[]) {
|
||||
this._items = data;
|
||||
$(this.refs.container).empty();
|
||||
data.map((item) => this.push(item, false));
|
||||
}
|
||||
get items(): GenericObject<any>[] {
|
||||
return this._items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set whether the current menu is a context menu
|
||||
*
|
||||
* Getter: Check whether the current menu is a context menu
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set context(v: boolean) {
|
||||
this.attsw(v, "context");
|
||||
$(this.refs.wrapper).removeClass("context");
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.wrapper).addClass("context");
|
||||
$(this).hide();
|
||||
}
|
||||
get context(): boolean {
|
||||
return this.hasattr("context");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set menu select event handle
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set onmenuselect(v: TagEventCallback<MenuEventData>) {
|
||||
this._onmenuselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the default tag name of the menu item.
|
||||
* If the tag is not specified in an item data,
|
||||
* this value will be used
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the default menu entry tag name
|
||||
*
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
set contentag(v: string) {
|
||||
$(this).attr("contentag", v);
|
||||
}
|
||||
get contentag(): string {
|
||||
return $(this).attr("contentag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the reference to the function that triggers
|
||||
* the menu select event
|
||||
*
|
||||
* @readonly
|
||||
* @type {TagEventCallback}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
get onmenuitemselect(): TagEventCallback<MenuEventData> {
|
||||
return this.handleselect;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function triggers the menu select event
|
||||
*
|
||||
* @private
|
||||
* @param {TagEventType} e
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private handleselect(e: TagEventType<MenuEventData>): void {
|
||||
if (this.context) {
|
||||
$(this).hide();
|
||||
}
|
||||
e.id = this.aid;
|
||||
this._onmenuselect(e);
|
||||
this.observable.trigger("menuselect", e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the current menu. This function is called
|
||||
* only if the current menu is a context menu
|
||||
*
|
||||
* @param {JQuery.MouseEventBase} e JQuery mouse event
|
||||
* @returns {void}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
show(e: JQuery.MouseEventBase): void {
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
$(this)
|
||||
.css("top", e.clientY - 15 + "px")
|
||||
.css("left", e.clientX - 5 + "px")
|
||||
.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether the current menu is the root menu
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
private is_root(): boolean {
|
||||
return this.root === undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the menu tag and bind some basic events
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.container).css("display", "contents");
|
||||
if (!this.context) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.wrapper).mouseleave((e) => {
|
||||
if (!this.is_root()) {
|
||||
return;
|
||||
}
|
||||
return $(this).hide();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a menu entry to the beginning of the current
|
||||
* menu
|
||||
*
|
||||
* @param {GenericObject<any>} item menu entry data
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
unshift(item: GenericObject<any>): void {
|
||||
this.push(item, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a menu entry
|
||||
*
|
||||
* @param {MenuEntryTag} item reference to the DOM element of an menu entry
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
delete(item: MenuEntryTag): void {
|
||||
const el = item.data;
|
||||
const data = this.items;
|
||||
if (data.includes(el)) {
|
||||
data.splice(data.indexOf(el), 1);
|
||||
}
|
||||
$(item).remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an menu entry to the beginning or end of the menu
|
||||
*
|
||||
* @param {GenericObject<any>} item menu entry data
|
||||
* @param {boolean} flag indicates whether the entry should be added to the beginning of the menu
|
||||
* @returns {MenuEntryTag}
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
push(item: GenericObject<any>, flag: boolean): MenuEntryTag {
|
||||
let tag = this.contentag;
|
||||
if (item.tag) {
|
||||
tag = item.tag;
|
||||
}
|
||||
const el = $(`<${tag}>`);
|
||||
if (flag) {
|
||||
$(this.refs.container).prepend(el[0]);
|
||||
if (!this.items.includes(item)) {
|
||||
this.items.unshift(item);
|
||||
}
|
||||
} else {
|
||||
el.appendTo(this.refs.container);
|
||||
if (!this.items.includes(item)) {
|
||||
this.items.push(item);
|
||||
}
|
||||
}
|
||||
const entry = el[0] as MenuEntryTag;
|
||||
entry.uify(this.observable);
|
||||
entry.parent = this.parent;
|
||||
entry.root = this.parent ? this.parent.root : this;
|
||||
entry.data = item;
|
||||
item.domel = entry;
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Menu tag layout definition
|
||||
*
|
||||
* @returns
|
||||
* @memberof MenuTag
|
||||
*/
|
||||
layout() {
|
||||
return [
|
||||
{
|
||||
el: "ul",
|
||||
ref: "wrapper",
|
||||
children: [
|
||||
{ el: "li", class: "afx-corner-fix" },
|
||||
{ el: "div", ref: "container" },
|
||||
{ el: "li", class: "afx-corner-fix" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-menu", MenuTag);
|
||||
define("afx-menu-entry", SimpleMenuEntryTag);
|
||||
}
|
||||
}
|
||||
}
|
207
src/core/tags/NSpinnerTag.ts
Normal file
207
src/core/tags/NSpinnerTag.ts
Normal file
@ -0,0 +1,207 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A simple number sinner tag
|
||||
*
|
||||
* @export
|
||||
* @class NSpinnerTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class NSpinnerTag extends AFXTag {
|
||||
/**
|
||||
* Placeholder for value change event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<number>}
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
private _onchange: TagEventCallback<number>;
|
||||
|
||||
/**
|
||||
* Placeholder for the spinner data
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
private _value: number;
|
||||
|
||||
/**
|
||||
* Place holder for the spinner step
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
step: number;
|
||||
|
||||
/**
|
||||
*Creates an instance of NSpinnerTag.
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._onchange = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the spinner value to `0` and step to `1`
|
||||
*
|
||||
* @protected
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this._value = 0;
|
||||
this.step = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Set the value change event handle
|
||||
*
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
set onvaluechange(f: TagEventCallback<number>) {
|
||||
this._onchange = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag and bind basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.holder).attr("type", "text");
|
||||
$(this.refs.incr).click((e) => {
|
||||
this.value = this.value + this.step;
|
||||
});
|
||||
|
||||
$(this.refs.decr).click((e) => {
|
||||
this.value = this.value - this.step;
|
||||
});
|
||||
|
||||
// @observable.on "calibrate", () -> @calibrate()
|
||||
this.observable.on("resize", () => this.calibrate());
|
||||
|
||||
$(this.refs.holder).on("keyup", (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
let val = parseInt(
|
||||
(this.refs.holder as HTMLInputElement).value
|
||||
);
|
||||
if (!isNaN(val)) {
|
||||
if (val < 0) {
|
||||
val = this.value;
|
||||
}
|
||||
return (this.value = val);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the layout of the spinner
|
||||
*
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
$(this.refs.holder).css(
|
||||
"width",
|
||||
$(this).width() - 20 + "px"
|
||||
);
|
||||
$(this.refs.holder).css("height", $(this).height() + "px");
|
||||
$(this.refs.spinner)
|
||||
.css("width", "20px")
|
||||
.css("height", $(this).height() + "px");
|
||||
$(this.refs.incr)
|
||||
.css("height", $(this).height() / 2 - 2 + "px")
|
||||
.css("position", "relative");
|
||||
$(this.refs.decr)
|
||||
.css("height", $(this).height() / 2 - 2 + "px")
|
||||
.css("position", "relative");
|
||||
$(this.refs.spinner)
|
||||
.find("li")
|
||||
.css("display", "block")
|
||||
.css("text-align", "center")
|
||||
.css("vertical-align", "middle");
|
||||
$(this.refs.spinner)
|
||||
.find("i")
|
||||
.css("font-size", "16px")
|
||||
.css("position", "absolute");
|
||||
const fn = function (ie: HTMLElement, pos: string) {
|
||||
const el = $(ie).find("i");
|
||||
el.css(
|
||||
pos,
|
||||
($(ie).height() - el.height()) / 2 + "px"
|
||||
).css("left", ($(ie).width() - el.width()) / 2 + "px");
|
||||
};
|
||||
fn(this.refs.decr, "bottom");
|
||||
fn(this.refs.incr, "top");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the spinner value
|
||||
*
|
||||
* Getter: Get the spinner value
|
||||
*
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
set value(v: number) {
|
||||
this._value = v;
|
||||
$(this.refs.holder).val(this._value);
|
||||
const evt = { id: this.aid, data: v };
|
||||
this._onchange(evt);
|
||||
this.observable.trigger("nspin", evt);
|
||||
}
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spinner layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof NSpinnerTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "input",
|
||||
ref: "holder",
|
||||
},
|
||||
{
|
||||
el: "ul",
|
||||
ref: "spinner",
|
||||
children: [
|
||||
{
|
||||
el: "li",
|
||||
class: "incr",
|
||||
ref: "incr",
|
||||
children: [{ el: "i" }],
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "decr",
|
||||
ref: "decr",
|
||||
children: [{ el: "i" }],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-nspinner", NSpinnerTag);
|
||||
}
|
||||
}
|
||||
}
|
159
src/core/tags/OverlayTag.ts
Normal file
159
src/core/tags/OverlayTag.ts
Normal file
@ -0,0 +1,159 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* An overlay tag is a layout tag that alway stay on top of
|
||||
* the virtual desktop environment. Tile layout elements ([[VBoxTag]], [[HboxTag]])
|
||||
* can be used inside this tag to compose elements
|
||||
*
|
||||
* @export
|
||||
* @class OverlayTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class OverlayTag extends AFXTag {
|
||||
/**
|
||||
* Tag width placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
private _width: string;
|
||||
|
||||
/**
|
||||
* Tag height place holder
|
||||
*
|
||||
* @private
|
||||
* @type {string}
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
private _height: string;
|
||||
|
||||
/**
|
||||
*Creates an instance of OverlayTag.
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
//.css "display", "flex"
|
||||
//.css "flex-direction", "column"
|
||||
//$(@refs.yield).css "flex", "1"
|
||||
|
||||
/**
|
||||
* Put the tag on top of the virtual desktop environment
|
||||
*
|
||||
* @protected
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
protected init(): void {
|
||||
$(this.refs.yield)
|
||||
.css("position", "relative")
|
||||
.css("width", "100%")
|
||||
.css("height", "100%");
|
||||
$(this).css("position", "absolute").css("z-index", 1000000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the width of the tag, the tag width should be in form of:
|
||||
* `100px` of `80%`
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the tag width
|
||||
*
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
set width(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this._width = v;
|
||||
this.calibrate();
|
||||
}
|
||||
get width(): string {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the tag height, the tag height should be in form of:
|
||||
* `100px` of `80%`
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the tag height
|
||||
*
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
set height(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this._height = v;
|
||||
this.calibrate();
|
||||
}
|
||||
get height(): string {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the element when mounting
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
return this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the width and height of the tag
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
$(this).css("width", this.width).css("height", this.height);
|
||||
return this.observable.trigger("resize", {
|
||||
id: this.aid,
|
||||
data: {
|
||||
w: this.width,
|
||||
h: this.height,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the tag
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof OverlayTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "afx-vbox",
|
||||
ref: "yield",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
define("afx-overlay", OverlayTag);
|
||||
}
|
||||
}
|
||||
}
|
239
src/core/tags/ResizerTag.ts
Normal file
239
src/core/tags/ResizerTag.ts
Normal file
@ -0,0 +1,239 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A `resizer` tag is basically used to dynamically resize an element using mouse.
|
||||
* It is usually put inside a [[TileLayoutTag]] an can be attached to any element. Example:
|
||||
*
|
||||
* The resizer tag in the following example will be attached to the first `afx-vbox`,
|
||||
* and allows to resize this element using mouse
|
||||
*
|
||||
* ```xml
|
||||
* <afx-hbox>
|
||||
* <afx-vbox>...</afx-vbox>
|
||||
* <afx-resizer></afx-resizer>
|
||||
* <afx-vbox>...</afx-vbox>
|
||||
* </afx-hbox>
|
||||
* ```
|
||||
*
|
||||
* @export
|
||||
* @class ResizerTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class ResizerTag extends AFXTag {
|
||||
/**
|
||||
* Reference to the element that this tag is attached to
|
||||
*
|
||||
* @private
|
||||
* @type {*}
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
private _resizable_el: any;
|
||||
|
||||
/**
|
||||
* Reference to the parent tag of the current tag.
|
||||
* The parent tag should be an instance of a [[TileLayoutTag]]
|
||||
* such as [[VBoxTag]] or [[HBoxTag]]
|
||||
*
|
||||
* @private
|
||||
* @type {*}
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
private _parent: any;
|
||||
|
||||
/**
|
||||
* Placeholder of the minimum value that
|
||||
* the attached element can be resized
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
private _minsize: number;
|
||||
|
||||
/**
|
||||
*Creates an instance of ResizerTag.
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the properties of the tag to default values
|
||||
*
|
||||
* @protected
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.dir = "hz";
|
||||
this._resizable_el = undefined;
|
||||
this._parent = $(this).parent().parent()[0];
|
||||
this._minsize = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set resize direction, two possible values:
|
||||
* - `hz` - horizontal direction, resize by width
|
||||
* - `ve` - vertical direction, resize by height
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the resize direction
|
||||
*
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
set dir(v: string) {
|
||||
$(this).attr("dir", v);
|
||||
}
|
||||
get dir(): string {
|
||||
return $(this).attr("dir");
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag to the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
let att: string;
|
||||
$(this).css(" display", "block");
|
||||
const tagname = $(this._parent).prop("tagName");
|
||||
this._resizable_el =
|
||||
$(this).prev().length === 1
|
||||
? $(this).prev()[0]
|
||||
: undefined;
|
||||
if (tagname === "AFX-HBOX") {
|
||||
this.dir = "hz";
|
||||
$(this).css("cursor", "col-resize");
|
||||
$(this).addClass("horizontal");
|
||||
if (this._resizable_el) {
|
||||
att = $(this._resizable_el).attr("min-width");
|
||||
if (att) {
|
||||
this._minsize = parseInt(att);
|
||||
}
|
||||
}
|
||||
} else if (tagname === "AFX-VBOX") {
|
||||
this.dir = "ve";
|
||||
$(this).css("cursor", "row-resize");
|
||||
$(this).addClass("vertical");
|
||||
if (this._resizable_el) {
|
||||
att = $(this._resizable_el).attr("min-height");
|
||||
if (att) {
|
||||
this._minsize = parseInt(att);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.dir = "none";
|
||||
}
|
||||
if (this._minsize === 0) {
|
||||
this._minsize = 10;
|
||||
}
|
||||
this.make_draggable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable draggable on the element
|
||||
*
|
||||
* @private
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
private make_draggable(): void {
|
||||
$(this).css("user-select", "none");
|
||||
$(this).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
$(window).on("mousemove", (evt) => {
|
||||
if (!this._resizable_el) {
|
||||
return;
|
||||
}
|
||||
if (this.dir === "hz") {
|
||||
return this.horizontalResize(evt);
|
||||
} else if (this.dir === "ve") {
|
||||
return this.verticalResize(evt);
|
||||
}
|
||||
});
|
||||
|
||||
return $(window).on("mouseup", function (evt) {
|
||||
$(window).unbind("mousemove", null);
|
||||
$(window).unbind("mouseup", null);
|
||||
|
||||
return $(window).unbind("mouseup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the attached element in the horizontal direction (width)
|
||||
*
|
||||
* @private
|
||||
* @param {JQuery.MouseEventBase} e JQuery mouse event
|
||||
* @returns {void}
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
private horizontalResize(e: JQuery.MouseEventBase): void {
|
||||
if (!this._resizable_el) {
|
||||
return;
|
||||
}
|
||||
const offset = $(this._resizable_el).offset();
|
||||
let w = Math.round(e.clientX - offset.left);
|
||||
if (w < this._minsize) {
|
||||
w = this._minsize;
|
||||
}
|
||||
$(this._resizable_el).attr("data-width", w.toString());
|
||||
this.observable.trigger("resize", {
|
||||
id: this.aid,
|
||||
data: { w },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize the attached element in the vertical direction (height)
|
||||
*
|
||||
* @protected
|
||||
* @param {JQuery.MouseEventBase} e JQuery mouse event
|
||||
* @returns {void}
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
protected verticalResize(e: JQuery.MouseEventBase): void {
|
||||
if (!this._resizable_el) {
|
||||
return;
|
||||
}
|
||||
const offset = $(this._resizable_el).offset();
|
||||
let h = Math.round(e.clientY - offset.top);
|
||||
if (h < this._minsize) {
|
||||
h = this._minsize;
|
||||
}
|
||||
$(this._resizable_el).attr("data-height", h.toString());
|
||||
return this.observable.trigger("resize", {
|
||||
id: this.aid,
|
||||
data: { h },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the tag, empty layout
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof ResizerTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-resizer", ResizerTag);
|
||||
}
|
||||
}
|
||||
}
|
275
src/core/tags/SliderTag.ts
Normal file
275
src/core/tags/SliderTag.ts
Normal file
@ -0,0 +1,275 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A slider or track bar is a graphical control element with which
|
||||
* a user may set a value by moving an indicator, usually horizontally
|
||||
*
|
||||
* @class SliderTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
class SliderTag extends AFXTag {
|
||||
/**
|
||||
* Slider max value placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
private _max: number;
|
||||
|
||||
/**
|
||||
* Current slider value placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
private _value: number;
|
||||
|
||||
/**
|
||||
* Placeholder of the on change event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<number>}
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
private _onchange: TagEventCallback<number>;
|
||||
|
||||
/**
|
||||
* Placeholder of the on changing event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<number>}
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
private _onchanging: TagEventCallback<number>;
|
||||
|
||||
/**
|
||||
* Creates an instance of SliderTag.
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the default value of the slider:
|
||||
* - `max`: 100
|
||||
* - `value`: 0
|
||||
*
|
||||
* @protected
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.enable = true;
|
||||
this._max = 100;
|
||||
this._value = 0;
|
||||
this._onchange = this._onchanging = () => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Set value change event handle.
|
||||
* This handle will be triggered when the
|
||||
* slider indicator is released
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
set onvaluechange(f: TagEventCallback<number>) {
|
||||
this._onchange = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set value changing event handle.
|
||||
* This handle is triggered when moving the
|
||||
* slider indicator
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
set onvaluechanging(f: TagEventCallback<number>) {
|
||||
this._onchanging = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Enable/disable the slider
|
||||
*
|
||||
* Getter: Check whether the slider is enabled
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
set enable(v: boolean) {
|
||||
this.attsw(v, "enable");
|
||||
if (v) {
|
||||
$(this)
|
||||
.mouseover(() => {
|
||||
return $(this.refs.point).show();
|
||||
})
|
||||
.mouseout(() => {
|
||||
return $(this.refs.point).hide();
|
||||
});
|
||||
} else {
|
||||
$(this.refs.point).hide();
|
||||
$(this).unbind("mouseover").unbind("mouseout");
|
||||
}
|
||||
}
|
||||
get enable(): boolean {
|
||||
return this.hasattr("enable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the slider value
|
||||
*
|
||||
* Getter: Get the current slider value
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
set value(v: number) {
|
||||
this._value = v;
|
||||
this.calibrate();
|
||||
}
|
||||
get value(): number {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the maximum value of the slider
|
||||
*
|
||||
* Getter: Get the maximum value of the slider
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
set max(v: number) {
|
||||
this._max = v;
|
||||
this.calibrate();
|
||||
}
|
||||
get max(): number {
|
||||
return this._max;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag and bind some basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this.enable_dragging();
|
||||
$(this.refs.point).css("position", "absolute");
|
||||
$(this.refs.point).hide();
|
||||
this.observable.on("resize", (e) => {
|
||||
return this.calibrate();
|
||||
});
|
||||
$(this.refs.container).click((e) => {
|
||||
const offset = $(this.refs.container).offset();
|
||||
const left = e.clientX - offset.left;
|
||||
const maxw = $(this.refs.container).width();
|
||||
this.value = (left * this.max) / maxw;
|
||||
this.calibrate();
|
||||
const evt = { id: this.aid, data: this.value };
|
||||
this._onchange(evt);
|
||||
return this._onchanging(evt);
|
||||
});
|
||||
this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the slide based on its value and max value
|
||||
*
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
if (this.value > this.max) {
|
||||
this.value = this.max;
|
||||
}
|
||||
$(this.refs.container).css("width", $(this).width() + "px");
|
||||
const w =
|
||||
($(this.refs.container).width() * this.value) /
|
||||
this.max;
|
||||
$(this.refs.prg)
|
||||
.css("width", w + "px")
|
||||
.css("height", $(this.refs.container).height() + "px");
|
||||
if (this.enable) {
|
||||
const ow = w - $(this.refs.point).width() / 2;
|
||||
const top = Math.floor(
|
||||
($(this.refs.prg).height() -
|
||||
$(this.refs.point).height()) /
|
||||
2
|
||||
);
|
||||
$(this.refs.point)
|
||||
.css("left", ow + "px")
|
||||
.css("top", top + "px");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* enable dragging on the slider indicator
|
||||
*
|
||||
* @private
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
private enable_dragging(): void {
|
||||
$(this.refs.point)
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default");
|
||||
$(this.refs.point).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
const offset = $(this.refs.container).offset();
|
||||
$(window).on("mousemove", (e) => {
|
||||
let left = e.clientX - offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
const maxw = $(this.refs.container).width();
|
||||
left = left > maxw ? maxw : left;
|
||||
this.value = (left * this.max) / maxw;
|
||||
this.calibrate();
|
||||
return this._onchanging({
|
||||
id: this.aid,
|
||||
data: this.value,
|
||||
});
|
||||
});
|
||||
|
||||
$(window).on("mouseup", (e) => {
|
||||
this._onchange({
|
||||
id: this.aid,
|
||||
data: this.value,
|
||||
});
|
||||
$(window).unbind("mousemove", null);
|
||||
return $(window).unbind("mouseup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof SliderTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
class: "container",
|
||||
ref: "container",
|
||||
children: [
|
||||
{ el: "div", class: "progress", ref: "prg" },
|
||||
{ el: "div", class: "dragpoint", ref: "point" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-slider", SliderTag);
|
||||
}
|
||||
}
|
||||
}
|
144
src/core/tags/SwitchTag.ts
Normal file
144
src/core/tags/SwitchTag.ts
Normal file
@ -0,0 +1,144 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A switch tag is basically used to visualize an boolean data value.
|
||||
*
|
||||
* @export
|
||||
* @class SwitchTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class SwitchTag extends AFXTag {
|
||||
/**
|
||||
* Placeholder for the onchange event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<boolean>}
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
private _onchange: TagEventCallback<boolean>;
|
||||
|
||||
/**
|
||||
* Setter: Turn on/off the switch
|
||||
*
|
||||
* Getter: Check whether the switch is turned on
|
||||
*
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
set swon(v: boolean) {
|
||||
this.attsw(v, "swon");
|
||||
$(this.refs.switch).removeClass();
|
||||
if (v) {
|
||||
$(this.refs.switch).addClass("swon");
|
||||
}
|
||||
}
|
||||
get swon(): boolean {
|
||||
return this.hasattr("swon");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Enable the switch
|
||||
*
|
||||
* Getter: Check whether the switch is enabled
|
||||
*
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
set enable(v: boolean) {
|
||||
this.attsw(v, "enable");
|
||||
}
|
||||
get enable(): boolean {
|
||||
return this.hasattr("enable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the onchange event handle
|
||||
*
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
set onswchange(v: TagEventCallback<boolean>) {
|
||||
this._onchange = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag and bind the click event to the switch
|
||||
*
|
||||
* @protected
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.switch).click((e) => {
|
||||
return this.makechange(e);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will turn the switch (on/off)
|
||||
* and trigger the onchange event
|
||||
*
|
||||
* @private
|
||||
* @param {JQuery.ClickEvent} e
|
||||
* @returns
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
private makechange(e: JQuery.ClickEvent) {
|
||||
if (!this.enable) {
|
||||
return;
|
||||
}
|
||||
this.swon = !this.swon;
|
||||
const evt = { id: this.aid, data: this.swon };
|
||||
this._onchange(evt);
|
||||
return this.observable.trigger("switch", evt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tag layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
protected layout() {
|
||||
return [
|
||||
{
|
||||
el: "span",
|
||||
ref: "switch",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag:
|
||||
* - switch is turn off
|
||||
* - switch is enabled
|
||||
*
|
||||
* @protected
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.swon = false;
|
||||
this.enable = true;
|
||||
this._onchange = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof SwitchTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
}
|
||||
|
||||
define("afx-switch", SwitchTag);
|
||||
}
|
||||
}
|
||||
}
|
387
src/core/tags/SystemPanelTag.ts
Normal file
387
src/core/tags/SystemPanelTag.ts
Normal file
@ -0,0 +1,387 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A system panel contains the following elements:
|
||||
* - Spotlight to access to applications menu
|
||||
* - Current focused application menu
|
||||
* - System tray for all running services running in background
|
||||
*
|
||||
* @export
|
||||
* @class SystemPanelTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class SystemPanelTag extends AFXTag {
|
||||
/**
|
||||
* Reference to spotlight data
|
||||
*
|
||||
* @private
|
||||
* @type {(GenericObject<string | FormattedString>)}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private _osmenu: GenericObject<string | FormattedString>;
|
||||
|
||||
/**
|
||||
* Placeholder indicates whether the spotlight is currently shown
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private _view: boolean;
|
||||
|
||||
/**
|
||||
* Place holder for a private callback function
|
||||
*
|
||||
* @private
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private _cb: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
/**
|
||||
*Creates an instance of SystemPanelTag.
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._osmenu = {
|
||||
text: __("Start"),
|
||||
iconclass: "fa fa-circle",
|
||||
};
|
||||
this._view = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
protected init(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Attach a service to the system tray on the pannel,
|
||||
* this operation is performed when a service is started
|
||||
*
|
||||
* @param {BaseService} s
|
||||
* @returns
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
attachservice(s: application.BaseService) {
|
||||
(this.refs.systray as MenuTag).unshift(s);
|
||||
return s.attach(this.refs.systray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the selected application from the spotlight
|
||||
* applications list
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private open(): void {
|
||||
const applist = this.refs.applist as ListViewTag;
|
||||
const el = applist.selectedItem;
|
||||
if (!el) {
|
||||
return;
|
||||
}
|
||||
if (!el.data || el.data.dataid === "header") {
|
||||
return;
|
||||
}
|
||||
this.toggle(false);
|
||||
// launch the app or open the file
|
||||
Ant.OS.GUI.openWith(el.data as AppArgumentsType);
|
||||
applist.unselect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform spotlight search operation on keyboard event
|
||||
*
|
||||
* @private
|
||||
* @param {JQuery.KeyboardEventBase} e
|
||||
* @returns {void}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private search(e: JQuery.KeyboardEventBase): void {
|
||||
const applist = this.refs.applist as ListViewTag;
|
||||
switch (e.which) {
|
||||
case 27:
|
||||
// escape key
|
||||
return this.toggle(false);
|
||||
|
||||
case 37:
|
||||
return e.preventDefault();
|
||||
case 38:
|
||||
applist.selectPrev();
|
||||
return e.preventDefault();
|
||||
case 39:
|
||||
return e.preventDefault();
|
||||
case 40:
|
||||
applist.selectNext();
|
||||
return e.preventDefault();
|
||||
case 13:
|
||||
e.preventDefault();
|
||||
return this.open();
|
||||
default:
|
||||
var text = (this.refs.search as HTMLInputElement)
|
||||
.value;
|
||||
if (!(text.length >= 3)) {
|
||||
return this.refreshAppList();
|
||||
}
|
||||
var result = Ant.OS.API.search(text);
|
||||
if (result.length === 0) {
|
||||
return;
|
||||
}
|
||||
applist.data = result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* detach a service from the system tray of the panel.
|
||||
* This function is called when the corresponding running
|
||||
* service is killed
|
||||
*
|
||||
* @param {BaseService} s
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
detachservice(s: application.BaseService): void {
|
||||
(this.refs.systray as MenuTag).delete(
|
||||
s.domel as MenuEntryTag
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the panel
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
ref: "panel",
|
||||
children: [
|
||||
{
|
||||
el: "afx-menu",
|
||||
ref: "osmenu",
|
||||
class: "afx-panel-os-menu",
|
||||
},
|
||||
{
|
||||
el: "afx-menu",
|
||||
id: "appmenu",
|
||||
ref: "appmenu",
|
||||
class: "afx-panel-os-app",
|
||||
},
|
||||
{
|
||||
el: "afx-menu",
|
||||
id: "systray",
|
||||
ref: "systray",
|
||||
class: "afx-panel-os-stray",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
el: "afx-overlay",
|
||||
id: "start-panel",
|
||||
ref: "overlay",
|
||||
children: [
|
||||
{
|
||||
el: "afx-hbox",
|
||||
height: 30,
|
||||
children: [
|
||||
{
|
||||
el: "div",
|
||||
width: 30,
|
||||
id: "searchicon",
|
||||
},
|
||||
{ el: "input", ref: "search" },
|
||||
],
|
||||
},
|
||||
{
|
||||
el: "afx-list-view",
|
||||
id: "applist",
|
||||
ref: "applist",
|
||||
},
|
||||
{
|
||||
el: "afx-hbox",
|
||||
id: "btlist",
|
||||
height: 30,
|
||||
children: [
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "btscreen",
|
||||
tooltip: __("ct:Toggle fullscreen"),
|
||||
},
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "btuser",
|
||||
tooltip: __(
|
||||
"ct:User: {0}",
|
||||
Ant.OS.setting.user.username
|
||||
),
|
||||
},
|
||||
{
|
||||
el: "afx-button",
|
||||
ref: "btlogout",
|
||||
tooltip: __("ct:Logout"),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh applications list on the spotlight widget
|
||||
* from system packages meta-data
|
||||
*
|
||||
* @private
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private refreshAppList(): void {
|
||||
let k: string, v: API.PackageMetaType;
|
||||
const list = [];
|
||||
for (k in Ant.OS.setting.system.packages) {
|
||||
v = Ant.OS.setting.system.packages[k];
|
||||
if (v && v.app) {
|
||||
list.push(v);
|
||||
}
|
||||
}
|
||||
for (k in Ant.OS.setting.system.menu) {
|
||||
v = Ant.OS.setting.system.menu[k];
|
||||
list.push(v);
|
||||
}
|
||||
list.sort(function (a, b) {
|
||||
if (a.text < b.text) {
|
||||
return -1;
|
||||
} else if (a.text > b.text) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
(this.refs.applist as ListViewTag).data = list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show/hide the spotlight
|
||||
*
|
||||
* @private
|
||||
* @param {boolean} flag
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
private toggle(flag: boolean): void {
|
||||
this._view = flag;
|
||||
if (flag) {
|
||||
$(this.refs.overlay).show();
|
||||
this.refreshAppList();
|
||||
|
||||
this.calibrate();
|
||||
$(document).on("click", this._cb);
|
||||
(this.refs.search as HTMLInputElement).value = "";
|
||||
$(this.refs.search).focus();
|
||||
} else {
|
||||
$(this.refs.overlay).hide();
|
||||
$(document).unbind("click", this._cb);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calibrate the spotlight widget
|
||||
*
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
(this.refs.overlay as OverlayTag).height = `${
|
||||
$(window).height() - $(this.refs.panel).height()
|
||||
}px`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag bind some basic event
|
||||
*
|
||||
* @protected
|
||||
* @memberof SystemPanelTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
(this.refs.osmenu as MenuTag).items = [this._osmenu];
|
||||
this._cb = (e) => {
|
||||
if (
|
||||
!$(e.target).closest($(this.refs.overlay)).length &&
|
||||
!$(e.target).closest(this.refs.osmenu).length
|
||||
) {
|
||||
return this.toggle(false);
|
||||
} else {
|
||||
return $(this.refs.search).focus();
|
||||
}
|
||||
};
|
||||
$(this.refs.appmenu).css("z-index", 1000000);
|
||||
$(this.refs.systray).css("z-index", 1000000);
|
||||
(this.refs.btscreen as ButtonTag).set({
|
||||
iconclass: "fa fa-tv",
|
||||
onbtclick: (e) => {
|
||||
this.toggle(false);
|
||||
return Ant.OS.GUI.toggleFullscreen();
|
||||
},
|
||||
});
|
||||
(this.refs.btuser as ButtonTag).set({
|
||||
iconclass: "fa fa-user-circle-o",
|
||||
onbtclick: (e) => {
|
||||
this.toggle(false);
|
||||
return Ant.OS.GUI.openDialog(
|
||||
"InfoDialog",
|
||||
Ant.OS.setting.user
|
||||
);
|
||||
},
|
||||
});
|
||||
(this.refs.btlogout as ButtonTag).set({
|
||||
iconclass: "fa fa-power-off",
|
||||
onbtclick: (e) => {
|
||||
this.toggle(false);
|
||||
return Ant.OS.exit();
|
||||
},
|
||||
});
|
||||
(this.refs.osmenu as MenuTag).onmenuselect = (e) => {
|
||||
return this.toggle(true);
|
||||
};
|
||||
|
||||
$(this.refs.search).keyup((e) => {
|
||||
return this.search(e);
|
||||
});
|
||||
|
||||
$(this.refs.applist).click((e) => {
|
||||
return this.open();
|
||||
});
|
||||
Ant.OS.GUI.bindKey("CTRL- ", (e) => {
|
||||
if (this._view === false) {
|
||||
return this.toggle(true);
|
||||
} else {
|
||||
return this.toggle(false);
|
||||
}
|
||||
});
|
||||
Ant.OS.announcer.trigger("syspanelloaded", undefined);
|
||||
$(this.refs.overlay)
|
||||
.css("left", 0)
|
||||
.css("top", `${$(this.refs.panel).height()}px`)
|
||||
.css("bottom", "0")
|
||||
.hide();
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-sys-panel", SystemPanelTag);
|
||||
}
|
||||
}
|
||||
}
|
204
src/core/tags/TabBarTag.ts
Normal file
204
src/core/tags/TabBarTag.ts
Normal file
@ -0,0 +1,204 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Tag event data type definition
|
||||
*/
|
||||
type TabEventData = TagEventDataType<ListViewItemTag>;
|
||||
/**
|
||||
* a TabBar allows to control a collection of tabs
|
||||
*
|
||||
* @export
|
||||
* @class TabBarTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class TabBarTag extends AFXTag {
|
||||
/**
|
||||
* Placeholder of currently selected tab index
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
private _selected: number;
|
||||
|
||||
/**
|
||||
* Placeholder of tab close event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
private _ontabclose: (e: TagEventType<TabEventData>) => boolean;
|
||||
|
||||
/**
|
||||
* Placeholder of tab select event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<TabEventData>}
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
private _ontabselect: TagEventCallback<TabEventData>;
|
||||
|
||||
/**
|
||||
*Creates an instance of TabBarTag.
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._ontabclose = (e) => true;
|
||||
this._ontabselect = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.selected = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Enable/disable a tab to be closed
|
||||
*
|
||||
* Getter: Check whether tabs can be closed
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
set closable(v: boolean) {
|
||||
this.attsw(v, "closable");
|
||||
}
|
||||
get closable(): boolean {
|
||||
return this.hasattr("closable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tab in the end of the tab bar
|
||||
*
|
||||
* @param {GenericObject<any>} item tab data
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
push(item: GenericObject<any>): ListViewItemTag {
|
||||
item.closable = this.closable;
|
||||
return (this.refs.list as ListViewTag).push(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a tab
|
||||
*
|
||||
* @param {ListViewItemTag} el reference to DOM element of a tab
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
delete(el: ListViewItemTag) {
|
||||
(this.refs.list as ListViewTag).delete(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a tab to the beginning of the tab bar
|
||||
*
|
||||
* @param {GenericObject<any>} item tab data
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
unshift(item: GenericObject<any>): ListViewItemTag {
|
||||
item.closable = this.closable;
|
||||
return (this.refs.list as ListViewTag).unshift(item);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set tabs data
|
||||
*
|
||||
* Getter: Get all tabs data
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
set items(v: GenericObject<any>[]) {
|
||||
for (let i of v) {
|
||||
i.closable = this.closable;
|
||||
}
|
||||
(this.refs.list as ListViewTag).data = v;
|
||||
}
|
||||
get items(): GenericObject<any>[] {
|
||||
return (this.refs.list as ListViewTag).data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Select a tab by its index
|
||||
*
|
||||
* Getter: Get the currently selected tab
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
set selected(v: number | number[]) {
|
||||
(this.refs.list as ListViewTag).selected = v;
|
||||
}
|
||||
get selected(): number | number[] {
|
||||
return (this.refs.list as ListViewTag).selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab close event handle
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
set ontabclose(v: (e: TagEventType<TabEventData>) => boolean) {
|
||||
this._ontabclose = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab select event handle
|
||||
*
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
set ontabselect(v: TagEventCallback<TabEventData>) {
|
||||
this._ontabselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tab bar and bind some basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.list).css("height", "100%");
|
||||
(this.refs.list as ListViewTag).onitemclose = (e) => {
|
||||
e.id = this.aid;
|
||||
return this._ontabclose(e);
|
||||
};
|
||||
(this.refs.list as ListViewTag).onlistselect = (e) => {
|
||||
this._ontabselect(e);
|
||||
return this.observable.trigger("tabselect", e);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* TabBar layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof TabBarTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "afx-list-view",
|
||||
ref: "list",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-tab-bar", TabBarTag);
|
||||
}
|
||||
}
|
||||
}
|
245
src/core/tags/TabContainerTag.ts
Normal file
245
src/core/tags/TabContainerTag.ts
Normal file
@ -0,0 +1,245 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
/**
|
||||
* Tab container data type definition
|
||||
*
|
||||
* @export
|
||||
* @interface TabContainerTabType
|
||||
*/
|
||||
export interface TabContainerTabType {
|
||||
/**
|
||||
* Reference to the DOM element of the current container
|
||||
*
|
||||
* @type {HTMLElement}
|
||||
* @memberof TabContainerTabType
|
||||
*/
|
||||
container: HTMLElement;
|
||||
|
||||
[propName: string]: any;
|
||||
}
|
||||
export namespace tag {
|
||||
/**
|
||||
* A tab container allows to attach each tab on a [[TabBarTag]]
|
||||
* with a container widget. The attached container widget should be
|
||||
* composed inside a [[HBoxTag]]
|
||||
*
|
||||
* The tab bar in a tab container can be configured to display tabs
|
||||
* in horizontal (row) or vertical (column) order. Default to vertical order
|
||||
*
|
||||
* Once a tab is selected, its attached container will be shown
|
||||
*
|
||||
* @export
|
||||
* @class TabContainerTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class TabContainerTag extends AFXTag {
|
||||
/**
|
||||
* Reference to the currently selected tab DOM element
|
||||
*
|
||||
* @private
|
||||
* @type {TabContainerTabType}
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
private _selectedTab: TabContainerTabType;
|
||||
|
||||
/**
|
||||
* Placeholder of the tab select event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<TabContainerTabType>}
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
private _ontabselect: TagEventCallback<TabContainerTabType>;
|
||||
|
||||
/**
|
||||
*Creates an instance of TabContainerTag.
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
this._ontabselect = (e) => {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tab bar direction to vertical (column)
|
||||
*
|
||||
* @protected
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.dir = "column"; // or row
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Set the tab select event handle
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set ontabselect(f: TagEventCallback<TabContainerTabType>) {
|
||||
this._ontabselect = f;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the tab bar direction:
|
||||
* - `row`: horizontal direction
|
||||
* - `column`: vertical direction
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the tab bar direction
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set dir(v: "row" | "column") {
|
||||
$(this).attr("dir", v);
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
(this.refs.wrapper as TileLayoutTag).dir = v;
|
||||
}
|
||||
get dir(): "row" | "column" {
|
||||
return $(this).attr("dir") as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Select a tab using the its tab data type.
|
||||
* This will show the attached container to the tab
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the tab data of the currently selected Tab
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set selectedTab(v: TabContainerTabType) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
const selected = this._selectedTab;
|
||||
this._selectedTab = v;
|
||||
if (selected) {
|
||||
$(selected.container).hide();
|
||||
}
|
||||
$(v.container).show();
|
||||
this.observable.trigger("resize", undefined);
|
||||
}
|
||||
get selectedTab(): TabContainerTabType {
|
||||
return this._selectedTab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab bar width, this function only
|
||||
* works when the tab bar direction is set to
|
||||
* `row`
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set tabbarwidth(v: number) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this.refs.bar).attr("data-width", `${v}`);
|
||||
(this.refs.wrapper as TileLayoutTag).calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tab bar height, this function only works
|
||||
* when the tab bar direction is set to `column`
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
set tabbarheight(v: number) {
|
||||
$(this.refs.bar).attr("data-height", `${v}`);
|
||||
(this.refs.wrapper as TileLayoutTag).calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag and bind basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
(this.refs.bar as TabBarTag).ontabselect = (e) => {
|
||||
const data = (e.data.item as ListViewItemTag)
|
||||
.data as TabContainerTabType;
|
||||
this.selectedTab = data;
|
||||
return this._ontabselect({ data: data, id: this.aid });
|
||||
};
|
||||
this.observable.one("mounted", (id) => {
|
||||
$(this.refs.yield)
|
||||
.children()
|
||||
.each((i, e) => {
|
||||
const item = {} as GenericObject<any>;
|
||||
if ($(e).attr("tabname")) {
|
||||
item.text = $(e).attr("tabname");
|
||||
}
|
||||
if ($(e).attr("icon")) {
|
||||
item.icon = $(e).attr("icon");
|
||||
}
|
||||
if ($(e).attr("iconclass")) {
|
||||
item.iconclass = $(e).attr("iconclass");
|
||||
}
|
||||
item.container = e;
|
||||
$(e)
|
||||
.css("width", "100%")
|
||||
.css("height", "100%")
|
||||
.hide();
|
||||
const el = (this.refs.bar as TabBarTag).push(
|
||||
item
|
||||
);
|
||||
el.selected = true;
|
||||
});
|
||||
});
|
||||
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* calibrate the tab container
|
||||
*
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
$(this.refs.wrapper).css("height", `${$(this).height()}px`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof TabContainerTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "afx-tile",
|
||||
ref: "wrapper",
|
||||
children: [
|
||||
{ el: "afx-tab-bar", ref: "bar" },
|
||||
{ el: "div", ref: "yield" },
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-tab-container", TabContainerTag);
|
||||
}
|
||||
}
|
||||
}
|
298
src/core/tags/TileLayoutTags.ts
Normal file
298
src/core/tags/TileLayoutTags.ts
Normal file
@ -0,0 +1,298 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A tile layout organize it child elements
|
||||
* in a fixed horizontal or vertical direction.
|
||||
*
|
||||
* The size of each child element is attributed based
|
||||
* on its configuration of automatically based on the
|
||||
* remaining space in the layout
|
||||
*
|
||||
*
|
||||
* @export
|
||||
* @class TileLayoutTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class TileLayoutTag extends AFXTag {
|
||||
/**
|
||||
*C reates an instance of TileLayoutTag.
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
protected init(): void {}
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Set the name of the tile container, should be: `hbox` or `vbox`
|
||||
*
|
||||
* Getter: Get the name of the tile container
|
||||
*
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
set name(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this).attr("name", v);
|
||||
$(this.refs.yield)
|
||||
.removeClass()
|
||||
.addClass(`afx-${v}-container`);
|
||||
this.calibrate();
|
||||
}
|
||||
get name(): string {
|
||||
return $(this).attr("name");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* SET the layout direction, should be:
|
||||
* - `row`: horizontal direction
|
||||
* - `column`: vertical direction
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get layout direction
|
||||
*
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
set dir(v: "row" | "column") {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this).attr("dir", v);
|
||||
$(this.refs.yield).css("flex-direction", v);
|
||||
this.calibrate();
|
||||
}
|
||||
get dir(): "row" | "column" {
|
||||
return $(this).attr("dir") as any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the element
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this).css("display", "block");
|
||||
$(this.refs.yield)
|
||||
.css("display", "flex")
|
||||
.css("width", "100%")
|
||||
.css("height", "100%");
|
||||
this.observable.on("resize", (e) => this.calibrate());
|
||||
return this.calibrate();
|
||||
}
|
||||
|
||||
/**
|
||||
* re-organize the layout
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
calibrate(): void {
|
||||
if (this.dir === "row") {
|
||||
return this.hcalibrate();
|
||||
}
|
||||
if (this.dir === "column") {
|
||||
return this.vcalibrate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Organize the layout in horizontal direction, only work when
|
||||
* the layout direction set to horizontal
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
private hcalibrate(): void {
|
||||
const auto_width = [];
|
||||
let ocwidth = 0;
|
||||
const avaiheight = $(this).height();
|
||||
const avaiWidth = $(this).width();
|
||||
//$(this.refs.yield).css("height", `${avaiheight}px`);
|
||||
$(this.refs.yield)
|
||||
.children()
|
||||
.each(function (e) {
|
||||
$(this).css("height", "100%");
|
||||
let attv = $(this).attr("data-width");
|
||||
let dw = 0;
|
||||
if (attv && attv !== "grow") {
|
||||
if (attv[attv.length - 1] === "%") {
|
||||
dw =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiWidth) /
|
||||
100;
|
||||
} else {
|
||||
dw = parseInt(attv);
|
||||
}
|
||||
$(this).css("width", `${dw}px`);
|
||||
ocwidth += dw;
|
||||
} else {
|
||||
$(this).css("flex-grow", "1");
|
||||
auto_width.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
const csize = (avaiWidth - ocwidth) / auto_width.length;
|
||||
if (csize > 0) {
|
||||
$.each(auto_width, (i, v) =>
|
||||
$(v).css("width", `${csize}px`)
|
||||
);
|
||||
}
|
||||
return this.observable.trigger("hboxchange", {
|
||||
id: this.aid,
|
||||
data: { w: avaiWidth, h: avaiheight },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Organize the layout in vertical direction, only work when
|
||||
* the layout direction set to vertical
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
private vcalibrate(): void {
|
||||
const auto_height = [];
|
||||
let ocheight = 0;
|
||||
const avaiheight = $(this).height();
|
||||
const avaiwidth = $(this).width();
|
||||
//$(this.refs.yield).css("height", `${avaiheight}px`);
|
||||
$(this.refs.yield)
|
||||
.children()
|
||||
.each(function (e) {
|
||||
let dh = 0;
|
||||
$(this).css("width", "100%");
|
||||
let attv = $(this).attr("data-height");
|
||||
if (attv && attv !== "grow") {
|
||||
if (attv[attv.length - 1] === "%") {
|
||||
dh =
|
||||
(parseInt(attv.slice(0, -1)) *
|
||||
avaiheight) /
|
||||
100;
|
||||
} else {
|
||||
dh = parseInt(attv);
|
||||
}
|
||||
$(this).css("height", `${dh}px`);
|
||||
ocheight += dh;
|
||||
} else {
|
||||
$(this).css("flex-grow", "1");
|
||||
auto_height.push(this);
|
||||
}
|
||||
});
|
||||
|
||||
const csize = (avaiheight - ocheight) / auto_height.length;
|
||||
if (csize > 0) {
|
||||
$.each(auto_height, (i, v) =>
|
||||
$(v).css("height", `${csize}px`)
|
||||
);
|
||||
}
|
||||
|
||||
return this.observable.trigger("vboxchange", {
|
||||
id: this.aid,
|
||||
data: { w: avaiwidth, h: avaiheight },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition
|
||||
*
|
||||
* @returns
|
||||
* @memberof TileLayoutTag
|
||||
*/
|
||||
layout() {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
ref: "yield",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A HBox organize its child elements in horizontal direction
|
||||
*
|
||||
* @export
|
||||
* @class HBoxTag
|
||||
* @extends {TileLayoutTag}
|
||||
*/
|
||||
export class HBoxTag extends TileLayoutTag {
|
||||
/**
|
||||
* Creates an instance of HBoxTag.
|
||||
* @memberof HBoxTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof HBoxTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
super.mount();
|
||||
this.dir = "row";
|
||||
this.name = "hbox";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A VBox organize its child elements in vertical direction
|
||||
*
|
||||
* @export
|
||||
* @class VBoxTag
|
||||
* @extends {TileLayoutTag}
|
||||
*/
|
||||
export class VBoxTag extends TileLayoutTag {
|
||||
/**
|
||||
*Creates an instance of VBoxTag.
|
||||
* @memberof VBoxTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof VBoxTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
super.mount();
|
||||
this.dir = "column";
|
||||
this.name = "vbox";
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-tile", TileLayoutTag);
|
||||
define("afx-hbox", HBoxTag);
|
||||
define("afx-vbox", VBoxTag);
|
||||
}
|
||||
}
|
||||
}
|
993
src/core/tags/TreeViewTag.ts
Normal file
993
src/core/tags/TreeViewTag.ts
Normal file
@ -0,0 +1,993 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* Tree view data type definition
|
||||
*
|
||||
* @export
|
||||
* @interface TreeViewDataType
|
||||
*/
|
||||
export interface TreeViewDataType {
|
||||
/**
|
||||
* The child nodes data of the current tree node
|
||||
*
|
||||
* @type {TreeViewDataType[]}
|
||||
* @memberof TreeViewDataType
|
||||
*/
|
||||
nodes?: TreeViewDataType[];
|
||||
|
||||
/**
|
||||
* Boolean indicates whether the current node is opened.
|
||||
* Only work when the current node is not a leaf node
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof TreeViewDataType
|
||||
*/
|
||||
open?: boolean;
|
||||
|
||||
/**
|
||||
* The node's path from the root node
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TreeViewDataType
|
||||
*/
|
||||
path?: string;
|
||||
|
||||
/**
|
||||
* Indicates whether this node should be selected
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof TreeViewDataType
|
||||
*/
|
||||
selected?: boolean;
|
||||
[propName: string]: any;
|
||||
}
|
||||
/**
|
||||
* Tree node event data type definition
|
||||
*/
|
||||
export type TreeItemEventData = TagEventDataType<
|
||||
TreeViewItemPrototype
|
||||
>;
|
||||
/**
|
||||
* Abstract prototype of a tree node. All tree node definition should
|
||||
* extend this class
|
||||
*
|
||||
* @class TreeViewItemPrototype
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export abstract class TreeViewItemPrototype extends AFXTag {
|
||||
/**
|
||||
* Node data placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {TreeViewDataType}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
private _data: TreeViewDataType;
|
||||
|
||||
/**
|
||||
* Placeholder for the indent level of the current node from root node
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
private _indent: number;
|
||||
|
||||
/**
|
||||
* private event object used by current node event
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventType<TreeItemEventData>}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
private _evt: TagEventType<TreeItemEventData>;
|
||||
|
||||
/**
|
||||
* Reference to the root node
|
||||
*
|
||||
* @type {TreeViewTag}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
treeroot: TreeViewTag;
|
||||
|
||||
/**
|
||||
* The tree path from the root node
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
treepath: string;
|
||||
|
||||
/**
|
||||
* Reference to the parent node of the current node
|
||||
*
|
||||
* @type {TreeViewTag}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
parent: TreeViewTag;
|
||||
|
||||
/**
|
||||
* Placeholder for the `fetch` function of the node.
|
||||
* This function is used to fetch the child nodes of the
|
||||
* current nodes. This function should return a promise on
|
||||
* a list of [[TreeViewDataType]]
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
fetch: (
|
||||
d: TreeViewItemPrototype
|
||||
) => Promise<TreeViewDataType[]>;
|
||||
/**
|
||||
*Creates an instance of TreeViewItemPrototype.
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the tree, this function
|
||||
* is used to refresh/expand/collapse the
|
||||
* current node based on the input parameter
|
||||
*
|
||||
* @protected
|
||||
* @param {*} p string indication, the value should be:
|
||||
* - `expand`: expand the current node
|
||||
* - `collapse`: collapse the current node
|
||||
* - other string: this string is considered as a tree path of a node. If this value
|
||||
* is the value of current node tree path, the node will be refreshed. Otherwise, nothing
|
||||
* happens
|
||||
* @returns {void}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
protected reload(p: any): void {
|
||||
if (!p || typeof p !== "string") {
|
||||
return;
|
||||
}
|
||||
switch (p) {
|
||||
case "expand":
|
||||
this.open = true;
|
||||
break;
|
||||
case "collapse":
|
||||
this.open = false;
|
||||
break;
|
||||
default:
|
||||
if (p !== this.treepath) {
|
||||
return;
|
||||
}
|
||||
this.open = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the data of the current node. This will trigger the
|
||||
* [[ondatachange]] function
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the current node's data
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
set data(v: TreeViewDataType) {
|
||||
this._data = v;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.open = v.open;
|
||||
if (v.path) {
|
||||
this.treepath = v.path;
|
||||
}
|
||||
this.selected = v.selected;
|
||||
v.domel = this;
|
||||
this.ondatachange();
|
||||
}
|
||||
get data(): TreeViewDataType {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Select or unselect the current node.
|
||||
* This will trigger the item select event
|
||||
* on the tree root if the parameter is `true`
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the current node is selected
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
set selected(v: boolean) {
|
||||
if (!this._data) {
|
||||
return;
|
||||
}
|
||||
this.attsw(v, "selected");
|
||||
$(this.refs.wrapper).removeClass();
|
||||
this._data.selected = v;
|
||||
if (v) {
|
||||
this.treeroot.unselect();
|
||||
// set selectedItem but not trigger the update
|
||||
this.treeroot.itemclick(this._evt);
|
||||
this._evt.data.dblclick = false;
|
||||
$(this.refs.wrapper).addClass("afx_tree_item_selected");
|
||||
}
|
||||
}
|
||||
get selected(): boolean {
|
||||
return this.hasattr("selected");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Refresh the current node and expands its sub tree.
|
||||
* This function only works if the current node is not
|
||||
* a leaf node
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Check whether the current node is expanded
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
set open(v: boolean) {
|
||||
if (!this.is_folder()) {
|
||||
return;
|
||||
}
|
||||
this.attsw(v, "open");
|
||||
$(this.refs.toggle).removeClass();
|
||||
if (v) {
|
||||
if (this.fetch) {
|
||||
this.fetch(this)
|
||||
.then((d: TreeViewDataType[]) => {
|
||||
if (!d) {
|
||||
return;
|
||||
}
|
||||
return (this.nodes = d);
|
||||
})
|
||||
.catch((e: Error) =>
|
||||
announcer.oserror(e.toString(), e)
|
||||
);
|
||||
} else {
|
||||
this.nodes = this.nodes;
|
||||
}
|
||||
$(this.refs.childnodes).show();
|
||||
} else {
|
||||
$(this.refs.childnodes).hide();
|
||||
}
|
||||
if (v) {
|
||||
$(this.refs.toggle).addClass(
|
||||
"afx-tree-view-folder-open"
|
||||
);
|
||||
} else {
|
||||
$(this.refs.toggle).addClass(
|
||||
"afx-tree-view-folder-close"
|
||||
);
|
||||
}
|
||||
}
|
||||
get open(): boolean {
|
||||
return this.hasattr("open");
|
||||
}
|
||||
/**
|
||||
* Setter: Set the current indent level of this node from the root node
|
||||
*
|
||||
* Getter: Get the current indent level
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
get indent(): number {
|
||||
return this._indent;
|
||||
}
|
||||
set indent(v: number) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this._indent = v;
|
||||
$(this.refs.padding)
|
||||
.css("display", "inline-block")
|
||||
.css("height", "1px")
|
||||
.css("padding", 0)
|
||||
.css("margin", 0)
|
||||
.css("background-color", "transparent")
|
||||
.css("width", v * 15 + "px");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current node is not a leaf node
|
||||
*
|
||||
* @private
|
||||
* @returns {boolean}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
private is_folder(): boolean {
|
||||
if (this.nodes) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter: Get the child nodes data of the current node
|
||||
*
|
||||
* Setter: Set the child nodes data of the current node
|
||||
*
|
||||
* @type {TreeViewDataType[]}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
get nodes(): TreeViewDataType[] {
|
||||
if (!this._data) return undefined;
|
||||
return this._data.nodes;
|
||||
}
|
||||
set nodes(nodes: TreeViewDataType[]) {
|
||||
if (!nodes || !this.data) {
|
||||
return;
|
||||
}
|
||||
this._data.nodes = nodes;
|
||||
// return unless @get("nodes") and @get("nodes").length > 0
|
||||
$(this.refs.childnodes).empty();
|
||||
$(this.refs.wrapper).addClass("afx_folder_item");
|
||||
const root = this.treeroot;
|
||||
const result = [];
|
||||
for (let v of nodes) {
|
||||
const el = $("<afx-tree-view>").appendTo(
|
||||
this.refs.childnodes
|
||||
);
|
||||
el[0].uify(this.observable);
|
||||
const element = el[0] as TreeViewTag;
|
||||
element.treeroot = root;
|
||||
element.indent = this.indent + 1;
|
||||
element.open = this.open;
|
||||
element.parent = this.parent;
|
||||
element.treepath = `${this.treepath}/${element.aid}`;
|
||||
element.fetch = this.fetch;
|
||||
element.data = v;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tag with default properties data
|
||||
*
|
||||
* @protected
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
protected init(): void {
|
||||
this.treeroot = undefined;
|
||||
this.treepath = this.aid.toString();
|
||||
this._evt = {
|
||||
id: this.aid,
|
||||
data: { item: this, dblclick: false },
|
||||
};
|
||||
this._indent = 0;
|
||||
}
|
||||
/**
|
||||
* Mount the tag and bind basic events
|
||||
*
|
||||
* @protected
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
protected mount(): void {
|
||||
$(this.refs.container)
|
||||
.css("padding", 0)
|
||||
.css("margin", 0)
|
||||
.css("white-space", "nowrap");
|
||||
$(this.refs.itemholder).css("display", "inline-block");
|
||||
$(this.refs.wrapper).click((e) => {
|
||||
this.selected = true;
|
||||
});
|
||||
$(this.refs.wrapper).dblclick((e) => {
|
||||
this._evt.data.dblclick = true;
|
||||
this.selected = true;
|
||||
});
|
||||
|
||||
$(this.refs.toggle)
|
||||
.css("display", "inline-block")
|
||||
.css("width", "15px")
|
||||
.addClass("afx-tree-view-item")
|
||||
.click((e) => {
|
||||
this.open = !this.open;
|
||||
e.preventDefault();
|
||||
return e.stopPropagation();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of a node. This function
|
||||
* returns the definition of the base outer layout
|
||||
* of a node. Custom inner layout of the node should
|
||||
* be defined in the [[itemlayout]] function
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
ref: "wrapper",
|
||||
children: [
|
||||
{
|
||||
el: "ul",
|
||||
ref: "container",
|
||||
children: [
|
||||
{ el: "li", ref: "padding" },
|
||||
{ el: "li", ref: "toggle" },
|
||||
{
|
||||
el: "li",
|
||||
ref: "itemholder",
|
||||
class: "itemname",
|
||||
children: this.itemlayout(),
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
el: "ul",
|
||||
ref: "childnodes",
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function need to be implemented by all subclasses
|
||||
* to define the inner layout of the node
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
protected abstract itemlayout(): TagLayoutType[];
|
||||
|
||||
/**
|
||||
* This function is called when the node data change.
|
||||
* It needs to be implemented on all subclasses of this
|
||||
* class
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
protected abstract ondatachange(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* SimpleTreeViewItem extends [[TreeViewItemPrototype]] and
|
||||
* define it inner layout using a [[LabelTag]]
|
||||
*
|
||||
* @export
|
||||
* @class SimpleTreeViewItem
|
||||
* @extends {TreeViewItemPrototype}
|
||||
*/
|
||||
export class SimpleTreeViewItem extends TreeViewItemPrototype {
|
||||
/**
|
||||
*Creates an instance of SimpleTreeViewItem.
|
||||
* @memberof SimpleTreeViewItem
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the label when data changed
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof SimpleTreeViewItem
|
||||
*/
|
||||
protected ondatachange(): void {
|
||||
if (!this.data) {
|
||||
return;
|
||||
}
|
||||
const v = this.data;
|
||||
const label = this.refs.label as LabelTag;
|
||||
label.set(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns
|
||||
* @memberof SimpleTreeViewItem
|
||||
*/
|
||||
protected itemlayout() {
|
||||
return [{ el: "afx-label", ref: "label" }];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A tree view widget presents a hierarchical list of nodes.
|
||||
*
|
||||
* @export
|
||||
* @class TreeViewTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class TreeViewTag extends AFXTag {
|
||||
/**
|
||||
* Reference to the selected node
|
||||
*
|
||||
* @private
|
||||
* @type {TreeViewItemPrototype}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _selectedItem: TreeViewItemPrototype;
|
||||
|
||||
/**
|
||||
* Placeholder for tree select event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<TreeItemEventData>}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _ontreeselect: TagEventCallback<TreeItemEventData>;
|
||||
|
||||
/**
|
||||
* Place holder for tree double click event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<TreeItemEventData>}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _ontreedbclick: TagEventCallback<TreeItemEventData>;
|
||||
|
||||
/**
|
||||
* Placeholder for drag and drop event handle
|
||||
*
|
||||
* @private
|
||||
* @type {TagEventCallback<DnDEventDataType<TreeViewTag>>}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _ondragndrop: TagEventCallback<
|
||||
DnDEventDataType<TreeViewTag>
|
||||
>;
|
||||
|
||||
/**
|
||||
* Tree data placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {TreeViewDataType}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _data: TreeViewDataType;
|
||||
|
||||
/**
|
||||
* Placeholder for private dragndrop mouse down event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _treemousedown: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
/**
|
||||
* Placeholder for private dragndrop mouse up event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _treemouseup: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
/**
|
||||
* Placeholder for private dragndrop mouse move event handle
|
||||
*
|
||||
* @private
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _treemousemove: (e: JQuery.MouseEventBase) => void;
|
||||
|
||||
/**
|
||||
* Private data object passing between dragndrop mouse event
|
||||
*
|
||||
* @private
|
||||
* @type {{ from: TreeViewTag; to: TreeViewTag }}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
private _dnd: { from: TreeViewTag; to: TreeViewTag };
|
||||
|
||||
/**
|
||||
* Reference to parent tree of the current tree.
|
||||
* This value is undefined if the current tree is the root
|
||||
*
|
||||
* @type {TreeViewTag}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
parent: TreeViewTag;
|
||||
|
||||
/**
|
||||
* Reference to the root tree, this value is undefined
|
||||
* if the curent tree is root
|
||||
*
|
||||
* @type {TreeViewTag}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
treeroot: TreeViewTag;
|
||||
|
||||
/**
|
||||
* tree path of the current tree from the root
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
treepath: string;
|
||||
|
||||
/**
|
||||
* Indent level of the current tree
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
indent: number;
|
||||
|
||||
/**
|
||||
* Indicates whether the tree should be expanded
|
||||
*
|
||||
* @type {boolean}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
open: boolean;
|
||||
/**
|
||||
* Placeholder for the `fetch` function of the tree.
|
||||
* This function is used to fetch the child nodes of the
|
||||
* current tree. This function should return a promise on
|
||||
* a list of [[TreeViewDataType]]
|
||||
*
|
||||
* @memberof TreeViewItemPrototype
|
||||
*/
|
||||
fetch: (
|
||||
d: TreeViewItemPrototype
|
||||
) => Promise<TreeViewDataType[]>;
|
||||
|
||||
/**
|
||||
*Creates an instance of TreeViewTag.
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the tree view before mounting:
|
||||
*
|
||||
* @protected
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this.itemtag = "afx-tree-view-item";
|
||||
this._ontreeselect = this._ondragndrop = this._ontreedbclick = (
|
||||
e
|
||||
) => {};
|
||||
|
||||
this.indent = 0;
|
||||
this.open = true;
|
||||
this.treepath = this.aid.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
/**
|
||||
* Setter: Enable/disable drag and drop event on the tree
|
||||
*
|
||||
* Getter: Check whether the drag and drop event is enabled
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
set dragndrop(v: boolean) {
|
||||
this.attsw(v, "dragndrop");
|
||||
}
|
||||
get dragndrop(): boolean {
|
||||
return this.hasattr("dragndrop");
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tree select event handle
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
set ontreeselect(v: TagEventCallback<TreeItemEventData>) {
|
||||
this._ontreeselect = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the tree double click event handle
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
set ontreedbclick(v: TagEventCallback<TreeItemEventData>) {
|
||||
this._ontreedbclick = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the default tag name of the tree node.
|
||||
* If there is no tag name in the node data,
|
||||
* this value will be used when creating node.
|
||||
*
|
||||
* Defaut to `afx-tree-view-item`
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the default node tag name
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
set itemtag(v: string) {
|
||||
$(this).attr("itemtag", v);
|
||||
}
|
||||
get itemtag(): string {
|
||||
return $(this).attr("itemtag");
|
||||
}
|
||||
|
||||
/**
|
||||
* Unselect the selected element in the tree
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
unselect(): void {
|
||||
if (this.selectedItem) {
|
||||
this._selectedItem.selected = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the selected node using its DOM element
|
||||
*
|
||||
* Getter: Get the DOM element of the selected node
|
||||
*
|
||||
* @type {TreeViewItemPrototype}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
get selectedItem(): TreeViewItemPrototype {
|
||||
return this._selectedItem;
|
||||
}
|
||||
set selectedItem(v: TreeViewItemPrototype) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
if (v === this.selectedItem) {
|
||||
return;
|
||||
}
|
||||
v.selected = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand all nodes in the tree
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
expandAll(): void {
|
||||
if (this.is_leaf()) {
|
||||
return;
|
||||
}
|
||||
return this.update("expand");
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse all nodes in the tree
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
collapseAll(): void {
|
||||
if (this.is_leaf()) {
|
||||
return;
|
||||
}
|
||||
return this.update("collapse");
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will trigger the tree select or tree double click
|
||||
* event
|
||||
*
|
||||
* @param {TagEventType} e
|
||||
* @returns {void}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
itemclick(e: TagEventType<TreeItemEventData>): void {
|
||||
if (!e || !e.data) {
|
||||
return;
|
||||
}
|
||||
if (e.data.item === this.selectedItem && !e.data.dblclick) {
|
||||
return;
|
||||
}
|
||||
this._selectedItem = e.data.item;
|
||||
const evt = { id: this.aid, data: e.data };
|
||||
if (e.data.dblclick) {
|
||||
this._ontreedbclick(evt);
|
||||
return this.observable.trigger("treedbclick", evt);
|
||||
} else {
|
||||
this._ontreeselect(evt);
|
||||
return this.observable.trigger("treeselect", evt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current tree is a root tree
|
||||
*
|
||||
* @returns {boolean}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
is_root(): boolean {
|
||||
return this.treeroot === undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the current tree tag is a leaf
|
||||
*
|
||||
* @returns {boolean}
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
is_leaf(): boolean {
|
||||
const data = this.data;
|
||||
if (!data) {
|
||||
return true;
|
||||
}
|
||||
if (data.nodes) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set drag and drop event handle
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
set ondragndrop(
|
||||
v: TagEventCallback<DnDEventDataType<TreeViewTag>>
|
||||
) {
|
||||
this._ondragndrop = v;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter:
|
||||
*
|
||||
* Set the tree data. This operation will create
|
||||
* all tree node elements of the current tree
|
||||
*
|
||||
* Getter:
|
||||
*
|
||||
* Get the tree data
|
||||
*
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
set data(v: TreeViewDataType) {
|
||||
this._selectedItem = undefined;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this._data = v;
|
||||
$(this).empty();
|
||||
if (v.path) {
|
||||
this.treepath = v.path;
|
||||
}
|
||||
let tag = this.itemtag;
|
||||
if (v.tag) {
|
||||
tag = v.tag;
|
||||
}
|
||||
const el = $(`<${tag}>`).appendTo(this);
|
||||
el[0].uify(this.observable);
|
||||
const element = el[0] as TreeViewItemPrototype;
|
||||
element.treeroot = this.is_root() ? this : this.treeroot;
|
||||
element.indent = this.indent;
|
||||
element.treepath = this.treepath;
|
||||
element.open = this.open;
|
||||
element.fetch = this.fetch;
|
||||
element.parent = this;
|
||||
element.data = v;
|
||||
if (this.is_root()) {
|
||||
$(this).off("mousedown", this._treemousedown);
|
||||
if (this.dragndrop) {
|
||||
$(this).on("mousedown", this._treemousedown);
|
||||
}
|
||||
}
|
||||
}
|
||||
get data(): TreeViewDataType {
|
||||
return this._data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the tree view
|
||||
*
|
||||
* @protected
|
||||
* @memberof TreeViewTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
this._treemousedown = (e) => {
|
||||
let obj: any = $(e.target).closest("afx-tree-view");
|
||||
if (obj.length === 0) {
|
||||
return;
|
||||
}
|
||||
let el = obj[0] as TreeViewTag;
|
||||
if (el === this) {
|
||||
return;
|
||||
}
|
||||
this._dnd.from = el;
|
||||
this._dnd.to = undefined;
|
||||
$(window).on("mouseup", this._treemouseup);
|
||||
return $(window).on("mousemove", this._treemousemove);
|
||||
};
|
||||
|
||||
this._treemouseup = (e) => {
|
||||
$(window).off("mouseup", this._treemouseup);
|
||||
$(window).off("mousemove", this._treemousemove);
|
||||
$("#systooltip").hide();
|
||||
let obj = $(e.target).closest("afx-tree-view");
|
||||
if (obj.length === 0) {
|
||||
return;
|
||||
}
|
||||
let el = obj[0] as TreeViewTag;
|
||||
if (el.is_leaf()) {
|
||||
el = el.parent;
|
||||
}
|
||||
if (
|
||||
el === this._dnd.from ||
|
||||
el === this._dnd.from.parent
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this._dnd.to = el;
|
||||
this._ondragndrop({
|
||||
id: this.aid,
|
||||
data: this._dnd,
|
||||
});
|
||||
this._dnd = {
|
||||
from: undefined,
|
||||
to: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
this._treemousemove = (e) => {
|
||||
if (!e) {
|
||||
return;
|
||||
}
|
||||
if (!this._dnd.from) {
|
||||
return;
|
||||
}
|
||||
const data = this._dnd.from.data;
|
||||
const $label = $("#systooltip");
|
||||
const top = e.clientY + 5;
|
||||
const left = e.clientX + 5;
|
||||
$label.show();
|
||||
const label = $label[0] as LabelTag;
|
||||
label.set(data);
|
||||
$label.css("top", top + "px").css("left", left + "px");
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-tree-view", TreeViewTag);
|
||||
define("afx-tree-view-item", SimpleTreeViewItem);
|
||||
}
|
||||
}
|
||||
}
|
532
src/core/tags/WindowTag.ts
Normal file
532
src/core/tags/WindowTag.ts
Normal file
@ -0,0 +1,532 @@
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
export namespace tag {
|
||||
/**
|
||||
* A WindowTag represents a virtual window element
|
||||
* used by AntOS applications and dialogs.
|
||||
*
|
||||
* @export
|
||||
* @class WindowTag
|
||||
* @extends {AFXTag}
|
||||
*/
|
||||
export class WindowTag extends AFXTag {
|
||||
/**
|
||||
* The element ID of the virtual desktop element
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
desktop: string;
|
||||
|
||||
/**
|
||||
* Window width placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _width: number;
|
||||
|
||||
/**
|
||||
* Window height placeholder
|
||||
*
|
||||
* @private
|
||||
* @type {number}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _height: number;
|
||||
|
||||
/**
|
||||
* Placeholder indicates whether the current window is shown
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _shown: boolean;
|
||||
|
||||
/**
|
||||
* Placeholder indicates whether the current window is maximized
|
||||
*
|
||||
* @private
|
||||
* @type {boolean}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _isMaxi: boolean;
|
||||
|
||||
/**
|
||||
* This placeholder stores the latest offset of the current window.
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _history: GenericObject<any>;
|
||||
|
||||
/**
|
||||
* This placeholder stores the offset of the virtual desktop element
|
||||
*
|
||||
* @private
|
||||
* @type {GenericObject<any>}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private _desktop_pos: GenericObject<any>;
|
||||
|
||||
/**
|
||||
* Creates an instance of WindowTag.
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init window tag
|
||||
* - `shown`: false
|
||||
* - `isMaxi`: false
|
||||
* - `minimizable`: false
|
||||
* - `resizable`: true
|
||||
* - `apptitle`: Untitled
|
||||
*
|
||||
* @protected
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected init(): void {
|
||||
this._shown = false;
|
||||
this._isMaxi = false;
|
||||
this._history = {};
|
||||
this.desktop = GUI.workspace;
|
||||
this._desktop_pos = $(this.desktop).offset();
|
||||
this.minimizable = true;
|
||||
this.resizable = true;
|
||||
this.apptitle = "Untitled";
|
||||
}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* Do nothing
|
||||
*
|
||||
* @protected
|
||||
* @param {*} [d]
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected reload(d?: any): void {}
|
||||
|
||||
/**
|
||||
* Setter: Set the window width
|
||||
*
|
||||
* Getter: Get the window width
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set width(v: number) {
|
||||
this._width = v;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.setsize({ w: v, h: this.height });
|
||||
}
|
||||
get width(): number {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the window height
|
||||
*
|
||||
* Getter: Get the window height
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set height(v: number) {
|
||||
this._height = v;
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
this.setsize({
|
||||
w: this.width,
|
||||
h: v,
|
||||
});
|
||||
}
|
||||
get height(): number {
|
||||
return this._height;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: enable/disable window minimizable
|
||||
*
|
||||
* getter: Check whether the window is minimizable
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set minimizable(v: boolean) {
|
||||
this.attsw(v, "minimizable");
|
||||
if (v) {
|
||||
$(this.refs["minbt"]).show();
|
||||
} else {
|
||||
$(this.refs["minbt"]).hide();
|
||||
}
|
||||
}
|
||||
get minimizable(): boolean {
|
||||
return this.hasattr("minimizable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: enable/disable widow resizable
|
||||
*
|
||||
* Getter: Check whether the current window is resizable
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set resizable(v: boolean) {
|
||||
this.attsw(v, "resizable");
|
||||
if (v) {
|
||||
$(this.refs["maxbt"]).show();
|
||||
$(this.refs["grip"]).show();
|
||||
} else {
|
||||
$(this.refs["maxbt"]).hide();
|
||||
$(this.refs["grip"]).hide();
|
||||
}
|
||||
}
|
||||
get resizable(): boolean {
|
||||
return this.hasattr("resizable");
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: Set the window title
|
||||
*
|
||||
* Getter: Get window title
|
||||
*
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
set apptitle(v: string | FormattedString) {
|
||||
$(this).attr("apptitle", v.__());
|
||||
if (v) {
|
||||
(this.refs["txtTitle"] as LabelTag).text = v;
|
||||
}
|
||||
}
|
||||
get apptitle(): string | FormattedString {
|
||||
return $(this).attr("apptitle");
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize all the children of the window based on its width and height
|
||||
*
|
||||
* @private
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private resize(): void {
|
||||
const ch =
|
||||
$(this.refs["yield"]).height() /
|
||||
$(this.refs["yield"]).children().length;
|
||||
$(this.refs["yield"])
|
||||
.children()
|
||||
.each(function (e) {
|
||||
$(this).css("height", `${ch}px`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount the window tag and bind basic events
|
||||
*
|
||||
* @protected
|
||||
* @returns {void}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected mount(): void {
|
||||
this.contextmenuHandle = function (e) {};
|
||||
$(this.refs["minbt"]).click((e) => {
|
||||
return this.observable.trigger("hide", {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
|
||||
$(this.refs["maxbt"]).click((e) => {
|
||||
return this.toggle_window();
|
||||
});
|
||||
|
||||
$(this.refs["closebt"]).click((e) => {
|
||||
return this.observable.trigger("exit", {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
const left = ($(this.desktop).width() - this.width) / 2;
|
||||
const top = ($(this.desktop).height() - this.height) / 2;
|
||||
$(this)
|
||||
.css("position", "absolute")
|
||||
.css("left", `${left}px`)
|
||||
.css("top", `${top}px`)
|
||||
.css("z-index", Ant.OS.GUI.zindex++);
|
||||
$(this).on("mousedown", (e) => {
|
||||
if (this._shown) {
|
||||
return;
|
||||
}
|
||||
return this.observable.trigger("focus", {
|
||||
id: this.aid,
|
||||
});
|
||||
});
|
||||
|
||||
$(this.refs["dragger"]).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.observable.on("blur", () => {
|
||||
this._shown = false;
|
||||
return $(this).addClass("unactive");
|
||||
});
|
||||
this.observable.on("hide", () => {
|
||||
$(this).hide();
|
||||
return (this._shown = false);
|
||||
});
|
||||
|
||||
this.observable.on("toggle", () => {
|
||||
if (this._shown) {
|
||||
return this.observable.trigger("hide", {
|
||||
id: this.aid,
|
||||
});
|
||||
} else {
|
||||
return this.observable.trigger("focus", {
|
||||
id: this.aid,
|
||||
});
|
||||
}
|
||||
});
|
||||
this.enable_dragging();
|
||||
this.enable_resize();
|
||||
this.setsize({
|
||||
w: this.width,
|
||||
h: this.height,
|
||||
});
|
||||
return this.observable.trigger("rendered", {
|
||||
id: this.aid,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the window size
|
||||
*
|
||||
* @private
|
||||
* @param {GenericObject<any>} o format: `{ w: window_width, h: window_height }`
|
||||
* @returns {void}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private setsize(o: GenericObject<any>): void {
|
||||
if (!o) {
|
||||
return;
|
||||
}
|
||||
this._width = o.w;
|
||||
this._height = o.h;
|
||||
$(this).css("width", `${o.w}px`).css("height", `${o.h}px`);
|
||||
$(this.refs.winwrapper).css("height", `${o.h}px`);
|
||||
this.observable.trigger("resize", {
|
||||
id: this.aid,
|
||||
data: o,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable to drag window on the virtual desktop
|
||||
*
|
||||
* @private
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private enable_dragging(): void {
|
||||
$(this.refs["dragger"])
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default");
|
||||
$(this.refs["dragger"]).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
const offset = $(this).offset();
|
||||
offset.top = e.clientY - offset.top;
|
||||
offset.left = e.clientX - offset.left;
|
||||
$(window).on("mousemove", (e) => {
|
||||
let left: number, top: number;
|
||||
if (this._isMaxi) {
|
||||
this.toggle_window();
|
||||
top = 0;
|
||||
const letf = e.clientX - $(this).width() / 2;
|
||||
offset.top = 10;
|
||||
offset.left = $(this).width() / 2;
|
||||
} else {
|
||||
top =
|
||||
e.clientY -
|
||||
offset.top -
|
||||
this._desktop_pos.top;
|
||||
left =
|
||||
e.clientX -
|
||||
this._desktop_pos.top -
|
||||
offset.left;
|
||||
left = left < 0 ? 0 : left;
|
||||
top = top < 0 ? 0 : top;
|
||||
}
|
||||
|
||||
return $(this)
|
||||
.css("top", `${top}px`)
|
||||
.css("left", `${left}px`);
|
||||
});
|
||||
return $(window).on("mouseup", function (e) {
|
||||
$(window).unbind("mousemove", null);
|
||||
return $(window).unbind("mouseup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable window resize, this only works if the window
|
||||
* is resizable
|
||||
*
|
||||
* @private
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private enable_resize(): void {
|
||||
$(this.refs["grip"])
|
||||
.css("user-select", "none")
|
||||
.css("cursor", "default")
|
||||
.css("position", "absolute")
|
||||
.css("bottom", "0")
|
||||
.css("right", "0")
|
||||
.css("cursor", "nwse-resize");
|
||||
|
||||
$(this.refs["grip"]).on("mousedown", (e) => {
|
||||
e.preventDefault();
|
||||
const offset = { top: 0, left: 0 };
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
$(window).on("mousemove", (e) => {
|
||||
let w = $(this).width() + e.clientX - offset.left;
|
||||
let h = $(this).height() + e.clientY - offset.top;
|
||||
w = w < 100 ? 100 : w;
|
||||
h = h < 100 ? 100 : h;
|
||||
offset.top = e.clientY;
|
||||
offset.left = e.clientX;
|
||||
this._isMaxi = false;
|
||||
this.setsize({ w, h });
|
||||
});
|
||||
|
||||
$(window).on("mouseup", function (e) {
|
||||
$(window).unbind("mousemove", null);
|
||||
return $(window).unbind("mouseup", null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Maximize the window or restore its previous width, height,
|
||||
* and position
|
||||
*
|
||||
* @private
|
||||
* @returns {void}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
private toggle_window(): void {
|
||||
let h: number, w: number;
|
||||
if (!this.resizable) {
|
||||
return;
|
||||
}
|
||||
if (this._isMaxi === false) {
|
||||
this._history = {
|
||||
top: $(this).css("top"),
|
||||
left: $(this).css("left"),
|
||||
width: $(this).css("width"),
|
||||
height: $(this).css("height"),
|
||||
};
|
||||
w = $(this.desktop).width();
|
||||
h = $(this.desktop).height();
|
||||
$(this).css("top", "0").css("left", "0");
|
||||
this.setsize({ w, h });
|
||||
this._isMaxi = true;
|
||||
} else {
|
||||
this._isMaxi = false;
|
||||
$(this)
|
||||
.css("top", this._history.top)
|
||||
.css("left", this._history.left);
|
||||
this.setsize({
|
||||
w: parseInt(this._history.width),
|
||||
h: parseInt(this._history.height),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layout definition of the window tag
|
||||
*
|
||||
* @protected
|
||||
* @returns {TagLayoutType[]}
|
||||
* @memberof WindowTag
|
||||
*/
|
||||
protected layout(): TagLayoutType[] {
|
||||
return [
|
||||
{
|
||||
el: "div",
|
||||
class: "afx-window-wrapper",
|
||||
ref: "winwrapper",
|
||||
children: [
|
||||
{
|
||||
el: "ul",
|
||||
class: "afx-window-top",
|
||||
children: [
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-close",
|
||||
ref: "closebt",
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-minimize",
|
||||
ref: "minbt",
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-maximize",
|
||||
ref: "maxbt",
|
||||
},
|
||||
{
|
||||
el: "li",
|
||||
class: "afx-window-title",
|
||||
ref: "dragger",
|
||||
children: [
|
||||
{
|
||||
el: "afx-label",
|
||||
ref: "txtTitle",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{ el: "div", class: "afx-clear" },
|
||||
{
|
||||
el: "div",
|
||||
ref: "yield",
|
||||
class: "afx-window-content",
|
||||
},
|
||||
{
|
||||
el: "div",
|
||||
ref: "grip",
|
||||
class: "afx-window-grip",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
define("afx-app-window", WindowTag);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,246 +0,0 @@
|
||||
<afx-app-window ref = "window" >
|
||||
<div class = "afx-window-wrapper">
|
||||
<ul class= "afx-window-top" >
|
||||
<li class = "afx-window-close" onclick = {close}></li>
|
||||
<li if = {minimizable == true} class = "afx-window-minimize" onclick = {minimize}></li>
|
||||
<li if = {resizable == true} class = "afx-window-maximize" onclick={maximize}></li>
|
||||
<li ref = "dragger" class = "afx-window-title">{ apptitle?apptitle.__():apptitle }</li>
|
||||
</ul>
|
||||
<div class = "afx-clear"></div>
|
||||
<div ref = "content" class = "afx-window-content">
|
||||
<yield/>
|
||||
</div>
|
||||
<div if = {resizable == true} ref = "grip" class = "afx-window-grip">
|
||||
</div>
|
||||
|
||||
<script>
|
||||
this.apptitle = opts.apptitle || ""
|
||||
if(opts.minimizable == undefined)
|
||||
this.minimizable = true
|
||||
else
|
||||
this.minimizable = eval(opts.minimizable)
|
||||
if(opts.resizable == undefined)
|
||||
this.resizable = true
|
||||
else
|
||||
this.resizable = eval(opts.resizable)
|
||||
var self = this
|
||||
var offset = {top:0,left:0}
|
||||
var desktop_pos = $("#desktop").offset()
|
||||
var isMaxi = false
|
||||
var history = {}
|
||||
var width = opts.width || 400
|
||||
var height = opts.height || 300
|
||||
this.root.observable = opts.observable || riot.observable()
|
||||
if(!window._zindex) window._zindex = 10
|
||||
this.shown = false
|
||||
|
||||
self.root.contextmenuHandler = function (e) {}
|
||||
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
if(k == "apptitle")
|
||||
self.root.observable.trigger("apptitlechange")
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
minimize()
|
||||
{
|
||||
this.root.observable.trigger("hide")
|
||||
}
|
||||
close()
|
||||
{
|
||||
this.root.observable.trigger("exit")
|
||||
}
|
||||
this.on('mount', function() {
|
||||
var left,top
|
||||
//left = 20 + Math.floor(Math.random() * ($("#desktop").width() - width))
|
||||
//top = 20 + Math.floor(Math.random() * ($("#desktop").height() - height))
|
||||
left = ($("#desktop").width() - width)/2
|
||||
top = ($("#desktop").height() - height)/2
|
||||
$(self.refs.window)
|
||||
.css("position",'absolute')
|
||||
.css("left",left + "px")
|
||||
.css("top",top + "px")
|
||||
.css("width",width + "px")
|
||||
.css("height", height + "px")
|
||||
.css("z-index",window._zindex++)
|
||||
$(self.refs.window).on("mousedown", function(e){
|
||||
if(self.shown == false)
|
||||
self.root.observable.trigger("focus")
|
||||
})
|
||||
$(self.refs.window).click(function(e) {
|
||||
//e.stopPropagation()
|
||||
//e.windowactive = true
|
||||
//self.root.observable.trigger("windowselect")
|
||||
})
|
||||
enable_dragging()
|
||||
if(self.resizable)
|
||||
enable_resize()
|
||||
$(self.refs.dragger).dblclick(function(e){
|
||||
toggle_window()
|
||||
})
|
||||
$(self.refs.content).children().each(function(e){
|
||||
this.observable = self.root.observable
|
||||
})
|
||||
var fn = function()
|
||||
{
|
||||
var ch = $(self.refs.content).height()/ $(self.refs.content).children().length
|
||||
$(self.refs.content).children().each(function(e){
|
||||
$(this).css("height",ch+"px")
|
||||
})
|
||||
}
|
||||
fn()
|
||||
self.root.observable.on("resize", function(){ fn()})
|
||||
self.root.observable.on("focus",function(){
|
||||
window._zindex++
|
||||
$(self.refs.window)
|
||||
.show()
|
||||
.css("z-index",window._zindex)
|
||||
.removeClass("unactive")
|
||||
|
||||
self.shown = true
|
||||
})
|
||||
self.root.observable.on("blur", function(){
|
||||
self.shown = false
|
||||
$(self.refs.window)
|
||||
.addClass("unactive")
|
||||
// add css to blur app :)
|
||||
})
|
||||
self.root.observable.on("hide", function()
|
||||
{
|
||||
$(self.refs.window).hide()
|
||||
self.shown = false
|
||||
})
|
||||
|
||||
self.root.observable.on("toggle", function(){
|
||||
if(self.shown)
|
||||
self.root.observable.trigger("hide")
|
||||
else
|
||||
self.root.observable.trigger("focus")
|
||||
})
|
||||
self.root.observable.trigger("rendered", self.root)
|
||||
})
|
||||
var enable_dragging = function()
|
||||
{
|
||||
$(self.refs.dragger)
|
||||
.css("user-select","none")
|
||||
.css("cursor","default")
|
||||
$(self.refs.dragger).on("mousedown", function(e){
|
||||
e.preventDefault()
|
||||
offset = $(self.refs.window).offset()
|
||||
offset.top = e.clientY - offset.top
|
||||
offset.left = e.clientX - offset.left
|
||||
$(window).on("mousemove", function(e){
|
||||
var top,left
|
||||
if(isMaxi)
|
||||
{
|
||||
toggle_window()
|
||||
top = 0
|
||||
letf = e.clientX - $(self.refs.window).width()/2
|
||||
offset.top = 10 //center
|
||||
offset.left = $(self.refs.window).width()/2
|
||||
} else
|
||||
{
|
||||
top = e.clientY - offset.top - desktop_pos.top
|
||||
left = e.clientX - desktop_pos.top - offset.left
|
||||
left = left < 0?0:left;
|
||||
top = top < 0?0:top;
|
||||
}
|
||||
|
||||
$(self.refs.window).css("top", top +"px")
|
||||
.css("left",left + "px")
|
||||
})
|
||||
$(window).on("mouseup", function(e){
|
||||
//console.log("unbind mouse up")
|
||||
$(window).unbind("mousemove", null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var enable_resize = function()
|
||||
{
|
||||
if(!self.resizable) return
|
||||
$(self.refs.grip)
|
||||
.css("user-select","none")
|
||||
.css("cursor","default")
|
||||
.css("position","absolute")
|
||||
.css("bottom","0")
|
||||
.css("right","0")
|
||||
.css("cursor","nwse-resize")
|
||||
$(self.refs.grip).on("mousedown", function(e){
|
||||
e.preventDefault()
|
||||
offset.top = e.clientY
|
||||
offset.left = e.clientX
|
||||
$(window).on("mousemove", function(e){
|
||||
var w,h
|
||||
w = $(self.refs.window).width() + e.clientX - offset.left
|
||||
h = $(self.refs.window).height() + e.clientY - offset.top
|
||||
w = w < 100 ? 100:w
|
||||
h = h < 100 ?100:h
|
||||
offset.top = e.clientY
|
||||
offset.left = e.clientX
|
||||
$(self.refs.window)
|
||||
.css("width", w +"px")
|
||||
.css("height",h + "px")
|
||||
isMaxi = false
|
||||
self.root.observable.trigger('resize',
|
||||
{id:$(self.root).attr("data-id"),w:w,h:h})
|
||||
})
|
||||
$(window).on("mouseup", function(e){
|
||||
$(window).unbind("mousemove", null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var toggle_window = function()
|
||||
{
|
||||
if(!self.resizable) return
|
||||
if(isMaxi == false)
|
||||
{
|
||||
history = {
|
||||
top: $(self.refs.window).css("top"),
|
||||
left:$(self.refs.window).css("left"),
|
||||
width:$(self.refs.window).css("width"),
|
||||
height:$(self.refs.window).css("height")
|
||||
}
|
||||
var w,h
|
||||
w = ($("#desktop").width() - 5)
|
||||
h = ($("#desktop").height() - 10)
|
||||
$(self.refs.window)
|
||||
.css("width", w + "px")
|
||||
.css("height", h + "px")
|
||||
.css("top","0").css("left","0")
|
||||
self.root.observable.trigger('resize',
|
||||
{id:$(self.root).attr("data-id"),w:w,h:h})
|
||||
isMaxi = true
|
||||
}
|
||||
else
|
||||
{
|
||||
isMaxi = false
|
||||
$(self.refs.window)
|
||||
.css("width",history.width)
|
||||
.css("height",history.height)
|
||||
.css("top",history.top).css("left",history.left)
|
||||
self.root.observable.trigger('resize',
|
||||
{id:$(self.root).attr("data-id"),w:history.width,h:history.height} )
|
||||
}
|
||||
|
||||
}
|
||||
maximize()
|
||||
{
|
||||
toggle_window()
|
||||
}
|
||||
</script>
|
||||
</afx-app-window>
|
@ -1,82 +0,0 @@
|
||||
<afx-apps-dock>
|
||||
<afx-button class = {selected: parent.selectedApp && it.app.pid == parent.selectedApp.pid} each={ it,i in items} iconclass = {it.iconclass} icon = {it.icon} appindex = {i} text = {it.text} onbtclick = {it.onbtclick} tooltip= {"cr:" + it.app.title()} >
|
||||
</afx-button>
|
||||
<script>
|
||||
this.items = opts.items || []
|
||||
var self = this
|
||||
self.selectedApp = null
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
{
|
||||
self[k] = v
|
||||
if(k == "selectedApp")
|
||||
{
|
||||
for(var i in self.items)
|
||||
self.items[i].app.blur()
|
||||
if(v)
|
||||
$("#desktop")[0].set("selected", -1)
|
||||
}
|
||||
}
|
||||
self.update()
|
||||
}
|
||||
self.root.newapp = function(i)
|
||||
{
|
||||
self.items.push(i)
|
||||
self.selectedApp = i.app
|
||||
self.update()
|
||||
for(var i in self.items)
|
||||
self.items[i].app.blur()
|
||||
}
|
||||
|
||||
self.root.removeapp = function(a)
|
||||
{
|
||||
var i = -1;
|
||||
for(var k in self.items)
|
||||
if(self.items[k].app.pid == a.pid)
|
||||
{
|
||||
i = k; break;
|
||||
}
|
||||
if(i != -1)
|
||||
{
|
||||
delete self.items[i].app
|
||||
self.items.splice(i,1)
|
||||
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
this.on("mount", function(){
|
||||
window.OS.courrier.trigger("sysdockloaded")
|
||||
})
|
||||
|
||||
self.root.contextmenuHandler = function(e, m)
|
||||
{
|
||||
if(e.target == self.root) return;
|
||||
var appidx = $(e.target).closest( "afx-button" ).attr("appindex")
|
||||
var app = self.items[appidx].app
|
||||
m.set("items", [
|
||||
{ text: "__(Show)", dataid:"show" },
|
||||
{ text: "__(Hide)", dataid:"hide" },
|
||||
{ text: "__(Close)", dataid:"quit" }
|
||||
])
|
||||
m.set("onmenuselect", function(evt)
|
||||
{
|
||||
if(app[evt.item.data.dataid])
|
||||
app[evt.item.data.dataid]()
|
||||
})
|
||||
m.show(e)
|
||||
}
|
||||
|
||||
</script>
|
||||
</afx-apps-dock>
|
@ -1,47 +0,0 @@
|
||||
<afx-button >
|
||||
<button class= { btactive: selected } disabled={ enable == false } onclick="{ _onbtclick }" ref = "mybtn" >
|
||||
<afx-label color = {color} icon={icon} iconclass = {iconclass} text = {text} ></afx-label>
|
||||
</button>
|
||||
<script>
|
||||
opts.enable = opts.enable || "true"
|
||||
this.enable = eval(opts.enable) || false
|
||||
this.icon = opts.icon
|
||||
this.iconclass = opts.iconclass
|
||||
this.color = opts.color
|
||||
this.text = opts.text || ""
|
||||
this.selected = eval(opts.selected) || false
|
||||
this.toggle = eval(opts.toggle) || false
|
||||
var self = this
|
||||
this.onbtclick = opts.onbtclick
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.trigger = function()
|
||||
{
|
||||
$(self.refs.mybtn).trigger("click")
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
this._onbtclick = function(e)
|
||||
{
|
||||
if(typeof self.onbtclick == 'string')
|
||||
eval(self.onbtclick)
|
||||
else if(self.onbtclick)
|
||||
self.onbtclick(e)
|
||||
if(self.root.observable)
|
||||
{
|
||||
self.root.observable.trigger("btclick",{id:$(self.root).attr("data-id"),data:self.root})
|
||||
}
|
||||
if(self.toggle)
|
||||
self.root.set("selected",!self.selected)
|
||||
}
|
||||
</script>
|
||||
</afx-button>
|
@ -1,107 +0,0 @@
|
||||
<afx-calendar-view>
|
||||
<div><i class ="prevmonth" onclick={prevmonth}></i>
|
||||
<afx-label text = {mtext}></afx-label>
|
||||
<afx-label text = {year}></afx-label>
|
||||
<i onclick={nextmonth} class="nextmonth"></i></div>
|
||||
<afx-grid-view data-id ={"grid_" + rid} style = "height:100%;" ref = "grid" header = {header}> </afx-grid-view>
|
||||
|
||||
<script >
|
||||
this.header = [{value:"__(Sun)"},{value:"__(Mon)"},{value:"__(Tue)"},{value:"__(Wed)"},{value:"__(Thu)"},{value:"__(Fri)"},{value:"__(Sat)"}]
|
||||
this.root.observable = opts.observable
|
||||
var self = this
|
||||
this.day = 0
|
||||
this.month = 0
|
||||
this.year = 0
|
||||
this.ondayselect = opts.ondayselect
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
this.selectedDate = undefined
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
|
||||
this.on("mount", function (e) {
|
||||
self.refs.grid.root.observable = self.root.observable
|
||||
calendar(null)
|
||||
self.root.observable.on("gridcellselect", function(d){
|
||||
if(d.id != "grid_" + self.rid) return
|
||||
if(d.data.value == "") return
|
||||
var data = {id:self.rid, data:new Date(self.year, self.month,d.data.value)};
|
||||
if(self.ondayselect)
|
||||
self.ondayselect(data)
|
||||
self.selectedDate = data.data
|
||||
self.root.observable.trigger("dayselect",data)
|
||||
})
|
||||
})
|
||||
prevmonth()
|
||||
{
|
||||
self.selectedDate = undefined
|
||||
this.month--
|
||||
if(this.month < 0)
|
||||
{
|
||||
this.month = 11
|
||||
this.year--
|
||||
}
|
||||
calendar(new Date(this.year, this.month,1))
|
||||
}
|
||||
nextmonth()
|
||||
{
|
||||
self.selectedDate = undefined
|
||||
this.month++
|
||||
if(this.month > 11)
|
||||
{
|
||||
this.month = 0
|
||||
this.year++
|
||||
}
|
||||
calendar(new Date(this.year, this.month,1))
|
||||
}
|
||||
var calendar = function (date) {
|
||||
|
||||
if (date === null)
|
||||
date = new Date()
|
||||
|
||||
self.day = date.getDate()
|
||||
self.month = date.getMonth()
|
||||
self.year = date.getFullYear()
|
||||
|
||||
var now ={ d:(new Date()).getDate(), m:(new Date()).getMonth(), y:(new Date()).getFullYear()}
|
||||
months = ["__(January)", "__(February)", "__(March)", "__(April)", "__(May)", "__(June)", "__(July)", "__(August)", "__(September)", "__(October)", "__(November)", "__(December)"]
|
||||
|
||||
this_month = new Date(self.year, self.month, 1)
|
||||
next_month = new Date(self.year, self.month + 1, 1)
|
||||
|
||||
// Find out when this month starts and ends.
|
||||
first_week_day = this_month.getDay()
|
||||
days_in_this_month = Math.round((next_month.getTime() - this_month.getTime()) / (1000 * 60 * 60 * 24))
|
||||
self.mtext = months[self.month]
|
||||
var rows = []
|
||||
var row = []
|
||||
// Fill the first week of the month with the appropriate number of blanks.
|
||||
for (week_day = 0; week_day < first_week_day; week_day++)
|
||||
row.push({value:""})
|
||||
|
||||
week_day = first_week_day;
|
||||
for (day_counter = 1; day_counter <= days_in_this_month; day_counter++) {
|
||||
week_day %= 7
|
||||
if (week_day == 0)
|
||||
{
|
||||
rows.push(row)
|
||||
row =[]
|
||||
}
|
||||
|
||||
// Do something different for the current day.
|
||||
|
||||
if (now.d == day_counter && self.month == now.m && self.year == now.y)
|
||||
row.push({value:day_counter, selected:true})
|
||||
else
|
||||
row.push({value:day_counter})
|
||||
|
||||
week_day++;
|
||||
}
|
||||
for(var i = 0; i <= 7 - row.length;i++)
|
||||
row.push({value:""})
|
||||
rows.push(row)
|
||||
self.refs.grid.root.set("rows",rows)
|
||||
}
|
||||
</script>
|
||||
</afx-calendar-view>
|
@ -1,110 +0,0 @@
|
||||
<afx-color-picker>
|
||||
<div style = "width:310px; height:190px;display:block; padding:3px;">
|
||||
<canvas class = "color-palette" width="284" height="155" style ="float:left;" ref = "palette" ></canvas>
|
||||
<div class = "color-sample" style= "width:15px; height:155px; text-align:center; margin-left:3px; display:block;float:left;" ref = "colorval"></div>
|
||||
<div class = "afx-clear"></div>
|
||||
<div style ="margin-top:3px;">
|
||||
<span>Hex:</span><input type = "text" ref = "hextext" style = "width:70px; margin-left:3px;margin-right:5px;"></input>
|
||||
<span ref = 'rgbtext'></span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var self = this
|
||||
var colorctx = undefined
|
||||
self.root.observable = opts.observable
|
||||
self.oncolorsetect = opts.oncolorsetect
|
||||
self.selectedColor = undefined
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
|
||||
var build_palette = function()
|
||||
{
|
||||
colorctx = $(self.refs.palette).get(0).getContext('2d')
|
||||
var gradient = colorctx.createLinearGradient(0,0,$(self.refs.palette).width(),0)
|
||||
// fill color
|
||||
gradient.addColorStop(0, "rgb(255, 0, 0)")
|
||||
gradient.addColorStop(0.15, "rgb(255, 0, 255)")
|
||||
gradient.addColorStop(0.33, "rgb(0, 0, 255)")
|
||||
gradient.addColorStop(0.49, "rgb(0, 255, 255)")
|
||||
gradient.addColorStop(0.67, "rgb(0, 255, 0)")
|
||||
gradient.addColorStop(0.84, "rgb(255, 255, 0)")
|
||||
gradient.addColorStop(1, "rgb(255, 0, 0)")
|
||||
gradient.addColorStop(0, "rgb(0, 0, 0)")
|
||||
// Apply gradient to canvas
|
||||
colorctx.fillStyle = gradient;
|
||||
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height)
|
||||
|
||||
// Create semi transparent gradient (white -> trans. -> black)
|
||||
gradient = colorctx.createLinearGradient(0, 0, 0, $(self.refs.palette).width())
|
||||
gradient.addColorStop(0, "rgba(255, 255, 255, 1)")
|
||||
gradient.addColorStop(0.5, "rgba(255, 255, 255, 0)")
|
||||
gradient.addColorStop(0.5, "rgba(0, 0, 0, 0)")
|
||||
gradient.addColorStop(1, "rgba(0, 0, 0, 1)")
|
||||
// Apply gradient to canvas
|
||||
colorctx.fillStyle = gradient
|
||||
colorctx.fillRect(0, 0, colorctx.canvas.width, colorctx.canvas.height)
|
||||
//$(self.refs.palette).css("position", "absolute")
|
||||
// now add mouse move event
|
||||
var getHex = function(c)
|
||||
{
|
||||
s = c.toString(16)
|
||||
if(s.length == 1) s = "0" + s
|
||||
return s
|
||||
}
|
||||
var pick_color = function(e)
|
||||
{
|
||||
$(self.refs.palette).css("cursor","crosshair")
|
||||
var offset = $(self.refs.palette).offset()
|
||||
var x = e.pageX - offset.left
|
||||
var y = e.pageY - offset.top
|
||||
var color = colorctx.getImageData(x,y, 1, 1)
|
||||
var data = {
|
||||
r:color.data[0],
|
||||
g:color.data[1],
|
||||
b:color.data[2],
|
||||
text:'rgb(' + color.data[0] + ', ' + color.data[1] + ', ' + color.data[2] + ')',
|
||||
hex:'#' + getHex(color.data[0]) + getHex(color.data[1]) + getHex(color.data[2])
|
||||
}
|
||||
return data
|
||||
}
|
||||
var mouse_move_h = function(e)
|
||||
{
|
||||
var data = pick_color(e)
|
||||
$(self.refs.colorval).css("background-color", data.text)
|
||||
}
|
||||
$(self.refs.palette).mouseenter(function(e){
|
||||
$(self.refs.palette).on("mousemove",mouse_move_h)
|
||||
})
|
||||
$(self.refs.palette).mouseout(function(e){
|
||||
$(self.refs.palette).unbind("mousemove",mouse_move_h)
|
||||
if(self.selectedColor)
|
||||
$(self.refs.colorval).css("background-color", self.selectedColor.text)
|
||||
})
|
||||
$(self.refs.palette).on("click", function(e){
|
||||
data = pick_color(e)
|
||||
$(self.refs.rgbtext).html(data.text)
|
||||
$(self.refs.hextext).val(data.hex)
|
||||
self.selectedColor = data
|
||||
if(self.oncolorsetect)
|
||||
self.oncolorsetect(data)
|
||||
if(! self.root.observable) return
|
||||
self.root.observable.trigger("colorselect",data)
|
||||
})
|
||||
}
|
||||
|
||||
this.on("mount", function(){
|
||||
build_palette()
|
||||
})
|
||||
</script>
|
||||
</afx-color-picker>
|
@ -1,10 +0,0 @@
|
||||
<afx-dummy>
|
||||
<yield/>
|
||||
<script>
|
||||
var self = this
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
</script>
|
||||
</afx-dummy>
|
@ -1,226 +0,0 @@
|
||||
<afx-file-view>
|
||||
<afx-list-view ref="listview" observable = {root.observable}></afx-list-view>
|
||||
<afx-grid-view ref = "gridview" header = {header} observable = {root.observable}></afx-grid-view>
|
||||
<div class = "treecontainer" ref="treecontainer">
|
||||
<afx-tree-view ref = "treeview" observable = {root.observable}></afx-tree-view>
|
||||
</div>
|
||||
<afx-label if = {status == true} class = "status" ref = "stbar"></afx-label>
|
||||
<script>
|
||||
var self = this
|
||||
self.root.observable = opts.observable || riot.observable()
|
||||
self.view = opts.view || 'list'
|
||||
self.data = opts.data || []
|
||||
self.path = opts.path || "home:///"
|
||||
self.onfileselect
|
||||
self.onfileopen
|
||||
this.status = opts.status == undefined?true:opts.status
|
||||
this.selectedFile = undefined
|
||||
this.showhidden = opts.showhidden
|
||||
this.preventUpdate = false
|
||||
this.fetch = opts.fetch
|
||||
this.chdir = opts.chdir
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
this.header = [{value:"__(File name)"},{value: "__(Type)", width:150}, {value: "__(Size)", width:70}]
|
||||
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
if(k == 'view')
|
||||
switchView()
|
||||
if(k == "data")
|
||||
self.selectedFile = undefined
|
||||
if(k != "preventUpdate")
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
var sortByType = function(a,b)
|
||||
{
|
||||
return a.type < b.type ? -1 : ( a.type > b.type ? 1: 0 )
|
||||
}
|
||||
var calibre_size = function()
|
||||
{
|
||||
var h = $(self.root).outerHeight()
|
||||
var w = $(self.root).width()
|
||||
if(self.refs.stbar)
|
||||
h -= ($(self.refs.stbar.root).height() + 10)
|
||||
$(self.refs.listview.root).css("height", h + "px")
|
||||
$(self.refs.gridview.root).css("height", h + "px")
|
||||
$(self.refs.treecontainer).css("height", h + "px")
|
||||
$(self.refs.listview.root).css("width", w + "px")
|
||||
$(self.refs.gridview.root).css("width", w + "px")
|
||||
$(self.refs.treecontainer).css("width", w + "px")
|
||||
}
|
||||
var refreshList = function(){
|
||||
var items = []
|
||||
$.each(self.data, function(i, v){
|
||||
if(v.filename[0] == '.' && !self.showhidden) return
|
||||
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
|
||||
items.push(v)
|
||||
})
|
||||
self.refs.listview.root.set("items", items)
|
||||
}
|
||||
var refreshGrid = function(){
|
||||
var rows = []
|
||||
$.each(self.data, function(i,v){
|
||||
if(v.filename[0] == '.' && !self.showhidden) return
|
||||
var row = [{value:v.filename, iconclass: v.iconclass?v.iconclass:v.type, icon:v.icon},{value:v.mime},{value:v.size},{idx:i}]
|
||||
rows.push(row)
|
||||
})
|
||||
self.refs.gridview.root.set("rows",rows)
|
||||
}
|
||||
var refreshTree = function(){
|
||||
self.refs.treeview.root.set("selectedItem", null)
|
||||
var tdata = {}
|
||||
tdata.name = self.path
|
||||
tdata.nodes = getTreeData(self.data)
|
||||
self.refs.treeview.root.set("data", tdata)
|
||||
}
|
||||
var getTreeData = function(data)
|
||||
{
|
||||
nodes = []
|
||||
$.each(data, function(i,v){
|
||||
if(v.filename[0] == '.' && !self.showhidden) return
|
||||
v.name = v.filename
|
||||
if(v.type == 'dir')
|
||||
{
|
||||
v.nodes = []
|
||||
v.open = false
|
||||
}
|
||||
v.iconclass = v.iconclass?v.iconclass:v.type
|
||||
v.icon = v.icon
|
||||
nodes.push(v)
|
||||
})
|
||||
return nodes
|
||||
}
|
||||
var refreshData = function(){
|
||||
self.data.sort(sortByType)
|
||||
if(self.view == "icon")
|
||||
refreshList()
|
||||
else if(self.view == "list")
|
||||
refreshGrid()
|
||||
else
|
||||
refreshTree()
|
||||
}
|
||||
var switchView = function()
|
||||
{
|
||||
$(self.refs.listview.root).hide()
|
||||
$(self.refs.gridview.root).hide()
|
||||
$(self.refs.treecontainer).hide()
|
||||
self.selectedFile = undefined
|
||||
self.refs.listview.root.set("selected", -1)
|
||||
self.refs.treeview.selectedItem = undefined
|
||||
self.refs.treeview.root.set("fetch",function(e,f){
|
||||
if(!self.fetch) return
|
||||
self.fetch(e, function(d){
|
||||
f(getTreeData(d))
|
||||
})
|
||||
})
|
||||
if(self.refs.stbar)
|
||||
self.refs.stbar.root.set("text", "")
|
||||
switch (self.view) {
|
||||
case 'icon':
|
||||
$(self.refs.listview.root).show()
|
||||
break;
|
||||
case 'list':
|
||||
$(self.refs.gridview.root).show()
|
||||
break;
|
||||
case 'tree':
|
||||
$(self.refs.treecontainer).show()
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
calibre_size()
|
||||
}
|
||||
self.on("updated", function(){
|
||||
if(self.preventUpdate)
|
||||
{
|
||||
self.preventUpdate = false
|
||||
}
|
||||
else
|
||||
refreshData()
|
||||
//console.log("update")
|
||||
//calibre_size()
|
||||
})
|
||||
self.on("mount", function(){
|
||||
switchView()
|
||||
self.refs.listview.onlistselect = function(data)
|
||||
{
|
||||
data.id = self.rid
|
||||
self.root.observable.trigger("fileselect",data)
|
||||
}
|
||||
self.refs.listview.onlistdbclick = function(data)
|
||||
{
|
||||
data.id = self.rid
|
||||
self.root.observable.trigger("filedbclick",data)
|
||||
}
|
||||
self.refs.gridview.root.observable = self.root.observable
|
||||
self.refs.gridview.ongridselect = function(d)
|
||||
{
|
||||
var data = {id:self.rid, data:self.data[d.data.child[3].idx], idx:d.data.child[3].idx}
|
||||
self.root.observable.trigger("fileselect",data)
|
||||
}
|
||||
self.refs.gridview.ongriddbclick = function(d)
|
||||
{
|
||||
var data = {id:self.rid, data:self.data[d.data.child[3].idx], idx:d.data.child[3].idx}
|
||||
self.root.observable.trigger("filedbclick",data)
|
||||
}
|
||||
self.refs.treeview.ontreeselect = function(d)
|
||||
{
|
||||
if(!d) return;
|
||||
var data;
|
||||
var el = d;
|
||||
if(d.treepath == 0)// select the root
|
||||
{
|
||||
el = self.path.asFileHandler()
|
||||
el.size = 0
|
||||
el.filename = el.path
|
||||
}
|
||||
var data = {id:self.rid, data:el}
|
||||
self.root.observable.trigger("fileselect",data)
|
||||
}
|
||||
self.refs.treeview.ontreedbclick = function(d)
|
||||
{
|
||||
if(!d || d.treepath == 0) return;
|
||||
var data = {id:self.rid, data:d}
|
||||
self.root.observable.trigger("filedbclick",data)
|
||||
}
|
||||
self.root.observable.on("fileselect", function(e){
|
||||
if(e.id != self.rid) return
|
||||
self.selectedFile = e.data
|
||||
if(self.onfileselect)
|
||||
self.onfileselect(e.data)
|
||||
if(self.refs.stbar)
|
||||
self.refs.stbar.root.set("text", __("Selected: {0} ({1} bytes)", e.data.filename, e.data.size?e.data.size:"0"))//.html()
|
||||
})
|
||||
self.root.observable.on("filedbclick", function(e){
|
||||
if(e.id != self.rid ) return
|
||||
if(e.data.type != "dir" && self.onfileopen)
|
||||
self.onfileopen(e.data)
|
||||
else if(self.chdir && e.data.type == "dir")
|
||||
self.chdir(e.data.path)
|
||||
})
|
||||
calibre_size()
|
||||
self.root.observable.on("resize", function(e){
|
||||
calibre_size()
|
||||
})
|
||||
self.root.observable.on("calibrate", function(e){
|
||||
calibre_size()
|
||||
})
|
||||
/*self.root.observable.on("*", function(e){
|
||||
console.log(e)
|
||||
})*/
|
||||
})
|
||||
</script>
|
||||
</afx-file-view>
|
@ -1,183 +0,0 @@
|
||||
<afx-float-list ref = "container">
|
||||
<div ref = "list">
|
||||
<div each={item,i in items } class={float_list_item:true, float_list_item_selected: parent._autoselect(item,i)} ondblclick = {parent._dbclick} onmousedown = {parent._select} oncontextmenu = {parent._select}>
|
||||
<afx-label color = {item.color} iconclass = {item.iconclass} icon = {item.icon} text = {item.text}></afx-label>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
this.items = opts.items || []
|
||||
var self = this
|
||||
self.selidx = -1
|
||||
self.onlistselect = opts.onlistselect
|
||||
self.onlistdbclick = opts.onlistdbclick
|
||||
self.fetch = undefined
|
||||
this.root.observable = opts.observable || riot.observable()
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
self.dir = opts.dir || "horizontal"
|
||||
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "selected")
|
||||
{
|
||||
if(self.selidx != -1)
|
||||
self.items[self.selidx].selected =false
|
||||
if(v == -1)
|
||||
self.selidx = -1
|
||||
else
|
||||
if(self.items[v]) self.items[v].selected = true
|
||||
}
|
||||
else if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
if(k == "selected")
|
||||
if(self.selidx == -1)
|
||||
return undefined
|
||||
else
|
||||
return self.items[self.selidx]
|
||||
return self[k]
|
||||
}
|
||||
self.root.push = function(e,u)
|
||||
{
|
||||
self.items.push(e)
|
||||
if(u) self.update()
|
||||
}
|
||||
self.root.unshift = function(e,u)
|
||||
{
|
||||
self.items.unshift(e)
|
||||
if(u) self.update()
|
||||
}
|
||||
self.root.remove = function(e,u)
|
||||
{
|
||||
var i = self.items.indexOf(e)
|
||||
if(i >= 0)
|
||||
{
|
||||
if(self.selidx != -1)
|
||||
{
|
||||
self.items[self.selidx].selected =false
|
||||
self.selidx = -1
|
||||
}
|
||||
self.items.splice(i, 1)
|
||||
if(u)
|
||||
self.update()
|
||||
}
|
||||
}
|
||||
|
||||
self.root.refresh = function()
|
||||
{
|
||||
_refresh()
|
||||
}
|
||||
|
||||
this.on("mount", function(){
|
||||
if(self.root.ready)
|
||||
self.root.ready(self.root)
|
||||
// now refresh the list
|
||||
_refresh()
|
||||
})
|
||||
|
||||
var _refresh = function()
|
||||
{
|
||||
var ctop = 20
|
||||
var cleft = 20
|
||||
var gw = $(self.refs.container).width()
|
||||
var gh = $(self.refs.container).height()
|
||||
$(self.refs.list)
|
||||
.children()
|
||||
.each(function(e)
|
||||
{
|
||||
$(this).unbind("mousedown")
|
||||
_enable_drag($(this))
|
||||
var w = $(this).width()
|
||||
var h = $(this).height()
|
||||
$(this).css("top", ctop + "px").css("left", cleft + "px")
|
||||
if(self.dir == "horizontal")
|
||||
{
|
||||
ctop += h + 20
|
||||
if(ctop > gh)
|
||||
{
|
||||
ctop = 20
|
||||
cleft += w + 20
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cleft += w + 20
|
||||
if(cleft > gw )
|
||||
{
|
||||
cleft = 20
|
||||
ctop += h + 20
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var _enable_drag = function(el)
|
||||
{
|
||||
var globalof = $(self.refs.container).offset()
|
||||
el
|
||||
.css("user-select","none")
|
||||
.css("cursor","default")
|
||||
.css("position",'absolute')
|
||||
.on("mousedown", function(e){
|
||||
e.preventDefault()
|
||||
offset = el.offset()
|
||||
offset.top = e.clientY - offset.top
|
||||
offset.left = e.clientX - offset.left
|
||||
$(window).on("mousemove", function(e){
|
||||
var top,left
|
||||
top = e.clientY - offset.top - globalof.top
|
||||
left = e.clientX - globalof.top - offset.left
|
||||
left = left < 0?0:left
|
||||
top = top < 0?0:top
|
||||
el.css("top", top +"px").css("left",left + "px")
|
||||
})
|
||||
$(window).on("mouseup", function(e){
|
||||
//console.log("unbind mouse up")
|
||||
$(window).unbind("mousemove", null)
|
||||
})
|
||||
})
|
||||
}
|
||||
_autoselect(it,i)
|
||||
{
|
||||
if(self.selidx == i) return true
|
||||
if(!it.selected || it.selected == false) return false
|
||||
var data = {
|
||||
id:self.rid,
|
||||
data:it,
|
||||
idx:i}
|
||||
//if(self.selidx != -1)
|
||||
// self.items[self.selidx].selected =false
|
||||
self.selidx = i
|
||||
|
||||
if(self.onlistselect)
|
||||
self.onlistselect(data)
|
||||
this.root.observable.trigger('listselect',data)
|
||||
return true
|
||||
}
|
||||
_select(event)
|
||||
{
|
||||
if(self.selidx != -1 && self.selidx < self.items.length)
|
||||
self.items[self.selidx].selected =false
|
||||
event.item.item.selected = true
|
||||
//console.log(self.items)
|
||||
self.update()
|
||||
//event.preventUpdate = true
|
||||
}
|
||||
|
||||
_dbclick(event)
|
||||
{
|
||||
data = {
|
||||
id:self.rid,
|
||||
data:event.item.item,
|
||||
idx: event.item.i}
|
||||
if(self.onlistdbclick)
|
||||
self.onlistdbclick(data)
|
||||
self.root.observable.trigger('listdbclick', data)
|
||||
}
|
||||
</script>
|
||||
</afx-float-list>
|
@ -1,218 +0,0 @@
|
||||
<afx-grid-view>
|
||||
<afx-grid-row ref="gridhead" rootid = {rid} observable = {root.observable} header="true" class = {grid_row_header:header} if = {header} cols = {header}> </afx-grid-row>
|
||||
<div ref = "scroller" style="width:100%; overflow:auto;">
|
||||
<div ref = "container" style ="padding-bottom:10px">
|
||||
<afx-grid-row each={ child, i in rows } class = {selected: child.selected} rootid = {parent.rid} observable = {parent.root.observable} index = {i} cols = {child} ondblclick = {parent._dbclick} onclick = {parent._select} oncontextmenu = {parent._select} head = {parent.refs.gridhead} ></afx-grid-row>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
this.header = opts.header
|
||||
this.rows = opts.rows || []
|
||||
var self = this
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
self.selidx = -1
|
||||
self.nrow = 0
|
||||
self.ongridselect = opts.ongridselect
|
||||
self.ongriddbclick = opts.ongriddbclick
|
||||
self.root.observable = opts.observable
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "selected")
|
||||
self._select({item:self.rows[v], preventDefault:function(){}})
|
||||
else if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
this.calibrate_size = function()
|
||||
{
|
||||
if(self.header && self.refs.gridhead)
|
||||
{
|
||||
$(self.refs.scroller).css("height",
|
||||
$(self.root).height() - $(self.refs.gridhead.root).children().first().height()
|
||||
+ "px")
|
||||
}
|
||||
else
|
||||
$(self.refs.scroller).css("height",
|
||||
$(self.root).height() + "px")
|
||||
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
if(k == "selected")
|
||||
return (self.selidx == -1?null:self.rows[self.selidx])
|
||||
return self[k]
|
||||
}
|
||||
|
||||
this.on("mount", function(){
|
||||
if(self.refs.gridhead)
|
||||
self.refs.gridhead.observable = self.root.observable
|
||||
$(self.refs.container)
|
||||
.css("display","table")
|
||||
//.css("flex-direction","column")
|
||||
.css("width","100%")
|
||||
self.calibrate_size()
|
||||
|
||||
self.root.observable.on("resize",function(){
|
||||
if(self.root)
|
||||
self.calibrate_size()
|
||||
})
|
||||
})
|
||||
this.on("updated",function(){
|
||||
if(self.selidx >= self.rows.length)
|
||||
self.selidx = -1
|
||||
if(self.nrow == self.rows.length) return
|
||||
self.nrow = self.rows.length
|
||||
self.calibrate_size()
|
||||
})
|
||||
_select(event)
|
||||
{
|
||||
var data = {
|
||||
id:self.rid,
|
||||
data:event.item}
|
||||
if(self.ongridselect)
|
||||
self.ongridselect(data)
|
||||
if(self.selidx != -1)
|
||||
self.rows[self.selidx].selected =false
|
||||
self.selidx = event.item.i
|
||||
self.rows[self.selidx].selected = true
|
||||
self.root.observable.trigger('gridselect',data)
|
||||
event.preventUpdate = true
|
||||
self.update()
|
||||
//event.preventDefault()
|
||||
}
|
||||
_dbclick(event)
|
||||
{
|
||||
data = {
|
||||
id:self.rid,
|
||||
data:event.item}
|
||||
if(self.ongriddbclick)
|
||||
self.ongriddbclick(data)
|
||||
self.root.observable.trigger('griddbclick', data)
|
||||
}
|
||||
|
||||
</script>
|
||||
</afx-grid-view>
|
||||
|
||||
<afx-grid-row>
|
||||
<div style = {!header? "display: table-cell;" :""} onclick = {parent._cell_select} each = { child,i in cols } class = {string:typeof child.value == "string", number: typeof child.value == "number", cellselected: parent._auto_cell_select(child,i)} >
|
||||
<afx-label color={child.color} icon = {child.icon} iconclass = {child.iconclass} text = {child.value} ></afx-label>
|
||||
</div>
|
||||
<script>
|
||||
this.cols = opts.cols || []
|
||||
var self = this
|
||||
this.rid = opts.rootid
|
||||
this.index = opts.index
|
||||
this.header = eval(opts.header)||false
|
||||
this.head = opts.head
|
||||
this.selidx = -1
|
||||
self.observable = opts.observable
|
||||
this.colssize = []
|
||||
var update_header_size = function()
|
||||
{
|
||||
if(!self.cols || self.cols.length == 0) return
|
||||
var totalw = $(self.root).parent().width()
|
||||
if(totalw == 0) return
|
||||
var ocw = 0
|
||||
var nauto = 0
|
||||
self.colssize = []
|
||||
$.each(self.cols, function(i,e){
|
||||
if(e.width)
|
||||
{
|
||||
self.colssize.push(e.width)
|
||||
ocw += e.width
|
||||
}
|
||||
else
|
||||
{
|
||||
self.colssize.push(-1)
|
||||
nauto++
|
||||
}
|
||||
})
|
||||
if(nauto > 0)
|
||||
{
|
||||
var cellw = parseInt((totalw - ocw)/ nauto)
|
||||
$.each(self.colssize,function(i,e){if(e == -1) self.colssize[i] = cellw})
|
||||
}
|
||||
calibrate_size()
|
||||
}
|
||||
var calibrate_size = function()
|
||||
{
|
||||
var i = 0
|
||||
$(self.root)
|
||||
.children()
|
||||
.each(function(){
|
||||
$(this).css("width", self.colssize[i]+"px")
|
||||
i++
|
||||
})
|
||||
}
|
||||
this.on("updated", function(){
|
||||
if(self.header)
|
||||
update_header_size()
|
||||
else if(self.head && self.index == 0)
|
||||
{
|
||||
self.colssize = self.head.colssize
|
||||
calibrate_size()
|
||||
}
|
||||
|
||||
})
|
||||
this.on("mount", function(){
|
||||
if (self.header)
|
||||
{
|
||||
$(self.root)
|
||||
.css("display", "flex")
|
||||
.css("flex-direction", "row")
|
||||
update_header_size()
|
||||
}
|
||||
else
|
||||
{
|
||||
$(self.root)
|
||||
.css("display","table-row")
|
||||
//.css("flex-direction","row")
|
||||
.css("width","100%")
|
||||
if(self.head && self.index == 0)
|
||||
{
|
||||
self.colssize = self.head.colssize
|
||||
calibrate_size()
|
||||
}
|
||||
}
|
||||
self.observable.on("gridcellselect", function(data){
|
||||
if(data.id != self.rid || self.selidx == -1) return;
|
||||
if(data.row != self.index)
|
||||
{
|
||||
self.cols[self.selidx].selected = false
|
||||
self.selidx = -1
|
||||
}
|
||||
})
|
||||
self.observable.on("resize",function(){
|
||||
self.update()
|
||||
})
|
||||
})
|
||||
_cell_select(event)
|
||||
{
|
||||
if(self.header) return;
|
||||
if(self.selidx != -1)
|
||||
{
|
||||
self.cols[self.selidx].selected = false
|
||||
self.selidx = -1
|
||||
}
|
||||
self.cols[event.item.i].selected = true
|
||||
|
||||
}
|
||||
_auto_cell_select(child,i)
|
||||
{
|
||||
if(!child.selected || self.header) return false;
|
||||
if(self.selidx == i) return true;
|
||||
var data = {
|
||||
id:self.rid,
|
||||
data:child,
|
||||
col:i,
|
||||
row:self.index}
|
||||
|
||||
self.selidx = i
|
||||
self.observable.trigger("gridcellselect",data)
|
||||
return true;
|
||||
}
|
||||
</script>
|
||||
</afx-grid-row>
|
@ -1,79 +0,0 @@
|
||||
<afx-hbox style = "display:block;">
|
||||
<div ref = "container" class="afx-hbox-container">
|
||||
<yield/>
|
||||
</div>
|
||||
<script>
|
||||
var self = this
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
this.on('mount', function(){
|
||||
self.root.observable = (self.parent && self.parent.root && self.parent.root.observable) || opts.observable || riot.observable()
|
||||
$(self.refs.container)
|
||||
.css("display","flex")
|
||||
.css("flex-direction","row")
|
||||
.css("width","100%")
|
||||
|
||||
calibrate_size()
|
||||
|
||||
if(self.root.observable)
|
||||
{
|
||||
self.root.observable.on("resize", function(w,h){
|
||||
calibrate_size()
|
||||
})
|
||||
self.root.observable.on("calibrate", function(){
|
||||
calibrate_size()
|
||||
})
|
||||
}
|
||||
})
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
var calibrate_size = function()
|
||||
{
|
||||
var auto_width = []
|
||||
var csize, ocwidth = 0, avaiheight;
|
||||
avaiheight = $(self.root).height()
|
||||
avaiWidth = $(self.root).width()
|
||||
/*if(avaiheight == 0)
|
||||
{
|
||||
avaiheight = $(self.parent.root).height()
|
||||
$(self.root).css("height", avaiheight+"px")
|
||||
}
|
||||
if(avaiWidth == 0)
|
||||
{
|
||||
avaiWidth = $(self.parent.root).width()
|
||||
$(self.root).css("height", avaiWidth+"px")
|
||||
}*/
|
||||
$(self.refs.container).css("height",avaiheight + "px")
|
||||
$(self.refs.container)
|
||||
.children()
|
||||
.each(function(e)
|
||||
{
|
||||
this.observable = self.root.observable
|
||||
//.css("height",avaiheight + "px")
|
||||
var dw = $(this).attr("data-width")
|
||||
if(dw)
|
||||
{
|
||||
if(dw == "grow") return
|
||||
if(dw[dw.length-1] === "%")
|
||||
dw = Number(dw.slice(0,-1))*avaiWidth/100;
|
||||
$(this).css("width",dw + "px")
|
||||
ocwidth += Number(dw)
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).css("flex-grow","1")
|
||||
auto_width.push(this)
|
||||
}
|
||||
})
|
||||
csize = (avaiWidth - ocwidth)/ (auto_width.length)
|
||||
if(csize > 0)
|
||||
$.each(auto_width, function(i,v)
|
||||
{
|
||||
$(v).css("width", csize + "px")
|
||||
})
|
||||
self.root.observable.trigger("hboxchange",
|
||||
{id:self.rid, w:csize, h:avaiheight})
|
||||
}
|
||||
</script>
|
||||
</afx-hbox>
|
@ -1,20 +0,0 @@
|
||||
<afx-html ref = "container">
|
||||
<script>
|
||||
this.content = opts.content
|
||||
this.root.innerHTML = this.content
|
||||
this.updateContent = undefined
|
||||
var self = this
|
||||
this.root.set = function(k, v)
|
||||
{
|
||||
self[k] = v
|
||||
if(k == "content")
|
||||
self.root.innerHTML = v
|
||||
else if(k == "updateContent")
|
||||
self.update()
|
||||
}
|
||||
this.on("update", function(){
|
||||
if(self.updateContent)
|
||||
self.root.innerHTML = self.updateContent()
|
||||
})
|
||||
</script>
|
||||
</afx-html>
|
@ -1,33 +0,0 @@
|
||||
<afx-label>
|
||||
<span style = {color?"color:" + color:""} >
|
||||
<i if={iconclass} class = {iconclass} ></i>
|
||||
<i if={icon} class="icon-style" style = { "background: url("+window.OS.API.handler.get+"/"+icon+");background-size: 100% 100%;background-repeat: no-repeat;" }></i>
|
||||
{ text?text.__():"" }
|
||||
</span>
|
||||
<script>
|
||||
this.iconclass = opts.iconclass
|
||||
this.icon = opts.icon
|
||||
this.text = opts.text
|
||||
this.color = opts.color
|
||||
var self = this
|
||||
this.on("update",function(){
|
||||
self.iconclass = opts.iconclass
|
||||
self.icon = opts.icon
|
||||
self.text = opts.text
|
||||
self.color = opts.color
|
||||
})
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
opts[i] = v[i]
|
||||
else
|
||||
opts[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
</script>
|
||||
</afx-label>
|
@ -1,227 +0,0 @@
|
||||
<afx-list-view class = {dropdown: opts.dropdown == "true"} style = "display:flex; flex-direction:column">
|
||||
<div class = "list-container" ref = "container" style="flex:1;">
|
||||
<div if = {opts.dropdown == "true"} ref = "current" onclick = {show_list}>
|
||||
<afx-label ref = "drlabel"></afx-label>
|
||||
</div>
|
||||
<ul ref = "mlist" >
|
||||
<li each={item,i in items } class={selected: parent._autoselect(item,i)} ondblclick = {parent._dbclick} onclick = {parent._select} oncontextmenu = {parent._select}>
|
||||
<afx-label class = {item.class} color = {item.color} iconclass = {item.iconclass} icon = {item.icon} text = {item.text}></afx-label>
|
||||
<i if = {item.closable} class = "closable" click = {parent._remove}></i>
|
||||
<ul if = {item.complex} class = "complex-content">
|
||||
<li each = {ctn,j in item.detail} class = {ctn.class}>{ctn.text.toString()}</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div if = {opts.dropdown != "true" && buttons} class = "button_container">
|
||||
<afx-button each = {btn,i in buttons} text = {btn.text} icon = {btn.icon} iconclass = {btn.iconclass} onbtclick = {btn.onbtclick}></afx-button>
|
||||
</div>
|
||||
<script>
|
||||
this.items = opts.items || []
|
||||
var self = this
|
||||
self.selidx = -1
|
||||
self.onlistselect = opts.onlistselect
|
||||
self.onlistdbclick = opts.onlistdbclick
|
||||
self.onitemclose = opts.onitemclose
|
||||
self.buttons = opts.buttons
|
||||
var onclose = false
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "selected")
|
||||
{
|
||||
if(self.selidx != -1)
|
||||
self.items[self.selidx].selected =false
|
||||
if(v == -1)
|
||||
self.selidx = -1
|
||||
else
|
||||
if(self.items[v]) self.items[v].selected = true
|
||||
}
|
||||
else if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
if(k == "selected")
|
||||
if(self.selidx != -1)
|
||||
return self.items[self.selidx]
|
||||
else
|
||||
return undefined
|
||||
else if(k == "count")
|
||||
return self.items.length
|
||||
return self[k]
|
||||
}
|
||||
self.root.selectNext = function()
|
||||
{
|
||||
var idx = self.selidx + 1
|
||||
if(idx >= self.items.length) return;
|
||||
if(self.selidx != -1)
|
||||
self.items[self.selidx].selected =false
|
||||
self.items[idx].selected =true
|
||||
self.update()
|
||||
}
|
||||
self.root.selectPrev = function()
|
||||
{
|
||||
var idx = self.selidx - 1
|
||||
if(idx < 0) return;
|
||||
if(self.selidx != -1)
|
||||
self.items[self.selidx].selected =false
|
||||
self.items[idx].selected =true
|
||||
self.update()
|
||||
}
|
||||
self.root.push = function(e,u)
|
||||
{
|
||||
self.items.push(e)
|
||||
if(u) self.update()
|
||||
}
|
||||
self.root.unshift = function(e,u)
|
||||
{
|
||||
self.items.unshift(e)
|
||||
if(u) self.update()
|
||||
}
|
||||
self.root.replaceItem = function(o, n, u)
|
||||
{
|
||||
var ix = self.items.indexOf(o)
|
||||
if(ix >= 0)
|
||||
{
|
||||
self.items[ix] = n
|
||||
if(u) self.update()
|
||||
}
|
||||
|
||||
}
|
||||
self.root.remove = function(e,u)
|
||||
{
|
||||
var i = self.items.indexOf(e)
|
||||
if(i >= 0)
|
||||
{
|
||||
if(self.selidx != -1)
|
||||
{
|
||||
self.items[self.selidx].selected =false
|
||||
self.selidx = -1
|
||||
}
|
||||
self.items.splice(i, 1)
|
||||
if(u)
|
||||
self.update()
|
||||
onclose = true
|
||||
}
|
||||
}
|
||||
if(opts.observable)
|
||||
this.root.observable = opts.observable
|
||||
else
|
||||
{
|
||||
this.root.observable = riot.observable()
|
||||
}
|
||||
|
||||
this.on("mount", function(){
|
||||
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
|
||||
|
||||
if(opts.dropdown == "true")
|
||||
{
|
||||
var cl = function()
|
||||
{
|
||||
$(self.refs.container).css("width", $(self.root).width() + "px" )
|
||||
$(self.refs.current).css("width", $(self.root).width() + "px" )
|
||||
$(self.refs.mlist).css("width", $(self.root).width() + "px" )
|
||||
}
|
||||
cl()
|
||||
self.root.observable.on("calibrate", function(){
|
||||
cl()
|
||||
})
|
||||
self.root.observable.on("resize", function(){
|
||||
cl()
|
||||
})
|
||||
$(document).click(function(event) {
|
||||
if(!$(event.target).closest(self.refs.container).length) {
|
||||
$(self.refs.mlist).hide()
|
||||
}
|
||||
})
|
||||
//$(self.root).css("position","relative")
|
||||
$(self.refs.container)
|
||||
.css("position","absolute")
|
||||
.css("display","inline-block")
|
||||
|
||||
$(self.refs.mlist)
|
||||
.css("position","absolute")
|
||||
.css("display","none")
|
||||
.css("top","100%")
|
||||
.css("left","0")
|
||||
|
||||
self.root.observable.on("vboxchange", function(e){
|
||||
if(e.id == self.parent.rid)
|
||||
$(self.refs.container).css("width", $(self.root).parent().innerWidth() + "px" )
|
||||
})
|
||||
}
|
||||
})
|
||||
show_list(event)
|
||||
{
|
||||
var desktoph = $("#desktop").height()
|
||||
var off = $(self.root).offset().top + $(self.refs.mlist).height()
|
||||
if( off > desktoph )
|
||||
$(self.refs.mlist)
|
||||
.css("top","-" + $(self.refs.mlist).outerHeight() + "px")
|
||||
else
|
||||
$(self.refs.mlist).css("top","100%")
|
||||
$(self.refs.mlist).show()
|
||||
//event.preventDefault()
|
||||
event.preventUpdate = true
|
||||
}
|
||||
_remove(event)
|
||||
{
|
||||
r = true
|
||||
if(self.onitemclose)
|
||||
r = self.onitemclose(event)
|
||||
if(r)
|
||||
self.root.remove(event.item.item, true)
|
||||
}
|
||||
_autoselect(it,i)
|
||||
{
|
||||
if(!it.selected || it.selected == false) return false
|
||||
if(self.selidx == i) return true
|
||||
var data = {
|
||||
id:self.rid,
|
||||
data:it,
|
||||
idx:i}
|
||||
//if(self.selidx != -1)
|
||||
// self.items[self.selidx].selected =false
|
||||
self.selidx = i
|
||||
if(opts.dropdown == "true")
|
||||
{
|
||||
$(self.refs.mlist).hide()
|
||||
|
||||
self.refs.drlabel.root.set("*",it)
|
||||
}
|
||||
|
||||
if(self.onlistselect)
|
||||
self.onlistselect(data)
|
||||
this.root.observable.trigger('listselect',data)
|
||||
//console.log("list select")
|
||||
return true
|
||||
}
|
||||
_select(event)
|
||||
{
|
||||
if(onclose)
|
||||
{
|
||||
onclose = false
|
||||
event.preventUpdate = true
|
||||
return
|
||||
}
|
||||
if(self.selidx != -1 && self.selidx < self.items.length)
|
||||
self.items[self.selidx].selected =false
|
||||
event.item.item.selected = true
|
||||
}
|
||||
_dbclick(event)
|
||||
{
|
||||
data = {
|
||||
id:self.rid,
|
||||
data:event.item.item,
|
||||
idx: event.item.i}
|
||||
if(self.onlistdbclick)
|
||||
self.onlistdbclick(data)
|
||||
self.root.observable.trigger('listdbclick', data)
|
||||
}
|
||||
</script>
|
||||
</afx-list-view>
|
@ -1,179 +0,0 @@
|
||||
<afx-menu >
|
||||
<ul class={context: opts.context == "true"}>
|
||||
<li class="afx-corner-fix"></li>
|
||||
<li ref = "container" each={ data,i in items } class = {afx_submenu:data.child != null && data.child.length > 0, fix_padding:data.icon} no-reorder>
|
||||
<a href="#" onclick = {parent.onselect}>
|
||||
<afx-switch if = {data.switch || data.radio} class = {checked:parent.checkItem(data)} enable = false swon = {data.checked} ></afx-switch>
|
||||
<afx-label color = {data.color} iconclass = {data.iconclass} icon = {data.icon} text = {data.text} ></afx-label>
|
||||
<span if={data.shortcut} class = "shortcut">{data.shortcut}</span>
|
||||
</a>
|
||||
|
||||
<afx-menu ref = "submenus" index = {i} if={data.child != null && data.child.length > 0} child={data.child} onmenuselect = {data.onmenuselect} observable = {parent.root.observable} rootid = {parent.rid}></afx-menu>
|
||||
</li>
|
||||
<li class="afx-corner-fix"></li>
|
||||
</ul>
|
||||
<script>
|
||||
this.items = opts.child || []
|
||||
if(opts.index != undefined)
|
||||
this.index = opts.index
|
||||
else
|
||||
this.index = -1
|
||||
var isRoot
|
||||
var lastChecked = undefined
|
||||
if(opts.rootid)
|
||||
{
|
||||
this.rid = opts.rootid
|
||||
isRoot = false
|
||||
}
|
||||
else
|
||||
{
|
||||
this.rid = $(this.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
isRoot = true
|
||||
}
|
||||
var self = this
|
||||
this.onmenuselect = opts.onmenuselect
|
||||
checkItem(d)
|
||||
{
|
||||
if(d.checked == true && d.radio)
|
||||
{
|
||||
if(lastChecked)
|
||||
lastChecked.checked = false
|
||||
lastChecked = d
|
||||
lastChecked.checked = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
|
||||
self.root.push = function(e,u)
|
||||
{
|
||||
self.items.push(e)
|
||||
if(u)
|
||||
self.update()
|
||||
}
|
||||
self.root.unshift = function(e,u)
|
||||
{
|
||||
self.items.unshift(e)
|
||||
if(u)
|
||||
self.update()
|
||||
}
|
||||
self.root.remove = function(e,u)
|
||||
{
|
||||
var i = self.items.indexOf(e)
|
||||
if(i >= 0)
|
||||
self.items.splice(i, 1)
|
||||
if(u)
|
||||
self.update()
|
||||
}
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
self.root.show = function(e)
|
||||
{
|
||||
//only for menucontext
|
||||
if(opts.context != "true") return
|
||||
$(self.root)
|
||||
.css("top", e.clientY - 15 + "px")
|
||||
.css("left",e.clientX -5 + "px")
|
||||
.show()
|
||||
$(document).on("click",mnhide)
|
||||
}
|
||||
|
||||
if(opts.observable)
|
||||
{
|
||||
this.root.observable = opts.observable
|
||||
}
|
||||
else
|
||||
{
|
||||
this.root.observable = riot.observable()
|
||||
this.root.observable.on('menuselect',function(data){
|
||||
if(self.onmenuselect)
|
||||
self.onmenuselect(data)
|
||||
|
||||
if(opts.context == "true")
|
||||
$(self.root).hide()
|
||||
else if(!data.root && self.refs.container)
|
||||
{
|
||||
var arr = self.refs.container.length?self.refs.container:[self.refs.container]
|
||||
for( var i in arr)
|
||||
$("afx-menu",arr[i]).first().hide()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
var mnhide = function(event)
|
||||
{
|
||||
if(opts.context == "true")
|
||||
{
|
||||
if(event.button == 2 || event.originalEvent.button == 2)
|
||||
{
|
||||
return
|
||||
}
|
||||
if(!$(event.target).closest(self.root).length) {
|
||||
$(self.root).hide()
|
||||
$(document).unbind("click",mnhide)
|
||||
}
|
||||
return
|
||||
}
|
||||
if(!$(event.target).closest(self.refs.container).length && self.refs.container) {
|
||||
var arr = self.refs.container.length?self.refs.container:[self.refs.container]
|
||||
for( var i in arr)
|
||||
$("afx-menu",arr[i]).first().hide()
|
||||
$(document).unbind("click",mnhide)
|
||||
}
|
||||
else
|
||||
{
|
||||
if(self.refs.container && self.refs.container.length)
|
||||
for(var i in self.refs.container)
|
||||
if(!$(event.target).closest(self.refs.container[i]).length) {
|
||||
$("afx-menu",self.refs.container[i]).first().hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onselect(event)
|
||||
{
|
||||
var data = {id:self.rid, root:isRoot, e:event, item:event.item}
|
||||
if(event.item.data.switch)
|
||||
{
|
||||
event.item.data.checked = !event.item.data.checked
|
||||
} else if(event.item.data.radio)
|
||||
{
|
||||
if(lastChecked)
|
||||
{
|
||||
lastChecked.checked = false
|
||||
}
|
||||
event.item.data.checked = true
|
||||
lastChecked = event.item.data
|
||||
}
|
||||
this.root.observable.trigger('menuselect',data)
|
||||
if( this.onmenuselect && !isRoot) this.onmenuselect(data)
|
||||
event.preventDefault()
|
||||
$(document).unbind("click",mnhide)
|
||||
if(opts.context == "true") return
|
||||
if(isRoot && self.refs.container)
|
||||
{
|
||||
if(self.refs.container.length)
|
||||
$("afx-menu",self.refs.container[event.item.i]).first().show()
|
||||
else
|
||||
$("afx-menu",self.refs.container).first().show()
|
||||
$(document).on("click",mnhide)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
</afx-menu>
|
@ -1,100 +0,0 @@
|
||||
<afx-nspinner>
|
||||
<input ref = "holder" type="text" value = {value}></input>
|
||||
<ul ref = "spinner">
|
||||
<li class = "incr" ref= "incr" onclick="{ _incr }"> <i></i> </li>
|
||||
<li class = "decr" ref = "decr" onclick="{ _decr }"> <i></i> </li>
|
||||
</ul>
|
||||
<script>
|
||||
|
||||
this.value = eval(opts.value) || 0
|
||||
this.step = Number(opts.step) || 1
|
||||
this.onchange = opts.onchange
|
||||
var self = this
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
|
||||
self._incr = function(e)
|
||||
{
|
||||
self.value = self.value + self.step;
|
||||
self.update();
|
||||
if(self.onchange) self.onchange(self.value);
|
||||
}
|
||||
|
||||
self.on("mount", function(){
|
||||
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
|
||||
$(self.refs.spinner).css("width", "20px" );
|
||||
var cl = function()
|
||||
{
|
||||
$(self.refs.holder).css("width", $(self.root).width() - 20 + "px" )
|
||||
$(self.refs.holder).css("height", $(self.root).height() + "px" )
|
||||
$(self.refs.spinner)
|
||||
.css("width","20px")
|
||||
.css("height", $(self.root).height() + "px" )
|
||||
|
||||
$(self.refs.incr)
|
||||
.css("height", $(self.root).height()/2 - 2 + "px")
|
||||
.css("position", "relative")
|
||||
$(self.refs.decr).css("height", $(self.root).height()/2 -2 + "px")
|
||||
.css("position", "relative")
|
||||
$(self.refs.spinner).find("li")
|
||||
.css("display","block")
|
||||
.css("text-align", "center")
|
||||
.css("vertical-align", "middle")
|
||||
$(self.refs.spinner).find("i")
|
||||
.css("font-size", "16px")
|
||||
.css("position", "absolute")
|
||||
var fn = function(ie, pos)
|
||||
{
|
||||
var el = $(ie).find("i")
|
||||
el
|
||||
.css(pos,($(ie).height()-el.height()) /2 + "px" )
|
||||
.css("left", ($(ie).width()-el.width())/2 + "px" )
|
||||
}
|
||||
fn(self.refs.decr, "bottom")
|
||||
fn(self.refs.incr, "top")
|
||||
}
|
||||
cl()
|
||||
self.root.observable.on("calibrate", function(){
|
||||
cl()
|
||||
})
|
||||
self.root.observable.on("resize", function(){
|
||||
cl()
|
||||
});
|
||||
$(self.refs.holder).on('keyup', function (e) {
|
||||
if (e.keyCode == 13) {
|
||||
var val = self.refs.holder.value;
|
||||
if(!isNaN(val))
|
||||
{
|
||||
val = eval(val)
|
||||
if(val < 0)
|
||||
val = self.value;
|
||||
self.value = val;
|
||||
}
|
||||
self.refs.holder.value = self.value;
|
||||
if(self.onchange) self.onchange(self.value);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
self._decr = function(e)
|
||||
{
|
||||
if(self.value == 0) return;
|
||||
self.value = self.value - self.step;
|
||||
self.update();
|
||||
if(self.onchange) self.onchange(self.value);
|
||||
}
|
||||
|
||||
</script>
|
||||
</afx-nspinner>
|
@ -1,45 +0,0 @@
|
||||
<afx-overlay>
|
||||
<yield/>
|
||||
<script>
|
||||
this.width = opts.width || 200
|
||||
this.height = opts.height || 400
|
||||
var self = this;
|
||||
self.commander = null
|
||||
this.root.observable = opts.observable || riot.observable()
|
||||
var id = $(self.root).attr("data-id")
|
||||
var calibre_size = function()
|
||||
{
|
||||
$(self.root)
|
||||
.css("width", self.width + "px")
|
||||
.css("height", self.height + "px")
|
||||
self.root.observable.trigger("resize", {id:id,w:self.width,h:self.height})
|
||||
}
|
||||
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
if( k == "width" || k == "height")
|
||||
calibre_size()
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
|
||||
self.on("mount", function(){
|
||||
$(self.root)
|
||||
.css("position", "absolute")
|
||||
//.css("z-index",1000000)
|
||||
$(self.root).children().each(function(e){
|
||||
this.observalbe = self.root.observalbe
|
||||
})
|
||||
calibre_size()
|
||||
self.root.observable.trigger("rendered", self.root)
|
||||
})
|
||||
</script>
|
||||
</afx-overlay>
|
@ -1,85 +0,0 @@
|
||||
<afx-resizer>
|
||||
<script>
|
||||
var self = this
|
||||
self.dir = "hz"
|
||||
self.resizable = undefined
|
||||
self.parent = undefined
|
||||
self.minsize = 0
|
||||
self.on("mount", function(){
|
||||
//self.parent = $(self.root).parent().parent()
|
||||
var tagname = $(self.parent.root).prop("tagName")
|
||||
self.resizable = $(self.root).prev().length == 1 ? $(self.root).prev()[0]: undefined
|
||||
//self.nextel = $(self.root).next().length == 1 ? $(self.root).next()[0]: undefined
|
||||
if(tagname == "AFX-HBOX")
|
||||
{
|
||||
self.dir = "hz"
|
||||
$(self.root).css("cursor", "col-resize")
|
||||
if(self.resizable)
|
||||
{
|
||||
self.minsize = parseInt($(self.resizable).attr("min-width"))
|
||||
}
|
||||
}
|
||||
else if(tagname == "AFX-VBOX")
|
||||
{
|
||||
self.dir = "ve"
|
||||
$(self.root).css("cursor", "row-resize")
|
||||
if(self.resizable)
|
||||
{
|
||||
self.minsize = parseInt($(self.resizable).attr("min-height"))
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//$(self.root).css("cursor", "normal")
|
||||
self.dir = "none"
|
||||
}
|
||||
if(!self.minsize)
|
||||
self.minsize = 10
|
||||
enable_dragging()
|
||||
})
|
||||
|
||||
var enable_dragging = function()
|
||||
{
|
||||
$(self.root)
|
||||
.css("user-select","none")
|
||||
$(self.root).on("mousedown", function(e){
|
||||
e.preventDefault()
|
||||
|
||||
$(window).on("mousemove", function(evt){
|
||||
if(!self.resizable) return
|
||||
if(self.dir == "hz")
|
||||
horizontalResize(evt)
|
||||
else if (self.dir == "ve")
|
||||
verticalResize(evt)
|
||||
})
|
||||
$(window).on("mouseup", function(evt){
|
||||
//console.log("unbind mouse up")
|
||||
$(window).unbind("mousemove", null)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
var horizontalResize = function(e)
|
||||
{
|
||||
if(!self.resizable) return
|
||||
var offset = $(self.resizable).offset()
|
||||
w = Math.round(e.clientX - offset.left)
|
||||
if(w < self.minsize) w = self.minsize
|
||||
$(self.resizable).attr("data-width", w.toString())
|
||||
self.parent.root.observable.trigger("calibrate", self.resizable)
|
||||
}
|
||||
|
||||
var verticalResize = function(e)
|
||||
{
|
||||
//console.log("vboz")
|
||||
if(!self.resizable) return
|
||||
var offset = $(self.resizable).offset()
|
||||
//console.log($(self.resizable).innerHeight())
|
||||
//console.log(e.clientY, offset.top)
|
||||
h = Math.round(e.clientY - offset.top)
|
||||
if(h < self.minsize) h = minsize
|
||||
$(self.resizable).attr("data-height", h.toString())
|
||||
self.parent.root.observable.trigger("calibrate", self.resizable)
|
||||
}
|
||||
</script>
|
||||
</afx-resizer>
|
@ -1,107 +0,0 @@
|
||||
<afx-slider>
|
||||
<div class= "container" ref="container">
|
||||
<div class = "progress" ref ="prg"></div>
|
||||
<div if = {dragable} class = "dragpoint" ref = "point"></div>
|
||||
</div>
|
||||
<script>
|
||||
this.value = Number(opts.value) || 0
|
||||
this.max = Number(opts.max) || 100
|
||||
if(opts.dragable != undefined)
|
||||
this.dragable = eval(opts.dragable)
|
||||
else
|
||||
this.dragable = true
|
||||
this.onchanging = opts.onchanging
|
||||
this.onchange = opts.onchange
|
||||
//this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
var self = this
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
/*if(k == "value")
|
||||
{
|
||||
if(self.onchange) self.onchange(self.value)
|
||||
if(self.onchanging) self.onchanging(self.value)
|
||||
}*/
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
|
||||
var calibrate = function() {
|
||||
if(self.value > self.max) self.value = self.max
|
||||
$(self.refs.container).css("width", $(self.root).width() + "px")
|
||||
var w = $(self.refs.container).width()*self.value/ self.max
|
||||
$(self.refs.prg).css("width", w + "px").css("height", $(self.refs.container).height()+"px")
|
||||
if(self.dragable)
|
||||
{
|
||||
var ow = w - $(self.refs.point).width()/2
|
||||
var top = Math.floor(($(self.refs.prg).height() - $(self.refs.point).height())/2)
|
||||
$(self.refs.point).css("left", ow + "px").css("top", top + "px")
|
||||
}
|
||||
}
|
||||
self.on("update", function(){
|
||||
calibrate()
|
||||
})
|
||||
var enable_dragging = function()
|
||||
{
|
||||
$(self.refs.point)
|
||||
.css("user-select","none")
|
||||
.css("cursor","default")
|
||||
$(self.refs.point).on("mousedown", function(e){
|
||||
e.preventDefault()
|
||||
offset = $(self.refs.container).offset()
|
||||
$(window).on("mousemove", function(e){
|
||||
var left
|
||||
left = e.clientX - offset.left
|
||||
left = left < 0?0:left
|
||||
var maxw = $(self.refs.container).width();
|
||||
left = left > maxw?maxw : left
|
||||
self.value = left*self.max/maxw
|
||||
calibrate()
|
||||
if(self.onchanging) self.onchanging(self.value)
|
||||
})
|
||||
$(window).on("mouseup", function(e){
|
||||
if(self.onchange) self.onchange(self.value)
|
||||
$(window).unbind("mousemove", null)
|
||||
$(window).unbind("mouseup", null)
|
||||
})
|
||||
})
|
||||
}
|
||||
self.on("mount", function(){
|
||||
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
|
||||
if(self.dragable)
|
||||
{
|
||||
$(self.refs.point).css("position", "absolute")
|
||||
$(self.refs.point).hide()
|
||||
$(self.root).mouseover(function(){
|
||||
$(self.refs.point).show()
|
||||
}).mouseout(function(){
|
||||
$(self.refs.point).hide()
|
||||
})
|
||||
enable_dragging()
|
||||
}
|
||||
$(self.refs.container).click( function(e){
|
||||
var offset = $(self.refs.container).offset()
|
||||
var left = e.clientX - offset.left
|
||||
var maxw = $(self.refs.container).width()
|
||||
self.value = left*self.max/maxw
|
||||
calibrate()
|
||||
if(self.onchange) self.onchange(self.value)
|
||||
if(self.onchanging) self.onchanging(self.value)
|
||||
})
|
||||
self.root.observable.on("calibrate",function(){
|
||||
calibrate()
|
||||
})
|
||||
self.root.observable.on("resize", function(){
|
||||
calibrate()
|
||||
})
|
||||
calibrate()
|
||||
})
|
||||
</script>
|
||||
</afx-slider>
|
@ -1,56 +0,0 @@
|
||||
<afx-switch>
|
||||
<span class = {swon: swon} onclick = {toggle}></span>
|
||||
<script>
|
||||
if(opts.swon != undefined)
|
||||
this.swon = opts.swon
|
||||
else
|
||||
this.swon = false
|
||||
var self = this
|
||||
//this.root.observable = opts.observable
|
||||
if(opts.enable != undefined)
|
||||
this.enable = opts.enable
|
||||
else
|
||||
this.enable = true
|
||||
this.onchange = opts.onchange
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
opts[i] = v[i]
|
||||
else
|
||||
opts[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
this.root.toggle = function()
|
||||
{
|
||||
opts.swon = !self.swon
|
||||
self.update()
|
||||
}
|
||||
/*this.on("mount", function(){
|
||||
|
||||
})*/
|
||||
this.on("update", function(e){
|
||||
self.swon = typeof opts.swon == "function"?opts.swon():opts.swon
|
||||
})
|
||||
toggle(e)
|
||||
{
|
||||
if(!self.enable) return
|
||||
opts.swon = !self.swon
|
||||
self.swon = opts.swon
|
||||
var data = {
|
||||
id: self.rid,
|
||||
data: opts.swon
|
||||
}
|
||||
if(opts.onchange)
|
||||
opts.onchange(data)
|
||||
if(self.root.observable)
|
||||
self.root.observable.trigger("switch", data)
|
||||
|
||||
}
|
||||
</script>
|
||||
</afx-switch>
|
@ -1,54 +0,0 @@
|
||||
<afx-sys-panel>
|
||||
<div>
|
||||
<afx-menu data-id = "os_menu" ref = "aOsmenu" child={osmenu.child} onmenuselect = {osmenu.onmenuselect} class="afx-panel-os-menu"></afx-menu>
|
||||
<afx-menu data-id = "appmenu" ref = "aAppmenu" child={appmenu.child} class = "afx-panel-os-app"></afx-menu>
|
||||
<afx-menu data-id = "sys_tray" ref = "aTray" child={systray.child} onmenuselect = {systray.onmenuselect} class = "afx-panel-os-stray"></afx-menu>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
this.osmenu = { child: [] }
|
||||
this.appmenu = { child: [] }
|
||||
this.systray = {
|
||||
child: [],
|
||||
onmenuselect: function(d){
|
||||
if(d.root)
|
||||
d.e.item.data.awake(d.e)
|
||||
}
|
||||
}
|
||||
|
||||
var self = this
|
||||
self.root.attachservice = function(s)
|
||||
{
|
||||
self.refs.aTray.root.unshift(s,true)
|
||||
s.attach(self.refs.aTray)
|
||||
}
|
||||
self.root.detachservice = function(s)
|
||||
{
|
||||
self.refs.aTray.root.remove(s, true)
|
||||
}
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
return self[k]
|
||||
}
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
this.on('mount', function() {
|
||||
//console.log(self.refs.aOsmenu.root)
|
||||
$(self.refs.aOsmenu.root).css("z-index",1000000)
|
||||
$(self.refs.aAppmenu.root).css("z-index",1000000)
|
||||
$(self.refs.aTray.root).css("z-index",1000000)
|
||||
window.OS.courrier.trigger("syspanelloaded")
|
||||
})
|
||||
</script>
|
||||
</afx-sys-panel>
|
@ -1,71 +0,0 @@
|
||||
<afx-tab-bar>
|
||||
<afx-list-view ref = "list" />
|
||||
<script>
|
||||
var self = this
|
||||
this.closable = opts.closable || false
|
||||
self.ontabselect = opts.ontabselect
|
||||
|
||||
self.root.get = function (k) {
|
||||
return self.refs.list.root.get(k)
|
||||
}
|
||||
|
||||
self.root.update = function(){
|
||||
self.update(true)
|
||||
}
|
||||
self.on("mount", function(){
|
||||
self.root.observable = opts.observable || (self.parent && self.parent.root && self.parent.root.observable) || riot.observable()
|
||||
self.refs.list.root.observable = self.root.observable
|
||||
/*self.root.observable.on("listselect", function(){
|
||||
console.log("list select")
|
||||
})*/
|
||||
self.refs.list.root.set ("onlistselect",function (e) {
|
||||
//console.log("tab is seleced")
|
||||
self.root.observable.trigger("tabselect", e)
|
||||
if(self.ontabselect)
|
||||
self.ontabselect(e)
|
||||
})
|
||||
})
|
||||
|
||||
self.root.set = function (k,v){
|
||||
if( k == "*")
|
||||
for(var i in v)
|
||||
self.refs.list.root.set(i,v[i])
|
||||
else if(k == "closable")
|
||||
{
|
||||
self.closable = v
|
||||
}
|
||||
else if(k == "ontabselect")
|
||||
self.ontabselect = v
|
||||
else
|
||||
{
|
||||
if(k == "items")
|
||||
{
|
||||
for(var i in v)
|
||||
v[i].closable = self.closable
|
||||
}
|
||||
self.refs.list.root.set(k,v)
|
||||
}
|
||||
//self.update()
|
||||
}
|
||||
|
||||
self.root.push = function(e,u)
|
||||
{
|
||||
e.closable = self.closable
|
||||
self.refs.list.root.push(e,u)
|
||||
}
|
||||
self.root.replaceItem = function(o,n,u)
|
||||
{
|
||||
n.closable = self.closable
|
||||
self.refs.list.root.replaceItem(o,n,u)
|
||||
}
|
||||
self.root.unshift = function(e,u)
|
||||
{
|
||||
self.refs.list.root.unshift(e,u)
|
||||
}
|
||||
self.root.remove = function(e,u)
|
||||
{
|
||||
self.refs.list.root.remove(e,u)
|
||||
}
|
||||
|
||||
</script>
|
||||
</afx-tab-bar>
|
@ -1,91 +0,0 @@
|
||||
<afx-tab-container>
|
||||
<afx-hbox ref = "mybox" if = {bar == "left"}>
|
||||
<afx-tab-bar data-ref="tabbar" ></afx-tab-bar>
|
||||
<div data-ref="container"></div>
|
||||
</afx-hbox>
|
||||
<afx-vbox ref = "mybox" if = { bar == "top"}>
|
||||
<afx-tab-bar data-ref="tabbar" ></afx-tab-bar>
|
||||
<div data-ref="container"></div>
|
||||
</afx-vbox>
|
||||
<script>
|
||||
this.bar = opts.bar || "top"
|
||||
this.barwidth = opts.barwidth
|
||||
this.barheight = opts.barheight
|
||||
var schemes = []
|
||||
var self = this
|
||||
var calibrate = function()
|
||||
{
|
||||
$(self.refs.mybox.root).css("width", $(self.root).width()+"px")
|
||||
$(self.refs.mybox.root).css("height", $(self.root).height()+"px")
|
||||
self.root.observable.trigger("calibrate")
|
||||
}
|
||||
self.on("mount", function () {
|
||||
self.tabbar = $("[data-ref='tabbar']", self.root)[0]
|
||||
self.container = $("[data-ref='container']", self.root)[0]
|
||||
if(self.barwidth)
|
||||
$(self.tabbar).attr("data-width", self.barwidth)
|
||||
if(self.barheight)
|
||||
$(self.tabbar).attr("data-height", self.barheight)
|
||||
self.root.observable = (self.parent && self.parent.root && self.parent.root.observable) || opts.observable || riot.observable()
|
||||
self.tabbar.set("ontabselect", function(e){
|
||||
$(self.container).children().each(function(el){
|
||||
$(this).hide()
|
||||
})
|
||||
$(e.data.scheme).show()
|
||||
e.data.handler.render()
|
||||
calibrate()
|
||||
})
|
||||
self.root.observable.on("resize", function(){
|
||||
calibrate()
|
||||
})
|
||||
})
|
||||
self.on("update", function(){
|
||||
$(self.container).children().each(function(e){
|
||||
if(this.update) this.update()
|
||||
})
|
||||
})
|
||||
var render = function(el)
|
||||
{
|
||||
var sch = $.parseHTML(el.scheme)
|
||||
$(self.container).append(sch)
|
||||
el.scheme = sch
|
||||
riot.mount(sch, {observable: self.root.observable})
|
||||
$(sch).hide()
|
||||
el.handler = el.handler(sch[0])
|
||||
//console.log(el.handler)
|
||||
//el.handler.render()
|
||||
self.root.observable.trigger("tabrendered")
|
||||
//self.root.observable.trigger("calibrate")
|
||||
}
|
||||
var addTab = function(el)
|
||||
{
|
||||
if(!el.f)
|
||||
el.f = (function(){})
|
||||
self.tabbar.push(el)
|
||||
if(el.url)
|
||||
{
|
||||
el.url.asFileHandler().read(function(d){
|
||||
el.scheme = d
|
||||
render(el)
|
||||
})
|
||||
}
|
||||
else
|
||||
{
|
||||
render(el)
|
||||
}
|
||||
}
|
||||
self.root.setTabs = function(arr)
|
||||
{
|
||||
if(arr.length <= 0)
|
||||
{
|
||||
self.tabbar.set("selected", 0)
|
||||
return
|
||||
}
|
||||
self.root.observable.one("tabrendered", function(){
|
||||
arr.splice(0,1)
|
||||
self.root.setTabs(arr)
|
||||
})
|
||||
addTab(arr[0])
|
||||
}
|
||||
</script>
|
||||
</afx-tab-container>
|
@ -1,169 +0,0 @@
|
||||
<afx-tree-view>
|
||||
<div class={afx_tree_item_selected:treeroot.selectedItem && treeroot.selectedItem.treepath == data.treepath, afx_folder_item: isFolder(), afx_tree_item_odd: index%2 != 0 } onclick={select} ondblclick = {_dbclick} oncontextmenu = {select}>
|
||||
<ul style = "padding:0;margin:0;white-space: nowrap;">
|
||||
<li ref = "padding" ></li>
|
||||
<li class = "itemname" style="display:inline-block;" >
|
||||
<i if={ !isFolder() && data.iconclass} class = {data.iconclass} ></i>
|
||||
<i if={!isFolder() && data.icon} class="icon-style" style = { "background: url("+data.icon+");background-size: 100% 100%;background-repeat: no-repeat;" }></i>
|
||||
|
||||
<span onclick={ toggle } if={ isFolder() } class={open ? 'afx-tree-view-folder-open' : 'afx-tree-view-folder-close'}></span>
|
||||
{ data.name }
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<ul if={ isFolder() } show={ isFolder() && open }>
|
||||
<li each={ child, i in data.nodes }>
|
||||
<afx-tree-view ontreeselect = {parent.ontreeselect} index = {i} fetch = {parent.fetch} ontreedbclick = {parent.ontreedbclick} data={child} indent={indent+1} observable = {parent.root.observable} path = {parent.data.treepath + ">" + i} treeroot= {parent.treeroot}></afx-tree-view>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<script>
|
||||
var self = this
|
||||
self.open = true
|
||||
self.data = { name:"", nodes:null, treepath: opts.path, i:-1}
|
||||
if(opts.data)
|
||||
{
|
||||
self.data = opts.data
|
||||
//self.name = opts.data.name
|
||||
//self.nodes = opts.data.nodes
|
||||
//self.icon = opts.data.icon
|
||||
self.open = opts.data.open == undefined?true:opts.data.open
|
||||
//self.iconclass = opts.data.iconclass
|
||||
}
|
||||
self.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
self.data.rid = self.rid
|
||||
self.data.i = opts.index
|
||||
self.ontreeselect = opts.ontreeselect
|
||||
self.ontreedbclick = opts.ontreedbclick
|
||||
self.fetch = opts.fetch
|
||||
self.indent = opts.indent || 0
|
||||
var istoggle = false
|
||||
if(opts.treeroot)
|
||||
{
|
||||
this.treeroot = opts.treeroot
|
||||
this.treeroot.counter++
|
||||
}
|
||||
else
|
||||
{
|
||||
this.treeroot = self
|
||||
this.treeroot.counter = 0
|
||||
}
|
||||
self.data.treepath = opts.path || 0
|
||||
//self.selected = false
|
||||
self.selectedItem = null
|
||||
self.index = this.treeroot.counter
|
||||
|
||||
var _dfind = function(l,d, k, v)
|
||||
{
|
||||
if( d[k] == v ) return l.push(d)
|
||||
if(d.nodes && d.nodes.length > 0)
|
||||
for(var i in d.nodes)
|
||||
_dfind(l, d.nodes[i],k,v)
|
||||
}
|
||||
self.root.find = function(k, v)
|
||||
{
|
||||
var l = []
|
||||
_dfind(l,self.data,k,v)
|
||||
return l
|
||||
}
|
||||
|
||||
self.root.set = function(k,v)
|
||||
{
|
||||
if(k == "*")
|
||||
for(var i in v)
|
||||
self[i] = v[i]
|
||||
else if (k == "data")
|
||||
for(var i in v)
|
||||
self.data[i] = v[i]
|
||||
else if (k == "selectedItem")
|
||||
{
|
||||
if(self.ontreeselect)
|
||||
self.ontreeselect(self.data)
|
||||
self.treeroot.selectedItem = v
|
||||
self.root.observable.trigger('treeselect',self.data)
|
||||
}
|
||||
else
|
||||
self[k] = v
|
||||
self.update()
|
||||
}
|
||||
self.root.get = function(k)
|
||||
{
|
||||
//if(k == "data")
|
||||
// return {name:self.name, nodes: self.nodes, icon:self.icon, iconclass: self.iconclass, selectedItem:self.selectedItem}
|
||||
return self[k]
|
||||
}
|
||||
|
||||
if(opts.observable)
|
||||
this.root.observable = opts.observable
|
||||
else
|
||||
{
|
||||
this.root.observable = riot.observable()
|
||||
}
|
||||
|
||||
this.on("mount", function(){
|
||||
$(self.refs.padding)
|
||||
.css("display", "inline-block")
|
||||
.css("height","1px")
|
||||
.css("padding",0)
|
||||
.css("margin", 0)
|
||||
.css("background-color","transparent")
|
||||
.css("width", self.indent*15 + "px" )
|
||||
})
|
||||
|
||||
isFolder() {
|
||||
return self.data.nodes //&& self.nodes.length
|
||||
}
|
||||
|
||||
toggle(e) {
|
||||
self.open = !self.open
|
||||
e.preventDefault()
|
||||
istoggle = true
|
||||
|
||||
if(self.open && self.data.nodes.length == 0 && self.fetch)
|
||||
{
|
||||
self.fetch(e.item, function(d){
|
||||
self.data.nodes = d
|
||||
self.update()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
select(event)
|
||||
{
|
||||
if(istoggle)
|
||||
{
|
||||
istoggle = false
|
||||
return
|
||||
}
|
||||
/*var data = {
|
||||
id:self.rid,
|
||||
data:event.item,
|
||||
path:self.data.path
|
||||
} */
|
||||
if(self.ontreeselect)
|
||||
self.ontreeselect(self.data)
|
||||
self.treeroot.selectedItem = self.data
|
||||
this.root.observable.trigger('treeselect',self.data)
|
||||
event.preventUpdate = true
|
||||
self.treeroot.update()
|
||||
event.preventDefault()
|
||||
}
|
||||
_dbclick(event)
|
||||
{
|
||||
if(istoggle)
|
||||
{
|
||||
istoggle = false
|
||||
return
|
||||
}
|
||||
/*data = {
|
||||
id:self.rid,
|
||||
data:event.item,
|
||||
path: self.data.path}*/
|
||||
if(self.ontreedbclick)
|
||||
{
|
||||
self.ontreedbclick(self.data)
|
||||
}
|
||||
self.root.observable.trigger('treedbclick', self.data)
|
||||
}
|
||||
</script>
|
||||
</afx-tree-view>
|
@ -1,81 +0,0 @@
|
||||
<afx-vbox style = "display:block;">
|
||||
<div ref = "container" class="afx-vbox-container">
|
||||
<yield/>
|
||||
</div>
|
||||
<script>
|
||||
var self = this
|
||||
this.rid = $(self.root).attr("data-id") || Math.floor(Math.random() * 100000) + 1
|
||||
this.on('mount', function(){
|
||||
self.root.observable = (self.parent && self.parent.root && self.parent.root.observable) || opts.observable || riot.observable()
|
||||
$(self.refs.container)
|
||||
.css("display","flex")
|
||||
.css("flex-direction","column")
|
||||
.css("width","100%")
|
||||
//.css("background-color","red")
|
||||
//.css("overflow", "hidden")
|
||||
|
||||
calibrate_size()
|
||||
|
||||
if(self.root.observable)
|
||||
{
|
||||
self.root.observable.on("resize", function(w,h){
|
||||
calibrate_size()
|
||||
})
|
||||
self.root.observable.on("calibrate", function(){
|
||||
calibrate_size()
|
||||
})
|
||||
}
|
||||
})
|
||||
self.root.update = function()
|
||||
{
|
||||
self.update()
|
||||
}
|
||||
var calibrate_size = function()
|
||||
{
|
||||
var auto_height = []
|
||||
var csize, ocheight = 0, avaiheight;
|
||||
avaiheight = $(self.root).height()
|
||||
avaiwidth = $(self.root).width()
|
||||
/*if(avaiheight == 0)
|
||||
{
|
||||
avaiheight = $(self.parent.root).height()
|
||||
$(self.root).css("height", avaiheight+"px")
|
||||
}
|
||||
if(avaiwidth == 0)
|
||||
{
|
||||
avaiwidth = $(self.parent.root).width()
|
||||
$(self.root).css("height", avaiwidth+"px")
|
||||
}*/
|
||||
$(self.refs.container).css("height",avaiheight + "px")
|
||||
$(self.refs.container)
|
||||
.children()
|
||||
.each(function(e)
|
||||
{
|
||||
this.observable = self.root.observable
|
||||
//.css("border","1px solid black")
|
||||
var dw = $(this).attr("data-height")
|
||||
if(dw)
|
||||
{
|
||||
if(dw == "grow") return
|
||||
if(dw[dw.length-1] === "%")
|
||||
dw = Number(dw.slice(0,-1))*avaiheight/100;
|
||||
$(this).css("height",dw + "px")
|
||||
ocheight += Number(dw)
|
||||
}
|
||||
else
|
||||
{
|
||||
$(this).css("flex-grow","1")
|
||||
auto_height.push(this)
|
||||
}
|
||||
})
|
||||
csize = (avaiheight - ocheight)/ (auto_height.length)
|
||||
if(csize > 0)
|
||||
$.each(auto_height, function(i,v)
|
||||
{
|
||||
$(v).css("height", csize + "px")
|
||||
})
|
||||
self.root.observable.trigger("vboxchange",
|
||||
{id:self.rid, w:avaiwidth, h:csize})
|
||||
}
|
||||
</script>
|
||||
</afx-vbox>
|
665
src/core/tags/tag.ts
Normal file
665
src/core/tags/tag.ts
Normal file
@ -0,0 +1,665 @@
|
||||
/**
|
||||
*
|
||||
* Extend the HTMLElement interface with some utility function need
|
||||
* by AFX API
|
||||
*
|
||||
* @interface HTMLElement
|
||||
*/
|
||||
interface HTMLElement {
|
||||
/**
|
||||
* Recursively update a tag and all its children
|
||||
*
|
||||
* @param {*} [d] data to send to all element in the DOM subtree
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
update(d?: any): void;
|
||||
|
||||
/**
|
||||
*
|
||||
* AFX will automatically bind the context menu on an HTMLElement
|
||||
* if this function is defined on that element. The function should
|
||||
* define the content of the context menu and its action
|
||||
*
|
||||
* Once the context menu is bound to the element, all context menu handle
|
||||
* defined on any child of this element will be ignored.
|
||||
*
|
||||
* @param {JQuery.MouseEventBase} e a mouse event
|
||||
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]]
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void;
|
||||
|
||||
/**
|
||||
* Mount the element and all the children on its DOM subtree. This action
|
||||
* is performed in a top-down manner
|
||||
*
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
sync(): void;
|
||||
|
||||
/**
|
||||
*
|
||||
* This action allows to generated all the DOM nodes defined by all AFX tags
|
||||
* in its hierarchy.
|
||||
* It performs two operations, one top-down operation to generate all the
|
||||
* necessary DOM nodes, another bottom-up operation to init all the AFX tag
|
||||
* in the current element DOM hierarchy
|
||||
*
|
||||
* @param {OS.API.Announcer} o an AntOS observable object
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
afxml(o: OS.API.Announcer): void;
|
||||
|
||||
/**
|
||||
* Perform DOM generation ([[afxml]]) then mount ([[sync]]) all the
|
||||
* elements.
|
||||
*
|
||||
* @param {OS.API.Announcer} o an AntOS observable object
|
||||
* @param {boolean} [flag] indicates whether this is the top-most call of the operation
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
uify(o: OS.API.Announcer, flag?: boolean): void;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
mozRequestFullScreen: any;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
webkitRequestFullscreen: any;
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @type {*}
|
||||
* @memberof HTMLElement
|
||||
*/
|
||||
msRequestFullscreen: any;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
* @interface Document
|
||||
*/
|
||||
interface Document {
|
||||
mozCancelFullScreen: any;
|
||||
webkitExitFullscreen: any;
|
||||
cancelFullScreen: any;
|
||||
}
|
||||
namespace OS {
|
||||
export namespace GUI {
|
||||
/**
|
||||
* [[TagLayoutType]] interface using by AFX tags to defined
|
||||
* its internal DOM hierarchy
|
||||
*
|
||||
* @export
|
||||
* @interface TagLayoutType
|
||||
*/
|
||||
export interface TagLayoutType {
|
||||
/**
|
||||
* Element tag name
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
el: string;
|
||||
|
||||
/**
|
||||
* Children layout of the current element
|
||||
*
|
||||
* @type {TagLayoutType[]}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
children?: TagLayoutType[];
|
||||
|
||||
/**
|
||||
* Reference name of the element used by AFX Tag
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
ref?: string;
|
||||
|
||||
/**
|
||||
* CSS class of the element
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
class?: string;
|
||||
|
||||
/**
|
||||
* this is the `data-id` attribute of the element,
|
||||
* can be query by the [[aid]] Tag API function.
|
||||
* Not to be confused with the DOM `id` attribute
|
||||
*
|
||||
* @type {(string | number)}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
id?: string | number;
|
||||
|
||||
/**
|
||||
* Tooltip text of the element
|
||||
*
|
||||
* @type {(string | FormattedString)}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
tooltip?: string | FormattedString;
|
||||
|
||||
/**
|
||||
* `data-width` of the element, not to be confused with
|
||||
* the `width` attribute of the DOM element
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
width?: number;
|
||||
|
||||
/**
|
||||
** `data-height` of the element, not to be confused with
|
||||
* the `height` attribute of the DOM element
|
||||
*
|
||||
* @type {number}
|
||||
* @memberof TagLayoutType
|
||||
*/
|
||||
height?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Data type for event issued by AFX tags
|
||||
*
|
||||
* @export
|
||||
* @interface TagEventDataType
|
||||
* @template T item template
|
||||
*/
|
||||
export interface TagEventDataType<T> {
|
||||
/**
|
||||
* Reference to the item involved in the event
|
||||
*
|
||||
* @type {T}
|
||||
* @memberof TagEventDataType
|
||||
*/
|
||||
item?: T;
|
||||
|
||||
[propName: string]: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format of the event issued by AFX tags
|
||||
*
|
||||
* @export
|
||||
* @interface TagEventType
|
||||
* @template T data type
|
||||
*/
|
||||
export interface TagEventType<T> {
|
||||
/**
|
||||
* `data-id` of the tag that trigger the
|
||||
* event
|
||||
*
|
||||
* @type {(number | string)}
|
||||
* @memberof TagEventType
|
||||
*/
|
||||
id: number | string;
|
||||
|
||||
/**
|
||||
* Data object of the event
|
||||
*
|
||||
* @type {T}
|
||||
* @memberof TagEventType
|
||||
*/
|
||||
data: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Drag and Drop data type sent between mouse events
|
||||
*
|
||||
* @export
|
||||
* @interface DnDEventDataType
|
||||
* @template T
|
||||
*/
|
||||
export interface DnDEventDataType<T> {
|
||||
/**
|
||||
* Reference to the source DOM element
|
||||
*
|
||||
* @type {T}
|
||||
* @memberof DnDEventDataType
|
||||
*/
|
||||
from: T;
|
||||
|
||||
/**
|
||||
* Reference to the target DOM element
|
||||
*
|
||||
* @type {T}
|
||||
* @memberof DnDEventDataType
|
||||
*/
|
||||
to: T;
|
||||
}
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class AFXTag
|
||||
* @extends {HTMLElement}
|
||||
*/
|
||||
export abstract class AFXTag extends HTMLElement {
|
||||
/**
|
||||
* The announcer object of the tag
|
||||
*
|
||||
* @type {API.Announcer}
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
observable: API.Announcer;
|
||||
|
||||
/**
|
||||
* Reference to some of the tag's children
|
||||
* element. This reference object is built
|
||||
* based on the `ref` property found in the
|
||||
* tag layout [[TagLayoutType]]
|
||||
*
|
||||
* @protected
|
||||
* @type {GenericObject<HTMLElement>}
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected refs: GenericObject<HTMLElement>;
|
||||
|
||||
/**
|
||||
* boolean value indicated whether the tag
|
||||
* is already mounted in the DOM tree
|
||||
*
|
||||
* @protected
|
||||
* @type {boolean}
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected _mounted: boolean;
|
||||
|
||||
/**
|
||||
*Creates an instance of AFXTag.
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
if (!this.observable) {
|
||||
this.observable = new Ant.OS.API.Announcer();
|
||||
}
|
||||
this._mounted = false;
|
||||
this.refs = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function verifies if a property name of the input object
|
||||
* corresponds to a setter of the current tag. If this is the
|
||||
* case, it sets the value of that property to the setter
|
||||
*
|
||||
* @param {GenericObject<any>} v input object
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
set(v: GenericObject<any>) {
|
||||
for (let k in v) {
|
||||
let descriptor = this.descriptor_of(k);
|
||||
if (descriptor && descriptor.set) {
|
||||
this[k] = v[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter to set the tooltip text to the current tag.
|
||||
* The text should be in the following format:
|
||||
* ```text
|
||||
* cr|cl|ct|cb: tooltip text
|
||||
* ```
|
||||
*
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
set tooltip(v: string) {
|
||||
if (!v) {
|
||||
return;
|
||||
}
|
||||
$(this).attr("tooltip", v);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* This function looking for a property name of the tag
|
||||
* in its prototype chain. The descriptor of the property
|
||||
* will be returned if it exists
|
||||
*
|
||||
* @private
|
||||
* @param {string} k the property name to be queried
|
||||
* @returns {PropertyDescriptor} the property descriptor or undefined
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
private descriptor_of(k: string): PropertyDescriptor {
|
||||
let desc: PropertyDescriptor;
|
||||
let obj = this;
|
||||
do {
|
||||
desc = Object.getOwnPropertyDescriptor(obj, k);
|
||||
} while (!desc && (obj = Object.getPrototypeOf(obj)));
|
||||
return desc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter: set the id of the tag in string or number
|
||||
*
|
||||
* Getter: get the id of the current tag
|
||||
*
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
set aid(v: string | number) {
|
||||
$(this).attr("data-id", v);
|
||||
}
|
||||
|
||||
get aid(): string | number {
|
||||
return $(this).attr("data-id");
|
||||
}
|
||||
|
||||
/**
|
||||
* Implementation from HTMLElement interface,
|
||||
* this function mount the current tag hierarchy
|
||||
*
|
||||
* @returns {void}
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
sync(): void {
|
||||
if (this._mounted) {
|
||||
return;
|
||||
}
|
||||
this._mounted = true;
|
||||
this.mount();
|
||||
super.sync();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the DOM hierarchy of the current tag
|
||||
*
|
||||
* @param {API.Announcer} o observable object
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
afxml(o: API.Announcer): void {
|
||||
if (o) this.observable = o;
|
||||
if (!this.aid)
|
||||
this.aid = (
|
||||
Math.floor(Math.random() * 100000) + 1
|
||||
).toString();
|
||||
const children = $(this).children();
|
||||
for (let obj of this.layout()) {
|
||||
const dom = this.mkui(obj);
|
||||
if (dom) {
|
||||
$(dom).appendTo(this);
|
||||
}
|
||||
}
|
||||
if (this.refs.yield) {
|
||||
for (let v of children) {
|
||||
$(v).detach().appendTo(this.refs.yield);
|
||||
}
|
||||
}
|
||||
const attrs = {};
|
||||
for (let i = 0; i < this.attributes.length; i++) {
|
||||
const element = this.attributes[i];
|
||||
let descriptor = this.descriptor_of(element.nodeName);
|
||||
if (descriptor && descriptor.set) {
|
||||
let value = "";
|
||||
try {
|
||||
value = JSON.parse(element.nodeValue);
|
||||
} catch (e) {
|
||||
value = element.nodeValue;
|
||||
}
|
||||
attrs[element.nodeName] = value;
|
||||
}
|
||||
}
|
||||
super.afxml(this.observable);
|
||||
this.init();
|
||||
for (let k in attrs) {
|
||||
this[k] = attrs[k];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the current tag hierarchy
|
||||
*
|
||||
* @param {*} d any data object
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
update(d: any): void {
|
||||
this.reload(d);
|
||||
super.update(d);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init the current tag, this function
|
||||
* is called before the [[mount]] function
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected abstract init(): void;
|
||||
|
||||
/**
|
||||
* Mount only the current tag
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected abstract mount(): void;
|
||||
|
||||
/**
|
||||
* Layout definition of a tag
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @returns {TagLayoutType[]} tag layout object
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected abstract layout(): TagLayoutType[];
|
||||
|
||||
/**
|
||||
* Update only the current tag, this function is
|
||||
* called by [[update]] before chaining the
|
||||
* update process to its children
|
||||
*
|
||||
* @protected
|
||||
* @abstract
|
||||
* @param {*} [d]
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected abstract reload(d?: any): void;
|
||||
|
||||
/**
|
||||
* This function is used to re-render the current
|
||||
* tag
|
||||
*
|
||||
* @protected
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected calibrate(): void {}
|
||||
|
||||
/**
|
||||
* This function parses the input layout object
|
||||
* and generates all the elements defined by
|
||||
* the tag
|
||||
*
|
||||
* @private
|
||||
* @param {TagLayoutType} tag tag layout object
|
||||
* @returns {Element} the DOM element specified by the tag layout
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
private mkui(tag: TagLayoutType): Element {
|
||||
if (!tag) {
|
||||
return undefined;
|
||||
}
|
||||
const dom = $(`<${tag.el}>`);
|
||||
if (tag.class) {
|
||||
$(dom).addClass(tag.class);
|
||||
}
|
||||
if (tag.id) {
|
||||
$(dom).attr("data-id", tag.id);
|
||||
}
|
||||
if (tag.height) {
|
||||
$(dom).attr("data-height", tag.height);
|
||||
}
|
||||
if (tag.width) {
|
||||
$(dom).attr("data-width", tag.width);
|
||||
}
|
||||
if (tag.tooltip) {
|
||||
$(dom).attr("tooltip", tag.tooltip.__());
|
||||
}
|
||||
if (tag.children) {
|
||||
for (let v of tag.children) {
|
||||
$(this.mkui(v)).appendTo(dom);
|
||||
}
|
||||
}
|
||||
if (tag.ref) {
|
||||
this.refs[tag.ref] = dom[0];
|
||||
}
|
||||
// dom.mount @observable
|
||||
return dom[0]; //.uify(@observable)
|
||||
}
|
||||
|
||||
/**
|
||||
* This function inserts or removes an attribute name
|
||||
* to/from the target element based on the input `flag`.
|
||||
*
|
||||
* @protected
|
||||
* @param {boolean} flag indicates whether the attribute name should be inserted o removed
|
||||
* @param {string} v the attribute name
|
||||
* @param {HTMLElement} [el] the target element
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected attsw(flag: boolean, v: string, el?: HTMLElement): void {
|
||||
if (flag) this.atton(v, el);
|
||||
else this.attoff(v, el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert the attribute name to the target element
|
||||
*
|
||||
* @protected
|
||||
* @param {string} v the attribute name
|
||||
* @param {HTMLElement} [el] the target element
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected atton(v: string, el?: HTMLElement): void {
|
||||
const element = el ? el : this;
|
||||
$(element).attr(v, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the attribute name from the target element
|
||||
*
|
||||
* @protected
|
||||
* @param {string} v attribute name
|
||||
* @param {HTMLElement} [el] the target element
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected attoff(v: string, el?: HTMLElement): void {
|
||||
const element = el ? el : this;
|
||||
element.removeAttribute(v);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if the target element has an attribute name
|
||||
*
|
||||
* @protected
|
||||
* @param {string} v attribute name
|
||||
* @param {HTMLElement} [el] target element
|
||||
* @returns {boolean}
|
||||
* @memberof AFXTag
|
||||
*/
|
||||
protected hasattr(v: string, el?: HTMLElement): boolean {
|
||||
const element = el ? el : this;
|
||||
return element.hasAttribute(v);
|
||||
}
|
||||
}
|
||||
|
||||
HTMLElement.prototype.update = function (d): void {
|
||||
$(this)
|
||||
.children()
|
||||
.each(function () {
|
||||
return this.update(d);
|
||||
});
|
||||
};
|
||||
HTMLElement.prototype.sync = function (): void {
|
||||
$(this)
|
||||
.children()
|
||||
.each(function () {
|
||||
return this.sync();
|
||||
});
|
||||
};
|
||||
HTMLElement.prototype.afxml = function (o: API.Announcer): void {
|
||||
$(this)
|
||||
.children()
|
||||
.each(function () {
|
||||
return this.afxml(o);
|
||||
});
|
||||
};
|
||||
HTMLElement.prototype.uify = function (
|
||||
o: API.Announcer,
|
||||
toplevel?: boolean
|
||||
): void {
|
||||
this.afxml(o);
|
||||
this.sync();
|
||||
if (o && toplevel) o.trigger("mounted", this.aid);
|
||||
};
|
||||
/**
|
||||
* All the AFX tags are defined in this namespace,
|
||||
* these tags are defined as custom DOM elements and will be
|
||||
* stored in the `customElements` registry of the browser
|
||||
*/
|
||||
export namespace tag {
|
||||
/**
|
||||
* Define an AFX tag as a custom element and add it to the
|
||||
* global `customElements` registry. If the tag is redefined, i.e.
|
||||
* the tag already exists, its behavior will be updated with the
|
||||
* new definition
|
||||
*
|
||||
* @export
|
||||
* @template T all classes that extends [[AFXTag]]
|
||||
* @param {string} name name of the tag
|
||||
* @param {{ new (): T }} cls the class that defines the tag
|
||||
* @returns {void}
|
||||
*/
|
||||
export function define<T extends AFXTag>(
|
||||
name: string,
|
||||
cls: { new (): T }
|
||||
): void {
|
||||
try {
|
||||
customElements.define(name, cls);
|
||||
} catch (error) {
|
||||
const proto = customElements.get(name);
|
||||
if (cls) {
|
||||
const props = Object.getOwnPropertyNames(cls.prototype);
|
||||
// redefine the class
|
||||
for (let prop of props) {
|
||||
proto.prototype[prop] = cls.prototype[prop];
|
||||
}
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
String.prototype.asFileHandler = () ->
|
||||
list = @split "://"
|
||||
handlers = _API.VFS.findHandlers list[0]
|
||||
if not handlers or handlers.length is 0
|
||||
_courrier.osfail __("VFS unknown handler: {0}", @), (_API.throwe "OS.VFS"), @
|
||||
return null
|
||||
return new handlers[0](@)
|
||||
|
||||
this.OS.API.VFS =
|
||||
handlers: { }
|
||||
register: ( protos, cls ) ->
|
||||
return self.OS.API.VFS.handlers[protos] = cls # if typeof protos is "string"
|
||||
#_API.VFS.handlers[v] = cls for v in protos
|
||||
findHandlers: (proto) ->
|
||||
l = (v for k, v of _API.VFS.handlers when proto.trim().match (new RegExp k , "g"))
|
||||
return l
|
||||
|
||||
class BaseFileHandler
|
||||
constructor: (path) ->
|
||||
@dirty = false
|
||||
@cache = undefined
|
||||
@setPath path
|
||||
|
||||
setPath: (p) ->
|
||||
@ready = false
|
||||
return unless p
|
||||
@path = p.toString()
|
||||
list = @path.split "://"
|
||||
@protocol = list[0]
|
||||
return unless list.length > 1
|
||||
re = list[1].replace(/^\/+|\/+$/g, '')
|
||||
return if re is ""
|
||||
@genealogy = re.split("/")
|
||||
@basename = @genealogy[@genealogy.length - 1] unless @isRoot()
|
||||
@ext = @basename.split( "." ).pop() unless @basename.lastIndexOf(".") is 0 or @basename.indexOf( "." ) is -1
|
||||
asFileHandler: () ->
|
||||
@
|
||||
isRoot: () -> (not @genealogy) or (@genealogy.size is 0)
|
||||
|
||||
child: (name) ->
|
||||
if @isRoot()
|
||||
return @path + name
|
||||
else
|
||||
return @path + "/" + name
|
||||
|
||||
isHidden: () ->
|
||||
return false if not @basename
|
||||
@basename[0] is "."
|
||||
|
||||
hash: () ->
|
||||
return -1 unless @path
|
||||
return @path.hash()
|
||||
|
||||
sendB64: (p, f) ->
|
||||
me = @
|
||||
m = if p is "object" then "text/plain" else p
|
||||
return f "" unless @cache
|
||||
if p is "object" or typeof @cache is "string"
|
||||
b64 = if p is "object" then (JSON.stringify @cache).asBase64() else @cache.asBase64()
|
||||
b64 = "data:#{m};base64,#{b64}"
|
||||
f(b64)
|
||||
else
|
||||
reader = new FileReader()
|
||||
reader.readAsDataURL(@cache)
|
||||
reader.onload = () ->
|
||||
f reader.result
|
||||
reader.onerror = (e) ->
|
||||
return _courrier.osfail __("VFS Cannot encode file: {0}", me.path), (_API.throwe "OS.VFS"), e
|
||||
parent: () ->
|
||||
return @ if @isRoot()
|
||||
return (@protocol + "://" + (@genealogy.slice 0 , @genealogy.length - 1).join "/")
|
||||
|
||||
onready: (f, err) ->
|
||||
# read meta data
|
||||
return f() if @ready
|
||||
me = @
|
||||
me.meta (d) ->
|
||||
if d.error
|
||||
return if err then err d else _courrier.osfail "#{me.path}: #{d.error}", (_API.throwe "OS.VFS"), d.error
|
||||
me.info = d.result
|
||||
me.ready = true
|
||||
f()
|
||||
|
||||
read: (f, t) ->
|
||||
me = @
|
||||
@onready (() -> me.action "read", t, f)
|
||||
|
||||
write: (d, f) ->
|
||||
me = @
|
||||
@action "write", d, (r) ->
|
||||
_courrier.ostrigger "VFS", { m: "write", file: me } if r.result
|
||||
f r
|
||||
|
||||
mk: (d, f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "mk", d, (r) ->
|
||||
_courrier.ostrigger "VFS", { m: "mk", file: me } if r.result
|
||||
f r)
|
||||
|
||||
remove: (f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "remove", null, (r) ->
|
||||
_courrier.ostrigger "VFS", { m: "remove", file: me } if r.result
|
||||
f r)
|
||||
|
||||
upload: (f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "upload", null, (r) ->
|
||||
_courrier.ostrigger "VFS", { m: "upload", file: me } if r.result
|
||||
f r)
|
||||
publish: (f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "publish", null, (r) ->
|
||||
_courrier.ostrigger "VFS", { m: "publish", file: me } if r.result
|
||||
f r)
|
||||
download: (f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "download", null, f)
|
||||
|
||||
move: (d, f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "move", d, (r) ->
|
||||
_courrier.ostrigger "VFS", { m: "move", file: d.asFileHandler() } if r.result
|
||||
f r)
|
||||
|
||||
execute: (f) ->
|
||||
me = @
|
||||
@onready (() -> me.action "execute", null, f)
|
||||
|
||||
#mk: (f) ->
|
||||
|
||||
meta: (f) ->
|
||||
|
||||
getlink: () -> @path
|
||||
# for main action read, write, remove, execute
|
||||
# must be implemented by subclasses
|
||||
action: (n, p, f) ->
|
||||
return _courrier.osfail __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
|
||||
# now export the class
|
||||
self.OS.API.VFS.BaseFileHandler = BaseFileHandler
|
||||
|
||||
# Remote file handle
|
||||
class RemoteFileHandler extends self.OS.API.VFS.BaseFileHandler
|
||||
constructor: (path) ->
|
||||
super path
|
||||
|
||||
meta: (f) ->
|
||||
_API.handler.fileinfo @path, f
|
||||
|
||||
getlink: () ->
|
||||
_API.handler.get + "/" + @path
|
||||
|
||||
action: (n, p, f) ->
|
||||
me = @
|
||||
switch n
|
||||
when "read"
|
||||
return _API.handler.scandir @path, f if @info.type is "dir"
|
||||
#read the file
|
||||
return _API.handler.fileblob @path, f if p is "binary"
|
||||
_API.handler.readfile @path, f, if p then p else "text"
|
||||
when "mk"
|
||||
return f { error: __("{0} is not a directory", @path) } if @info.type is "file"
|
||||
_API.handler.mkdir "#{@path}/#{p}", f
|
||||
when "write"
|
||||
return _API.handler.write me.path, me.cache, f if p is "base64"
|
||||
@sendB64 p, (data) ->
|
||||
_API.handler.write me.path, data, f
|
||||
when "upload"
|
||||
return if @info.type is "file"
|
||||
_API.handler.upload @path, f
|
||||
when "remove"
|
||||
_API.handler.delete @path, f
|
||||
when "publish"
|
||||
_API.handler.sharefile @path, true , f
|
||||
when "download"
|
||||
return if @info.type is "dir"
|
||||
_API.handler.fileblob @path, (d) ->
|
||||
blob = new Blob [d], { type: "octet/stream" }
|
||||
_API.saveblob me.basename, blob
|
||||
when "move"
|
||||
_API.handler.move @path, p, f
|
||||
else
|
||||
return _courrier.osfail __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
|
||||
self.OS.API.VFS.register "^(home|desktop|os|Untitled)$", RemoteFileHandler
|
||||
|
||||
# Application Handler
|
||||
class ApplicationHandler extends self.OS.API.VFS.BaseFileHandler
|
||||
constructor: (path) ->
|
||||
super path
|
||||
@info = _OS.setting.system.packages[@basename] if @basename
|
||||
@ready = true
|
||||
|
||||
meta: (f) ->
|
||||
f()
|
||||
|
||||
action: (n, p, f) ->
|
||||
me = @
|
||||
switch n
|
||||
when "read"
|
||||
return f { result: @info } if @info
|
||||
return unless @isRoot()
|
||||
f { result: ( v for k, v of _OS.setting.system.packages ) }
|
||||
|
||||
when "mk"
|
||||
return
|
||||
|
||||
when "write"
|
||||
return
|
||||
|
||||
when "upload"
|
||||
# install
|
||||
return
|
||||
|
||||
when "remove"
|
||||
#uninstall
|
||||
return
|
||||
when "publish"
|
||||
return
|
||||
when "download"
|
||||
return
|
||||
|
||||
when "move"
|
||||
return
|
||||
else
|
||||
return _courrier.osfail __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
|
||||
self.OS.API.VFS.register "^app$", ApplicationHandler
|
||||
|
||||
class BufferFileHandler extends self.OS.API.VFS.BaseFileHandler
|
||||
constructor: (path, mime, data) ->
|
||||
super path
|
||||
@cache = data if data
|
||||
@info =
|
||||
mime: mime
|
||||
path: path
|
||||
size: if data then data.length else 0
|
||||
name: @basename
|
||||
type: "file"
|
||||
meta: (f) ->
|
||||
f()
|
||||
|
||||
onchange: (f) ->
|
||||
@onchange = f
|
||||
|
||||
action: (n, p, f) ->
|
||||
me = @
|
||||
switch n
|
||||
when "read"
|
||||
return f { result: @cache }
|
||||
|
||||
when "mk"
|
||||
return
|
||||
|
||||
when "write"
|
||||
@cache = p
|
||||
@onchange @ if @onchange
|
||||
f { result: true }
|
||||
|
||||
when "upload"
|
||||
# install
|
||||
return
|
||||
|
||||
when "remove"
|
||||
#uninstall
|
||||
return
|
||||
when "publish"
|
||||
return
|
||||
when "download"
|
||||
blob = new Blob [@cache], { type: "octet/stream" }
|
||||
_API.saveblob me.basename, blob
|
||||
|
||||
when "move"
|
||||
return
|
||||
else
|
||||
return _courrier.osfail __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
|
||||
self.OS.API.VFS.register "^mem$", BufferFileHandler
|
||||
|
||||
class URLFileHandler extends self.OS.API.VFS.BaseFileHandler
|
||||
constructor: (path) ->
|
||||
super path
|
||||
@ready = true
|
||||
meta: (f) ->
|
||||
f { result: true }
|
||||
action: (n, p, f) ->
|
||||
me = @
|
||||
switch n
|
||||
when "read"
|
||||
_API.get @path, (d) ->
|
||||
f(d)
|
||||
, (e, s) ->
|
||||
_courrier.oserror __("VFS cannot read : {0}", me.path), e, s
|
||||
, if p then p else "text"
|
||||
else
|
||||
return _courrier.oserror __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
self.OS.API.VFS.register "^(http|https)$", URLFileHandler
|
||||
|
||||
class SharedFileHandler extends self.OS.API.VFS.BaseFileHandler
|
||||
constructor: (path) ->
|
||||
super path
|
||||
@ready = true if @isRoot()
|
||||
meta: (f) ->
|
||||
_API.handler.fileinfo @path, f
|
||||
|
||||
action: (n, p, f) ->
|
||||
me = @
|
||||
switch n
|
||||
when "read"
|
||||
return _API.get "#{_API.handler.shared}/all", f, ((e, s)->) if @isRoot()
|
||||
#read the file
|
||||
return _API.handler.fileblob @path, f if p is "binary"
|
||||
_API.handler.readfile @path, f, if p then p else "text"
|
||||
when "mk"
|
||||
return
|
||||
|
||||
when "write"
|
||||
_API.handler.write @path, p, f
|
||||
|
||||
when "remove"
|
||||
_API.handler.sharefile @basename, false, f
|
||||
|
||||
when "upload"
|
||||
return
|
||||
|
||||
when "publish"
|
||||
return f { result: @basename }
|
||||
|
||||
when "download"
|
||||
return if @info.type is "dir"
|
||||
_API.handler.fileblob @path, (d) ->
|
||||
blob = new Blob [d], { type: "octet/stream" }
|
||||
_API.saveblob me.basename, blob
|
||||
when "move"
|
||||
return
|
||||
else
|
||||
return _courrier.osfail __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
|
||||
self.OS.API.VFS.register "^shared$", SharedFileHandler
|
1702
src/core/vfs.ts
Normal file
1702
src/core/vfs.ts
Normal file
File diff suppressed because it is too large
Load Diff
401
src/core/vfs/GoogleDriveHandle.js
Normal file
401
src/core/vfs/GoogleDriveHandle.js
Normal file
@ -0,0 +1,401 @@
|
||||
/*
|
||||
* 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)
|
||||
|
@ -1,355 +0,0 @@
|
||||
|
||||
# Copyright 2017-2018 Xuan Sang LE <xsang.le AT gmail DOT com>
|
||||
|
||||
# AnTOS Web desktop is is licensed under the GNU General Public
|
||||
# License v3.0, see the LICENCE file for more information
|
||||
|
||||
# This program is free software: you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License as
|
||||
# published by the Free Software Foundation, either version 3 of
|
||||
# the License, or (at your option) any later version.
|
||||
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
# General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
#along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
|
||||
# GoogleDrive File Handler
|
||||
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } }
|
||||
|
||||
class GoogleDriveHandler extends this.OS.API.VFS.BaseFileHandler
|
||||
constructor: (path) ->
|
||||
super path
|
||||
me = @
|
||||
@setting = _OS.setting.VFS.gdrive
|
||||
return _courrier.oserror __("Unknown API setting for {0}", "GAPI"), (_API.throwe "OS.VFS"), null unless @setting
|
||||
@gid = 'root' if @isRoot()
|
||||
@cache = ""
|
||||
|
||||
oninit: (f) ->
|
||||
me = @
|
||||
return unless @setting
|
||||
fn = (r) ->
|
||||
return f() if r
|
||||
# perform the login
|
||||
G_CACHE = {"gdv://":{ id: "root", mime: 'dir' } }
|
||||
gapi.auth2.getAuthInstance().signIn()
|
||||
|
||||
if _API.libready @setting.apilink
|
||||
gapi.auth2.getAuthInstance().isSignedIn.listen (r) ->
|
||||
fn(r)
|
||||
fn(gapi.auth2.getAuthInstance().isSignedIn.get())
|
||||
else
|
||||
_API.require @setting.apilink, () ->
|
||||
# avoid popup block
|
||||
q = _courrier.getMID()
|
||||
gapi.load "client:auth2", () ->
|
||||
_API.loading q, "GAPI"
|
||||
gapi.client.init {
|
||||
apiKey: me.setting.API_KEY,
|
||||
clientId: me.setting.CLIENT_ID,
|
||||
discoveryDocs: me.setting.DISCOVERY_DOCS,
|
||||
scope: me.setting.SCOPES
|
||||
}
|
||||
.then () ->
|
||||
_API.loaded q, "OK"
|
||||
gapi.auth2.getAuthInstance().isSignedIn.listen (r) ->
|
||||
fn(r)
|
||||
_GUI.openDialog "YesNoDialog", (d) ->
|
||||
return _courrier.osinfo __("User abort the authentication") unless d
|
||||
fn(gapi.auth2.getAuthInstance().isSignedIn.get())
|
||||
, __("Authentication")
|
||||
, { text: __("Would you like to login to {0}?", "Google Drive") }
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot init {0}: {1}", "GAPI",err.error), (_API.throwe "OS.VFS"), err
|
||||
|
||||
meta: (f) ->
|
||||
me = @
|
||||
@oninit () ->
|
||||
q = _courrier.getMID()
|
||||
me.gid = G_CACHE[me.path].id if G_CACHE[me.path]
|
||||
if me.gid
|
||||
#console.log "Gid exists ", me.gid
|
||||
_API.loading q, "GAPI"
|
||||
gapi.client.drive.files.get {
|
||||
fileId: me.gid,
|
||||
fields: me.fields()
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
return unless r.result
|
||||
r.result.mime = r.result.mimeType
|
||||
f(r)
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot get meta data for {0}", me.gid), (_API.throwe "OS.VFS"), err
|
||||
else
|
||||
#console.log "Find file in ", me.parent()
|
||||
fp = me.parent().asFileHandler()
|
||||
fp.meta (d) ->
|
||||
file = d.result
|
||||
q1 = _courrier.getMID()
|
||||
_API.loading q1, "GAPI"
|
||||
G_CACHE[fp.path] = { id: file.id, mime: file.mimeType }
|
||||
gapi.client.drive.files.list {
|
||||
q: "name = '#{me.basename}' and '#{file.id}' in parents and trashed = false",
|
||||
fields: "files(#{me.fields()})"
|
||||
}
|
||||
.then (r) ->
|
||||
#console.log r
|
||||
_API.loaded q1, "OK"
|
||||
return unless r.result.files and r.result.files.length > 0
|
||||
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
|
||||
f { result: r.result.files[0] }
|
||||
.catch (err) ->
|
||||
_API.loaded q1, "FAIL"
|
||||
_courrier.oserror __("VFS cannot get meta data for {0}", me.path), (_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 @info.mimeType is "application/vnd.google-apps.folder"
|
||||
|
||||
save: (id, m, f) ->
|
||||
me = @
|
||||
user = gapi.auth2.getAuthInstance().currentUser.get()
|
||||
oauthToken = user.getAuthResponse().access_token
|
||||
q = _courrier.getMID()
|
||||
xhr = new XMLHttpRequest()
|
||||
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'
|
||||
_API.loading q, "GAPI"
|
||||
error = (e, s) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot save : {0}", me.path), e, s
|
||||
xhr.onreadystatechange = () ->
|
||||
if ( xhr.readyState == 4 )
|
||||
if ( xhr.status == 200 )
|
||||
_API.loaded q, "OK"
|
||||
f { result: JSON.parse(xhr.responseText) }
|
||||
else
|
||||
error xhr, xhr.status
|
||||
xhr.onerror = () ->
|
||||
error xhr, xhr.status
|
||||
return xhr.send me.cache.replace /^data:[^;]+;base64,/g, "" if m is "base64"
|
||||
@sendB64 m, (data) ->
|
||||
xhr.send data.replace /^data:[^;]+;base64,/g, ""
|
||||
|
||||
getlink: () ->
|
||||
return @info.webContentLink if @ready
|
||||
return undefined
|
||||
|
||||
action: (n, p, f) ->
|
||||
me = @
|
||||
q = _courrier.getMID()
|
||||
_API.loading q, "GAPI"
|
||||
switch n
|
||||
when "read"
|
||||
return unless @info.id
|
||||
if @isFolder()
|
||||
gapi.client.drive.files.list {
|
||||
q: "'#{me.info.id}' in parents and trashed = false",
|
||||
fields: "files(#{me.fields()})"
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
return unless r.result.files
|
||||
for file in 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 is "application/vnd.google-apps.folder"
|
||||
file.mime = "dir"
|
||||
file.type = "dir"
|
||||
file.size = 0
|
||||
G_CACHE[file.path] = { id: file.gid, mime: file.mime }
|
||||
f { result: r.result.files }
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot read : {0}", me.path), (_API.throwe "OS.VFS"), err
|
||||
else
|
||||
gapi.client.drive.files.get {
|
||||
fileId: me.info.id,
|
||||
alt: 'media'
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
return f r.body unless p is "binary"
|
||||
f r.body.asUint8Array()
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot read : {0}", me.path), (_API.throwe "OS.VFS"), err
|
||||
|
||||
when "mk"
|
||||
return f { error: __("{0} is not a directory", @path) } unless @isFolder()
|
||||
meta =
|
||||
name: p,
|
||||
parents: [@info.id],
|
||||
mimeType: 'application/vnd.google-apps.folder'
|
||||
|
||||
gapi.client.drive.files.create {
|
||||
resource: meta,
|
||||
fields: 'id'
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
#console.log r
|
||||
return _courrier.oserror __("VFS cannot create : {0}", p), (_API.throwe "OS.VFS"), r unless r and r.result
|
||||
G_CACHE[me.child p] = { id: r.result.id, mime: "dir" }
|
||||
f r
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot create : {0}", p), (_API.throwe "OS.VFS"), err
|
||||
|
||||
return
|
||||
|
||||
when "write"
|
||||
gid = undefined
|
||||
gid = G_CACHE[me.path].id if G_CACHE[me.path]
|
||||
if gid
|
||||
_API.loaded q, "OK"
|
||||
@save gid, p, f
|
||||
else
|
||||
dir = @parent().asFileHandler()
|
||||
dir.onready () ->
|
||||
meta =
|
||||
name: me.basename,
|
||||
mimeType: p,
|
||||
parents: [dir.info.id]
|
||||
|
||||
gapi.client.drive.files.create {
|
||||
resource: meta,
|
||||
fields: 'id'
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
return _courrier.oserror __("VFS cannot write : {0}", me.path), (_API.throwe "OS.VFS"), r unless r and r.result
|
||||
G_CACHE[me.path] = { id: r.result.id, mime: p }
|
||||
me.save r.result.id, p, f
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot write : {0}", me.path), (_API.throwe "OS.VFS"), err
|
||||
|
||||
when "upload"
|
||||
return unless @isFolder()
|
||||
#insert a temporal file selector
|
||||
o = ($ '<input>').attr('type', 'file').css("display", "none")
|
||||
_API.loaded q, "OK"
|
||||
o.change () ->
|
||||
#_API.loading q, p
|
||||
fo = o[0].files[0]
|
||||
file = (me.child fo.name).asFileHandler()
|
||||
file.cache = fo
|
||||
file.write fo.type, f
|
||||
o.remove()
|
||||
|
||||
#_API.loaded q, p, "OK"
|
||||
#_API.loaded q, p, "FAIL"
|
||||
|
||||
o.click()
|
||||
|
||||
when "remove"
|
||||
return unless @info.id
|
||||
gapi.client.drive.files.delete {
|
||||
fileId: me.info.id
|
||||
}
|
||||
.then (r) ->
|
||||
#console.log r
|
||||
_API.loaded q, "OK"
|
||||
return _courrier.oserror __("VFS cannot delete : {0}", me.path), (_API.throwe "OS.VFS"), r unless r
|
||||
G_CACHE[me.path] = null
|
||||
f { result: true }
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot delete : {0}", me.path), (_API.throwe "OS.VFS"), err
|
||||
|
||||
when "publish"
|
||||
_API.loaded q, "OK"
|
||||
return
|
||||
|
||||
when "download"
|
||||
gapi.client.drive.files.get {
|
||||
fileId: me.info.id,
|
||||
alt: 'media'
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
return _courrier.oserror __("VFS cannot download file : {0}", me.path), (_API.throwe "OS.VFS"), r unless r.body
|
||||
bytes = []
|
||||
for i in [0..(r.body.length - 1)]
|
||||
bytes.push r.body.charCodeAt i
|
||||
bytes = new Uint8Array(bytes)
|
||||
blob = new Blob [bytes], { type: "octet/stream" }
|
||||
_API.saveblob me.basename, blob
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot download file : {0}", me.path), (_API.throwe "OS.VFS"), err
|
||||
|
||||
when "move"
|
||||
dest = p.asFileHandler().parent().asFileHandler()
|
||||
dest.onready () ->
|
||||
previousParents = me.info.parents.join ','
|
||||
gapi.client.drive.files.update {
|
||||
fileId: me.info.id,
|
||||
addParents: dest.info.id,
|
||||
removeParents: previousParents,
|
||||
fields: "id"
|
||||
}
|
||||
.then (r) ->
|
||||
_API.loaded q, "OK"
|
||||
return _courrier.oserror __("VFS cannot move : {0}", me.path), (_API.throwe "OS.VFS"), r unless r
|
||||
f r
|
||||
.catch (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
_courrier.oserror __("VFS cannot move : {0}", me.gid), (_API.throwe "OS.VFS"), err
|
||||
, (err) ->
|
||||
_API.loaded q, "FAIL"
|
||||
else
|
||||
_API.loaded q, "FAIL"
|
||||
return _courrier.osfail __("VFS unknown action: {0}", n), (_API.throwe "OS.VFS"), n
|
||||
|
||||
|
||||
self.OS.API.VFS.register "^gdv$", GoogleDriveHandler
|
||||
# search the cache for file
|
||||
self.OS.API.onsearch "Google Drive", (t) ->
|
||||
arr = []
|
||||
term = new RegExp t, "i"
|
||||
for k, v of G_CACHE
|
||||
if (k.match term) or (v and v.mime.match term)
|
||||
file = k.asFileHandler()
|
||||
file.text = file.basename
|
||||
file.mime = v.mime
|
||||
file.iconclass = "fa fa-file"
|
||||
file.iconclass = "fa fa-folder" if file.mime is "dir"
|
||||
file.complex = true
|
||||
file.detail = [{ text: file.path }]
|
||||
arr.push file
|
||||
return arr
|
||||
|
||||
self.OS.onexit "cleanUpGoogleDrive", () ->
|
||||
G_CACHE = { "gdv://": { id: "root", mime: 'dir' } }
|
||||
return unless _OS.setting.VFS.gdrive and _API.libready _OS.setting.VFS.gdrive.apilink
|
||||
auth2 = gapi.auth2.getAuthInstance()
|
||||
return unless auth2
|
||||
if auth2.isSignedIn.get()
|
||||
el = $ '<iframe/>', {
|
||||
src: 'https://www.google.com/accounts/Logout',
|
||||
frameborder: 0,
|
||||
onload: () ->
|
||||
#console.log("disconnect")
|
||||
auth2.disconnect()
|
||||
#$(this).remove()
|
||||
}
|
||||
#($ "body").append(el)
|
||||
|
@ -23,12 +23,9 @@
|
||||
<title>AntOS webOS</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link href="resources/themes/system/font-awesome.css" rel="stylesheet">
|
||||
<link href="resources/themes/system/system.css" rel="stylesheet">
|
||||
<link id="ostheme" rel="stylesheet" href="">
|
||||
<!--link href="theme/antos/style.css" rel="stylesheet"-->
|
||||
<script src="scripts/jquery-3.2.1.min.js"></script>
|
||||
<script src="resources/antos_tags.js" type="riot/tag"></script>
|
||||
<script src="scripts/riot.compiler.min.js"> </script>
|
||||
<script src="scripts/antos.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,8 @@
|
||||
define("ace/ext/beautify/php_rules",["require","exports","module","ace/token_iterator"],function(e,t,n){"use strict";var r=e("ace/token_iterator").TokenIterator;t.newLines=[{type:"support.php_tag",value:"<?php"},{type:"support.php_tag",value:"<?"},{type:"support.php_tag",value:"?>"},{type:"paren.lparen",value:"{",indent:!0},{type:"paren.rparen",breakBefore:!0,value:"}",indent:!1},{type:"paren.rparen",breakBefore:!0,value:"})",indent:!1,dontBreak:!0},{type:"comment"},{type:"text",value:";"},{type:"text",value:":",context:"php"},{type:"keyword",value:"case",indent:!0,dontBreak:!0},{type:"keyword",value:"default",indent:!0,dontBreak:!0},{type:"keyword",value:"break",indent:!1,dontBreak:!0},{type:"punctuation.doctype.end",value:">"},{type:"meta.tag.punctuation.end",value:">"},{type:"meta.tag.punctuation.begin",value:"<",blockTag:!0,indent:!0,dontBreak:!0},{type:"meta.tag.punctuation.begin",value:"</",indent:!1,breakBefore:!0,dontBreak:!0},{type:"punctuation.operator",value:";"}],t.spaces=[{type:"xml-pe",prepend:!0},{type:"entity.other.attribute-name",prepend:!0},{type:"storage.type",value:"var",append:!0},{type:"storage.type",value:"function",append:!0},{type:"keyword.operator",value:"="},{type:"keyword",value:"as",prepend:!0,append:!0},{type:"keyword",value:"function",append:!0},{type:"support.function",next:/[^\(]/,append:!0},{type:"keyword",value:"or",append:!0,prepend:!0},{type:"keyword",value:"and",append:!0,prepend:!0},{type:"keyword",value:"case",append:!0},{type:"keyword.operator",value:"||",append:!0,prepend:!0},{type:"keyword.operator",value:"&&",append:!0,prepend:!0}],t.singleTags=["!doctype","area","base","br","hr","input","img","link","meta"],t.transform=function(e,n,r){var i=e.getCurrentToken(),s=t.newLines,o=t.spaces,u=t.singleTags,a="",f=0,l=!1,c,h,p={},d,v={},m=!1,g="";while(i!==null){if(!i){i=e.stepForward();continue}i.type=="support.php_tag"&&i.value!="?>"?r="php":i.type=="support.php_tag"&&i.value=="?>"?r="html":i.type=="meta.tag.name.style"&&r!="css"?r="css":i.type=="meta.tag.name.style"&&r=="css"?r="html":i.type=="meta.tag.name.script"&&r!="js"?r="js":i.type=="meta.tag.name.script"&&r=="js"&&(r="html"),v=e.stepForward(),v&&v.type.indexOf("meta.tag.name")==0&&(d=v.value),p.type=="support.php_tag"&&p.value=="<?="&&(l=!0),i.type=="meta.tag.name"&&(i.value=i.value.toLowerCase()),i.type=="text"&&(i.value=i.value.trim());if(!i.value){i=v;continue}g=i.value;for(var y in o)i.type==o[y].type&&(!o[y].value||i.value==o[y].value)&&v&&(!o[y].next||o[y].next.test(v.value))&&(o[y].prepend&&(g=" "+i.value),o[y].append&&(g+=" "));i.type.indexOf("meta.tag.name")==0&&(c=i.value),m=!1;for(y in s)if(i.type==s[y].type&&(!s[y].value||i.value==s[y].value)&&(!s[y].blockTag||u.indexOf(d)===-1)&&(!s[y].context||s[y].context===r)){s[y].indent===!1&&f--;if(s[y].breakBefore&&(!s[y].prev||s[y].prev.test(p.value))){a+="\n",m=!0;for(y=0;y<f;y++)a+=" "}break}if(l===!1)for(y in s)if(p.type==s[y].type&&(!s[y].value||p.value==s[y].value)&&(!s[y].blockTag||u.indexOf(c)===-1)&&(!s[y].context||s[y].context===r)){s[y].indent===!0&&f++;if(!s[y].dontBreak&&!m){a+="\n";for(y=0;y<f;y++)a+=" "}break}a+=g,p.type=="support.php_tag"&&p.value=="?>"&&(l=!1),h=c,p=i,i=v;if(i===null)break}return a}}),define("ace/ext/beautify",["require","exports","module","ace/token_iterator","ace/ext/beautify/php_rules"],function(e,t,n){"use strict";var r=e("ace/token_iterator").TokenIterator,i=e("./beautify/php_rules").transform;t.beautify=function(e){var t=new r(e,0,0),n=t.getCurrentToken(),s=e.$modeId.split("/").pop(),o=i(t,s);e.doc.setValue(o)},t.commands=[{name:"beautify",exec:function(e){t.beautify(e.session)},bindKey:"Ctrl-Shift-B"}]});
|
||||
(function() {
|
||||
window.require(["ace/ext/beautify"], function() {});
|
||||
define("ace/ext/beautify",["require","exports","module","ace/token_iterator"],function(e,t,n){"use strict";function i(e,t){return e.type.lastIndexOf(t+".xml")>-1}var r=e("../token_iterator").TokenIterator;t.singletonTags=["area","base","br","col","command","embed","hr","html","img","input","keygen","link","meta","param","source","track","wbr"],t.blockTags=["article","aside","blockquote","body","div","dl","fieldset","footer","form","head","header","html","nav","ol","p","script","section","style","table","tbody","tfoot","thead","ul"],t.beautify=function(e){var n=new r(e,0,0),s=n.getCurrentToken(),o=e.getTabString(),u=t.singletonTags,a=t.blockTags,f,l=!1,c=!1,h=!1,p="",d="",v="",m=0,g=0,y=0,b=0,w=0,E=0,S=0,x,T=0,N=0,C=[],k=!1,L,A=!1,O=!1,M=!1,_=!1,D={0:0},P=[],H=function(){f&&f.value&&f.type!=="string.regexp"&&(f.value=f.value.replace(/^\s*/,""))},B=function(){p=p.replace(/ +$/,"")},j=function(){p=p.trimRight(),l=!1};while(s!==null){T=n.getCurrentTokenRow(),C=n.$rowTokens,f=n.stepForward();if(typeof s!="undefined"){d=s.value,w=0,M=v==="style"||e.$modeId==="ace/mode/css",i(s,"tag-open")?(O=!0,f&&(_=a.indexOf(f.value)!==-1),d==="</"&&(_&&!l&&N<1&&N++,M&&(N=1),w=1,_=!1)):i(s,"tag-close")?O=!1:i(s,"comment.start")?_=!0:i(s,"comment.end")&&(_=!1),!O&&!N&&s.type==="paren.rparen"&&s.value.substr(0,1)==="}"&&N++,T!==x&&(N=T,x&&(N-=x));if(N){j();for(;N>0;N--)p+="\n";l=!0,!i(s,"comment")&&!s.type.match(/^(comment|string)$/)&&(d=d.trimLeft())}if(d){s.type==="keyword"&&d.match(/^(if|else|elseif|for|foreach|while|switch)$/)?(P[m]=d,H(),h=!0,d.match(/^(else|elseif)$/)&&p.match(/\}[\s]*$/)&&(j(),c=!0)):s.type==="paren.lparen"?(H(),d.substr(-1)==="{"&&(h=!0,A=!1,O||(N=1)),d.substr(0,1)==="{"&&(c=!0,p.substr(-1)!=="["&&p.trimRight().substr(-1)==="["?(j(),c=!1):p.trimRight().substr(-1)===")"?j():B())):s.type==="paren.rparen"?(w=1,d.substr(0,1)==="}"&&(P[m-1]==="case"&&w++,p.trimRight().substr(-1)==="{"?j():(c=!0,M&&(N+=2))),d.substr(0,1)==="]"&&p.substr(-1)!=="}"&&p.trimRight().substr(-1)==="}"&&(c=!1,b++,j()),d.substr(0,1)===")"&&p.substr(-1)!=="("&&p.trimRight().substr(-1)==="("&&(c=!1,b++,j()),B()):s.type!=="keyword.operator"&&s.type!=="keyword"||!d.match(/^(=|==|===|!=|!==|&&|\|\||and|or|xor|\+=|.=|>|>=|<|<=|=>)$/)?s.type==="punctuation.operator"&&d===";"?(j(),H(),h=!0,M&&N++):s.type==="punctuation.operator"&&d.match(/^(:|,)$/)?(j(),H(),d.match(/^(,)$/)&&S>0&&E===0?N++:(h=!0,l=!1)):s.type==="support.php_tag"&&d==="?>"&&!l?(j(),c=!0):i(s,"attribute-name")&&p.substr(-1).match(/^\s$/)?c=!0:i(s,"attribute-equals")?(B(),H()):i(s,"tag-close")&&(B(),d==="/>"&&(c=!0)):(j(),H(),c=!0,h=!0);if(l&&(!s.type.match(/^(comment)$/)||!!d.substr(0,1).match(/^[/#]$/))&&(!s.type.match(/^(string)$/)||!!d.substr(0,1).match(/^['"]$/))){b=y;if(m>g){b++;for(L=m;L>g;L--)D[L]=b}else m<g&&(b=D[m]);g=m,y=b,w&&(b-=w),A&&!E&&(b++,A=!1);for(L=0;L<b;L++)p+=o}s.type==="keyword"&&d.match(/^(case|default)$/)&&(P[m]=d,m++),s.type==="keyword"&&d.match(/^(break)$/)&&P[m-1]&&P[m-1].match(/^(case|default)$/)&&m--,s.type==="paren.lparen"&&(E+=(d.match(/\(/g)||[]).length,S+=(d.match(/\{/g)||[]).length,m+=d.length),s.type==="keyword"&&d.match(/^(if|else|elseif|for|while)$/)?(A=!0,E=0):!E&&d.trim()&&s.type!=="comment"&&(A=!1);if(s.type==="paren.rparen"){E-=(d.match(/\)/g)||[]).length,S-=(d.match(/\}/g)||[]).length;for(L=0;L<d.length;L++)m--,d.substr(L,1)==="}"&&P[m]==="case"&&m--}s.type=="text"&&(d=d.replace(/\s+$/," ")),c&&!l&&(B(),p.substr(-1)!=="\n"&&(p+=" ")),p+=d,h&&(p+=" "),l=!1,c=!1,h=!1;if(i(s,"tag-close")&&(_||a.indexOf(v)!==-1)||i(s,"doctype")&&d===">")_&&f&&f.value==="</"?N=-1:N=1;i(s,"tag-open")&&d==="</"?m--:i(s,"tag-open")&&d==="<"&&u.indexOf(f.value)===-1?m++:i(s,"tag-name")?v=d:i(s,"tag-close")&&d==="/>"&&u.indexOf(v)===-1&&m--,x=T}}s=f}p=p.trim(),e.doc.setValue(p)},t.commands=[{name:"beautify",description:"Format selection (Beautify)",exec:function(e){t.beautify(e.session)},bindKey:"Ctrl-Shift-B"}]}); (function() {
|
||||
window.require(["ace/ext/beautify"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
8
src/libs/ace/ext-code_lens.js
Normal file
8
src/libs/ace/ext-code_lens.js
Normal file
@ -0,0 +1,8 @@
|
||||
define("ace/ext/code_lens",["require","exports","module","ace/line_widgets","ace/lib/event","ace/lib/lang","ace/lib/dom","ace/editor","ace/config"],function(e,t,n){"use strict";function u(e){var t=e.$textLayer,n=t.$lenses;n&&n.forEach(function(e){e.remove()}),t.$lenses=null}function a(e,t){var n=e&t.CHANGE_LINES||e&t.CHANGE_FULL||e&t.CHANGE_SCROLL||e&t.CHANGE_TEXT;if(!n)return;var r=t.session,i=t.session.lineWidgets,s=t.$textLayer,a=s.$lenses;if(!i){a&&u(t);return}var f=t.$textLayer.$lines.cells,l=t.layerConfig,c=t.$padding;a||(a=s.$lenses=[]);var h=0;for(var p=0;p<f.length;p++){var d=f[p].row,v=i[d],m=v&&v.lenses;if(!m||!m.length)continue;var g=a[h];g||(g=a[h]=o.buildDom(["div",{"class":"ace_codeLens"}],t.container)),g.style.height=l.lineHeight+"px",h++;for(var y=0;y<m.length;y++){var b=g.childNodes[2*y];b||(y!=0&&g.appendChild(o.createTextNode("\u00a0|\u00a0")),b=o.buildDom(["a"],g)),b.textContent=m[y].title,b.lensCommand=m[y]}while(g.childNodes.length>2*y-1)g.lastChild.remove();var w=t.$cursorLayer.getPixelPosition({row:d,column:0},!0).top-l.lineHeight*v.rowsAbove-l.offset;g.style.top=w+"px";var E=t.gutterWidth,S=r.getLine(d).search(/\S|$/);S==-1&&(S=0),E+=S*l.characterWidth,E-=t.scrollLeft,g.style.paddingLeft=c+E+"px"}while(h<a.length)a.pop().remove()}function f(e){if(!e.lineWidgets)return;var t=e.widgetManager;e.lineWidgets.forEach(function(e){e&&e.lenses&&t.removeLineWidget(e)})}function l(e){e.codeLensProviders=[],e.renderer.on("afterRender",a),e.$codeLensClickHandler||(e.$codeLensClickHandler=function(t){var n=t.target.lensCommand;n&&e.execCommand(n.id,n.arguments)},i.addListener(e.container,"click",e.$codeLensClickHandler,e)),e.$updateLenses=function(){function o(){var r=n.selection.cursor,i=n.documentToScreenRow(r);t.setLenses(n,s);var o=n.$undoManager&&n.$undoManager.$lastDelta;if(o&&o.action=="remove"&&o.lines.length>1)return;var u=n.documentToScreenRow(r),a=e.renderer.layerConfig.lineHeight,f=n.getScrollTop()+(u-i)*a;n.setScrollTop(f)}var n=e.session;if(!n)return;n.widgetManager||(n.widgetManager=new r(n),n.widgetManager.attach(e));var i=e.codeLensProviders.length,s=[];e.codeLensProviders.forEach(function(e){e.provideCodeLenses(n,function(e,t){if(e)return;t.forEach(function(e){s.push(e)}),i--,i==0&&o()})})};var n=s.delayedCall(e.$updateLenses);e.$updateLensesOnInput=function(){n.delay(250)},e.on("input",e.$updateLensesOnInput)}function c(e){e.off("input",e.$updateLensesOnInput),e.renderer.off("afterRender",a),e.$codeLensClickHandler&&e.container.removeEventListener("click",e.$codeLensClickHandler)}var r=e("../line_widgets").LineWidgets,i=e("../lib/event"),s=e("../lib/lang"),o=e("../lib/dom");t.setLenses=function(e,t){var n=Number.MAX_VALUE;f(e),t&&t.forEach(function(t){var r=t.start.row,i=t.start.column,s=e.lineWidgets&&e.lineWidgets[r];if(!s||!s.lenses)s=e.widgetManager.$registerLineWidget({rowCount:1,rowsAbove:1,row:r,column:i,lenses:[]});s.lenses.push(t.command),r<n&&(n=r)}),e._emit("changeFold",{data:{start:{row:n}}})},t.registerCodeLensProvider=function(e,t){e.setOption("enableCodeLens",!0),e.codeLensProviders.push(t),e.$updateLensesOnInput()},t.clear=function(e){t.setLenses(e,null)};var h=e("../editor").Editor;e("../config").defineOptions(h.prototype,"editor",{enableCodeLens:{set:function(e){e?l(this):c(this)}}}),o.importCssString(".ace_codeLens { position: absolute; color: #aaa; font-size: 88%; background: inherit; width: 100%; display: flex; align-items: flex-end; pointer-events: none;}.ace_codeLens > a { cursor: pointer; pointer-events: auto;}.ace_codeLens > a:hover { color: #0000ff; text-decoration: underline;}.ace_dark > .ace_codeLens > a:hover { color: #4e94ce;}","")}); (function() {
|
||||
window.require(["ace/ext/code_lens"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@ -1,5 +1,8 @@
|
||||
define("ace/ext/elastic_tabstops_lite",["require","exports","module","ace/editor","ace/config"],function(e,t,n){"use strict";var r=function(e){this.$editor=e;var t=this,n=[],r=!1;this.onAfterExec=function(){r=!1,t.processRows(n),n=[]},this.onExec=function(){r=!0},this.onChange=function(e){r&&(n.indexOf(e.start.row)==-1&&n.push(e.start.row),e.end.row!=e.start.row&&n.push(e.end.row))}};(function(){this.processRows=function(e){this.$inChange=!0;var t=[];for(var n=0,r=e.length;n<r;n++){var i=e[n];if(t.indexOf(i)>-1)continue;var s=this.$findCellWidthsForBlock(i),o=this.$setBlockCellWidthsToMax(s.cellWidths),u=s.firstRow;for(var a=0,f=o.length;a<f;a++){var l=o[a];t.push(u),this.$adjustRow(u,l),u++}}this.$inChange=!1},this.$findCellWidthsForBlock=function(e){var t=[],n,r=e;while(r>=0){n=this.$cellWidthsForRow(r);if(n.length==0)break;t.unshift(n),r--}var i=r+1;r=e;var s=this.$editor.session.getLength();while(r<s-1){r++,n=this.$cellWidthsForRow(r);if(n.length==0)break;t.push(n)}return{cellWidths:t,firstRow:i}},this.$cellWidthsForRow=function(e){var t=this.$selectionColumnsForRow(e),n=[-1].concat(this.$tabsForRow(e)),r=n.map(function(e){return 0}).slice(1),i=this.$editor.session.getLine(e);for(var s=0,o=n.length-1;s<o;s++){var u=n[s]+1,a=n[s+1],f=this.$rightmostSelectionInCell(t,a),l=i.substring(u,a);r[s]=Math.max(l.replace(/\s+$/g,"").length,f-u)}return r},this.$selectionColumnsForRow=function(e){var t=[],n=this.$editor.getCursorPosition();return this.$editor.session.getSelection().isEmpty()&&e==n.row&&t.push(n.column),t},this.$setBlockCellWidthsToMax=function(e){var t=!0,n,r,i,s=this.$izip_longest(e);for(var o=0,u=s.length;o<u;o++){var a=s[o];if(!a.push){console.error(a);continue}a.push(NaN);for(var f=0,l=a.length;f<l;f++){var c=a[f];t&&(n=f,i=0,t=!1);if(isNaN(c)){r=f;for(var h=n;h<r;h++)e[h][o]=i;t=!0}i=Math.max(i,c)}}return e},this.$rightmostSelectionInCell=function(e,t){var n=0;if(e.length){var r=[];for(var i=0,s=e.length;i<s;i++)e[i]<=t?r.push(i):r.push(0);n=Math.max.apply(Math,r)}return n},this.$tabsForRow=function(e){var t=[],n=this.$editor.session.getLine(e),r=/\t/g,i;while((i=r.exec(n))!=null)t.push(i.index);return t},this.$adjustRow=function(e,t){var n=this.$tabsForRow(e);if(n.length==0)return;var r=0,i=-1,s=this.$izip(t,n);for(var o=0,u=s.length;o<u;o++){var a=s[o][0],f=s[o][1];i+=1+a,f+=r;var l=i-f;if(l==0)continue;var c=this.$editor.session.getLine(e).substr(0,f),h=c.replace(/\s*$/g,""),p=c.length-h.length;l>0&&(this.$editor.session.getDocument().insertInLine({row:e,column:f+1},Array(l+1).join(" ")+" "),this.$editor.session.getDocument().removeInLine(e,f,f+1),r+=l),l<0&&p>=-l&&(this.$editor.session.getDocument().removeInLine(e,f+l,f),r+=l)}},this.$izip_longest=function(e){if(!e[0])return[];var t=e[0].length,n=e.length;for(var r=1;r<n;r++){var i=e[r].length;i>t&&(t=i)}var s=[];for(var o=0;o<t;o++){var u=[];for(var r=0;r<n;r++)e[r][o]===""?u.push(NaN):u.push(e[r][o]);s.push(u)}return s},this.$izip=function(e,t){var n=e.length>=t.length?t.length:e.length,r=[];for(var i=0;i<n;i++){var s=[e[i],t[i]];r.push(s)}return r}}).call(r.prototype),t.ElasticTabstopsLite=r;var i=e("../editor").Editor;e("../config").defineOptions(i.prototype,"editor",{useElasticTabstops:{set:function(e){e?(this.elasticTabstops||(this.elasticTabstops=new r(this)),this.commands.on("afterExec",this.elasticTabstops.onAfterExec),this.commands.on("exec",this.elasticTabstops.onExec),this.on("change",this.elasticTabstops.onChange)):this.elasticTabstops&&(this.commands.removeListener("afterExec",this.elasticTabstops.onAfterExec),this.commands.removeListener("exec",this.elasticTabstops.onExec),this.removeListener("change",this.elasticTabstops.onChange))}}})});
|
||||
(function() {
|
||||
window.require(["ace/ext/elastic_tabstops_lite"], function() {});
|
||||
define("ace/ext/elastic_tabstops_lite",["require","exports","module","ace/editor","ace/config"],function(e,t,n){"use strict";var r=function(e){this.$editor=e;var t=this,n=[],r=!1;this.onAfterExec=function(){r=!1,t.processRows(n),n=[]},this.onExec=function(){r=!0},this.onChange=function(e){r&&(n.indexOf(e.start.row)==-1&&n.push(e.start.row),e.end.row!=e.start.row&&n.push(e.end.row))}};(function(){this.processRows=function(e){this.$inChange=!0;var t=[];for(var n=0,r=e.length;n<r;n++){var i=e[n];if(t.indexOf(i)>-1)continue;var s=this.$findCellWidthsForBlock(i),o=this.$setBlockCellWidthsToMax(s.cellWidths),u=s.firstRow;for(var a=0,f=o.length;a<f;a++){var l=o[a];t.push(u),this.$adjustRow(u,l),u++}}this.$inChange=!1},this.$findCellWidthsForBlock=function(e){var t=[],n,r=e;while(r>=0){n=this.$cellWidthsForRow(r);if(n.length==0)break;t.unshift(n),r--}var i=r+1;r=e;var s=this.$editor.session.getLength();while(r<s-1){r++,n=this.$cellWidthsForRow(r);if(n.length==0)break;t.push(n)}return{cellWidths:t,firstRow:i}},this.$cellWidthsForRow=function(e){var t=this.$selectionColumnsForRow(e),n=[-1].concat(this.$tabsForRow(e)),r=n.map(function(e){return 0}).slice(1),i=this.$editor.session.getLine(e);for(var s=0,o=n.length-1;s<o;s++){var u=n[s]+1,a=n[s+1],f=this.$rightmostSelectionInCell(t,a),l=i.substring(u,a);r[s]=Math.max(l.replace(/\s+$/g,"").length,f-u)}return r},this.$selectionColumnsForRow=function(e){var t=[],n=this.$editor.getCursorPosition();return this.$editor.session.getSelection().isEmpty()&&e==n.row&&t.push(n.column),t},this.$setBlockCellWidthsToMax=function(e){var t=!0,n,r,i,s=this.$izip_longest(e);for(var o=0,u=s.length;o<u;o++){var a=s[o];if(!a.push){console.error(a);continue}a.push(NaN);for(var f=0,l=a.length;f<l;f++){var c=a[f];t&&(n=f,i=0,t=!1);if(isNaN(c)){r=f;for(var h=n;h<r;h++)e[h][o]=i;t=!0}i=Math.max(i,c)}}return e},this.$rightmostSelectionInCell=function(e,t){var n=0;if(e.length){var r=[];for(var i=0,s=e.length;i<s;i++)e[i]<=t?r.push(i):r.push(0);n=Math.max.apply(Math,r)}return n},this.$tabsForRow=function(e){var t=[],n=this.$editor.session.getLine(e),r=/\t/g,i;while((i=r.exec(n))!=null)t.push(i.index);return t},this.$adjustRow=function(e,t){var n=this.$tabsForRow(e);if(n.length==0)return;var r=0,i=-1,s=this.$izip(t,n);for(var o=0,u=s.length;o<u;o++){var a=s[o][0],f=s[o][1];i+=1+a,f+=r;var l=i-f;if(l==0)continue;var c=this.$editor.session.getLine(e).substr(0,f),h=c.replace(/\s*$/g,""),p=c.length-h.length;l>0&&(this.$editor.session.getDocument().insertInLine({row:e,column:f+1},Array(l+1).join(" ")+" "),this.$editor.session.getDocument().removeInLine(e,f,f+1),r+=l),l<0&&p>=-l&&(this.$editor.session.getDocument().removeInLine(e,f+l,f),r+=l)}},this.$izip_longest=function(e){if(!e[0])return[];var t=e[0].length,n=e.length;for(var r=1;r<n;r++){var i=e[r].length;i>t&&(t=i)}var s=[];for(var o=0;o<t;o++){var u=[];for(var r=0;r<n;r++)e[r][o]===""?u.push(NaN):u.push(e[r][o]);s.push(u)}return s},this.$izip=function(e,t){var n=e.length>=t.length?t.length:e.length,r=[];for(var i=0;i<n;i++){var s=[e[i],t[i]];r.push(s)}return r}}).call(r.prototype),t.ElasticTabstopsLite=r;var i=e("../editor").Editor;e("../config").defineOptions(i.prototype,"editor",{useElasticTabstops:{set:function(e){e?(this.elasticTabstops||(this.elasticTabstops=new r(this)),this.commands.on("afterExec",this.elasticTabstops.onAfterExec),this.commands.on("exec",this.elasticTabstops.onExec),this.on("change",this.elasticTabstops.onChange)):this.elasticTabstops&&(this.commands.removeListener("afterExec",this.elasticTabstops.onAfterExec),this.commands.removeListener("exec",this.elasticTabstops.onExec),this.removeListener("change",this.elasticTabstops.onChange))}}})}); (function() {
|
||||
window.require(["ace/ext/elastic_tabstops_lite"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,8 @@
|
||||
;
|
||||
(function() {
|
||||
window.require(["ace/ext/error_marker"], function() {});
|
||||
; (function() {
|
||||
window.require(["ace/ext/error_marker"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@ -1,5 +1,8 @@
|
||||
define("ace/ext/menu_tools/overlay_page",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../../lib/dom"),i="#ace_settingsmenu, #kbshortcutmenu {background-color: #F7F7F7;color: black;box-shadow: -5px 4px 5px rgba(126, 126, 126, 0.55);padding: 1em 0.5em 2em 1em;overflow: auto;position: absolute;margin: 0;bottom: 0;right: 0;top: 0;z-index: 9991;cursor: default;}.ace_dark #ace_settingsmenu, .ace_dark #kbshortcutmenu {box-shadow: -20px 10px 25px rgba(126, 126, 126, 0.25);background-color: rgba(255, 255, 255, 0.6);color: black;}.ace_optionsMenuEntry:hover {background-color: rgba(100, 100, 100, 0.1);transition: all 0.3s}.ace_closeButton {background: rgba(245, 146, 146, 0.5);border: 1px solid #F48A8A;border-radius: 50%;padding: 7px;position: absolute;right: -8px;top: -8px;z-index: 100000;}.ace_closeButton{background: rgba(245, 146, 146, 0.9);}.ace_optionsMenuKey {color: darkslateblue;font-weight: bold;}.ace_optionsMenuCommand {color: darkcyan;font-weight: normal;}.ace_optionsMenuEntry input, .ace_optionsMenuEntry button {vertical-align: middle;}.ace_optionsMenuEntry button[ace_selected_button=true] {background: #e7e7e7;box-shadow: 1px 0px 2px 0px #adadad inset;border-color: #adadad;}.ace_optionsMenuEntry button {background: white;border: 1px solid lightgray;margin: 0px;}.ace_optionsMenuEntry button:hover{background: #f0f0f0;}";r.importCssString(i),n.exports.overlayPage=function(t,n,i,s,o,u){function l(e){e.keyCode===27&&a.click()}i=i?"top: "+i+";":"",o=o?"bottom: "+o+";":"",s=s?"right: "+s+";":"",u=u?"left: "+u+";":"";var a=document.createElement("div"),f=document.createElement("div");a.style.cssText="margin: 0; padding: 0; position: fixed; top:0; bottom:0; left:0; right:0;z-index: 9990; background-color: rgba(0, 0, 0, 0.3);",a.addEventListener("click",function(){document.removeEventListener("keydown",l),a.parentNode.removeChild(a),t.focus(),a=null}),document.addEventListener("keydown",l),f.style.cssText=i+s+o+u,f.addEventListener("click",function(e){e.stopPropagation()});var c=r.createElement("div");c.style.position="relative";var h=r.createElement("div");h.className="ace_closeButton",h.addEventListener("click",function(){a.click()}),c.appendChild(h),f.appendChild(c),f.appendChild(n),a.appendChild(f),document.body.appendChild(a),t.blur()}}),define("ace/ext/menu_tools/get_editor_keyboard_shortcuts",["require","exports","module","ace/lib/keys"],function(e,t,n){"use strict";var r=e("../../lib/keys");n.exports.getEditorKeybordShortcuts=function(e){var t=r.KEY_MODS,n=[],i={};return e.keyBinding.$handlers.forEach(function(e){var t=e.commandKeyBinding;for(var r in t){var s=r.replace(/(^|-)\w/g,function(e){return e.toUpperCase()}),o=t[r];Array.isArray(o)||(o=[o]),o.forEach(function(e){typeof e!="string"&&(e=e.name),i[e]?i[e].key+="|"+s:(i[e]={key:s,command:e},n.push(i[e]))})}}),n}}),define("ace/ext/keybinding_menu",["require","exports","module","ace/editor","ace/ext/menu_tools/overlay_page","ace/ext/menu_tools/get_editor_keyboard_shortcuts"],function(e,t,n){"use strict";function i(t){if(!document.getElementById("kbshortcutmenu")){var n=e("./menu_tools/overlay_page").overlayPage,r=e("./menu_tools/get_editor_keyboard_shortcuts").getEditorKeybordShortcuts,i=r(t),s=document.createElement("div"),o=i.reduce(function(e,t){return e+'<div class="ace_optionsMenuEntry"><span class="ace_optionsMenuCommand">'+t.command+"</span> : "+'<span class="ace_optionsMenuKey">'+t.key+"</span></div>"},"");s.id="kbshortcutmenu",s.innerHTML="<h1>Keyboard Shortcuts</h1>"+o+"</div>",n(t,s,"0","0","0",null)}}var r=e("ace/editor").Editor;n.exports.init=function(e){r.prototype.showKeyboardShortcuts=function(){i(this)},e.commands.addCommands([{name:"showKeyboardShortcuts",bindKey:{win:"Ctrl-Alt-h",mac:"Command-Alt-h"},exec:function(e,t){e.showKeyboardShortcuts()}}])}});
|
||||
(function() {
|
||||
window.require(["ace/ext/keybinding_menu"], function() {});
|
||||
define("ace/ext/menu_tools/overlay_page",["require","exports","module","ace/lib/dom"],function(e,t,n){"use strict";var r=e("../../lib/dom"),i="#ace_settingsmenu, #kbshortcutmenu {background-color: #F7F7F7;color: black;box-shadow: -5px 4px 5px rgba(126, 126, 126, 0.55);padding: 1em 0.5em 2em 1em;overflow: auto;position: absolute;margin: 0;bottom: 0;right: 0;top: 0;z-index: 9991;cursor: default;}.ace_dark #ace_settingsmenu, .ace_dark #kbshortcutmenu {box-shadow: -20px 10px 25px rgba(126, 126, 126, 0.25);background-color: rgba(255, 255, 255, 0.6);color: black;}.ace_optionsMenuEntry:hover {background-color: rgba(100, 100, 100, 0.1);transition: all 0.3s}.ace_closeButton {background: rgba(245, 146, 146, 0.5);border: 1px solid #F48A8A;border-radius: 50%;padding: 7px;position: absolute;right: -8px;top: -8px;z-index: 100000;}.ace_closeButton{background: rgba(245, 146, 146, 0.9);}.ace_optionsMenuKey {color: darkslateblue;font-weight: bold;}.ace_optionsMenuCommand {color: darkcyan;font-weight: normal;}.ace_optionsMenuEntry input, .ace_optionsMenuEntry button {vertical-align: middle;}.ace_optionsMenuEntry button[ace_selected_button=true] {background: #e7e7e7;box-shadow: 1px 0px 2px 0px #adadad inset;border-color: #adadad;}.ace_optionsMenuEntry button {background: white;border: 1px solid lightgray;margin: 0px;}.ace_optionsMenuEntry button:hover{background: #f0f0f0;}";r.importCssString(i),n.exports.overlayPage=function(t,n,r){function o(e){e.keyCode===27&&u()}function u(){if(!i)return;document.removeEventListener("keydown",o),i.parentNode.removeChild(i),t&&t.focus(),i=null,r&&r()}function a(e){s=e,e&&(i.style.pointerEvents="none",n.style.pointerEvents="auto")}var i=document.createElement("div"),s=!1;return i.style.cssText="margin: 0; padding: 0; position: fixed; top:0; bottom:0; left:0; right:0;z-index: 9990; "+(t?"background-color: rgba(0, 0, 0, 0.3);":""),i.addEventListener("click",function(e){s||u()}),document.addEventListener("keydown",o),n.addEventListener("click",function(e){e.stopPropagation()}),i.appendChild(n),document.body.appendChild(i),t&&t.blur(),{close:u,setIgnoreFocusOut:a}}}),define("ace/ext/menu_tools/get_editor_keyboard_shortcuts",["require","exports","module","ace/lib/keys"],function(e,t,n){"use strict";var r=e("../../lib/keys");n.exports.getEditorKeybordShortcuts=function(e){var t=r.KEY_MODS,n=[],i={};return e.keyBinding.$handlers.forEach(function(e){var t=e.commandKeyBinding;for(var r in t){var s=r.replace(/(^|-)\w/g,function(e){return e.toUpperCase()}),o=t[r];Array.isArray(o)||(o=[o]),o.forEach(function(e){typeof e!="string"&&(e=e.name),i[e]?i[e].key+="|"+s:(i[e]={key:s,command:e},n.push(i[e]))})}}),n}}),define("ace/ext/keybinding_menu",["require","exports","module","ace/editor","ace/ext/menu_tools/overlay_page","ace/ext/menu_tools/get_editor_keyboard_shortcuts"],function(e,t,n){"use strict";function i(t){if(!document.getElementById("kbshortcutmenu")){var n=e("./menu_tools/overlay_page").overlayPage,r=e("./menu_tools/get_editor_keyboard_shortcuts").getEditorKeybordShortcuts,i=r(t),s=document.createElement("div"),o=i.reduce(function(e,t){return e+'<div class="ace_optionsMenuEntry"><span class="ace_optionsMenuCommand">'+t.command+"</span> : "+'<span class="ace_optionsMenuKey">'+t.key+"</span></div>"},"");s.id="kbshortcutmenu",s.innerHTML="<h1>Keyboard Shortcuts</h1>"+o+"</div>",n(t,s)}}var r=e("../editor").Editor;n.exports.init=function(e){r.prototype.showKeyboardShortcuts=function(){i(this)},e.commands.addCommands([{name:"showKeyboardShortcuts",bindKey:{win:"Ctrl-Alt-h",mac:"Command-Alt-h"},exec:function(e,t){e.showKeyboardShortcuts()}}])}}); (function() {
|
||||
window.require(["ace/ext/keybinding_menu"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
File diff suppressed because one or more lines are too long
@ -1,5 +1,8 @@
|
||||
define("ace/ext/linking",["require","exports","module","ace/editor","ace/config"],function(e,t,n){function i(e){var n=e.editor,r=e.getAccelKey();if(r){var n=e.editor,i=e.getDocumentPosition(),s=n.session,o=s.getTokenAt(i.row,i.column);t.previousLinkingHover&&t.previousLinkingHover!=o&&n._emit("linkHoverOut"),n._emit("linkHover",{position:i,token:o}),t.previousLinkingHover=o}else t.previousLinkingHover&&(n._emit("linkHoverOut"),t.previousLinkingHover=!1)}function s(e){var t=e.getAccelKey(),n=e.getButton();if(n==0&&t){var r=e.editor,i=e.getDocumentPosition(),s=r.session,o=s.getTokenAt(i.row,i.column);r._emit("linkClick",{position:i,token:o})}}var r=e("ace/editor").Editor;e("../config").defineOptions(r.prototype,"editor",{enableLinking:{set:function(e){e?(this.on("click",s),this.on("mousemove",i)):(this.off("click",s),this.off("mousemove",i))},value:!1}}),t.previousLinkingHover=!1});
|
||||
(function() {
|
||||
window.require(["ace/ext/linking"], function() {});
|
||||
define("ace/ext/linking",["require","exports","module","ace/editor","ace/config"],function(e,t,n){function i(e){var n=e.editor,r=e.getAccelKey();if(r){var n=e.editor,i=e.getDocumentPosition(),s=n.session,o=s.getTokenAt(i.row,i.column);t.previousLinkingHover&&t.previousLinkingHover!=o&&n._emit("linkHoverOut"),n._emit("linkHover",{position:i,token:o}),t.previousLinkingHover=o}else t.previousLinkingHover&&(n._emit("linkHoverOut"),t.previousLinkingHover=!1)}function s(e){var t=e.getAccelKey(),n=e.getButton();if(n==0&&t){var r=e.editor,i=e.getDocumentPosition(),s=r.session,o=s.getTokenAt(i.row,i.column);r._emit("linkClick",{position:i,token:o})}}var r=e("../editor").Editor;e("../config").defineOptions(r.prototype,"editor",{enableLinking:{set:function(e){e?(this.on("click",s),this.on("mousemove",i)):(this.off("click",s),this.off("mousemove",i))},value:!1}}),t.previousLinkingHover=!1}); (function() {
|
||||
window.require(["ace/ext/linking"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
@ -1,5 +1,8 @@
|
||||
define("ace/ext/modelist",["require","exports","module"],function(e,t,n){"use strict";function i(e){var t=a.text,n=e.split(/[\/\\]/).pop();for(var i=0;i<r.length;i++)if(r[i].supportsFile(n)){t=r[i];break}return t}var r=[],s=function(e,t,n){this.name=e,this.caption=t,this.mode="ace/mode/"+e,this.extensions=n;var r;/\^/.test(n)?r=n.replace(/\|(\^)?/g,function(e,t){return"$|"+(t?"^":"^.*\\.")})+"$":r="^.*\\.("+n+")$",this.extRe=new RegExp(r,"gi")};s.prototype.supportsFile=function(e){return e.match(this.extRe)};var o={ABAP:["abap"],ABC:["abc"],ActionScript:["as"],ADA:["ada|adb"],Apache_Conf:["^htaccess|^htgroups|^htpasswd|^conf|htaccess|htgroups|htpasswd"],AsciiDoc:["asciidoc|adoc"],Assembly_x86:["asm|a"],AutoHotKey:["ahk"],BatchFile:["bat|cmd"],Bro:["bro"],C_Cpp:["cpp|c|cc|cxx|h|hh|hpp|ino"],C9Search:["c9search_results"],Cirru:["cirru|cr"],Clojure:["clj|cljs"],Cobol:["CBL|COB"],coffee:["coffee|cf|cson|^Cakefile"],ColdFusion:["cfm"],CSharp:["cs"],Csound_Document:["csd"],Csound_Orchestra:["orc"],Csound_Score:["sco"],CSS:["css"],Curly:["curly"],D:["d|di"],Dart:["dart"],Diff:["diff|patch"],Dockerfile:["^Dockerfile"],Dot:["dot"],Drools:["drl"],Edifact:["edi"],Eiffel:["e|ge"],EJS:["ejs"],Elixir:["ex|exs"],Elm:["elm"],Erlang:["erl|hrl"],Forth:["frt|fs|ldr|fth|4th"],Fortran:["f|f90"],FTL:["ftl"],Gcode:["gcode"],Gherkin:["feature"],Gitignore:["^.gitignore"],Glsl:["glsl|frag|vert"],Gobstones:["gbs"],golang:["go"],GraphQLSchema:["gql"],Groovy:["groovy"],HAML:["haml"],Handlebars:["hbs|handlebars|tpl|mustache"],Haskell:["hs"],Haskell_Cabal:["cabal"],haXe:["hx"],Hjson:["hjson"],HTML:["html|htm|xhtml|vue|we|wpy"],HTML_Elixir:["eex|html.eex"],HTML_Ruby:["erb|rhtml|html.erb"],INI:["ini|conf|cfg|prefs"],Io:["io"],Jack:["jack"],Jade:["jade|pug"],Java:["java"],JavaScript:["js|jsm|jsx"],JSON:["json"],JSONiq:["jq"],JSP:["jsp"],JSSM:["jssm|jssm_state"],JSX:["jsx"],Julia:["jl"],Kotlin:["kt|kts"],LaTeX:["tex|latex|ltx|bib"],LESS:["less"],Liquid:["liquid"],Lisp:["lisp"],LiveScript:["ls"],LogiQL:["logic|lql"],LSL:["lsl"],Lua:["lua"],LuaPage:["lp"],Lucene:["lucene"],Makefile:["^Makefile|^GNUmakefile|^makefile|^OCamlMakefile|make"],Markdown:["md|markdown"],Mask:["mask"],MATLAB:["matlab"],Maze:["mz"],MEL:["mel"],MIXAL:["mixal"],MUSHCode:["mc|mush"],MySQL:["mysql"],Nix:["nix"],NSIS:["nsi|nsh"],ObjectiveC:["m|mm"],OCaml:["ml|mli"],Pascal:["pas|p"],Perl:["pl|pm"],pgSQL:["pgsql"],PHP:["php|phtml|shtml|php3|php4|php5|phps|phpt|aw|ctp|module"],Pig:["pig"],Powershell:["ps1"],Praat:["praat|praatscript|psc|proc"],Prolog:["plg|prolog"],Properties:["properties"],Protobuf:["proto"],Python:["py"],R:["r"],Razor:["cshtml|asp"],RDoc:["Rd"],Red:["red|reds"],RHTML:["Rhtml"],RST:["rst"],Ruby:["rb|ru|gemspec|rake|^Guardfile|^Rakefile|^Gemfile"],Rust:["rs"],SASS:["sass"],SCAD:["scad"],Scala:["scala"],Scheme:["scm|sm|rkt|oak|scheme"],SCSS:["scss"],SH:["sh|bash|^.bashrc"],SJS:["sjs"],Smarty:["smarty|tpl"],snippets:["snippets"],Soy_Template:["soy"],Space:["space"],SQL:["sql"],SQLServer:["sqlserver"],Stylus:["styl|stylus"],SVG:["svg"],Swift:["swift"],Tcl:["tcl"],Tex:["tex"],Text:["txt"],Textile:["textile"],Toml:["toml"],TSX:["tsx"],Twig:["twig|swig"],Typescript:["ts|typescript|str"],Vala:["vala"],VBScript:["vbs|vb"],Velocity:["vm"],Verilog:["v|vh|sv|svh"],VHDL:["vhd|vhdl"],Wollok:["wlk|wpgm|wtest"],XML:["xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl|xaml"],XQuery:["xq"],YAML:["yaml|yml"],Django:["html"]},u={ObjectiveC:"Objective-C",CSharp:"C#",golang:"Go",C_Cpp:"C and C++",Csound_Document:"Csound Document",Csound_Orchestra:"Csound",Csound_Score:"Csound Score",coffee:"CoffeeScript",HTML_Ruby:"HTML (Ruby)",HTML_Elixir:"HTML (Elixir)",FTL:"FreeMarker"},a={};for(var f in o){var l=o[f],c=(u[f]||f).replace(/_/g," "),h=f.toLowerCase(),p=new s(h,c,l[0]);a[h]=p,r.push(p)}n.exports={getModeForPath:i,modes:r,modesByName:a}});
|
||||
(function() {
|
||||
window.require(["ace/ext/modelist"], function() {});
|
||||
define("ace/ext/modelist",["require","exports","module"],function(e,t,n){"use strict";function i(e){var t=a.text,n=e.split(/[\/\\]/).pop();for(var i=0;i<r.length;i++)if(r[i].supportsFile(n)){t=r[i];break}return t}var r=[],s=function(e,t,n){this.name=e,this.caption=t,this.mode="ace/mode/"+e,this.extensions=n;var r;/\^/.test(n)?r=n.replace(/\|(\^)?/g,function(e,t){return"$|"+(t?"^":"^.*\\.")})+"$":r="^.*\\.("+n+")$",this.extRe=new RegExp(r,"gi")};s.prototype.supportsFile=function(e){return e.match(this.extRe)};var o={ABAP:["abap"],ABC:["abc"],ActionScript:["as"],ADA:["ada|adb"],Alda:["alda"],Apache_Conf:["^htaccess|^htgroups|^htpasswd|^conf|htaccess|htgroups|htpasswd"],Apex:["apex|cls|trigger|tgr"],AQL:["aql"],AsciiDoc:["asciidoc|adoc"],ASL:["dsl|asl"],Assembly_x86:["asm|a"],AutoHotKey:["ahk"],BatchFile:["bat|cmd"],C_Cpp:["cpp|c|cc|cxx|h|hh|hpp|ino"],C9Search:["c9search_results"],Cirru:["cirru|cr"],Clojure:["clj|cljs"],Cobol:["CBL|COB"],coffee:["coffee|cf|cson|^Cakefile"],ColdFusion:["cfm"],Crystal:["cr"],CSharp:["cs"],Csound_Document:["csd"],Csound_Orchestra:["orc"],Csound_Score:["sco"],CSS:["css"],Curly:["curly"],D:["d|di"],Dart:["dart"],Diff:["diff|patch"],Dockerfile:["^Dockerfile"],Dot:["dot"],Drools:["drl"],Edifact:["edi"],Eiffel:["e|ge"],EJS:["ejs"],Elixir:["ex|exs"],Elm:["elm"],Erlang:["erl|hrl"],Forth:["frt|fs|ldr|fth|4th"],Fortran:["f|f90"],FSharp:["fsi|fs|ml|mli|fsx|fsscript"],FSL:["fsl"],FTL:["ftl"],Gcode:["gcode"],Gherkin:["feature"],Gitignore:["^.gitignore"],Glsl:["glsl|frag|vert"],Gobstones:["gbs"],golang:["go"],GraphQLSchema:["gql"],Groovy:["groovy"],HAML:["haml"],Handlebars:["hbs|handlebars|tpl|mustache"],Haskell:["hs"],Haskell_Cabal:["cabal"],haXe:["hx"],Hjson:["hjson"],HTML:["html|htm|xhtml|vue|we|wpy"],HTML_Elixir:["eex|html.eex"],HTML_Ruby:["erb|rhtml|html.erb"],INI:["ini|conf|cfg|prefs"],Io:["io"],Jack:["jack"],Jade:["jade|pug"],Java:["java"],JavaScript:["js|jsm|jsx"],JSON:["json"],JSON5:["json5"],JSONiq:["jq"],JSP:["jsp"],JSSM:["jssm|jssm_state"],JSX:["jsx"],Julia:["jl"],Kotlin:["kt|kts"],LaTeX:["tex|latex|ltx|bib"],LESS:["less"],Liquid:["liquid"],Lisp:["lisp"],LiveScript:["ls"],LogiQL:["logic|lql"],LSL:["lsl"],Lua:["lua"],LuaPage:["lp"],Lucene:["lucene"],Makefile:["^Makefile|^GNUmakefile|^makefile|^OCamlMakefile|make"],Markdown:["md|markdown"],Mask:["mask"],MATLAB:["matlab"],Maze:["mz"],MediaWiki:["wiki|mediawiki"],MEL:["mel"],MIXAL:["mixal"],MUSHCode:["mc|mush"],MySQL:["mysql"],Nginx:["nginx|conf"],Nim:["nim"],Nix:["nix"],NSIS:["nsi|nsh"],Nunjucks:["nunjucks|nunjs|nj|njk"],ObjectiveC:["m|mm"],OCaml:["ml|mli"],Pascal:["pas|p"],Perl:["pl|pm"],Perl6:["p6|pl6|pm6"],pgSQL:["pgsql"],PHP:["php|inc|phtml|shtml|php3|php4|php5|phps|phpt|aw|ctp|module"],PHP_Laravel_blade:["blade.php"],Pig:["pig"],Powershell:["ps1"],Praat:["praat|praatscript|psc|proc"],Prisma:["prisma"],Prolog:["plg|prolog"],Properties:["properties"],Protobuf:["proto"],Puppet:["epp|pp"],Python:["py"],QML:["qml"],R:["r"],Razor:["cshtml|asp"],RDoc:["Rd"],Red:["red|reds"],RHTML:["Rhtml"],RST:["rst"],Ruby:["rb|ru|gemspec|rake|^Guardfile|^Rakefile|^Gemfile"],Rust:["rs"],SASS:["sass"],SCAD:["scad"],Scala:["scala|sbt"],Scheme:["scm|sm|rkt|oak|scheme"],SCSS:["scss"],SH:["sh|bash|^.bashrc"],SJS:["sjs"],Slim:["slim|skim"],Smarty:["smarty|tpl"],snippets:["snippets"],Soy_Template:["soy"],Space:["space"],SQL:["sql"],SQLServer:["sqlserver"],Stylus:["styl|stylus"],SVG:["svg"],Swift:["swift"],Tcl:["tcl"],Terraform:["tf","tfvars","terragrunt"],Tex:["tex"],Text:["txt"],Textile:["textile"],Toml:["toml"],TSX:["tsx"],Twig:["latte|twig|swig"],Typescript:["ts|typescript|str"],Vala:["vala"],VBScript:["vbs|vb"],Velocity:["vm"],Verilog:["v|vh|sv|svh"],VHDL:["vhd|vhdl"],Visualforce:["vfp|component|page"],Wollok:["wlk|wpgm|wtest"],XML:["xml|rdf|rss|wsdl|xslt|atom|mathml|mml|xul|xbl|xaml"],XQuery:["xq"],YAML:["yaml|yml"],Zeek:["zeek|bro"],Django:["html"]},u={ObjectiveC:"Objective-C",CSharp:"C#",golang:"Go",C_Cpp:"C and C++",Csound_Document:"Csound Document",Csound_Orchestra:"Csound",Csound_Score:"Csound Score",coffee:"CoffeeScript",HTML_Ruby:"HTML (Ruby)",HTML_Elixir:"HTML (Elixir)",FTL:"FreeMarker",PHP_Laravel_blade:"PHP (Blade Template)",Perl6:"Perl 6",AutoHotKey:"AutoHotkey / AutoIt"},a={};for(var f in o){var l=o[f],c=(u[f]||f).replace(/_/g," "),h=f.toLowerCase(),p=new s(h,c,l[0]);a[h]=p,r.push(p)}n.exports={getModeForPath:i,modes:r,modesByName:a}}); (function() {
|
||||
window.require(["ace/ext/modelist"], function(m) {
|
||||
if (typeof module == "object" && typeof exports == "object" && module) {
|
||||
module.exports = m;
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user