Skip to content

Commit 854af6e

Browse files
authored
✨ [Frontend] Drag&Drop: Projects and Folders (#6957)
1 parent a7d1e3a commit 854af6e

38 files changed

+777
-324
lines changed

services/static-webserver/client/source/class/osparc/auth/ui/LoginView.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ qx.Class.define("osparc.auth.ui.LoginView", {
140140
`;
141141
}
142142
const disclaimer = osparc.announcement.AnnouncementUIFactory.createLoginAnnouncement(this.tr("Disclaimer"), text);
143+
disclaimer.getChildren()[0].setFont("text-14"); // title
144+
disclaimer.getChildren()[1].setFont("text-12"); // description
143145
this.add(disclaimer);
144146

145147
this.add(new qx.ui.core.Spacer(), {

services/static-webserver/client/source/class/osparc/dashboard/CardBase.js

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
************************************************************************ */
1717

1818
qx.Class.define("osparc.dashboard.CardBase", {
19-
extend: qx.ui.form.ToggleButton,
19+
extend: qx.ui.core.Widget,
2020
implement: [qx.ui.form.IModel, osparc.filter.IFilterable],
2121
include: [qx.ui.form.MModelProperty, osparc.filter.MFilterable],
2222
type: "abstract",
@@ -33,6 +33,8 @@ qx.Class.define("osparc.dashboard.CardBase", {
3333
"pointerout",
3434
"focusout"
3535
].forEach(e => this.addListener(e, this._onPointerOut, this));
36+
37+
this.addListener("changeSelected", this.__evalSelectedButton, this);
3638
},
3739

3840
events: {
@@ -237,6 +239,20 @@ qx.Class.define("osparc.dashboard.CardBase", {
237239
nullable: true
238240
},
239241

242+
selected: {
243+
check: "Boolean",
244+
init: false,
245+
nullable: false,
246+
event: "changeSelected",
247+
},
248+
249+
icon: {
250+
check: "String",
251+
init: null,
252+
nullable: true,
253+
apply: "_applyIcon",
254+
},
255+
240256
resourceData: {
241257
check: "Object",
242258
nullable: false,
@@ -246,7 +262,8 @@ qx.Class.define("osparc.dashboard.CardBase", {
246262

247263
resourceType: {
248264
check: ["study", "template", "service"],
249-
nullable: false,
265+
init: true,
266+
nullable: true,
250267
event: "changeResourceType"
251268
},
252269

@@ -365,7 +382,7 @@ qx.Class.define("osparc.dashboard.CardBase", {
365382
check: "Boolean",
366383
init: false,
367384
nullable: false,
368-
apply: "_applyMultiSelectionMode"
385+
apply: "__applyMultiSelectionMode"
369386
},
370387

371388
fetching: {
@@ -444,6 +461,35 @@ qx.Class.define("osparc.dashboard.CardBase", {
444461
});
445462
},
446463

464+
__applyMultiSelectionMode: function(value) {
465+
if (!value) {
466+
this.setSelected(false);
467+
}
468+
this.__evalSelectedButton();
469+
},
470+
471+
__evalSelectedButton: function() {
472+
if (
473+
this.hasChildControl("menu-button") &&
474+
this.hasChildControl("tick-selected") &&
475+
this.hasChildControl("tick-unselected")
476+
) {
477+
const menuButton = this.getChildControl("menu-button");
478+
const tick = this.getChildControl("tick-selected");
479+
const untick = this.getChildControl("tick-unselected");
480+
if (this.isResourceType("study") && this.isMultiSelectionMode()) {
481+
const selected = this.getSelected();
482+
menuButton.setVisibility("excluded");
483+
tick.setVisibility(selected ? "visible" : "excluded");
484+
untick.setVisibility(selected ? "excluded" : "visible");
485+
} else {
486+
menuButton.setVisibility("visible");
487+
tick.setVisibility("excluded");
488+
untick.setVisibility("excluded");
489+
}
490+
}
491+
},
492+
447493
__applyUuid: function(value, old) {
448494
const resourceType = this.getResourceType() || "study";
449495
osparc.utils.Utils.setIdToWidget(this, resourceType + "BrowserListItem_" + value);
Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
*/
77

88
/**
9-
* Container for GridButtonItems and ListButtonItems (ToggleButtons), with some convenient methods.
9+
* Container for GridButtons and ListButtons (CardBase, FolderButtonBase and WorkspaceButtonBase), with some convenient methods.
1010
*/
11-
qx.Class.define("osparc.dashboard.ToggleButtonContainer", {
11+
qx.Class.define("osparc.dashboard.CardContainer", {
1212
extend: qx.ui.container.Composite,
1313

1414
construct: function() {
@@ -22,28 +22,38 @@ qx.Class.define("osparc.dashboard.ToggleButtonContainer", {
2222
"changeVisibility": "qx.event.type.Data"
2323
},
2424

25+
statics: {
26+
isValidCard: function(widget) {
27+
return (
28+
widget instanceof osparc.dashboard.CardBase ||
29+
widget instanceof osparc.dashboard.FolderButtonBase ||
30+
widget instanceof osparc.dashboard.WorkspaceButtonBase
31+
);
32+
},
33+
},
34+
2535
members: {
2636
__lastSelectedIdx: null,
2737

2838
// overridden
2939
add: function(child, options) {
30-
if (child instanceof qx.ui.form.ToggleButton) {
40+
if (this.self().isValidCard(child)) {
3141
if (osparc.dashboard.ResourceContainerManager.cardExists(this, child)) {
3242
return;
3343
}
3444
this.base(arguments, child, options);
35-
child.addListener("changeValue", () => this.fireDataEvent("changeSelection", this.getSelection()), this);
45+
child.addListener("changeSelected", () => this.fireDataEvent("changeSelection", this.getSelection()), this);
3646
child.addListener("changeVisibility", () => this.fireDataEvent("changeVisibility", this.__getVisibles()), this);
3747
} else {
38-
console.error("ToggleButtonContainer only allows ToggleButton as its children.");
48+
console.error("CardContainer only allows CardBase as its children.");
3949
}
4050
},
4151

4252
/**
4353
* Resets the selection so no toggle button is checked.
4454
*/
4555
resetSelection: function() {
46-
this.getChildren().map(button => button.setValue(false));
56+
this.getChildren().map(button => button.setSelected(false));
4757
this.__lastSelectedIdx = null;
4858
this.fireDataEvent("changeSelection", this.getSelection());
4959
},
@@ -52,7 +62,7 @@ qx.Class.define("osparc.dashboard.ToggleButtonContainer", {
5262
* Returns an array that contains all buttons that are checked.
5363
*/
5464
getSelection: function() {
55-
return this.getChildren().filter(button => button.getValue());
65+
return this.getChildren().filter(button => button.getSelected());
5666
},
5767

5868
/**
@@ -63,18 +73,18 @@ qx.Class.define("osparc.dashboard.ToggleButtonContainer", {
6373
},
6474

6575
/**
66-
* Sets the given button's value to true (checks it) and unchecks all other buttons. If the given button is not present,
67-
* every button in the container will get a false value (unchecked).
68-
* @param {qx.ui.form.ToggleButton} child Button that will be checked
76+
* Sets the given button's select prop to true (checks it) and unchecks all other buttons. If the given button is not present,
77+
* every button in the container will get a unselected (unchecked).
78+
* @param {qx.ui.form.CardBase} child Button that will be checked
6979
*/
7080
selectOne: function(child) {
71-
this.getChildren().map(button => button.setValue(button === child));
81+
this.getChildren().map(button => button.setSelected(button === child));
7282
this.setLastSelectedIndex(this.getIndex(child));
7383
},
7484

7585
/**
7686
* Gets the index in the container of the given button.
77-
* @param {qx.ui.form.ToggleButton} child Button that will be checked
87+
* @param {qx.ui.form.CardBase} child Button that will be checked
7888
*/
7989
getIndex: function(child) {
8090
return this.getChildren().findIndex(button => button === child);
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
/* ************************************************************************
2+
3+
osparc - the simcore frontend
4+
5+
https://osparc.io
6+
7+
Copyright:
8+
2024 IT'IS Foundation, https://itis.swiss
9+
10+
License:
11+
MIT: https://opensource.org/licenses/MIT
12+
13+
Authors:
14+
* Odei Maiz (odeimaiz)
15+
16+
************************************************************************ */
17+
18+
qx.Class.define("osparc.dashboard.DragDropHelpers", {
19+
type: "static",
20+
21+
statics: {
22+
moveStudy: {
23+
dragStart: function(event, studyItem, studyDataOrigin) {
24+
event.addAction("move");
25+
event.addType("osparc-moveStudy");
26+
event.addData("osparc-moveStudy", {
27+
"studyDataOrigin": studyDataOrigin,
28+
});
29+
30+
// init drag indicator
31+
const dragWidget = osparc.dashboard.DragWidget.getInstance();
32+
dragWidget.getChildControl("dragged-resource").set({
33+
label: studyDataOrigin["name"],
34+
icon: "@FontAwesome5Solid/file/16",
35+
});
36+
dragWidget.start();
37+
38+
// make it semi transparent while being dragged
39+
studyItem.setOpacity(0.2);
40+
},
41+
42+
dragOver: function(event, folderItem, workspaceDestId) {
43+
let compatible = false;
44+
const studyDataOrigin = event.getData("osparc-moveStudy")["studyDataOrigin"];
45+
const workspaceIdOrigin = studyDataOrigin["workspaceId"];
46+
const workspaceOrigin = osparc.store.Workspaces.getInstance().getWorkspace(workspaceIdOrigin);
47+
const workspaceDest = osparc.store.Workspaces.getInstance().getWorkspace(workspaceDestId);
48+
// Compatibility checks:
49+
// - Drag over "Shared Workspaces" (0)
50+
// - No
51+
// - My Workspace -> My Workspace (1)
52+
// - Yes
53+
// - My Workspace -> Shared Workspace (2)
54+
// - Delete on Study
55+
// - Write on dest Workspace
56+
// - Shared Workspace -> My Workspace (3)
57+
// - Delete on origin Workspace
58+
// - Shared Workspace -> Shared Workspace (4)
59+
// - Delete on origin Workspace
60+
// - Write on dest Workspace
61+
if (workspaceDestId === -1) { // (0)
62+
compatible = false;
63+
} else if (workspaceIdOrigin === null && workspaceDestId === null) { // (1)
64+
compatible = true;
65+
} else if (workspaceIdOrigin === null && workspaceDest) { // (2)
66+
compatible = osparc.data.model.Study.canIDelete(studyDataOrigin["accessRights"]) && workspaceDest.getMyAccessRights()["write"];
67+
} else if (workspaceOrigin && workspaceDestId === null) { // (3)
68+
compatible = workspaceOrigin.getMyAccessRights()["delete"];
69+
} else if (workspaceOrigin && workspaceDest) { // (4)
70+
compatible = workspaceOrigin.getMyAccessRights()["delete"] && workspaceDest.getMyAccessRights()["write"];
71+
}
72+
73+
if (!compatible) {
74+
// do not allow
75+
event.preventDefault();
76+
}
77+
78+
const dragWidget = osparc.dashboard.DragWidget.getInstance();
79+
dragWidget.setDropAllowed(compatible);
80+
81+
folderItem.getChildControl("icon").setTextColor(compatible ? "strong-main" : "text");
82+
},
83+
84+
drop: function(event, folderItem, destWorkspaceId, destFolderId) {
85+
const studyData = event.getData("osparc-moveStudy")["studyDataOrigin"];
86+
const studyToFolderData = {
87+
studyData,
88+
destWorkspaceId,
89+
destFolderId,
90+
};
91+
folderItem.getChildControl("icon").resetTextColor();
92+
return studyToFolderData;
93+
},
94+
},
95+
96+
moveFolder: {
97+
dragStart: function(event, folderItem, folderOrigin) {
98+
event.addAction("move");
99+
event.addType("osparc-moveFolder");
100+
event.addData("osparc-moveFolder", {
101+
"folderOrigin": folderOrigin,
102+
});
103+
104+
// init drag indicator
105+
const dragWidget = osparc.dashboard.DragWidget.getInstance();
106+
dragWidget.getChildControl("dragged-resource").set({
107+
label: folderOrigin.getName(),
108+
icon: "@FontAwesome5Solid/folder/16",
109+
});
110+
dragWidget.start();
111+
112+
// make it semi transparent while being dragged
113+
folderItem.setOpacity(0.2);
114+
},
115+
116+
dragOver: function(event, folderItem, workspaceDestId, folderDestId) {
117+
let compatible = false;
118+
const folderOrigin = event.getData("osparc-moveFolder")["folderOrigin"];
119+
const workspaceIdOrigin = folderOrigin.getWorkspaceId();
120+
const workspaceOrigin = osparc.store.Workspaces.getInstance().getWorkspace(workspaceIdOrigin);
121+
const workspaceDest = osparc.store.Workspaces.getInstance().getWorkspace(workspaceDestId);
122+
// Compatibility checks:
123+
// - Drag over "Shared Workspaces" (0)
124+
// - No
125+
// - My Workspace -> My Workspace (1)
126+
// - Yes
127+
// - My Workspace -> Shared Workspace (2)
128+
// - ~~Delete on Study~~
129+
// - Write on dest Workspace
130+
// - Shared Workspace -> My Workspace (3)
131+
// - Delete on origin Workspace
132+
// - Shared Workspace -> Shared Workspace (4)
133+
// - Delete on origin Workspace
134+
// - Write on dest Workspace
135+
if (workspaceDestId === -1) { // (0)
136+
compatible = false;
137+
} else if (folderOrigin.getFolderId() === folderDestId) {
138+
compatible = false;
139+
} else if (workspaceIdOrigin === null && workspaceDestId === null) { // (1)
140+
compatible = true;
141+
} else if (workspaceIdOrigin === null && workspaceDest) { // (2)
142+
compatible = workspaceDest.getMyAccessRights()["write"];
143+
} else if (workspaceOrigin && workspaceDestId === null) { // (3)
144+
compatible = workspaceOrigin.getMyAccessRights()["delete"];
145+
} else if (workspaceOrigin && workspaceDest) { // (4)
146+
compatible = workspaceOrigin.getMyAccessRights()["delete"] && workspaceDest.getMyAccessRights()["write"];
147+
}
148+
149+
if (!compatible) {
150+
// do not allow
151+
event.preventDefault();
152+
}
153+
154+
const dragWidget = osparc.dashboard.DragWidget.getInstance();
155+
dragWidget.setDropAllowed(compatible);
156+
157+
folderItem.getChildControl("icon").setTextColor(compatible ? "strong-main" : "text");
158+
},
159+
160+
drop: function(event, folderItem, destWorkspaceId, destFolderId) {
161+
const folderOrigin = event.getData("osparc-moveFolder")["folderOrigin"];
162+
const folderToFolderData = {
163+
folderId: folderOrigin.getFolderId(),
164+
destWorkspaceId,
165+
destFolderId,
166+
};
167+
folderItem.getChildControl("icon").resetTextColor();
168+
return folderToFolderData;
169+
},
170+
},
171+
172+
dragLeave: function(item) {
173+
const dragWidget = osparc.dashboard.DragWidget.getInstance();
174+
dragWidget.setDropAllowed(false);
175+
176+
item.getChildControl("icon").resetTextColor();
177+
},
178+
179+
dragEnd: function(draggedItem) {
180+
// bring back opacity after drag
181+
draggedItem.setOpacity(1);
182+
183+
// hide drag indicator
184+
const dragWidget = osparc.dashboard.DragWidget.getInstance();
185+
dragWidget.end();
186+
}
187+
}
188+
});

0 commit comments

Comments
 (0)