Rework on AntOS core to provide support to both mobile and desktop devices (experimental):

- Redesign the core UI API and tags to support Mobile devices
- Add new StackMenu tag
- Support touch events handling on touch devices
- Redesign File and Setting to work on mobile
- Improve Anouncement API
- Rework on default themes
This commit is contained in:
DanyLE
2022-12-08 14:50:38 +01:00
parent 2620d2ccb6
commit 92e27b653f
83 changed files with 2417 additions and 1039 deletions

View File

@ -107,21 +107,19 @@ namespace OS {
* @interface AnnouncerListenerType
*/
export interface AnnouncerListenerType {
[index: number]: {
/**
* The event name
*
* @type {string}
*/
e: string;
/**
* The event name
*
* @type {string}
*/
e: string;
/**
* The event callback
*
*/
f: (d: any) => void;
}[];
}
/**
* The event callback
*
*/
f: (d: any) => void;
};
/**
* This class is the based class used in AntOS event
@ -301,7 +299,7 @@ namespace OS {
/**
* Placeholder of all global events listeners
*/
export var listeners: API.AnnouncerListenerType = {};
export var listeners: Map<BaseModel | 0, API.AnnouncerListenerType[]> = new Map();
/**
* Subscribe to a global event
@ -311,14 +309,29 @@ namespace OS {
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
* @param {GUI.BaseModel} a the process (Application/service) related to the callback
*/
export function on(e: string, f: (d: API.AnnouncementDataType<any>) => void, a: BaseModel): void {
if (!announcer.listeners[a.pid]) {
announcer.listeners[a.pid] = [];
export function on(e: string, f: (d: API.AnnouncementDataType<any>) => void, a?: BaseModel): void {
let key: BaseModel | 0 = 0;
if(a)
key = a;
if (!announcer.listeners.has(key)) {
announcer.listeners.set(key, []);
}
announcer.listeners[a.pid].push({ e, f });
const collection = announcer.listeners.get(key);
collection.push({ e, f });
announcer.observable.on(e, f);
}
/**
* Subscribe to a global event once
*
* @export
* @param {string} e event name
* @param {(d: API.AnnouncementDataType<any>) => void} f event callback
*/
export function one(e: string, f: (d: API.AnnouncementDataType<any>) => void): void {
announcer.observable.one(e, f);
}
/**
* Trigger a global event
*
@ -390,16 +403,14 @@ namespace OS {
* @returns {void}
*/
export function unregister(app: BaseModel): void {
if (
!announcer.listeners[app.pid] ||
!(announcer.listeners[app.pid].length > 0)
) {
if (!announcer.listeners.has(app)) {
return;
}
for (let i of announcer.listeners[app.pid]) {
const collection = announcer.listeners.get(app);
for (let i of collection) {
announcer.observable.off(i.e, i.f);
}
delete announcer.listeners[app.pid];
announcer.listeners.delete(app);
}
/**

View File

@ -61,15 +61,6 @@ namespace OS {
*/
sysdock: GUI.tag.AppDockTag;
/**
* Reference to the system application menu located
* on the system panel
*
* @type {GUI.tag.MenuTag}
* @memberof BaseApplication
*/
appmenu: GUI.tag.MenuTag;
/**
* Loading animation check timeout
*
@ -120,14 +111,8 @@ namespace OS {
// 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.scheme as GUI.tag.WindowTag).onmenuopen = (el) => el.nodes = this.baseMenu() || [];
OS.PM.pidactive = this.pid;
this.appmenu.onmenuselect = (
d: GUI.tag.MenuEventData
): void => {
return this.trigger("menuselect", d);
};
this.trigger("focused", undefined);
if (this.dialog) {
return this.dialog.show();
@ -135,8 +120,6 @@ namespace OS {
});
this.on("hide", () => {
this.sysdock.selectedApp = null;
this.appmenu.items = [];
this.appmenu.pid = -1;
if (this.dialog) {
return this.dialog.hide();
}
@ -163,7 +146,6 @@ namespace OS {
this._pending_task.push(o.id);
this.trigger("loading", undefined);
});
this.subscribe("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
@ -360,9 +342,6 @@ namespace OS {
* @memberof BaseApplication
*/
blur(): void {
if (this.appmenu && this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
this.trigger("blur", undefined);
if(this.dialog)
{
@ -414,9 +393,6 @@ namespace OS {
protected onexit(evt: BaseEvent): void {
this.cleanup(evt);
if (!evt.prevent) {
if (this.pid === this.appmenu.pid) {
this.appmenu.items = [];
}
$(this.scheme).remove();
}
}

View File

@ -58,22 +58,15 @@ namespace OS {
}
/**
* Exit the sub-window
* Purge the model from the system
*
* @returns {void}
* @memberof SubWindow
* @protected
* @memberof BaseModel
*/
quit(): void {
const evt = new BaseEvent("exit", false);
this.onexit(evt);
if (!evt.prevent) {
delete this._observable;
if (this.scheme) {
$(this.scheme).remove();
}
if (this.dialog) {
return this.dialog.quit();
}
protected destroy(): void
{
if (this.scheme) {
$(this.scheme).remove();
}
}
@ -218,6 +211,19 @@ namespace OS {
}
}
/**
* Show the dialog
*
* @memberof BaseDialog
*/
show(): void {
this.trigger("focus", undefined);
this.trigger("focused", undefined);
if (this.dialog) {
this.dialog.show();
}
}
}
/**
@ -320,6 +326,7 @@ namespace OS {
}
win.resizable = false;
win.minimizable = false;
win.menu = undefined;
$(win).trigger("focus");
}
}
@ -1216,20 +1223,18 @@ namespace OS {
*/
FileDialog.scheme = `\
<afx-app-window width='400' height='300'>
<afx-hbox>
<afx-list-view data-id = "location" dropdown = "false" data-width = "120"></afx-list-view>
<afx-vbox>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" ></input>
<afx-hbox data-height = '30'>
<div style=' text-align:right;'>
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
<div data-width="5"></div>
</afx-hbox>
</afx-vbox>
</afx-hbox>
<afx-vbox>
<afx-list-view data-id = "location" dropdown = "true" data-height = "40"></afx-list-view>
<afx-file-view data-id = "fileview" view="tree" status = "false"></afx-file-view>
<input data-height = '26' type = "text" data-id = "filename" style="margin-left:5px; margin-right:5px;display:none;" ></input>
<afx-hbox data-height = '30'>
<div style=' text-align:right;'>
<afx-button data-id = "btnOk" text = "__(Ok)"></afx-button>
<afx-button data-id = "bt-cancel" text = "__(Cancel)"></afx-button>
</div>
<div data-width="5"></div>
</afx-hbox>
</afx-vbox>
</afx-app-window>\
`;

View File

@ -296,6 +296,8 @@ namespace OS {
this.on("exit", () => this.quit(false));
this.host = this._gui.desktop();
this.dialog = undefined;
// relay global events to local events
this.subscribe("desktopresize", (e) => this.observable.trigger("desktopresize", e));
}
/**
@ -334,7 +336,7 @@ namespace OS {
* @returns {void}
* @memberof BaseModel
*/
quit(force: boolean): void {
quit(force: boolean = false): void {
const evt = new BaseEvent("exit", force);
this.onexit(evt);
if (!evt.prevent) {
@ -346,10 +348,22 @@ namespace OS {
if (this.dialog) {
this.dialog.quit();
}
return PM.kill(this);
announcer.unregister(this);
this.destroy();
}
}
/**
* Purge the model from the system
*
* @protected
* @memberof BaseModel
*/
protected destroy(): void
{
return PM.kill(this);
}
/**
* Model meta data, need to be implemented by
* subclasses

View File

@ -807,6 +807,10 @@ namespace OS {
*/
export const REPOSITORY: string = "https://github.com/lxsang/antos";
/**
* Indicate whether the current de
*/
export var mobile: boolean = false;
/**
* Register a model prototype to the system namespace.
* There are two types of model to be registered, if the model
@ -875,6 +879,7 @@ namespace OS {
export function boot(): void {
//first login
console.log("Booting system");
// check whether we are on mobile device
API.handle
.auth()
.then(function (d: API.RequestResult) {
@ -1331,7 +1336,7 @@ namespace OS {
data.id = q;
data.message = p;
data.name = p;
data.u_data = PM.pidactive;
data.u_data = 0; //PM.pidactive;
announcer.trigger("loading", data);
}
@ -1612,11 +1617,11 @@ namespace OS {
for (let k in searchHandle) {
const ret = searchHandle[k](text);
if (ret.length > 0) {
ret.unshift({
/*ret.unshift({
text: k,
class: "search-header",
dataid: "header",
});
});*/
r = r.concat(ret);
}
}

View File

@ -94,7 +94,6 @@ namespace OS {
* is allowed at a time. A dialog may have sub dialog
*/
export var dialog: BaseDialog;
/**
* Placeholder for system shortcuts
*/
@ -192,7 +191,7 @@ namespace OS {
*/
export function systemDock(): GUI.tag.AppDockTag
{
return $("#sysdock")[0] as tag.AppDockTag;
return $("[data-id='sysdock']")[0] as tag.AppDockTag;
}
/**
@ -657,14 +656,10 @@ namespace OS {
if (!meta.icon && !meta.iconclass) {
data.iconclass = "fa fa-cogs";
}
const dock = $("#sysdock")[0] as tag.AppDockTag;
const dock = systemDock();
app.sysdock = dock;
app.init();
app.observable.one("rendered", function () {
app.appmenu = $(
"[data-id = 'appmenu']",
"#syspanel"
)[0] as tag.MenuTag;
dock.newapp(data);
});
}
@ -714,7 +709,7 @@ namespace OS {
* @returns
*/
export function undock(app: application.BaseApplication) {
return ($("#sysdock")[0] as tag.AppDockTag).removeapp(app);
return systemDock().removeapp(app);
}
/**
@ -753,7 +748,7 @@ namespace OS {
function bindContextMenu(event: JQuery.MouseEventBase): void {
var handle = function (e: HTMLElement) {
if (e.contextmenuHandle) {
const m = $("#contextmenu")[0] as tag.MenuTag;
const m = $("#contextmenu")[0] as tag.StackMenuTag;
m.onmenuselect = () => { };
return e.contextmenuHandle(event, m);
} else {
@ -896,9 +891,9 @@ namespace OS {
const scheme = $.parseHTML(schemes.ws);
$("#wrapper").append(scheme);
announcer.observable.one("sysdockloaded", () => {
announcer.one("sysdockloaded", () => {
$(window).on("keydown", function (event) {
const dock = $("#sysdock")[0] as tag.AppDockTag;
const dock = systemDock();
if (!dock) {
return;
}
@ -939,10 +934,10 @@ namespace OS {
});
// system menu and dock
$("#syspanel")[0].uify(undefined);
$("#sysdock")[0].uify(undefined);
$("#systooltip")[0].uify(undefined);
$("#contextmenu")[0].uify(undefined);
const ctxmenu = $("#contextmenu")[0];
ctxmenu.uify(undefined);
$("#wrapper").on("contextmenu", (e) => bindContextMenu(e));
// tooltip
$(document).on("mouseover", function (e) {
@ -1033,8 +1028,8 @@ namespace OS {
// load theme
loadTheme(setting.appearance.theme, true);
wallpaper(undefined);
OS.announcer.observable.one("syspanelloaded", async function () {
OS.announcer.observable.on("systemlocalechange", (_) =>
OS.announcer.one("syspanelloaded", async function () {
OS.announcer.on("systemlocalechange", (_) =>
$("#syspanel")[0].update()
);
@ -1085,10 +1080,10 @@ namespace OS {
});
// initDM
API.setLocale(setting.system.locale).then(() => initDM());
Ant.OS.announcer.observable.on("error", function (d) {
Ant.OS.announcer.on("error", function (d) {
console.log(d.u_data);
});
Ant.OS.announcer.observable.on("fail", function (d) {
Ant.OS.announcer.on("fail", function (d) {
console.log(d.u_data);
});
}
@ -1105,10 +1100,9 @@ namespace OS {
schemes.ws = `\
<afx-sys-panel id = "syspanel"></afx-sys-panel>
<div id = "workspace">
<afx-apps-dock id="sysdock"></afx-apps-dock>
<afx-desktop id = "desktop" dir="vertical" ></afx-desktop>
</div>
<afx-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-menu>
<afx-stack-menu id="contextmenu" data-id="contextmenu" context="true" style="display:none;"></afx-stack-menu>
<afx-label id="systooltip" data-id="systooltip" style="display:none;position:absolute;"></afx-label>
<textarea id="clipboard"></textarea>\
`;

View File

@ -139,7 +139,7 @@ namespace OS {
} else {
GUI.detachservice(app as application.BaseService);
}
announcer.unregister(app);
// announcer.unregister(app);
delete PM.processes[app.name][i];
PM.processes[app.name].splice(i, 1);
}

View File

@ -99,7 +99,7 @@ namespace OS {
}
}
if (i !== -1) {
$(this.items[i].domel).attr("tooltip", `cr:${app.title()}`);
$(this.items[i].domel).attr("tooltip", `ct:${app.title()}`);
}
}
@ -257,7 +257,7 @@ namespace OS {
"afx-button"
)[0] as any) as ButtonTag;
const app = bt.data as application.BaseApplication;
m.items = [
m.nodes = [
{ text: "__(New window)", dataid: "new" },
{ text: "__(Show)", dataid: "show" },
{ text: "__(Hide)", dataid: "hide" },

View File

@ -24,7 +24,22 @@ namespace OS {
* @type {GenericObject<any>}
* @memberof ButtonTag
*/
data: GenericObject<any>;
private _data: GenericObject<any>;
/**
* Custom user data setter/gettter
*
* @memberof ButtonTag
*/
set data(v: GenericObject<any>)
{
this._data = v;
this.set(v);
}
get data(): GenericObject<any>
{
return this._data;
}
/**
*Creates an instance of ButtonTag.

View File

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

View File

@ -27,6 +27,15 @@ namespace OS {
*/
private _onfileopen: TagEventCallback<API.FileInfoType>;
/**
* placeholder for directory changed event callback
*
* @private
* @type {TagEventCallback<API.VFS.BaseFileHandle>}
* @memberof FileViewTag
*/
private _ondirchanged: TagEventCallback<API.VFS.BaseFileHandle>;
/**
* Reference to the all selected files meta-datas
*
@ -92,6 +101,7 @@ namespace OS {
this.chdir = true;
this.view = "list";
this._onfileopen = this._onfileselect = (e) => { };
this._ondirchanged = (e) => { };
this._selectedFiles = [];
const fn = function(r1, r2, i) {
let t1 = r1[i].text;
@ -115,17 +125,17 @@ namespace OS {
t1 = t1.toString().toLowerCase();
t2 = t2.toString().toLowerCase();
}
if(this.__f)
if(this.desc)
{
this.desc = ! this.desc;
if(t1 < t2) { return -1; }
if(t1 > t2) { return 1; }
}
else
{
this.desc = ! this.desc;
if(t1 > t2) { return -1; }
if(t1 < t2) { return 1; }
if(t1 < t2) { return 1; };
}
return 0;
};
@ -176,6 +186,17 @@ namespace OS {
this._onfileselect = e;
}
/**
* set the callback handle for the directory changed event.
* The parameter of the callback should be an object
* of type [[TagEventType]]<T> with the data type `T` is [[API.VFS.BaseFileHandle]]
*
* @memberof FileViewTag
*/
set onchdir(e: TagEventCallback<API.VFS.BaseFileHandle>) {
this._ondirchanged = e;
}
/**
set the callback handle for the file open event.
* The parameter of the callback should be an object
@ -352,6 +373,9 @@ namespace OS {
if (this.status) {
(this.refs.status as LabelTag).text = " ";
}
const evt = { id: this.aid, data: v.asFileHandle() };
this._ondirchanged(evt);
this.observable.trigger("chdir", evt);
})
.catch((e: Error) =>
announcer.oserror(e.toString(), e)
@ -457,7 +481,7 @@ namespace OS {
let h = $(this).outerHeight();
const w = $(this).width();
if (this.status) {
h -= $(this.refs.status).height() + 10;
h -= $(this.refs.status).height();
}
$(this.refs.listview).css("height", h + "px");
$(this.refs.gridview).css("height", h + "px");
@ -647,7 +671,7 @@ namespace OS {
}
if (this.status) {
(this.refs.status as LabelTag).text = __(
"Selected: {0} ({1} bytes)",
"{0} ({1} bytes)",
e.filename,
e.size ? e.size : "0"
);
@ -669,10 +693,11 @@ namespace OS {
e.type = "dir";
e.mime = "dir";
}
const evt = { id: this.aid, data: e };
if (e.type === "dir" && this.chdir) {
this.path = e.path;
} else {
const evt = { id: this.aid, data: e };
this._onfileopen(evt);
this.observable.trigger("fileopen", evt);
}

View File

@ -48,30 +48,6 @@ namespace OS {
this._onready = v;
}
/**
* Setter:
*
* Set the direction of the list item layout.
* Two directions are available:
* - `vertical`
* - `horizontal`
*
* This setter acts as a DOM attribute
*
* Getter:
*
* Get the currently set direction of list
* item layout
*
* @memberof FloatListTag
*/
set dir(v: string) {
$(this).attr("dir", v);
this.calibrate();
}
get dir(): string {
return $(this).attr("dir");
}
/**
* Disable the dropdown option in this list
@ -164,9 +140,9 @@ namespace OS {
.css("cursor", "default")
.css("display", "block")
.css("position", "absolute")
.on("mousedown", (evt) => {
.on("pointerdown", (evt) => {
const globalof = $(this.refs.mlist).offset();
evt.preventDefault();
//evt.preventDefault();
const offset = $(el).offset();
offset.top = evt.clientY - offset.top;
offset.left = evt.clientX - offset.left;
@ -184,11 +160,11 @@ namespace OS {
};
var mouse_up = function (e: JQuery.MouseEventBase) {
$(window).off("mousemove", mouse_move);
return $(window).off("mouseup", mouse_up);
$(window).off("pointermove", mouse_move);
return $(window).off("pointerup", mouse_up);
};
$(window).on("mousemove", mouse_move);
return $(window).on("mouseup", mouse_up);
$(window).on("pointermove", mouse_move);
return $(window).on("pointerup", mouse_up);
});
}

View File

@ -279,7 +279,7 @@ namespace OS {
e: TagEventType<GridCellPrototype>
): void => {};
this.selected = false;
$(this).css("display", "block");
//$(this).css("display", "block");
$(this).on("click",(e) => {
let evt = { id: this.aid, data: this };
return this.cellselect(evt, false);
@ -683,6 +683,14 @@ namespace OS {
if(element.data.sort)
{
this.sort(element.data, element.data.sort);
if(element.data.desc)
{
$(element).attr("sort", "desc");
}
else
{
$(element).attr("sort", "asc");
}
}
};
i++;
@ -752,6 +760,10 @@ namespace OS {
set rows(rows: GenericObject<any>[][]) {
this._rows = rows;
if(!rows) return;
for(const el of this._header)
{
$(el.domel).attr("sort", "none");
}
// update existing row with new data
const ndrows = rows.length;
const ncrows = this.refs.grid.children.length;
@ -839,7 +851,7 @@ namespace OS {
return fn.call(context,a, b, index);
}
this._rows.sort(__fn);
context.__f = ! context.__f;
context.desc = ! context.desc;
this.rows = this._rows;
}
/**

View File

@ -173,6 +173,7 @@ namespace OS {
*/
protected mount(): void {
$(this.refs.item).attr("dataref", "afx-list-item");
$(this).addClass("afx-list-item");
$(this.refs.item).on("contextmenu", (e) => {
this._onctxmenu({ id: this.aid, data: this });
});
@ -202,14 +203,22 @@ namespace OS {
* @memberof ListViewItemTag
*/
protected layout(): TagLayoutType[] {
let children = [{el: "i", class: "closable", ref: "btcl"}] as TagLayoutType[];
const itemlayout = this.itemlayout();
if(Array.isArray(itemlayout))
{
children = children.concat(itemlayout);
}
else
{
children.unshift(itemlayout);
}
return [
{
el: "li",
ref: "item",
children: [
this.itemlayout(),
{ el: "i", class: "closable", ref: "btcl" },
],
children:children,
},
];
}
@ -240,10 +249,10 @@ namespace OS {
*
* @protected
* @abstract
* @returns {TagLayoutType}
* @returns {TagLayoutType | TagLayoutType[]}
* @memberof ListViewItemTag
*/
protected abstract itemlayout(): TagLayoutType;
protected abstract itemlayout(): TagLayoutType | TagLayoutType[];
/**
* This function is called when the item data is changed.
@ -329,14 +338,101 @@ namespace OS {
* List item custom layout definition
*
* @protected
* @returns {TagLayoutType}
* @returns {TagLayoutType | TagLayoutType[]}
* @memberof SimpleListItemTag
*/
protected itemlayout(): TagLayoutType {
protected itemlayout(): TagLayoutType | TagLayoutType[] {
return { el: "afx-label", ref: "label" };
}
}
/**
* The layout of a double line list item contains two
* AFX labels
*
* @export
* @class DoubleLineListItemTag
* @extends {ListViewItemTag}
*/
export class DoubleLineListItemTag extends ListViewItemTag {
/**
*Creates an instance of DoubleLineListItemTag.
* @memberof DoubleLineListItemTag
*/
constructor() {
super();
}
/**
* Reset some property to default
*
* @protected
* @memberof DoubleLineListItemTag
*/
protected init(): void {
this.closable = false;
this.data = {};
}
/**
* Do nothing
*
* @protected
* @memberof DoubleLineListItemTag
*/
protected calibrate(): void {}
/**
* Refresh the inner label when the item data
* is changed
*
* @protected
* @returns {void}
* @memberof DoubleLineListItemTag
*/
protected ondatachange(): void {
const v = this.data;
if (!v) {
return;
}
const line1 = this.refs.line1 as LabelTag;
const line2 = this.refs.line2 as LabelTag;
line1.set(v);
if(v.description)
{
line2.set(v.description);
}
if (v.selected) {
this.selected = v.selected;
}
if (v.closable) {
this.closable = v.closable;
}
}
/**
* Re-render the list item
*
* @protected
* @memberof DoubleLineListItemTag
*/
protected reload(): void {
this.data = this.data;
}
/**
* List item custom layout definition
*
* @protected
* @returns {TagLayoutType | TagLayoutType[]}
* @memberof DoubleLineListItemTag
*/
protected itemlayout(): TagLayoutType | TagLayoutType[] {
return [{ el: "afx-label", ref: "line1", class:"title" }, { el: "afx-label", ref: "line2", class:"description" }];
}
}
/**
* This tag defines a traditional or a dropdown list widget.
* It contains a collection of list items in which layout
@ -482,10 +578,8 @@ namespace OS {
this.dropdown = false;
this.selected = -1;
this.dragndrop = false;
$(this)
.css("display", "flex")
.css("flex-direction", "column");
this.itemtag = "afx-list-item";
$(this).addClass("afx-list-view");
}
/**
@ -508,7 +602,6 @@ namespace OS {
this.attsw(v, "dropdown");
$(this.refs.container).removeAttr("style");
$(this.refs.mlist).removeAttr("style");
$(this.refs.container).css("flex", 1);
$(this).removeClass("dropdown");
const drop = (e: any) => {
return this.dropoff(e);
@ -521,14 +614,7 @@ namespace OS {
$(this.refs.current).show();
$(document).on("click", drop);
$(this.refs.current).on("click", show);
$(this.refs.container)
.css("position", "absolute")
.css("display", "inline-block");
$(this.refs.mlist)
.css("position", "absolute")
.css("display", "none")
.css("top", "100%")
.css("left", "0");
$(this.refs.mlist).hide();
this.calibrate();
} else {
$(this.refs.current).hide();
@ -676,7 +762,28 @@ namespace OS {
(bt[0] as ButtonTag).set(item);
}
}
/**
* Getter: Get list direction: horizontal or vertical (default)
*
* Setter: Get list direction: horizontal or vertical
*
* @type {string}
* @memberof ListViewTag
*/
set dir(v: string) {
if(this.dropdown)
{
$(this).attr("dir", "vertical");
}
else
{
$(this).attr("dir", v);
}
this.calibrate();
}
get dir(): string {
return $(this).attr("dir");
}
/**
* Getter: Get data of the list
*
@ -769,7 +876,13 @@ namespace OS {
get selectedItems(): ListViewItemTag[] {
return this._selectedItems;
}
/**
* get the selected item index
*
* @readonly
* @type {number}
* @memberof ListViewTag
*/
get selected(): number | number[] {
if (this.multiselect) {
return this.selectedItems.map(function (
@ -808,7 +921,7 @@ namespace OS {
* Add an item to the beginning or end of the list
*
* @param {GenericObject<any>} item list item data
* @param {boolean} [flag] indicates whether to add the item in the beginning of the list
* @param {boolean} flag indicates whether to add the item in the beginning of the list
* @returns {ListViewItemTag} the added list item element
* @memberof ListViewTag
*/
@ -832,6 +945,7 @@ namespace OS {
}
el[0].uify(this.observable);
const element = el[0] as ListViewItemTag;
$(element).attr("list-id",this.aid);
element.onctxmenu = (e) => {
return this.iclick(e, true);
};
@ -955,12 +1069,12 @@ namespace OS {
/**
* This function triggers the double click event on an item
*
* @private
* @protected
* @param {TagEventType} e tag event object
* @returns
* @memberof ListViewTag
*/
private idbclick(e: TagEventType<ListViewItemTag>) {
protected idbclick(e: TagEventType<ListViewItemTag>) {
const evt: TagEventType<ListItemEventData> = {
id: this.aid,
data: { item: e.data },
@ -972,12 +1086,12 @@ namespace OS {
/**
* This function triggers the list item select event
*
* @private
* @protected
* @param {TagEventType} e tag event object
* @returns
* @memberof ListViewTag
*/
private iselect(e: TagEventType<ListViewItemTag>) {
protected iselect(e: TagEventType<ListViewItemTag>) {
if (!e.data) {
return;
}
@ -1167,16 +1281,22 @@ namespace OS {
if (!this.dropdown) {
return;
}
const desktoph = $(Ant.OS.GUI.workspace).height();
if(! $(this.refs.mlist).is(":hidden"))
{
$(this.refs.mlist).hide();
return;
}
const desktoph = $(Ant.OS.GUI.workspace).outerHeight();
const offset =
$(this).offset().top + $(this.refs.mlist).height();
$(this).offset().top + $(this.refs.mlist).outerHeight()*1.5;
if (offset > desktoph) {
$(this.refs.mlist).css(
"top",
`-${$(this.refs.mlist).outerHeight()}px`
);
} else {
$(this.refs.mlist).css("top", "100%");
$(this.refs.mlist).css("top", "98%");
}
$(this.refs.mlist).show();
}
@ -1208,7 +1328,9 @@ namespace OS {
return;
}
const w = `${$(this).width()}px`;
const h = `${$(this).outerHeight()}px`;
$(this.refs.container).css("width", w);
$(this.refs.container).css("height", h);
$(this.refs.current).css("width", w);
$(this.refs.mlist).css("width", w);
}
@ -1244,6 +1366,7 @@ namespace OS {
define("afx-list-view", ListViewTag);
define("afx-list-item", SimpleListItemTag);
define("afx-dbline-list-item", DoubleLineListItemTag);
}
}
}

View File

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

View File

@ -127,7 +127,9 @@ namespace OS {
* @memberof OverlayTag
*/
calibrate(): void {
$(this).css("width", this.width).css("height", this.height);
$(this)
.css("width", this.width)
.css("height", this.height);
return this.observable.trigger("resize", {
id: this.aid,
data: {

View File

@ -104,7 +104,7 @@ namespace OS {
set dir(v: string) {
let att: string;
$(this).attr("dir", v);
$(this).off("mousedown", null);
$(this).off("pointerdown", null);
if (v === "hz") {
$(this).css("cursor", "col-resize");
$(this).addClass("horizontal");
@ -208,24 +208,24 @@ namespace OS {
if (!this.dir || this.dir == "none") {
return;
}
$(this).on("mousedown", (e) => {
$(this).on("pointerdown", (e) => {
e.preventDefault();
$(window).on("mousemove", (evt) => {
$(window).on("pointermove", (evt) => {
if (!this._resizable_el) {
return;
}
if (this.dir === "hz") {
return this.horizontalResize(evt);
return this.horizontalResize(evt as JQuery.MouseEventBase);
} else if (this.dir === "ve") {
return this.verticalResize(evt);
return this.verticalResize(evt as JQuery.MouseEventBase);
}
});
return $(window).on("mouseup", function (evt) {
$(window).off("mousemove", null);
$(window).off("mouseup", null);
return $(window).on("pointerup", function (evt) {
$(window).off("pointermove", null);
$(window).off("pointerup", null);
return $(window).off("mouseup", null);
return $(window).off("pointerup", null);
});
});
}

View File

@ -65,6 +65,7 @@ namespace OS {
this.enable = true;
this._max = 100;
this._value = 0;
this.precision = false;
this._onchange = this._onchanging = () => {};
}
@ -98,7 +99,17 @@ namespace OS {
set onvaluechanging(f: TagEventCallback<number>) {
this._onchanging = f;
}
/**
* Setter/Getter: set and get precision reading
*
* @memberof SliderTag
*/
set precision(v: boolean) {
this.attsw(v, "precision");
}
get precision(): boolean {
return this.hasattr("precision");
}
/**
* Setter: Enable/disable the slider
*
@ -110,15 +121,15 @@ namespace OS {
this.attsw(v, "enable");
if (v) {
$(this)
.on("mouseover",() => {
.on("pointerover",() => {
return $(this.refs.point).show();
})
.on("mouseout",() => {
.on("pointerout",() => {
return $(this.refs.point).hide();
});
} else {
$(this.refs.point).hide();
$(this).off("mouseover").off("mouseout");
$(this).off("pointerover").off("pointerout");
}
}
get enable(): boolean {
@ -188,7 +199,16 @@ namespace OS {
*/
calibrate(): void {
if (this.value > this.max) {
this.value = this.max;
this._value = this.max;
}
if(! this.precision)
{
this._value = Math.round(this.value);
$(this.refs.point).text(this.value);
}
else
{
$(this.refs.point).text((Math.round(this.value * 100) / 100).toFixed(2));
}
$(this.refs.container).css("width", $(this).width() + "px");
const w =
@ -197,17 +217,17 @@ namespace OS {
$(this.refs.prg)
.css("width", w + "px")
.css("height", $(this.refs.container).height() + "px");
if (this.enable) {
const ow = w - $(this.refs.point).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");
}
//if (this.enable) {
const ow = w - ($(this.refs.point).outerWidth() / 2.0);
const top = Math.floor(
($(this.refs.prg).height() +
$(this.refs.point).height()) /
2 + 3
);
$(this.refs.point)
.css("left", ow + "px")
.css("bottom", top + "px");
//}
}
/**
@ -220,10 +240,10 @@ namespace OS {
$(this.refs.point)
.css("user-select", "none")
.css("cursor", "default");
$(this.refs.point).on("mousedown", (e) => {
$(this).on("pointerdown", (e) => {
e.preventDefault();
const offset = $(this.refs.container).offset();
$(window).on("mousemove", (e) => {
$(window).on("pointermove", (e) => {
let left = e.clientX - offset.left;
left = left < 0 ? 0 : left;
const maxw = $(this.refs.container).width();
@ -236,13 +256,13 @@ namespace OS {
});
});
$(window).on("mouseup", (e) => {
$(window).on("pointerup", (e) => {
this._onchange({
id: this.aid,
data: this.value,
});
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
$(window).off("pointermove", null);
return $(window).off("pointerup", null);
});
});
}

View File

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

View File

@ -142,21 +142,21 @@ namespace OS {
*/
private search(e: JQuery.KeyboardEventBase): void {
const applist = this.refs.applist as ListViewTag;
const catlist = this.refs.catlist as ListViewTag;
const catlist = this.refs.catlist as TabBarTag;
switch (e.which) {
case 27:
// escape key
return this.toggle(false);
case 37:
return e.preventDefault();
case 38:
applist.selectPrev();
return e.preventDefault();
case 38:
return e.preventDefault();
case 39:
applist.selectNext();
return e.preventDefault();
case 40:
applist.selectNext();
return e.preventDefault();
case 13:
e.preventDefault();
@ -216,10 +216,9 @@ namespace OS {
class: "afx-panel-os-pinned-app",
},
{
el: "afx-menu",
id: "appmenu",
ref: "appmenu",
class: "afx-panel-os-app",
el: "afx-apps-dock",
ref: "sysdock",
id: "sysdock"
},
{
el: "afx-menu",
@ -247,17 +246,13 @@ namespace OS {
],
},
{
el: "afx-hbox",
el: "afx-vbox",
children: [
{
el: "afx-list-view",
el: "afx-tab-bar",
id: "catlist",
ref: "catlist",
width:"40%"
},
{
el: "afx-resizer",
width: 3,
height:45
},
{
el: "afx-list-view",
@ -269,7 +264,7 @@ namespace OS {
{
el: "afx-hbox",
id: "btlist",
height: 30,
height: 40,
children: [
{
el: "afx-button",
@ -293,6 +288,7 @@ namespace OS {
},
],
},
];
}
@ -304,7 +300,7 @@ namespace OS {
* @memberof SystemPanelTag
*/
private refreshAppList(): void {
let catlist_el = (this.refs.catlist as tag.ListViewTag);
let catlist_el = (this.refs.catlist as tag.TabBarTag);
let k: string, v: API.PackageMetaType;
const catlist = new Set();
this.app_list = [];
@ -349,7 +345,7 @@ namespace OS {
iconclass: "bi bi-gear-wide"
});
});
catlist_el.data = cat_list_data;
catlist_el.items = cat_list_data;
catlist_el.selected = 0;
}
@ -369,7 +365,7 @@ namespace OS {
this.calibrate();
$(document).on("click", this._cb);
(this.refs.search as HTMLInputElement).value = "";
$(this.refs.search).trigger("focus");
$(this.refs.search).focus();
} else {
$(this.refs.overlay).hide();
$(document).off("click", this._cb);
@ -445,11 +441,8 @@ namespace OS {
!$(e.target).closest(this.refs.osmenu).length
) {
return this.toggle(false);
} else {
return $(this.refs.search).trigger("focus");
}
};
$(this.refs.appmenu).css("z-index", 1000000);
$(this.refs.systray).css("z-index", 1000000);
(this.refs.btscreen as ButtonTag).set({
iconclass: "fa fa-tv",
@ -476,7 +469,14 @@ namespace OS {
},
});
(this.refs.osmenu as MenuTag).onmenuselect = (e) => {
return this.toggle(true);
if($(this.refs.overlay).is(":hidden"))
{
this.toggle(true);
}
else
{
this.toggle(false);
}
};
$(this.refs.search).on("keyup", (e) => {
@ -493,8 +493,8 @@ namespace OS {
return this.toggle(false);
}
});
const catlist = (this.refs.catlist as tag.ListViewTag);
catlist.onlistselect = (e) => {
const catlist = (this.refs.catlist as tag.TabBarTag);
catlist.ontabselect = (e) => {
const applist = (this.refs.applist as ListViewTag);
if(catlist.selected === 0)
{
@ -510,9 +510,6 @@ namespace OS {
applist.selected = -1;
};
$(this.refs.overlay)
.css("left", 0)
.css("top", `${$(this.refs.panel).height()}px`)
.css("bottom", "0")
.hide();
(this.refs.pinned as GUI.tag.MenuTag).onmenuselect = (e) => {
const app = e.data.item.data.app;
@ -520,7 +517,6 @@ namespace OS {
return;
GUI.launch(app, []);
};
this.refs.appmenu.contextmenuHandle = (e, m) => { }
this.refs.osmenu.contextmenuHandle = (e, m) => { }
this.refs.systray.contextmenuHandle = (e, m) => { }
this.refs.pinned.contextmenuHandle = (e, m) => { }
@ -528,18 +524,16 @@ namespace OS {
let menu = [
{ text: __("Applications and services setting"), dataid: "app&srv" }
];
m.items = menu;
m.onmenuselect = function (
evt: TagEventType<tag.MenuEventData>
) {
m.nodes = menu;
m.onmenuselect = (evt) => {
GUI.launch("Setting",[]);
}
m.show(e);
};
announcer.observable.on("app-pinned", (_) => {
announcer.on("app-pinned", (_) => {
this.RefreshPinnedApp();
});
announcer.observable.on("loading", (o: API.AnnouncementDataType<number>) => {
announcer.on("loading", (o: API.AnnouncementDataType<number>) => {
if(o.u_data != 0)
{
return;
@ -550,7 +544,7 @@ namespace OS {
$(GUI.workspace).css("cursor", "wait");
});
announcer.observable.on("loaded", (o: API.AnnouncementDataType<number>) => {
announcer.on("loaded", (o: API.AnnouncementDataType<number>) => {
const i = this._pending_task.indexOf(o.id);
if (i >= 0) {
this._pending_task.splice(i, 1);
@ -561,6 +555,9 @@ namespace OS {
this._loading_toh = setTimeout(() => this.animation_check(),1000);
}
});
announcer.on("desktopresize", (e) => {
this.calibrate();
});
this.RefreshPinnedApp();
Ant.OS.announcer.trigger("syspanelloaded", undefined);
}

View File

@ -39,6 +39,13 @@ namespace OS {
*/
private _ontabselect: TagEventCallback<TabEventData>;
/**
* Cache of touch event
*
* @private
* @meberof TabBarTag
*/
private _previous_touch: {x: number, y: number};
/**
*Creates an instance of TabBarTag.
* @memberof TabBarTag
@ -57,6 +64,8 @@ namespace OS {
*/
protected init(): void {
this.selected = -1;
this.dir = "horizontal";
this._previous_touch = {x: 0, y:0};
}
/**
@ -82,6 +91,30 @@ namespace OS {
return this.hasattr("closable");
}
/**
* Setter:
*
* Set the tab bar direction:
* - `horizontal`: horizontal direction
* - `vertical`: vertical direction
*
* Getter:
*
* Get the tab bar direction
*
* @memberof TabBarTag
*/
set dir(v: string) {
$(this).attr("dir", v);
if (!v) {
return;
}
(this.refs.list as ListViewTag).dir = v;
}
get dir(): string {
return $(this).attr("dir") as any;
}
/**
* Add a tab in the end of the tab bar
*
@ -144,6 +177,16 @@ namespace OS {
get selected(): number | number[] {
return (this.refs.list as ListViewTag).selected;
}
/**
* Get the latest selected item
*
* @readonly
* @type {ListViewItemTag}
* @memberof TabBarTag
*/
get selectedItem(): ListViewItemTag {
return (this.refs.list as ListViewTag).selectedItem;
}
/**
* Set the tab close event handle
@ -179,6 +222,39 @@ namespace OS {
this._ontabselect(e);
return this.observable.trigger("tabselect", e);
};
const list_container = $(".list-container", this.refs.list);
list_container.each((i,el) => {
$(el).on("touchstart", e => {
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
});
$(el).on("touchmove", e => {
const offset = {x:0, y:0};
offset.x = this._previous_touch.x - e.touches[0].pageX ;
offset.y = this._previous_touch.y - e.touches[0].pageY;
if(this.dir == "horizontal")
{
el.scrollLeft += offset.x;
}
else
{
el.scrollTop += offset.y;
}
this._previous_touch.x = e.touches[0].pageX;
this._previous_touch.y = e.touches[0].pageY;
});
$(el).on("wheel", (evt)=>{
if(this.dir == "horizontal")
{
el.scrollLeft += (evt.originalEvent as WheelEvent).deltaY;
}
else
{
el.scrollTop += (evt.originalEvent as WheelEvent).deltaY;
}
});
});
}
/**

View File

@ -129,6 +129,14 @@ namespace OS {
return;
}
(this.refs.wrapper as TileLayoutTag).dir = v;
if(v === "row")
{
(this.refs.bar as TabBarTag).dir = "vertical";
}
else
{
(this.refs.bar as TabBarTag).dir = "horizontal";
}
}
get dir(): "row" | "column" {
return $(this).attr("dir") as any;
@ -175,7 +183,7 @@ namespace OS {
return;
}
$(this.refs.bar).attr("data-width", `${v}`);
(this.refs.wrapper as TileLayoutTag).calibrate();
this.observable.trigger("resize", undefined);
}
/**
@ -185,8 +193,11 @@ namespace OS {
* @memberof TabContainerTag
*/
set tabbarheight(v: number) {
if (!v) {
return;
}
$(this.refs.bar).attr("data-height", `${v}`);
(this.refs.wrapper as TileLayoutTag).calibrate();
this.observable.trigger("resize", undefined);
}
/**

View File

@ -128,9 +128,7 @@ namespace OS {
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) {
@ -160,10 +158,6 @@ namespace OS {
$(v).css("width", `${csize}px`)
);
}
return this.observable.trigger("hboxchange", {
id: this.aid,
data: { w: avaiWidth, h: avaiheight },
});
}
/**
@ -178,8 +172,6 @@ namespace OS {
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) {
@ -209,11 +201,6 @@ namespace OS {
$(v).css("height", `${csize}px`)
);
}
return this.observable.trigger("vboxchange", {
id: this.aid,
data: { w: avaiwidth, h: avaiheight },
});
}
/**

View File

@ -380,7 +380,7 @@ namespace OS {
.css("flex-direction", "row")
.css("align-items", "center")
.css("white-space", "nowrap");
$(this.refs.itemholder).css("display", "inline-block");
//$(this.refs.itemholder).css("display", "inline-block");
$(this.refs.wrapper).on("click",(e) => {
this.selected = true;
});
@ -390,7 +390,7 @@ namespace OS {
});
$(this.refs.toggle)
.css("display", "inline-block")
//.css("display", "inline-block")
.css("width", "15px")
.css("flex-shrink", 0)
.addClass("afx-tree-view-item")

View File

@ -63,6 +63,14 @@ namespace OS {
*/
private _history: GenericObject<any>;
/**
* This placeholder store the callback for the menu open event
*
* @private
* @type {(el: StackMenuTag) => void}
* @memberof WindowTag
*/
private _onmenuopen: (el: StackMenuTag) => void;
/**
* This placeholder stores the offset of the virtual desktop element
*
@ -79,7 +87,7 @@ namespace OS {
constructor() {
super();
}
/**
* blur overlay: If active the window overlay will be shown
* on inactive (blur event)
@ -96,7 +104,15 @@ namespace OS {
get blur_overlay(): boolean {
return this.hasattr("blur-overlay");
}
/**
* Setter: set menu open event handler
*
* @memberof WindowTag
*/
set onmenuopen(f: (el: StackMenuTag) => void)
{
this._onmenuopen = f;
}
/**
* Init window tag
* - `shown`: false
@ -117,6 +133,7 @@ namespace OS {
this.minimizable = true;
this.resizable = true;
this.apptitle = "Untitled";
this._onmenuopen = undefined;
}
/**
@ -174,6 +191,24 @@ namespace OS {
get height(): number {
return this._height;
}
/**
* Set the application menu content
*
* @memberof WindowTag
*/
set menu(v: GenericObject<any>[])
{
if(!v || v.length == 0)
{
$(this.refs.btnMenu).hide();
}
else
{
(this.refs.stackmenu as StackMenuTag).nodes = v;
$(this.refs.btnMenu).show();
}
}
/**
* Setter: enable/disable window minimizable
@ -262,22 +297,56 @@ namespace OS {
* @memberof WindowTag
*/
protected mount(): void {
const btn_menu = (this.refs.btnMenu as ButtonTag);
const min_btn = (this.refs["minbt"] as ButtonTag);
const max_btn = (this.refs["maxbt"] as ButtonTag);
const close_btn = (this.refs["closebt"] as ButtonTag);
const stackmenu = (this.refs.stackmenu as StackMenuTag);
stackmenu.context = true;
btn_menu.iconclass = "bi bi-list";
min_btn.iconclass = "bi bi-dash";
max_btn.iconclass = "bi bi-stop";
close_btn.iconclass = "bi bi-x";
this.contextmenuHandle = function (e) { };
$(this.refs["minbt"]).on("click", (e) => {
min_btn.onbtclick =(_) => {
return this.observable.trigger("hide", {
id: this.aid,
});
});
$(this.refs["maxbt"]).on("click", (e) => {
};
btn_menu.onbtclick = (e) => {
e.data.stopPropagation();
if($(stackmenu).is(":hidden"))
{
if(this._onmenuopen)
{
this._onmenuopen(stackmenu);
}
else
{
stackmenu.reset();
}
stackmenu.show();
}
else
{
$(stackmenu).hide();
}
};
max_btn.onbtclick = (_) => {
return this.toggle_window();
});
};
$(this.refs["closebt"]).on("click", (e) => {
close_btn.onbtclick = (_) => {
return this.observable.trigger("exit", {
id: this.aid,
});
});
};
stackmenu.onmenuselect = (e) => {
if(!e.data.item.data.nodes)
{
stackmenu.selected = -1;
}
}
const left = ($(this.desktop).width() - this.width) / 2;
const top = ($(this.desktop).height() - this.height) / 2;
$(this)
@ -285,7 +354,7 @@ namespace OS {
.css("left", `${left}px`)
.css("top", `${top}px`)
.css("z-index", 10);
$(this).on("mousedown", (e) => {
$(this).on("pointerdown", (e) => {
if (this._shown) {
return;
}
@ -293,7 +362,6 @@ namespace OS {
id: this.aid,
});
});
//$(this.refs.win_overlay).css("background-color", "red");
$(this.refs["dragger"]).on("dblclick", (e) => {
return this.toggle_window();
});
@ -347,7 +415,26 @@ namespace OS {
h: this.height,
});
$(this).attr("tabindex", 0).css("outline", "none");
return this.observable.trigger("rendered", {
if(OS.mobile)
{
this.toggle_window();
//this.minimizable = false;
this.resizable = false;
}
this.observable.on("desktopresize", (e) => {
if(this._isMaxi)
{
this._isMaxi = false;
this.toggle_window(true);
}
/*else
{
const w = this.width > e.data.width ? e.data.width: this.width;
const h = this.height > e.data.height ? e.data.height: this.height;
this.setsize({ w: w, h: h });
}*/
});
this.observable.trigger("rendered", {
id: this.aid,
});
}
@ -384,12 +471,12 @@ namespace OS {
$(this.refs["dragger"])
.css("user-select", "none")
.css("cursor", "default");
$(this.refs["dragger"]).on("mousedown", (e) => {
e.preventDefault();
$(this.refs.dragger).on("pointerdown", (e) => {
//e.preventDefault();
const offset = $(this).offset();
offset.top = e.clientY - offset.top;
offset.left = e.clientX - offset.left;
$(window).on("mousemove", (e) => {
$(window).on("pointermove", (e) => {
$(this.refs.win_overlay).show();
let left: number, top: number;
if (this._isMaxi) {
@ -415,10 +502,10 @@ namespace OS {
.css("top", `${top}px`)
.css("left", `${left}px`);
});
return $(window).on("mouseup", (e) => {
return $(window).on("pointerup", (e) => {
$(this.refs.win_overlay).hide();
$(window).off("mousemove", null);
return $(window).off("mouseup", null);
$(window).off("pointermove", null);
return $(window).off("pointerup", null);
});
});
}
@ -452,32 +539,32 @@ namespace OS {
}
const mouse_up_hdl = (e) => {
$(this.refs.win_overlay).hide();
$(window).off("mousemove", mouse_move_hdl);
return $(window).off("mouseup", mouse_up_hdl);
$(window).off("pointermove", mouse_move_hdl);
return $(window).off("pointerup", mouse_up_hdl);
}
$(this.refs["grip"]).on("mousedown", (e) => {
$(this.refs["grip"]).on("pointerdown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip;
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
});
$(this.refs.grip_bottom).on("mousedown", (e) => {
$(this.refs.grip_bottom).on("pointerdown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip_bottom;
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
});
$(this.refs.grip_right).on("mousedown", (e) => {
$(this.refs.grip_right).on("pointerdown", (e) => {
e.preventDefault();
offset.top = e.clientY;
offset.left = e.clientX;
target = this.refs.grip_right;
$(window).on("mousemove", mouse_move_hdl);
$(window).on("mouseup", mouse_up_hdl);
$(window).on("pointermove", mouse_move_hdl);
$(window).on("pointerup", mouse_up_hdl);
});
}
/**
@ -488,9 +575,9 @@ namespace OS {
* @returns {void}
* @memberof WindowTag
*/
private toggle_window(): void {
private toggle_window(force?: boolean): void {
let h: number, w: number;
if (!this.resizable) {
if (!this.resizable && !force) {
return;
}
if (this._isMaxi === false) {
@ -500,8 +587,8 @@ namespace OS {
width: $(this).css("width"),
height: $(this).css("height"),
};
w = $(this.desktop).width();
h = $(this.desktop).height();
w = $(this.desktop).width() - 2;
h = $(this.desktop).height() - 2;
$(this).css("top", "0").css("left", "0");
this.setsize({ w, h });
this._isMaxi = true;
@ -538,18 +625,12 @@ namespace OS {
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",
children: [
{
el: "afx-button",
ref: "btnMenu",
},
],
},
{
el: "li",
@ -562,9 +643,38 @@ namespace OS {
},
],
},
{
el: "li",
class: "afx-window-minimize",
children: [
{
el: "afx-button",
ref: "minbt",
}
]
},
{
el: "li",
class: "afx-window-maximize",
children: [
{
el: "afx-button",
ref: "maxbt",
}
]
},
{
el: "li",
class: "afx-window-close",
children: [
{
el: "afx-button",
ref: "closebt",
}
]
},
],
},
{ el: "div", class: "afx-clear" },
{
el: "div",
ref: "yield",
@ -590,6 +700,10 @@ namespace OS {
ref: "win_overlay",
class: "afx-window-overlay",
},
{
el: "afx-stack-menu",
ref: "stackmenu"
}
],
},
];

View File

@ -24,10 +24,10 @@ interface HTMLElement {
* defined on any child of this element will be ignored.
*
* @param {JQuery.MouseEventBase} e a mouse event
* @param {OS.GUI.tag.MenuTag} m The context menu element [[MenuTag]]
* @param {OS.GUI.tag.StackMenuTag} m The context menu element [[StackMenuTag]]
* @memberof HTMLElement
*/
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.MenuTag): void;
contextmenuHandle(e: JQuery.MouseEventBase, m: OS.GUI.tag.StackMenuTag): void;
/**
* Mount the element and all the children on its DOM subtree. This action