diff --git a/src/GruntFile.js b/src/GruntFile.js
index 1c911043c2..72a5735a7f 100644
--- a/src/GruntFile.js
+++ b/src/GruntFile.js
@@ -130,6 +130,7 @@ var WirecloudFiles = [
'wirecloud/platform/static/js/wirecloud/ui/WiringEditor/Endpoint.js',
'wirecloud/platform/static/js/wirecloud/ui/WiringEditor/KeywordSuggestion.js',
'wirecloud/platform/static/js/wirecloud/ui/WorkspaceView.js',
+ 'wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js',
'wirecloud/platform/static/js/wirecloud/ui/SharingWindowMenu.js',
'wirecloud/platform/static/js/wirecloud/wiring/Endpoint.js',
'wirecloud/platform/static/js/wirecloud/wiring/EndpointTypeError.js',
diff --git a/src/js_tests/wirecloud/bootstrap.js b/src/js_tests/wirecloud/bootstrap.js
index 6fe26ef3c7..6a018bc6a5 100644
--- a/src/js_tests/wirecloud/bootstrap.js
+++ b/src/js_tests/wirecloud/bootstrap.js
@@ -17,6 +17,7 @@ const Wirecloud = {
'wirecloud/component_sidebar': ' ',
'wirecloud/wiring/behaviour_sidebar': '
',
'wirecloud/wiring/footer': ' ',
+ 'wirecloud/workspace/empty_tab_message': '
',
'wirecloud/workspace/sharing_user': '
',
'wirecloud/workspace/visibility_option': '
'
}
diff --git a/src/js_tests/wirecloud/ui/WorkspaceTabViewSpec.js b/src/js_tests/wirecloud/ui/WorkspaceTabViewSpec.js
new file mode 100644
index 0000000000..2256c98f58
--- /dev/null
+++ b/src/js_tests/wirecloud/ui/WorkspaceTabViewSpec.js
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L.
+ *
+ * This file is part of Wirecloud Platform.
+ *
+ * Wirecloud Platform is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * Wirecloud is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
+ * License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with Wirecloud Platform. If not, see
+ * .
+ *
+ */
+
+/* globals StyledElements, Wirecloud */
+
+
+(function (ns, utils) {
+
+ "use strict";
+
+ const callEventListener = function callEventListener(instance, event) {
+ var largs = Array.prototype.slice.call(arguments, 2);
+ largs.unshift(instance);
+ instance.addEventListener.calls.allArgs().some(function (args) {
+ if (args[0] === event) {
+ args[1].apply(instance, largs);
+ return true;
+ }
+ });
+ };
+
+ const create_tab = function create_tab(options) {
+ return utils.merge({
+ addEventListener: jasmine.createSpy("addEventListener"),
+ createWidget: jasmine.createSpy("createWidget"),
+ id: "8",
+ preferences: {
+ get: jasmine.createSpy("get"),
+ addEventListener: jasmine.createSpy("addEventListener")
+ },
+ title: "Tab Title",
+ widgets: []
+ }, options);
+ };
+
+ const create_workspace = function create_workspace(options) {
+ let workspace = utils.merge({
+ id: "1",
+ owner: "user",
+ name: "empty",
+ title: "Dashboard Title",
+ tabs: [
+ {
+ id: "8",
+ widgets: [],
+ name: "tab",
+ title: "Tab",
+ initial: true
+ }
+ ],
+ operators: [],
+ preferences: {},
+ createTab: jasmine.createSpy("createTab"),
+ isAllowed: jasmine.createSpy("isAllowed").and.returnValue(false)
+ }, options);
+
+ workspace.addEventListener = jasmine.createSpy("addEventListener");
+ workspace.removeEventListener = jasmine.createSpy("removeEventListener");
+
+ return {
+ buildAddWidgetButton: jasmine.createSpy("buildAddWidgetButton").and.returnValue(),
+ model: workspace
+ };
+ };
+
+ describe("WorkspaceTabView", () => {
+
+ const notebook = new StyledElements.Notebook();
+
+ beforeAll(() => {
+ // TODO
+ ns.WidgetView = jasmine.createSpy("WidgetView").and.callFake(function (tab, options) {
+ this.id = options.id;
+ this.load = jasmine.createSpy("load");
+ });
+ ns.WorkspaceTabViewDragboard = jasmine.createSpy("WorkspaceTabViewDragboard").and.callFake(function () {
+ this.freeLayout = {
+ adaptHeight: jasmine.createSpy("adaptHeight").and.returnValue({inLU: 0}),
+ adaptWidth: jasmine.createSpy("adaptWidth").and.returnValue({inLU: 0}),
+ adaptColumnOffset: jasmine.createSpy("adaptColumnOffset").and.returnValue({inLU: 0}),
+ adaptRowOffset: jasmine.createSpy("adaptRowOffset").and.returnValue({inLU: 0})
+ };
+ this.baseLayout = {
+ adaptHeight: jasmine.createSpy("adaptHeight").and.returnValue({inLU: 0}),
+ adaptWidth: jasmine.createSpy("adaptWidth").and.returnValue({inLU: 0}),
+ adaptColumnOffset: jasmine.createSpy("adaptColumnOffset").and.returnValue({inLU: 0}),
+ adaptRowOffset: jasmine.createSpy("adaptRowOffset").and.returnValue({inLU: 0})
+ };
+ this.paint = jasmine.createSpy("paint");
+ this._notifyWindowResizeEvent = jasmine.createSpy("_notifyWindowResizeEvent");
+ this._updateBaseLayout = jasmine.createSpy("_updateBaseLayout");
+ });
+ ns.WorkspaceTabViewMenuItems = jasmine.createSpy("WorkspaceTabViewMenuItems");
+ utils.inherit(ns.WorkspaceTabViewMenuItems, StyledElements.DynamicMenuItems);
+ Wirecloud.TutorialCatalogue = {
+ buildTutorialReferences: jasmine.createSpy("buildTutorialReferences")
+ };
+ });
+
+ afterAll(() => {
+ // TODO
+ ns.WidgetView = null;
+ ns.WorkspaceTabViewDragboard = null;
+ Wirecloud.TutorialCatalogue = null;
+ });
+
+ describe("new WorkspaceTabView(id, notebook, options)", () => {
+
+ /*
+ it("should require the id parameter", () => {
+ expect(() => {
+ new ns.WorkspaceTabView();
+ }).toThrowError(TypeError);
+ });
+ */
+
+ it("should load empty tabs", () => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(tab.title).toEqual("Tab Title");
+ expect(tab.widgets).toEqual([]);
+ expect(tab.widgetsById).toEqual({});
+ });
+
+ it("should load tabs with widgets", () => {
+ let workspace = create_workspace();
+ let model = create_tab({
+ widgets: [{id: "9"}, {id: "5"}]
+ });
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(tab.title).toEqual("Tab Title");
+ expect(tab.widgets).toEqual([
+ jasmine.any(ns.WidgetView),
+ jasmine.any(ns.WidgetView)
+ ]);
+ expect(tab.widgetsById).toEqual({
+ "5": jasmine.any(ns.WidgetView),
+ "9": jasmine.any(ns.WidgetView)
+ });
+ });
+
+ it("should load editable tabs", () => {
+ let workspace = create_workspace();
+ workspace.model.isAllowed.and.returnValue(true);
+ let model = create_tab({
+ widgets: [{id: "9"}, {id: "5"}]
+ });
+ ns.WorkspaceTabViewMenuItems.calls.reset();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(ns.WorkspaceTabViewMenuItems).toHaveBeenCalledWith(tab);
+ });
+
+ });
+
+ describe("createWidget(resource[, options])", () => {
+
+ it("should commit changes by default", (done) => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ let widgetmodel = {id: 80};
+ model.createWidget.and.returnValue(Promise.resolve(widgetmodel));
+ let widget = {id: 80};
+ spyOn(tab, "findWidget").and.callFake((id) => {
+ expect(id).toBe(80);
+ return widget;
+ });
+ // TODO this make sense?
+ tab.dragboard.freeLayout.adaptHeight.and.returnValue({inLU: "invalid"});
+ tab.dragboard.freeLayout.adaptWidth.and.returnValue({inLU: 40});
+ tab.dragboard.freeLayout.columns = 20;
+ // END TODO
+
+ let p = tab.createWidget(
+ {
+ default_height: "120px",
+ default_width: "33%"
+ },
+ {
+ layout: 1,
+ top: 2,
+ left: 3,
+ height: "invalid",
+ width: 40
+ }
+ );
+
+ p.then((created_widget) => {
+ expect(created_widget).toBe(widget);
+ done();
+ });
+ });
+
+ it("should allow commit false", () => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ let widgetmodel = {id: 80};
+ model.createWidget.and.returnValue(widgetmodel);
+ let widget = {id: 80};
+ spyOn(tab, "findWidget").and.callFake((id) => {
+ expect(id).toBe(80);
+ return widget;
+ });
+
+ let created_widget = tab.createWidget(
+ {
+ default_height: "120px",
+ default_width: "33%"
+ }, {
+ commit: false
+ }
+ );
+
+ expect(created_widget).toBe(widget);
+ });
+
+ it("should honour initiallayout preference", (done) => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ model.preferences.get.and.returnValue("Free");
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ let widgetmodel = {id: 80};
+ model.createWidget.and.returnValue(Promise.resolve(widgetmodel));
+ let widget = {id: 80};
+ spyOn(tab, "findWidget").and.callFake((id) => {
+ expect(id).toBe(80);
+ return widget;
+ });
+ tab.dragboard.freeLayout._searchFreeSpace = jasmine.createSpy("_searchFreeSpace").and.returnValue({x: 1, y: 2});
+
+ let p = tab.createWidget({
+ default_height: "120px",
+ default_width: "33%"
+ });
+
+ p.then((created_widget) => {
+ expect(created_widget).toBe(widget);
+ done();
+ });
+ });
+
+ });
+
+ describe("highlight()", () => {
+
+ it("should add the highlight CSS class", () => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(tab.highlight()).toBe(tab);
+ });
+
+ });
+
+ describe("show()", () => {
+
+ it("should load all the widgets from the tab", () => {
+ let workspace = create_workspace();
+ let model = create_tab({
+ widgets: [{}, {}]
+ });
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(tab.show()).toBe(tab);
+ expect(tab.widgets[0].load).toHaveBeenCalled();
+ expect(tab.widgets[1].load).toHaveBeenCalled();
+ });
+
+ });
+
+ describe("showSettings()", () => {
+
+ it("should display the settings dialog", () => {
+ var show;
+ // TODO
+ ns.PreferencesWindowMenu = jasmine.createSpy("PreferencesWindowMenu").and.callFake(function () {
+ this.show = show = jasmine.createSpy("show");
+ });
+ let workspace = create_workspace();
+ let model = create_tab({
+ widgets: [{}, {}]
+ });
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(tab.showSettings()).toBe(tab);
+
+ expect(ns.PreferencesWindowMenu).toHaveBeenCalledWith('tab', model.preferences);
+ expect(show).toHaveBeenCalledWith();
+ });
+
+ });
+
+ describe("tab events", () => {
+
+ var workspace, model, tab;
+
+ beforeEach(() => {
+ workspace = create_workspace();
+ model = create_tab({});
+ tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ });
+
+ it("should ignore preferences changes not related to the baselayout", () => {
+ callEventListener(model.preferences, "pre-commit", {"other": 5});
+
+ expect(tab.dragboard._updateBaseLayout).not.toHaveBeenCalled();
+ });
+
+ it("should handle changes to the baselayout preference", () => {
+ callEventListener(model.preferences, "pre-commit", {"baselayout": 5});
+
+ expect(tab.dragboard._updateBaseLayout).toHaveBeenCalledWith();
+ });
+
+ it("should handle title changes", () => {
+ spyOn(StyledElements.Tab.prototype, "rename");
+ let newtitle = "NewTitle";
+ model.title = newtitle;
+
+ callEventListener(model, "change", ['title']);
+
+ expect(StyledElements.Tab.prototype.rename).toHaveBeenCalledWith(newtitle);
+ });
+
+ it("should handle name changes (visible tab)", () => {
+ spyOn(StyledElements.Tab.prototype, "rename");
+ spyOn(Wirecloud.HistoryManager, "getCurrentState").and.returnValue({
+ workspace_owner: "owner",
+ workspace_name: "dashboard",
+ tab: "oldname"
+ });
+ spyOn(Wirecloud.HistoryManager, "replaceState");
+ let newname = "new-name";
+ model.name = newname;
+ tab.hidden = false;
+
+ callEventListener(model, "change", ['name']);
+
+ expect(StyledElements.Tab.prototype.rename).not.toHaveBeenCalled();
+ expect(Wirecloud.HistoryManager.replaceState).toHaveBeenCalledWith({
+ workspace_owner: "owner",
+ workspace_name: "dashboard",
+ tab: newname
+ });
+ });
+
+ it("should handle name changes (hidden tab)", () => {
+ spyOn(StyledElements.Tab.prototype, "rename");
+ spyOn(Wirecloud.HistoryManager, "getCurrentState");
+ spyOn(Wirecloud.HistoryManager, "replaceState");
+ let newname = "new-name";
+ model.name = newname;
+ tab.hidden = true;
+
+ callEventListener(model, "change", ['name']);
+
+ expect(StyledElements.Tab.prototype.rename).not.toHaveBeenCalled();
+ expect(Wirecloud.HistoryManager.replaceState).not.toHaveBeenCalled();
+ });
+
+ it("should handle remove events", () => {
+ spyOn(StyledElements.Tab.prototype, "close");
+
+ callEventListener(model, "remove");
+
+ expect(StyledElements.Tab.prototype.close).toHaveBeenCalledWith();
+ });
+
+ it("should handle createwidget events (visible tab)", () => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ let widgetmodel = {id: "20"};
+ tab.hidden = false;
+
+ callEventListener(model, "createwidget", widgetmodel);
+
+ expect(tab.widgets).toEqual([
+ jasmine.any(ns.WidgetView)
+ ]);
+ expect(tab.widgetsById).toEqual({
+ "20": jasmine.any(ns.WidgetView)
+ });
+ expect(tab.widgets[0].load).toHaveBeenCalledWith();
+ });
+
+ it("should handle createwidget events (hidden tab)", () => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ let widgetmodel = {id: "20"};
+ tab.hidden = true;
+
+ callEventListener(model, "createwidget", widgetmodel);
+
+ expect(tab.widgets).toEqual([
+ jasmine.any(ns.WidgetView)
+ ]);
+ expect(tab.widgetsById).toEqual({
+ "20": jasmine.any(ns.WidgetView)
+ });
+ expect(tab.widgets[0].load).not.toHaveBeenCalled();
+ });
+
+ it("should handle removewidget events", () => {
+ let workspace = create_workspace();
+ let model = create_tab({
+ widgets: [{id: "9"}, {id: "5"}]
+ });
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+ let widget = tab.findWidget("5");
+
+ callEventListener(model, "removewidget", widget);
+
+ expect(tab.widgets).toEqual([
+ jasmine.any(ns.WidgetView)
+ ]);
+ expect(tab.widgetsById).toEqual({
+ "9": jasmine.any(ns.WidgetView)
+ });
+ });
+
+ });
+
+ describe("unhighlight()", () => {
+
+ it("should remove the highlight CSS class", () => {
+ let workspace = create_workspace();
+ let model = create_tab();
+ let tab = new ns.WorkspaceTabView("1", notebook, {
+ model: model,
+ workspace: workspace
+ });
+
+ expect(tab.unhighlight()).toBe(tab);
+ });
+
+ });
+
+ });
+
+})(Wirecloud.ui, StyledElements.Utils);
diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js
index a1dad64534..ee2b971021 100644
--- a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js
+++ b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabView.js
@@ -122,7 +122,7 @@
this.wrapperElement.classList.add("wc-workspace-tab-content");
this.wrapperElement.setAttribute('data-id', this.id);
- if (!this.workspace.model.restricted) {
+ if (this.workspace.model.isAllowed("edit")) {
var button = new se.PopupButton({
title: utils.gettext("Preferences"),
class: 'icon-tab-menu',
@@ -198,14 +198,11 @@
return this.findWidget(this.model.createWidget(resource, options).id);
}
- return new Promise(function (resolve, reject) {
-
- this.model.createWidget(resource, options).then(function (model) {
- resolve(this.findWidget(model.id));
- }.bind(this), function (reason) {
- reject(reason);
- });
- }.bind(this));
+ return this.model.createWidget(resource, options).then(
+ (model) => {
+ return Promise.resolve(this.findWidget(model.id));
+ }
+ );
},
/**
@@ -213,6 +210,7 @@
*/
highlight: function () {
this.tabElement.classList.add("highlight");
+ return this;
},
/**
@@ -224,54 +222,16 @@
return this.widgetsById[id];
},
- /**
- * @returns {Promise}
- */
- remove: function remove() {
-
- if (privates.get(this).widgets.length) {
- var dialog = new Wirecloud.ui.AlertWindowMenu(utils.gettext("The tab's widgets will also be removed. Would you like to continue?"));
- dialog.setHandler(() => {
- _remove.call(this);
- }).show();
- } else {
- _remove.call(this);
- }
-
- return this;
- },
-
- /**
- * Renames this tab
- *
- * @param {String} name
- *
- * @returns {Promise}
- */
- rename: function rename(name) {
- return this.model.rename(name).catch((reason) => {
- this.logManager.log(reason);
- return Promise.reject(reason);
- });
- },
-
repaint: function repaint() {
this.dragboard.paint();
this.dragboard._notifyWindowResizeEvent();
return this;
},
- /**
- * @returns {Promise}
- */
- setInitial: function setInitial() {
- return this.model.setInitial();
- },
-
show: function show() {
se.Tab.prototype.show.call(this);
- this.widgets.forEach(function (widget) {
+ privates.get(this).widgets.forEach(function (widget) {
widget.load();
});
@@ -285,6 +245,7 @@
unhighlight: function unhighlight() {
this.tabElement.classList.remove("highlight");
+ return this;
}
});
@@ -305,10 +266,6 @@
return widget;
};
- var _remove = function _remove() {
- this.model.remove();
- };
-
var clean_number = function clean_number(value, min, max) {
if (typeof value !== 'number' || value < min) {
diff --git a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js
index 2cb43f72ec..f4544f1491 100644
--- a/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js
+++ b/src/wirecloud/platform/static/js/wirecloud/ui/WorkspaceTabViewMenuItems.js
@@ -54,15 +54,15 @@
items = [];
- item = new se.MenuItem(utils.gettext("Rename"), function () {
- (new Wirecloud.ui.RenameWindowMenu(this, utils.gettext('Rename Workspace Tab'))).show();
- }.bind(this.tab));
+ item = new se.MenuItem(utils.gettext("Rename"), () => {
+ (new Wirecloud.ui.RenameWindowMenu(this.tab.model, utils.gettext('Rename Workspace Tab'))).show();
+ });
item.addIconClass("fa fa-pencil");
items.push(item);
item = new se.MenuItem(utils.gettext("Set as initial"), function () {
- this.setInitial();
- }.bind(this.tab));
+ this.tab.model.setInitial();
+ });
item.addIconClass("fa fa-home");
item.setDisabled(this.tab.model.initial);
items.push(item);
@@ -73,9 +73,16 @@
item.addIconClass("fa fa-cog");
items.push(item);
- item = new se.MenuItem(utils.gettext("Remove"), function () {
- this.remove();
- }.bind(this.tab));
+ item = new se.MenuItem(utils.gettext("Remove"), () => {
+ if (this.tab.widgets.length) {
+ var dialog = new Wirecloud.ui.AlertWindowMenu(utils.gettext("The tab's widgets will also be removed. Would you like to continue?"));
+ dialog.setHandler(() => {
+ this.tab.model.remove();
+ }).show();
+ } else {
+ this.tab.model.remove();
+ }
+ });
item.addIconClass("fa fa-trash");
item.setDisabled(!this.tab.model.isAllowed('remove'));
items.push(item);