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

View File

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

View File

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

View File

@ -19,6 +19,10 @@ interface Array<T> {
namespace OS { namespace OS {
export namespace GUI { export namespace GUI {
export namespace tag { export namespace tag {
/**
* Row item event data type
*/
export type GridRowEventData = TagEventDataType<GridRowTag>;
/** /**
* A grid Row is a simple element that * A grid Row is a simple element that
* contains a group of grid cell * contains a group of grid cell
@ -36,6 +40,15 @@ namespace OS {
*/ */
data: GenericObject<any>[]; 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. *Creates an instance of GridRowTag.
* @memberof GridRowTag * @memberof GridRowTag
@ -44,6 +57,36 @@ namespace OS {
super(); super();
this.refs.yield = this; 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>; 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. * Creates an instance of GridViewTag.
* @memberof GridViewTag * @memberof GridViewTag
@ -428,6 +492,68 @@ namespace OS {
super(); 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. * Init the grid view before mounting.
* Reset all the placeholders to default values * Reset all the placeholders to default values
@ -444,9 +570,13 @@ namespace OS {
this._selectedRow = undefined; this._selectedRow = undefined;
this._rows = []; this._rows = [];
this.resizable = false; this.resizable = false;
this.dragndrop = false;
this._oncellselect = this._onrowselect = this._oncelldbclick = ( this._oncellselect = this._onrowselect = this._oncelldbclick = (
e: TagEventType<CellEventData> e: TagEventType<CellEventData>
): void => {}; ): void => {};
this._ondragndrop = (
e: TagEventType<DnDEventDataType<GridRowTag>>
) => {};
} }
/** /**
@ -507,7 +637,14 @@ namespace OS {
* @memberof GridViewTag * @memberof GridViewTag
*/ */
set cellitem(v: string) { set cellitem(v: string) {
const currci = this.cellitem;
$(this).attr("cellitem", v); $(this).attr("cellitem", v);
if(v != currci)
{
// force render data
$(this.refs.grid).empty();
this.rows = this.rows;
}
} }
get cellitem(): string { get cellitem(): string {
return $(this).attr("cellitem"); return $(this).attr("cellitem");
@ -539,6 +676,12 @@ namespace OS {
element.uify(this.observable); element.uify(this.observable);
element.data = item; element.data = item;
item.domel = element; item.domel = element;
element.oncellselect = (e) => {
if(element.data.sort)
{
this.sort(element.data, element.data.sort);
}
};
i++; i++;
if (this.resizable) { if (this.resizable) {
if (i != v.length) { if (i != v.length) {
@ -604,9 +747,48 @@ namespace OS {
* @memberof GridViewTag * @memberof GridViewTag
*/ */
set rows(rows: GenericObject<any>[][]) { set rows(rows: GenericObject<any>[][]) {
$(this.refs.grid).empty();
this._rows = rows; 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>[][] { get rows(): GenericObject<any>[][] {
return this._rows; return this._rows;
@ -639,7 +821,24 @@ namespace OS {
get resizable(): boolean { get resizable(): boolean {
return this.hasattr("resizable"); 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 * Delete a grid rows
* *
@ -713,6 +912,10 @@ namespace OS {
element.oncelldbclick = (e) => this.cellselect(e, true); element.oncelldbclick = (e) => this.cellselect(e, true);
element.data = cell; element.data = cell;
} }
el.onrowselect = (e) => this.rowselect({
id: el.aid,
data: {item: el}
});
} }
/** /**
@ -751,7 +954,10 @@ namespace OS {
} else { } else {
this.observable.trigger("cellselect", e); this.observable.trigger("cellselect", e);
this._oncellselect(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 * This function triggers the row select event, a cell select
* event will also trigger this event * event will also trigger this event
* *
* @param {TagEventType<CellEventData>} e * @param {TagEventType<GridRowEventData>} e
* @returns {void} * @returns {void}
* @memberof GridViewTag * @memberof GridViewTag
*/ */
private rowselect(e: TagEventType<CellEventData>): void { private rowselect(e: TagEventType<GridRowEventData>): void {
if (!e.data.item) { if (!e.data.item) {
return; return;
} }
@ -774,39 +980,58 @@ namespace OS {
items: [], items: [],
}, },
}; };
const row = ($( const row = e.data.item as GridRowTag;
e.data.item
).parent()[0] as any) as GridRowTag;
if (this.multiselect) { if (this.multiselect) {
if (this.selectedRows.includes(row)) { if (this.selectedRows.includes(row)) {
this.selectedRows.splice( this.selectedRows.splice(
this.selectedRows.indexOf(row), this.selectedRows.indexOf(row),
1 1
); );
$(row).removeClass(); row.selected = false;
return;
} else { } else {
this.selectedRows.push(row); this.selectedRows.push(row);
$(row)
.removeClass()
.addClass("afx-grid-row-selected");
} }
evt.data.items = this.selectedRows; evt.data.items = this.selectedRows;
} else { } else {
if(this.selectedRows.length > 0)
{
for(const item of this.selectedRows)
{
if(item != row)
{
item.selected = false;
}
}
}
if (this.selectedRow === row) { if (this.selectedRow === row) {
return; return;
} }
$(this.selectedRow).removeClass(); if(this.selectedRow)
this._selectedRows = [row]; this.selectedRow.selected = false;
evt.data.item = row;
evt.data.items = [row]; evt.data.items = [row];
$(row).removeClass().addClass("afx-grid-row-selected");
this._selectedRows = [row]; this._selectedRows = [row];
} }
evt.data.item = row;
this._selectedRow = row; this._selectedRow = row;
this._onrowselect(evt); this._onrowselect(evt);
return this.observable.trigger("rowselect", 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 * Check whether the grid has header
* *
@ -904,7 +1129,6 @@ namespace OS {
*/ */
protected mount(): void { protected mount(): void {
$(this).css("overflow", "hidden"); $(this).css("overflow", "hidden");
$(this.refs.grid).css("display", "grid"); $(this.refs.grid).css("display", "grid");
$(this.refs.header).css("display", "grid"); $(this.refs.header).css("display", "grid");
this.observable.on("resize", (e) => this.calibrate()); this.observable.on("resize", (e) => this.calibrate());
@ -912,6 +1136,73 @@ namespace OS {
.css("width", "100%") .css("width", "100%")
.css("overflow-x", "hidden") .css("overflow-x", "hidden")
.css("overflow-y", "auto"); .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(); return this.calibrate();
} }

View File

@ -56,6 +56,7 @@ namespace OS {
this.icon = undefined; this.icon = undefined;
this.iconclass = undefined; this.iconclass = undefined;
this.text = undefined; this.text = undefined;
this.selectable = false;
} }
/** /**
@ -121,6 +122,33 @@ namespace OS {
return this._text; 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 * Lqbel layout definition
* *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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