diff --git a/api/js/etemplate/Et2Portlet/Et2Portlet.ts b/api/js/etemplate/Et2Portlet/Et2Portlet.ts
index 1e8862da18..8f30c2cb3f 100644
--- a/api/js/etemplate/Et2Portlet/Et2Portlet.ts
+++ b/api/js/etemplate/Et2Portlet/Et2Portlet.ts
@@ -13,8 +13,9 @@
import {Et2Widget} from "../Et2Widget/Et2Widget";
import {SlCard} from "@shoelace-style/shoelace";
import interact from "@interactjs/interactjs";
+import type {InteractEvent} from "@interactjs/core/InteractEvent";
import {egw} from "../../jsapi/egw_global";
-import {classMap, css, html} from "@lion/core";
+import {classMap, css, html, TemplateResult} from "@lion/core";
import {HasSlotController} from "@shoelace-style/shoelace/dist/internal/slot";
import shoelace from "../Styles/shoelace";
import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
@@ -25,7 +26,7 @@ import {HomeApp} from "../../../../home/js/app";
* Participate in Home
*/
-export class Et2Portlet extends (Et2Widget(SlCard))
+export class Et2Portlet extends Et2Widget(SlCard)
{
static get properties()
{
@@ -62,8 +63,12 @@ export class Et2Portlet extends (Et2Widget(SlCard))
...shoelace,
...(super.styles || []),
css`
+ :host {
+ --header-spacing: var(--sl-spacing-medium);
+ }
+
.portlet__header {
- flex: 1 0 auto;
+ flex: 0 0 auto;
display: flex;
font-style: inherit;
font-variant: inherit;
@@ -73,7 +78,9 @@ export class Et2Portlet extends (Et2Widget(SlCard))
font-size: var(--sl-font-size-medium);
line-height: var(--sl-line-height-dense);
padding: var(--header-spacing);
+ padding-right: calc(2em + var(--header-spacing));
margin: 0px;
+ position: relative;
}
.portlet__title {
@@ -84,8 +91,8 @@ export class Et2Portlet extends (Et2Widget(SlCard))
.portlet__header et2-button-icon {
display: none;
- order: 99;
- margin-left: auto;
+ position: absolute;
+ right: 0px;
}
.portlet__header:hover et2-button-icon {
@@ -97,11 +104,17 @@ export class Et2Portlet extends (Et2Widget(SlCard))
height: 100%
}
+ .card_header {
+ margin-right: calc(var(--sl-spacing-medium) + 1em);
+ }
+
.card__body {
/* display block to prevent overflow from our size */
display: block;
overflow: hidden;
+
flex: 1 1 auto;
+ padding: 0px;
}
@@ -151,6 +164,19 @@ export class Et2Portlet extends (Et2Widget(SlCard))
.then(() => this._setupMoveResize());
}
+ /**
+ * Load further details from content
+ *
+ * Normal load & attribute assign will cast our settings object to a string
+ * @param _template_node
+ */
+ transformAttributes(attrs)
+ {
+ super.transformAttributes(attrs);
+ let data = this.getArrayMgr("content").data.find(e => e.id && e.id == this.id);
+ this.settings = typeof attrs.settings == "string" ? data.value || data.settings || {} : attrs.settings;
+ }
+
/**
* Overriden from parent to add in default actions
*/
@@ -316,16 +342,17 @@ export class Et2Portlet extends (Et2Widget(SlCard))
headerTemplate()
{
return html`
- `;
+
${this.title}
`;
}
- footerTemplate()
+ bodyTemplate() : TemplateResult
{
- return '';
+ return html``;
+ }
+
+ footerTemplate() : TemplateResult
+ {
+ return html``;
}
@@ -343,7 +370,27 @@ export class Et2Portlet extends (Et2Widget(SlCard))
value: {
content: this.settings
},
- buttons: Et2Dialog.BUTTONS_OK_CANCEL
+ buttons: [
+ {
+ "button_id": Et2Dialog.OK_BUTTON,
+ label: this.egw().lang('ok'),
+ id: 'dialog[ok]',
+ image: 'check',
+ "default": true
+ },
+ {
+ label: this.egw().lang('delete'),
+ id: 'delete',
+ image: 'delete',
+ align: "right"
+ },
+ {
+ "button_id": Et2Dialog.CANCEL_BUTTON,
+ label: this.egw().lang('cancel'),
+ id: 'cancel',
+ image: 'cancel'
+ }
+ ],
});
// Set separately to avoid translation
dialog.title = this.egw().lang("Edit") + " " + (this.title || '');
@@ -354,11 +401,18 @@ export class Et2Portlet extends (Et2Widget(SlCard))
{
if(button_id != Et2Dialog.OK_BUTTON)
{
+ if(button_id == "delete")
+ {
+ this.update_settings('~remove~').then(() =>
+ {
+ this.remove();
+ });
+ }
return;
}
// Pass updated settings, unless we're removing
- this.update_settings((typeof value == 'string') ? {} : this.settings || {});
+ this.update_settings({...this.settings, ...value});
// Extend, not replace, because settings has types while value has just value
if(typeof value == 'object')
@@ -367,13 +421,19 @@ export class Et2Portlet extends (Et2Widget(SlCard))
}
}
- protected update_settings(settings)
+ public update_settings(settings)
{
+ // Skip any updates during loading
+ if(!this.getInstanceManager().isReady)
+ {
+ return;
+ }
+
// Save settings - server might reply with new content if the portlet needs an update,
// but ideally it doesn't
this.classList.add("loading");
- this.egw().jsonq("home.home_ui.ajax_set_properties", [this.id, [], settings, this.settings ? this.settings.group : false],
+ return this.egw().jsonq("home.home_ui.ajax_set_properties", [this.id, [], settings, this.settings ? this.settings.group : false],
function(data)
{
// This section not for us
@@ -437,8 +497,13 @@ export class Et2Portlet extends (Et2Widget(SlCard))
})}
>
${this.imageTemplate()}
-
-
+
+
+ ${this.bodyTemplate()}
`;
diff --git a/api/js/etemplate/et2_widget_box.ts b/api/js/etemplate/et2_widget_box.ts
index 17421ad3cc..7255cbbbcc 100644
--- a/api/js/etemplate/et2_widget_box.ts
+++ b/api/js/etemplate/et2_widget_box.ts
@@ -94,7 +94,7 @@ export class et2_box extends et2_baseWidget implements et2_IDetachedDOM
// Create the new element, if no expansion needed
var id = et2_readAttrWithDefault(node, "id", "");
- if(id.indexOf('$') < 0 || ['box', 'grid', 'et2-box'].indexOf(widgetType) == -1)
+ if(id.indexOf('$') < 0 || ['box', 'grid', 'et2-box'].indexOf(widgetType) == -1 && typeof customElements.get(widgetType) == "undefined")
{
this.createElementFromNode(node);
childIndex++;
diff --git a/home/inc/class.home_favorite_portlet.inc.php b/home/inc/class.home_favorite_portlet.inc.php
index 7797e973cc..02b8cde220 100644
--- a/home/inc/class.home_favorite_portlet.inc.php
+++ b/home/inc/class.home_favorite_portlet.inc.php
@@ -145,16 +145,57 @@ class home_favorite_portlet extends home_portlet
public static function process($content = array())
{
- unset($content); // not used, but required by function signature
+ unset($content); // not used, but required by function signature
// We need to keep the template going, thanks.
- Etemplate\Widget::setElementAttribute('','','');
+ Etemplate\Widget::setElementAttribute('', '', '');
}
- public function get_actions(){
+ public function get_actions()
+ {
return array();
}
+ public function get_type()
+ {
+ return 'et2-portlet-favorite';
+ }
+
+ /**
+ * Get a list of "Add" actions
+ * @return array
+ */
+ public function get_add_actions()
+ {
+ $desc = $this->get_description();
+ $actions = array();
+
+ // Add a list of favorites
+ if($this->context['appname'] && ($favorites = Framework\Favorites::get_favorites($this->context['appname'])))
+ {
+ foreach($favorites as $name => $favorite)
+ {
+ $actions[] = array(
+ 'id' => __CLASS__ . $name,
+ 'caption' => $name,
+ 'onExecute' => 'javaScript:app.home.add'
+ );
+ }
+ }
+ else
+ {
+ $actions[] = array(
+ 'id' => __CLASS__,
+ 'caption' => lang('List'),
+ 'hint' => $desc['description'],
+ 'onExecute' => 'javaScript:app.home.add',
+ 'acceptedTypes' => $this->accept_drop(),
+ 'allowOnMultiple' => $this->accept_multiple()
+ );
+ }
+ return $actions;
+ }
+
/**
* Some descriptive information about the portlet, so that users can decide if
* they want it or not, and for inclusion in lists, hover text, etc.
diff --git a/home/inc/class.home_list_portlet.inc.php b/home/inc/class.home_list_portlet.inc.php
index 4ef571521e..13a432f298 100644
--- a/home/inc/class.home_list_portlet.inc.php
+++ b/home/inc/class.home_list_portlet.inc.php
@@ -92,17 +92,22 @@ class home_list_portlet extends home_portlet
public function get_description()
{
return array(
- 'displayName'=> 'List of entries',
- 'title'=> $this->title,
- 'description'=> lang('Show a list of entries')
+ 'displayName' => 'List of entries',
+ 'title' => $this->title,
+ 'description' => lang('Show a list of entries')
);
}
+ public function get_type()
+ {
+ return 'et2-portlet-list';
+ }
+
/**
* Get a fragment of HTML for display
*
* @param id String unique ID, provided to the portlet so it can make sure content is
- * unique, if needed.
+ * unique, if needed.
* @return string HTML fragment for display
*/
public function exec($id = null, Etemplate &$etemplate = null)
@@ -127,7 +132,7 @@ class home_list_portlet extends home_portlet
}
}
- $etemplate->exec('home.home_list_portlet.exec',$content);
+ //$etemplate->exec('home.home_list_portlet.exec',$content);
}
/**
diff --git a/home/inc/class.home_portlet.inc.php b/home/inc/class.home_portlet.inc.php
index 2de595fc7b..cdcc980f1d 100644
--- a/home/inc/class.home_portlet.inc.php
+++ b/home/inc/class.home_portlet.inc.php
@@ -49,11 +49,39 @@ abstract class home_portlet
*/
public abstract function get_description();
+ /**
+ * Get the web-component tag used client side
+ *
+ * @return string
+ */
+ public function get_type()
+ {
+ return 'et2-portlet';
+ }
+
+ /**
+ * Get a list of "Add" actions
+ * @return array
+ */
+ public function get_add_actions()
+ {
+ $desc = $this->get_description();
+ return array(
+ array(
+ 'id' => __CLASS__,
+ 'caption' => $desc['displayName'],
+ 'hint' => $desc['description'],
+ 'onExecute' => 'javaScript:app.home.add',
+ 'acceptedTypes' => $this->accept_drop(),
+ 'allowOnMultiple' => $this->accept_multiple()
+ ));
+ }
+
/**
* Generate the display for the portlet
*
* @param id String unique ID, provided to the portlet so it can make sure content is
- * unique, if needed.
+ * unique, if needed.
* @param Etemplate $etemplate eTemplate to generate content
*/
public abstract function exec($id = null, Etemplate &$etemplate = null);
diff --git a/home/inc/class.home_ui.inc.php b/home/inc/class.home_ui.inc.php
index fb32e05bf1..d213647bde 100644
--- a/home/inc/class.home_ui.inc.php
+++ b/home/inc/class.home_ui.inc.php
@@ -115,8 +115,7 @@ class home_ui
'onExecute' => 'javaScript:app.home.add',
'children' => $add_portlets
),
- // Favorites are sortable which needs special handling,
- // handled directly through jQuery
+ // Favorites are sortable which needs special handling
);
// Add all known portlets as drop actions too. If there are multiple matches, there will be a menu
@@ -180,8 +179,9 @@ class home_ui
$portlet = new $classname($context);
$desc = $portlet->get_description();
$portlet_content = array(
- 'id' => $id
- ) + $desc + $context;
+ 'id' => $id,
+ 'type' => $portlet->get_type()
+ ) + $desc + $context;
// Get settings
@@ -566,6 +566,7 @@ class home_ui
else
{
$prefs->delete('home', $portlet_id);
+ $response->data([]);
}
}
else
@@ -594,7 +595,7 @@ class home_ui
$classname = substr($classname, 4);
}
$content = null;
- $portlet = $this->get_portlet($portlet_id, $context, $content, $attributes, $full_exec);
+ $portlet = $this->get_portlet("home-index_portlets_" . $portlet_id, $context, $content, $attributes, $full_exec);
$context['class'] = get_class($portlet);
foreach($portlet->get_properties() as $property)
diff --git a/home/inc/class.home_weather_portlet.inc.php b/home/inc/class.home_weather_portlet.inc.php
index fc858f854c..8a9763d8f5 100644
--- a/home/inc/class.home_weather_portlet.inc.php
+++ b/home/inc/class.home_weather_portlet.inc.php
@@ -39,7 +39,7 @@ class home_weather_portlet extends home_portlet
if (false) parent::__construct();
// City not set for new widgets created via context menu
- if(!$context['city'] || $context['height'] < 2)
+ if(!$context['city'])
{
// Set initial size to 3x2, default is too small
$context['width'] = 3;
diff --git a/home/js/Et2PortletFavorite.ts b/home/js/Et2PortletFavorite.ts
index 2b91ae5964..a8c6cda0d0 100644
--- a/home/js/Et2PortletFavorite.ts
+++ b/home/js/Et2PortletFavorite.ts
@@ -1,7 +1,58 @@
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
+import {css, html} from "@lion/core";
+import shoelace from "../../api/js/etemplate/Styles/shoelace";
+import {etemplate2} from "../../api/js/etemplate/etemplate2";
export class Et2PortletFavorite extends Et2Portlet
{
+ static get styles()
+ {
+ return [
+ ...shoelace,
+ ...(super.styles || []),
+ css`
+ .portlet__header et2-button {
+ visibility: hidden;
+ }
+
+ .portlet__header:hover et2-button {
+ visibility: visible;
+ }
+ `
+ ]
+ }
+
+ constructor()
+ {
+ super();
+ this.toggleHeader = this.toggleHeader.bind(this);
+ }
+
+ headerTemplate()
+ {
+ return html`${super.headerTemplate()}
+
+ `;
+ }
+
+ public toggleHeader()
+ {
+ //widget.set_class(widget.class == 'opened' ? 'closed' : 'opened');
+ // We operate on the DOM here, nm should be unaware of our fiddling
+ let nm = this.getWidgetById('nm') || etemplate2.getById(this.id) && etemplate2.getById(this.id).widgetContainer.getWidgetById('nm') || false;
+ if(!nm)
+ {
+ return;
+ }
+
+ // Hide header
+ nm.div.toggleClass('header_hidden');
+ nm.set_hide_header(nm.div.hasClass('header_hidden'));
+ nm.resize();
+ }
}
if(!customElements.get("et2-portlet-favorite"))
diff --git a/home/js/Et2PortletList.ts b/home/js/Et2PortletList.ts
new file mode 100644
index 0000000000..fd70a37ce5
--- /dev/null
+++ b/home/js/Et2PortletList.ts
@@ -0,0 +1,175 @@
+import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
+import {et2_createWidget} from "../../api/js/etemplate/et2_core_widget";
+import {css, html, TemplateResult} from "@lion/core";
+import shoelace from "../../api/js/etemplate/Styles/shoelace";
+
+/**
+ * Home portlet to show a list of entries
+ */
+export class Et2PortletList extends Et2Portlet
+{
+ static get styles()
+ {
+ return [
+ ...shoelace,
+ ...(super.styles || []),
+ css`
+ .delete_button {
+ padding-right: 10px;
+ }
+ `
+ ]
+ }
+
+ constructor()
+ {
+ super();
+ this.link_change = this.link_change.bind(this);
+ }
+
+ /**
+ * For list_portlet - opens a dialog to add a new entry to the list
+ *
+ * @param {egwAction} action Drop or add action
+ * @param {egwActionObject[]} Selected entries
+ * @param {egwActionObject} target_action Drop target
+ */
+ add_link(action, source, target_action)
+ {
+ // TODO
+ debugger;
+
+ // Actions got confused drop vs popup
+ if(source[0].id == 'portlets')
+ {
+ return this.add_link(action);
+ }
+
+ // Get widget
+ let widget = null;
+ while(action.parent != null)
+ {
+ if(action.data && action.data.widget)
+ {
+ widget = action.data.widget;
+ break;
+ }
+ action = action.parent;
+ }
+ if(target_action == null)
+ {
+ // use template base url from initial template, to continue using webdav, if that was loaded via webdav
+ let splitted = 'home.edit'.split('.');
+ let path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" +
+ splitted.join('.') + ".xet";
+ let dialog = et2_createWidget("dialog", {
+ callback: function(button_id, value)
+ {
+ if(button_id == et2_dialog.CANCEL_BUTTON)
+ {
+ return;
+ }
+ let new_list = widget.options.settings.list || [];
+ for(let i = 0; i < new_list.length; i++)
+ {
+ if(new_list[i].app == value.add.app && new_list[i].id == value.add.id)
+ {
+ // Duplicate - skip it
+ return;
+ }
+ }
+ value.add.link_id = value.add.app + ':' + value.add.id;
+ // Update server side
+ new_list.push(value.add);
+ widget._process_edit(button_id, {list: new_list});
+ // Update client side
+ let list = widget.getWidgetById('list');
+ if(list)
+ {
+ list.set_value(new_list);
+ }
+ },
+ buttons: et2_dialog.BUTTONS_OK_CANCEL,
+ title: app.home.egw.lang('add'),
+ template: path,
+ value: {content: [{label: app.home.egw.lang('add'), type: 'link-entry', name: 'add', size: ''}]}
+ });
+ }
+ else
+ {
+ // Drag'n'dropped something on the list - just send action IDs
+ let new_list = widget.options.settings.list || [];
+ let changed = false;
+ for(let i = 0; i < new_list.length; i++)
+ {
+ // Avoid duplicates
+ for(let j = 0; j < source.length; j++)
+ {
+ if(!source[j].id || new_list[i].app + "::" + new_list[i].id == source[j].id)
+ {
+ // Duplicate - skip it
+ source.splice(j, 1);
+ }
+ }
+ }
+ for(let i = 0; i < source.length; i++)
+ {
+ let explode = source[i].id.split('::');
+ new_list.push({app: explode[0], id: explode[1], link_id: explode.join(':')});
+ changed = true;
+ }
+ if(changed)
+ {
+ widget._process_edit(et2_dialog.OK_BUTTON, {
+ list: new_list || {}
+ });
+ }
+ // Filemanager support - links need app = 'file' and type set
+ for(let i = 0; i < new_list.length; i++)
+ {
+ if(new_list[i]['app'] == 'filemanager')
+ {
+ new_list[i]['app'] = 'file';
+ new_list[i]['path'] = new_list[i]['title'] = new_list[i]['icon'] = new_list[i]['id'];
+ }
+ }
+
+ widget.getWidgetById('list').set_value(new_list);
+ }
+ }
+
+ /**
+ * Remove a link from the list
+ */
+ link_change(event)
+ {
+ if(!this.getInstanceManager()?.isReady)
+ {
+ return;
+ }
+ debugger;
+ // Not used, but delete puts link in event.data
+ let link_data = event.data || false;
+
+ // Update settings on link delete
+ if(link_data)
+ {
+ this.update_settings({list: this.settings.list});
+ }
+ }
+
+ bodyTemplate() : TemplateResult
+ {
+ return html`
+
+ `
+ }
+
+}
+
+if(!customElements.get("et2-portlet-list"))
+{
+ customElements.define("et2-portlet-list", Et2PortletList);
+}
\ No newline at end of file
diff --git a/home/js/app.ts b/home/js/app.ts
index 028c14884c..dd2e6f31e0 100644
--- a/home/js/app.ts
+++ b/home/js/app.ts
@@ -8,12 +8,14 @@
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
-import {AppJS} from "../../api/js/jsapi/app_base.js";
import {et2_createWidget} from "../../api/js/etemplate/et2_core_widget";
import {EgwApp} from "../../api/js/jsapi/egw_app";
import {etemplate2} from "../../api/js/etemplate/etemplate2";
import {Et2Portlet} from "../../api/js/etemplate/Et2Portlet/Et2Portlet";
import {Et2PortletFavorite} from "./Et2PortletFavorite";
+import {loadWebComponent} from "../../api/js/etemplate/Et2Widget/Et2Widget";
+import "./Et2PortletList";
+import Sortable from "sortablejs/modular/sortable.complete.esm.js";
/**
* JS for home application
@@ -30,7 +32,7 @@ export class HomeApp extends EgwApp
/**
* Grid resolution. Must match et2_portlet GRID
*/
- public static GRID = 50;
+ public static GRID = 150;
/**
* Default size for new portlets
@@ -43,6 +45,7 @@ export class HomeApp extends EgwApp
// List of portlets
private portlets = {};
portlet_container : any;
+ private sortable : Sortable;
/**
* Constructor
@@ -68,10 +71,13 @@ export class HomeApp extends EgwApp
super.destroy(this.appname);
// Make sure all other sub-etemplates in portlets are done
- let others = etemplate2.getByApplication(this.appname);
- for(let i = 0; i < others.length; i++)
+ if(this == window.app.home)
{
- others[i].clear();
+ let others = etemplate2.getByApplication(this.appname);
+ for(let i = 0; i < others.length; i++)
+ {
+ others[i].clear();
+ }
}
}
@@ -96,12 +102,9 @@ export class HomeApp extends EgwApp
this.portlet_container = this.et2.getWidgetById("portlets");
- // Set up sorting of portlets
- //this._do_ordering();
-
// Accept drops of favorites, which aren't part of action system
- jQuery(this.et2.getDOMNode().parentNode).droppable({
- hoverClass: 'drop-hover',
+ this.sortable = new Sortable(this.et2.getDOMNode().parentNode, {
+ chosenClass: 'drop-hover',
accept: function(draggable)
{
// Check for direct support for that application
@@ -111,8 +114,9 @@ export class HomeApp extends EgwApp
}
return false;
},
- drop: function(event, ui)
+ onAdd: function(event, ui)
{
+ debugger;
// Favorite dropped on home - fake an action and divert to normal handler
let action = {
data: {
@@ -128,8 +132,9 @@ export class HomeApp extends EgwApp
action.ui = ui;
app.home.add_from_drop(action, [{data: ui.helper.context.dataset}])
}
- })
+ });
// Bind to unload to remove it from our list
+ /*
.on('clear', '.et2_container[id]', jQuery.proxy(function(e)
{
if(e.target && e.target.id && this.portlets[e.target.id])
@@ -138,6 +143,8 @@ export class HomeApp extends EgwApp
delete this.portlets[e.target.id];
}
}, this));
+
+ */
}
else if(et2.uniqueId)
{
@@ -148,7 +155,7 @@ export class HomeApp extends EgwApp
window.setTimeout(() => {this.et2_ready(et2, name);}, 200);
return;
}
- let portlet = portlet_container.getWidgetById(et2.uniqueId);
+ let portlet = portlet_container.getWidgetById(et2.uniqueId) || et2.DOMContainer;
// Check for existing etemplate, this one loaded over it
// NOTE: Moving them around like this can cause problems with event handlers
let existing = etemplate2.getById(et2.uniqueId);
@@ -163,14 +170,19 @@ export class HomeApp extends EgwApp
}
}
// Set size & position
- let settings = portlet_container.getArrayMgr("content").data.find(e => e.id == et2.uniqueId) || {};
+ let et2_data = et2.widgetContainer.getArrayMgr("content").data;
+ let settings = et2_data && et2_data.id == portlet.id && et2_data || portlet_container.getArrayMgr("content").data.find(e => et2.uniqueId.endsWith(e.id)) || {settings: {}};
+ portlet.settings = settings.settings || {};
portlet.style.gridArea = settings.row + "/" + settings.col + "/ span " + (settings.height || 1) + "/ span " + (settings.width || 1);
-
+
+
// It's in the right place for original load, but move it into portlet
+
let misplaced = jQuery(etemplate2.getById('home-index').DOMContainer).siblings('#' + et2.DOMContainer.id);
- if(portlet)
+
+ if(portlet && et2.DOMContainer !== portlet)
{
- portlet.addChild(et2.widgetContainer);
+ portlet.append(et2.DOMContainer);
et2.resize();
}
if(portlet && misplaced.length)
@@ -180,6 +192,10 @@ export class HomeApp extends EgwApp
// Instanciate custom code for this portlet
this._get_portlet_code(portlet);
+
+ // Ordering of portlets
+ // Only needs to be done once, but its hard to tell when everything is loaded
+ this._do_ordering();
}
}
@@ -248,10 +264,11 @@ export class HomeApp extends EgwApp
{
let $portlet_container = jQuery(this.portlet_container.getDOMNode());
attrs.row = Math.max(1, Math.round((action.menu_context.posy - $portlet_container.offset().top) / HomeApp.GRID) + 1);
- attrs.col = Math.max(1, Math.round((action.menu_context.posx - $portlet_container.offset().left) / HomeApp.GRID) + 1);
+ // Use "auto" col to avoid any overlap or overflow
+ attrs.col = "auto";
}
- let portlet = et2_createWidget('et2-portlet', attrs, this.portlet_container);
+ let portlet = loadWebComponent('et2-portlet', attrs, this.portlet_container);
portlet.loadingFinished();
// Get actual attributes & settings, since they're not available client side yet
@@ -277,48 +294,38 @@ export class HomeApp extends EgwApp
// Basic portlet attributes
let attrs = {
+ ...HomeApp.DEFAULT,
id: this._create_id(),
class: action.data.class || action.id.substr(5),
- width: this.DEFAULT.WIDTH,
- height: this.DEFAULT.HEIGHT
+ dropped_data: []
};
// Try to find where the drop was
if(action != null && action.ui && action.ui.position)
{
- attrs.row = Math.max(1, Math.round((action.ui.position.top - $portlet_container.offset().top) / this.GRID));
- attrs.col = Math.max(1, Math.round((action.ui.position.left - $portlet_container.offset().left) / this.GRID));
+ attrs.row = Math.max(1, Math.round((action.ui.position.top - $portlet_container.offset().top) / HomeApp.GRID));
+ // Use "auto" col to avoid any overlap or overflow
+ attrs.col = "auto";
}
- let portlet = et2_createWidget('portlet', jQuery.extend({}, attrs), this.portlet_container);
- portlet.loadingFinished();
- // Immediately add content ID so etemplate loads into the right place
- portlet.content.append('');
-
// Get actual attributes & settings, since they're not available client side yet
- let drop_data = [];
for(let i = 0; i < source.length; i++)
{
if(source[i].id)
{
- drop_data.push(source[i].id);
+ attrs.dropped_data.push(source[i].id);
}
else
{
- drop_data.push(source[i].data);
+ attrs.dropped_data.push(source[i].data);
}
}
- // Don't pass default width & height so class can set it
- delete attrs.width;
- delete attrs.height;
- portlet._process_edit(et2_dialog.OK_BUTTON, jQuery.extend({dropped_data: drop_data}, attrs));
- // Set up sorting/grid of new portlet
- $portlet_container.data("gridster").add_widget(
- portlet.getDOMNode(),
- this.DEFAULT.WIDTH, this.DEFAULT.HEIGHT,
- attrs.col, attrs.row
- );
+ let portlet = loadWebComponent('et2-portlet', attrs, this.portlet_container);
+ portlet.loadingFinished();
+
+ // Get actual attributes & settings, since they're not available client side yet
+ portlet.update_settings(attrs);
// Instanciate custom code for this portlet
this._get_portlet_code(portlet);
@@ -399,7 +406,7 @@ export class HomeApp extends EgwApp
let p = this.portlet_container.getWidgetById(id);
if(p)
{
- p._process_edit(et2_dialog.OK_BUTTON, '~reload~');
+ p.update_settings('~reload~');
}
}
@@ -462,95 +469,25 @@ export class HomeApp extends EgwApp
*/
_do_ordering()
{
- let $portlet_container = jQuery(this.portlet_container.getDOMNode());
- $portlet_container
- /* Gridster */
- .gridster({
- widget_selector: 'div.et2_portlet',
- // Dimensions + margins = grid spacing
- widget_base_dimensions: [home.GRID - 5, home.GRID - 5],
- widget_margins: [5, 5],
- extra_rows: 1,
- extra_cols: 1,
- min_cols: 3,
- min_rows: 3,
- /**
- * Set which parameters we want when calling serialize().
- * @param $w jQuery jQuery-wrapped element
- * @param grid Object Grid settings
- * @return Object - will be returned by gridster.serialize()
- */
- serialize_params: function($w, grid)
- {
- return {
- id: $w.attr('id').replace(app.home.portlet_container.getInstanceManager().uniqueId + '_', ''),
- row: grid.row,
- col: grid.col,
- width: grid.size_x,
- height: grid.size_y
- };
- },
- /**
- * Gridster's internal drag settings
- */
- draggable: {
- handle: '.ui-widget-header',
- stop: function(event, ui)
- {
- // Update widget(s)
- let changed = this.serialize_changed();
-
- // Reset changed, or they keep accumulating
- this.$changed = jQuery([]);
-
- for(let key in changed)
- {
- if(!changed[key].id)
- {
- continue;
- }
- // Changed ID is the ID
- let widget = window.app.home.portlet_container.getWidgetById(changed[key].id);
- if(!widget || widget == window.app.home.portlet_container)
- {
- continue;
- }
-
- egw().jsonq("home.home_ui.ajax_set_properties", [changed[key].id, {}, {
- row: changed[key].row,
- col: changed[key].col
- }, widget.settings ? widget.settings.group : false],
- null,
- widget, true, widget
- );
- }
- }
- }
-
- });
-
- // Rescue selectboxes from Firefox
- $portlet_container.on('mousedown touchstart', 'select', function(e)
+ if(!this.portlet_container)
{
- e.stopPropagation();
- });
- // Bind window resize to re-layout gridster
- jQuery(window).one("resize." + this.et2._inst.uniqueId, function()
+ return;
+ }
+
+ let col_map = {};
+ this.portlet_container.getDOMNode().querySelectorAll("[style*='grid-area']").forEach((n) =>
{
- // Note this doesn't change the positions, just makes them invalid
- $portlet_container.data('gridster').recalculate_faux_grid();
- });
- // Bind resize to update gridster - this may happen _before_ the widget gets a
- // chance to update itself, so we can't use the widget
- $portlet_container
- .on("resizestop", function(event, ui)
+ let [col, span] = (getComputedStyle(n).gridColumn || "").split(" / ");
+ if(typeof col_map[col] !== "undefined")
{
- $portlet_container.data("gridster").resize_widget(
- ui.element,
- Math.round(ui.size.width / app.home.GRID),
- Math.round(ui.size.height / app.home.GRID)
- );
- });
+ // Set column to auto to avoid overlap
+ n.style.gridColumn = "auto / " + span;
+ }
+ else
+ {
+ col_map[col] = true;
+ }
+ });
}
/**
@@ -572,126 +509,12 @@ export class HomeApp extends EgwApp
/**
* Functions for the list portlet
*/
- /**
- * For list_portlet - opens a dialog to add a new entry to the list
- *
- * @param {egwAction} action Drop or add action
- * @param {egwActionObject[]} Selected entries
- * @param {egwActionObject} target_action Drop target
- */
- add_link(action, source, target_action)
- {
- // Actions got confused drop vs popup
- if(source[0].id == 'portlets')
- {
- return this.add_link(action);
- }
-
- // Get widget
- let widget = null;
- while(action.parent != null)
- {
- if(action.data && action.data.widget)
- {
- widget = action.data.widget;
- break;
- }
- action = action.parent;
- }
- if(target_action == null)
- {
- // use template base url from initial template, to continue using webdav, if that was loaded via webdav
- let splitted = 'home.edit'.split('.');
- let path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" +
- splitted.join('.') + ".xet";
- let dialog = et2_createWidget("dialog", {
- callback: function(button_id, value)
- {
- if(button_id == et2_dialog.CANCEL_BUTTON)
- {
- return;
- }
- let new_list = widget.options.settings.list || [];
- for(let i = 0; i < new_list.length; i++)
- {
- if(new_list[i].app == value.add.app && new_list[i].id == value.add.id)
- {
- // Duplicate - skip it
- return;
- }
- }
- value.add.link_id = value.add.app + ':' + value.add.id;
- // Update server side
- new_list.push(value.add);
- widget._process_edit(button_id, {list: new_list});
- // Update client side
- let list = widget.getWidgetById('list');
- if(list)
- {
- list.set_value(new_list);
- }
- },
- buttons: et2_dialog.BUTTONS_OK_CANCEL,
- title: app.home.egw.lang('add'),
- template: path,
- value: {content: [{label: app.home.egw.lang('add'), type: 'link-entry', name: 'add', size: ''}]}
- });
- }
- else
- {
- // Drag'n'dropped something on the list - just send action IDs
- let new_list = widget.options.settings.list || [];
- let changed = false;
- for(let i = 0; i < new_list.length; i++)
- {
- // Avoid duplicates
- for(let j = 0; j < source.length; j++)
- {
- if(!source[j].id || new_list[i].app + "::" + new_list[i].id == source[j].id)
- {
- // Duplicate - skip it
- source.splice(j, 1);
- }
- }
- }
- for(let i = 0; i < source.length; i++)
- {
- let explode = source[i].id.split('::');
- new_list.push({app: explode[0], id: explode[1], link_id: explode.join(':')});
- changed = true;
- }
- if(changed)
- {
- widget._process_edit(et2_dialog.OK_BUTTON, {
- list: new_list || {}
- });
- }
- // Filemanager support - links need app = 'file' and type set
- for(let i = 0; i < new_list.length; i++)
- {
- if(new_list[i]['app'] == 'filemanager')
- {
- new_list[i]['app'] = 'file';
- new_list[i]['path'] = new_list[i]['title'] = new_list[i]['icon'] = new_list[i]['id'];
- }
- }
-
- widget.getWidgetById('list').set_value(new_list);
- }
- }
-
/**
* Remove a link from the list
*/
link_change(list, link_id, row)
{
- // Quick response client side
- row.slideUp(row.remove);
-
- // Actual removal
- let portlet = list._parent._parent;
- portlet.options.settings.list.splice(row.index(), 1);
- portlet._process_edit(et2_dialog.OK_BUTTON, {list: portlet.options.settings.list || {}});
+ list.link_change(link_id, row);
}
/**
diff --git a/home/templates/default/app.css b/home/templates/default/app.css
index bd1ad0ff6b..452902d7ec 100644
--- a/home/templates/default/app.css
+++ b/home/templates/default/app.css
@@ -11,8 +11,9 @@
#home-index_portlets {
background-color: inherit;
display: grid;
- grid-auto-columns: 50ex;
- grid-auto-rows: 50ex;
+ grid-auto-columns: 25ex;
+ grid-auto-rows: 25ex;
+ grid-auto-flow: dense;
gap: 2ex;
diff --git a/home/templates/default/favorite.xet b/home/templates/default/favorite.xet
index 4159889141..9b7d215ed8 100644
--- a/home/templates/default/favorite.xet
+++ b/home/templates/default/favorite.xet
@@ -2,7 +2,6 @@
-
\ No newline at end of file
diff --git a/home/templates/default/index.xet b/home/templates/default/index.xet
index b4341be2de..b2fed3843c 100644
--- a/home/templates/default/index.xet
+++ b/home/templates/default/index.xet
@@ -8,26 +8,13 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file