support dnd and grid sort

This commit is contained in:
DanyLE 2022-03-29 18:25:07 +02:00
parent 079af3b0ce
commit 2cdd8f9a43
12 changed files with 695 additions and 107 deletions

147
d.ts/antos.d.ts vendored
View File

@ -3089,13 +3089,13 @@ declare namespace OS {
*/
private _onfileopen;
/**
* Reference to the currently selected file meta-data
* Reference to the all selected files meta-datas
*
* @private
* @type {API.FileInfoType}
* @type {API.FileInfoType[]}
* @memberof FileViewTag
*/
private _selectedFile;
private _selectedFiles;
/**
* Data placeholder of the current working directory
*
@ -3116,7 +3116,7 @@ declare namespace OS {
* Header definition of the widget grid view
*
* @private
* @type {(GenericObject<string | number>[])}
* @type {(GenericObject<any>[])}
* @memberof FileViewTag
*/
private _header;
@ -3227,6 +3227,19 @@ declare namespace OS {
*/
set showhidden(v: boolean);
get showhidden(): boolean;
/**
* Setter:
*
* Allow multiple selection on file view
*
* Getter:
*
* Check whether the multiselection is actived
*
* @memberof FileViewTag
*/
set multiselect(v: boolean);
get multiselect(): boolean;
/**
* Get the current selected file
*
@ -3235,6 +3248,14 @@ declare namespace OS {
* @memberof FileViewTag
*/
get selectedFile(): API.FileInfoType;
/**
* Get all selected files
*
* @readonly
* @type {API.FileInfoType[]}
* @memberof FileViewTag
*/
get selectedFiles(): API.FileInfoType[];
/**
* Setter:
*
@ -3266,7 +3287,7 @@ declare namespace OS {
*
* @memberof FileViewTag
*/
set ondragndrop(v: TagEventCallback<DnDEventDataType<TreeViewTag | ListViewItemTag>>);
set ondragndrop(v: TagEventCallback<DnDEventDataType<TreeViewTag | ListViewItemTag | GridCellPrototype>>);
/**
* Sort file by its type
*
@ -3593,7 +3614,7 @@ declare namespace OS {
* @type {T}
* @memberof DnDEventDataType
*/
from: T;
from: T[];
/**
* Reference to the target DOM element
*
@ -4148,7 +4169,7 @@ declare namespace OS {
* drag and drop on the list
*
* @private
* @type {{ from: ListViewItemTag; to: ListViewItemTag }}
* @type {{ from: ListViewItemTag[]; to: ListViewItemTag }}
* @memberof ListViewTag
*/
private _dnd;
@ -5056,6 +5077,15 @@ declare namespace OS {
*/
set text(v: string | FormattedString);
get text(): string | FormattedString;
/**
* Setter: Turn on/off text selection
*
* Getter: Check whether the label is selectable
*
* @memberof LabelTag
*/
set selectable(v: boolean);
get swon(): boolean;
/**
* Lqbel layout definition
*
@ -5275,6 +5305,10 @@ interface Array<T> {
declare namespace OS {
namespace GUI {
namespace tag {
/**
* Row item event data type
*/
type GridRowEventData = TagEventDataType<GridRowTag>;
/**
* A grid Row is a simple element that
* contains a group of grid cell
@ -5291,11 +5325,34 @@ declare namespace OS {
* @memberof GridRowTag
*/
data: GenericObject<any>[];
/**
* placeholder for the row select event callback
*
* @private
* @type {TagEventCallback<GridRowEventData>}
* @memberof ListViewItemTag
*/
private _onselect;
/**
*Creates an instance of GridRowTag.
* @memberof GridRowTag
*/
constructor();
/**
* Set item select event handle
*
* @memberof ListViewItemTag
*/
set onrowselect(v: TagEventCallback<GridRowEventData>);
/**
* Setter: select/unselect the current item
*
* Getter: Check whether the current item is selected
*
* @memberof ListViewItemTag
*/
set selected(v: boolean);
get selected(): boolean;
/**
* Mount the tag, do nothing
*
@ -5576,11 +5633,64 @@ declare namespace OS {
* @memberof GridViewTag
*/
private _oncelldbclick;
/**
* Event data passing between mouse event when performing
* drag and drop on the list
*
* @private
* @type {{ from: GridRowTag[]; to: GridRowTag }}
* @memberof GridViewTag
*/
private _dnd;
/**
* placeholder of list drag and drop event handle
*
* @private
* @type {TagEventCallback<DnDEventDataType<GridRowTag>>}
* @memberof GridViewTag
*/
private _ondragndrop;
/**
* Creates an instance of GridViewTag.
* @memberof GridViewTag
*/
constructor();
/**
* Set drag and drop event handle
*
* @memberof GridViewTag
*/
set ondragndrop(v: TagEventCallback<DnDEventDataType<GridRowTag>>);
/**
* Setter: Enable/disable drag and drop event in the list
*
* Getter: Check whether the drag and drop event is enabled
*
* @memberof GridViewTag
*/
set dragndrop(v: boolean);
get dragndrop(): boolean;
/**
* placeholder of drag and drop mouse down event handle
*
* @private
* @memberof GridViewTag
*/
private _onmousedown;
/**
* placeholder of drag and drop mouse up event handle
*
* @private
* @memberof GridViewTag
*/
private _onmouseup;
/**
* placeholder of drag and drop mouse move event handle
*
* @private
* @memberof GridViewTag
*/
private _onmousemove;
/**
* Init the grid view before mounting.
* Reset all the placeholders to default values
@ -5694,6 +5804,16 @@ declare namespace OS {
*/
set resizable(v: boolean);
get resizable(): boolean;
/**
* Sort the grid using a sort function
*
* @param {context: any} context of the executed function
* @param {(a:GenericObject<any>[], b:GenericObject<any>[]) => boolean} a sort function that compares two rows data
* * @param {index: number} current header index
* @returns {void}
* @memberof GridViewTag
*/
sort(context: any, fn: (a: GenericObject<any>[], b: GenericObject<any>[], index?: number) => number): void;
/**
* Delete a grid rows
*
@ -5732,11 +5852,18 @@ declare namespace OS {
* This function triggers the row select event, a cell select
* event will also trigger this event
*
* @param {TagEventType<CellEventData>} e
* @param {TagEventType<GridRowEventData>} e
* @returns {void}
* @memberof GridViewTag
*/
private rowselect;
/**
* Unselect all the selected rows in the grid
*
* @returns {void}
* @memberof GridViewTag
*/
unselect(): void;
/**
* Check whether the grid has header
*
@ -6584,7 +6711,7 @@ declare namespace OS {
* Private data object passing between dragndrop mouse event
*
* @private
* @type {{ from: TreeViewTag; to: TreeViewTag }}
* @type {{ from: TreeViewTag[]; to: TreeViewTag }}
* @memberof TreeViewTag
*/
private _dnd;
@ -6631,7 +6758,7 @@ declare namespace OS {
* current tree. This function should return a promise on
* a list of [[TreeViewDataType]]
*
* @memberof TreeViewItemPrototype
* @memberof TreeViewTag
*/
fetch: (d: TreeViewItemPrototype) => Promise<TreeViewDataType[]>;
/**

View File

@ -264,7 +264,6 @@ namespace OS {
*/
main(): void {
const win = this.scheme as tag.WindowTag;
$(win).attr("tabindex", 0);
$(win).on('keydown', (e) => {
switch (e.which) {
case 27:
@ -672,7 +671,7 @@ namespace OS {
}
for (let k in this.data) {
const v = this.data[k];
rows.push([{ text: k }, { text: v }]);
rows.push([{ text: k }, { text: v, selectable: true }]);
}
const grid = this.find("grid") as tag.GridViewTag;
grid.header = [
@ -1052,7 +1051,7 @@ namespace OS {
__("Resource not found: {0}", path)
);
}
return (fileview.path = path);
fileview.path = path;
};
if (!this.data || !this.data.root) {
@ -1088,11 +1087,14 @@ namespace OS {
}
};
(this.find("btnOk") as tag.ButtonTag).onbtclick = (_e) => {
const f = fileview.selectedFile;
let f = fileview.selectedFile;
if (!f) {
return this.notify(
__("Please select a file/fofler")
);
const sel = location.selectedItem;
if(!sel)
return this.notify(
__("Please select a file/fofler")
);
f = sel.data as API.FileInfoType;
}
if (
this.data &&

View File

@ -28,13 +28,13 @@ namespace OS {
private _onfileopen: TagEventCallback<API.FileInfoType>;
/**
* Reference to the currently selected file meta-data
* Reference to the all selected files meta-datas
*
* @private
* @type {API.FileInfoType}
* @type {API.FileInfoType[]}
* @memberof FileViewTag
*/
private _selectedFile: API.FileInfoType;
private _selectedFiles: API.FileInfoType[];
/**
* Data placeholder of the current working directory
@ -58,10 +58,10 @@ namespace OS {
* Header definition of the widget grid view
*
* @private
* @type {(GenericObject<string | number>[])}
* @type {(GenericObject<any>[])}
* @memberof FileViewTag
*/
private _header: GenericObject<string | number>[];
private _header: GenericObject<any>[];
/**
* placeholder for the user-specified meta-data fetch function
@ -92,10 +92,37 @@ namespace OS {
this.chdir = true;
this.view = "list";
this._onfileopen = this._onfileselect = (e) => { };
this._selectedFiles = [];
const fn = function(r1, r2, i) {
let t1 = r1[i].text;
let t2 = r2[i].text;
if(!t1 || !t2) return 0;
t1 = t1.toString().toLowerCase();
t2 = t2.toString().toLowerCase();
if(this.__f)
{
this.desc = ! this.desc;
if(t1 < t2) { return -1; }
if(t1 > t2) { return 1; }
}
else
{
this.desc = ! this.desc;
if(t1 > t2) { return -1; }
if(t1 < t2) { return 1; }
}
return 0;
};
this._header = [
{ text: "__(File name)" },
{
text: "__(File name)",
sort: fn
},
{ text: "__(Type)" },
{ text: "__(Size)" },
{
text: "__(Size)",
sort: fn
},
];
}
@ -227,6 +254,28 @@ namespace OS {
return this.hasattr("showhidden");
}
/**
* Setter:
*
* Allow multiple selection on file view
*
* Getter:
*
* Check whether the multiselection is actived
*
* @memberof FileViewTag
*/
set multiselect(v: boolean) {
this.attsw(v, "multiselect");
(this.refs.listview as ListViewTag).multiselect = v;
(this.refs.gridview as GridViewTag).multiselect = v;
}
get multiselect(): boolean {
return this.hasattr("multiselect");
}
/**
* Get the current selected file
*
@ -235,7 +284,21 @@ namespace OS {
* @memberof FileViewTag
*/
get selectedFile(): API.FileInfoType {
return this._selectedFile;
if(this._selectedFiles.length == 0)
return undefined;
return this._selectedFiles[this._selectedFiles.length - 1];
}
/**
* Get all selected files
*
* @readonly
* @type {API.FileInfoType[]}
* @memberof FileViewTag
*/
get selectedFiles(): API.FileInfoType[] {
return this._selectedFiles;
}
/**
@ -286,7 +349,7 @@ namespace OS {
*
* @memberof FileViewTag
*/
set data(v: API.FileInfoType[]) {
set data(v: API.FileInfoType[]) {
if (!v) {
return;
}
@ -305,11 +368,21 @@ namespace OS {
*/
set ondragndrop(
v: TagEventCallback<
DnDEventDataType<TreeViewTag | ListViewItemTag>
DnDEventDataType<TreeViewTag | ListViewItemTag | GridCellPrototype>
>
) {
(this.refs.treeview as TreeViewTag).ondragndrop = v;
(this.refs.listview as ListViewTag).ondragndrop = v;
(this.refs.gridview as GridViewTag).ondragndrop = (e) => {
const evt = {
id: this.aid,
data: {
from: e.data.from.map(x => x.data[0].domel),
to: e.data.to.data[0].domel
}
};
v(evt);
};
}
/**
@ -514,7 +587,7 @@ namespace OS {
$(this.refs.listview).hide();
$(this.refs.gridview).hide();
$(this.refs.treecontainer).hide();
this._selectedFile = undefined;
this._selectedFiles = [];
switch (this.view) {
case "icon":
$(this.refs.listview).show();
@ -552,7 +625,6 @@ namespace OS {
);
}
const evt = { id: this.aid, data: e };
this._selectedFile = e;
this._onfileselect(evt);
this.observable.trigger("fileselect", evt);
}
@ -612,18 +684,22 @@ namespace OS {
grid.header = this._header;
tree.dragndrop = true;
list.dragndrop = true;
grid.dragndrop = true;
// even handles
list.onlistselect = (e) => {
this.fileselect(e.data.item.data as API.FileInfoType);
this._selectedFiles = e.data.items.map( x => x.data as API.FileInfoType);
};
grid.onrowselect = (e) => {
this.fileselect(
($(e.data.item).children()[0] as GridCellPrototype)
.data as API.FileInfoType
);
this._selectedFiles = e.data.items.map( x => ($(x).children()[0] as GridCellPrototype).data as API.FileInfoType);
};
tree.ontreeselect = (e) => {
this.fileselect(e.data.item.data as API.FileInfoType);
this._selectedFiles = [e.data.item.data as API.FileInfoType];
};
// dblclick
list.onlistdbclick = (e) => {

View File

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

View File

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

View File

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

View File

@ -586,10 +586,10 @@ namespace OS {
* Private data object passing between dragndrop mouse event
*
* @private
* @type {{ from: TreeViewTag; to: TreeViewTag }}
* @type {{ from: TreeViewTag[]; to: TreeViewTag }}
* @memberof TreeViewTag
*/
private _dnd: { from: TreeViewTag; to: TreeViewTag };
private _dnd: { from: TreeViewTag[]; to: TreeViewTag };
/**
* Reference to parent tree of the current tree.
@ -638,7 +638,7 @@ namespace OS {
* current tree. This function should return a promise on
* a list of [[TreeViewDataType]]
*
* @memberof TreeViewItemPrototype
* @memberof TreeViewTag
*/
fetch: (
d: TreeViewItemPrototype
@ -920,7 +920,7 @@ namespace OS {
*/
protected mount(): void {
this._dnd = {
from: undefined,
from: [],
to: undefined,
};
this._treemousedown = (e) => {
@ -932,7 +932,7 @@ namespace OS {
if (el === this) {
return;
}
this._dnd.from = el;
this._dnd.from = [el];
this._dnd.to = undefined;
$(window).on("mouseup", this._treemouseup);
return $(window).on("mousemove", this._treemousemove);
@ -951,8 +951,8 @@ namespace OS {
el = el.parent;
}
if (
el === this._dnd.from ||
el === this._dnd.from.parent
el === this._dnd.from[0] ||
el === this._dnd.from[0].parent
) {
return;
}
@ -962,7 +962,7 @@ namespace OS {
data: this._dnd,
});
this._dnd = {
from: undefined,
from: [],
to: undefined,
};
};
@ -974,7 +974,7 @@ namespace OS {
if (!this._dnd.from) {
return;
}
const data = this._dnd.from.data;
const data = this._dnd.from[0].data;
const $label = $("#systooltip");
const top = e.clientY + 5;
const left = e.clientX + 5;

View File

@ -306,6 +306,7 @@ namespace OS {
.removeClass("unactive");
this._shown = true;
$(this.refs.win_overlay).hide();
$(this).trigger("focus");
});
this.observable.on("blur", () => {
@ -336,6 +337,7 @@ namespace OS {
w: this.width,
h: this.height,
});
$(this).attr("tabindex", 0).css("outline", "none");
return this.observable.trigger("rendered", {
id: this.aid,
});

View File

@ -233,7 +233,7 @@ namespace OS {
* @type {T}
* @memberof DnDEventDataType
*/
from: T;
from: T[];
/**
* Reference to the target DOM element

View File

@ -21,7 +21,7 @@ namespace OS {
interface FilesClipboardType {
cut: boolean;
file: API.VFS.BaseFileHandle;
files: API.VFS.BaseFileHandle[];
}
interface FilesViewType {
icon: boolean;
@ -228,43 +228,54 @@ namespace OS {
};
this.vfs_event_flag = true;
this.view.ondragndrop = (e) => {
this.view.ondragndrop = async (e) => {
if (!e) {
return;
}
const src = e.data.from.data;
const src = e.data.from;
const des = e.data.to.data;
if (des.type === "file") {
return;
}
const file = src.path.asFileHandle();
// ask to confirm
const r = await this.ask({
title: __("Move files"),
text: __("Move selected file to {0}?", des.text)
});
if(!r)
{
return;
}
// disable the vfs event on
// we update it manually
this.vfs_event_flag = false;
return file
.move(`${des.path}/${file.basename}`)
.then(() => {
if (this.view.view === "icon") {
this.view.path = this.view.path;
} else {
this.view.update(file.parent().path);
this.view.update(des.path);
}
//reenable the vfs event
return (this.vfs_event_flag = true);
})
.catch((e: Error) => {
// reenable the vfs event
this.vfs_event_flag = true;
return this.error(
const promises = [];
for(const item of src)
{
let file = item.data.path.asFileHandle();
promises.push(
file.move(`${des.path}/${file.basename}`));
}
try{
await Promise.all(promises);
if (this.view.view === "tree") {
this.view.update(src[0].data.path.asFileHandle().parent().path);
this.view.update(des.path);
} else {
this.view.path = this.view.path;
}
}
catch(error)
{
this.error(
__(
"Unable to move: {0} -> {1}",
src.path,
"Unable to move files to: {0}",
des.path
),
e
error
);
});
}
this.vfs_event_flag = true;
};
// application setting
@ -355,6 +366,23 @@ namespace OS {
this.view.view = "list";
this.viewType.list = true;
};
// enable or disable multi-select by CTRL key
$(this.scheme).on("keydown", (evt)=>{
if(evt.ctrlKey && evt.which == 17)
{
this.view.multiselect = true;
}
else
{
this.view.multiselect = false;
}
});
$(this.scheme).on("keyup", (evt)=>{
if(!evt.ctrlKey)
{
this.view.multiselect = false;
}
});
this.view.path = this.currdir.path;
}
@ -585,20 +613,22 @@ namespace OS {
title: "__(Delete)",
iconclass: "fa fa-question-circle",
text: __(
"Do you really want to delete: {0}?",
file.filename
"Do you really want to delete selected files?"
),
}).then(async (d) => {
if (!d) {
return;
}
const promises = [];
for(const f of this.view.selectedFiles)
{
promises.push(f.path.asFileHandle().remove());
}
try {
return file.path
.asFileHandle()
.remove();
await Promise.all(promises);
}
catch (e) {
return this.error(__("Fail to delete: {0}", file.path), e);
return this.error(__("Fail to delete selected files"), e);
}
});
break;
@ -609,9 +639,9 @@ namespace OS {
}
this.clipboard = {
cut: true,
file: file.path.asFileHandle(),
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
};
return this.notify(__("File {0} cut", file.filename));
return this.notify(__("{0} files cut", this.clipboard.files.length));
case `${this.name}-copy`:
if (!file) {
@ -619,10 +649,10 @@ namespace OS {
}
this.clipboard = {
cut: false,
file: file.path.asFileHandle(),
files: this.view.selectedFiles.map(x => x.path.asFileHandle()),
};
return this.notify(
__("File {0} copied", file.filename)
__("{0} files copied", this.clipboard.files.length)
);
case `${this.name}-paste`:
@ -630,29 +660,33 @@ namespace OS {
return;
}
if (this.clipboard.cut) {
this.clipboard.file
.move(
`${this.currdir.path}/${this.clipboard.file.basename}`
)
const promises = [];
for(const file of this.clipboard.files)
{
promises.push(file.move(
`${this.currdir.path}/${file.basename}`
));
}
Promise.all(promises)
.then((r) => {
return (this.clipboard = undefined);
})
.catch((e) => {
return this.error(
__(
"Fail to paste: {0}",
this.clipboard.file.path
"Fail to paste to: {0}",
this.currdir.path
),
e
);
});
} else {
API.VFS.copy([this.clipboard.file.path],this.currdir.path)
API.VFS.copy(this.clipboard.files.map(x => x.path),this.currdir.path)
.then(() => {
return (this.clipboard = undefined);
})
.catch((e) => {
return this.error(__("Fail to paste: {0}", this.clipboard.file.path), e);
return this.error(__("Fail to paste to: {0}", this.currdir.path), e);
});
}
break;

View File

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

View File

@ -5,5 +5,4 @@ afx-label i.label-text{
font-weight: normal;
font-style: normal;
margin-left: 3px;
user-select:text;
}