diff --git a/addressbook/templates/default/home.link.xet b/addressbook/templates/default/home.link.xet new file mode 100644 index 0000000000..4c9a16ca8c --- /dev/null +++ b/addressbook/templates/default/home.link.xet @@ -0,0 +1,54 @@ + + + + \ No newline at end of file diff --git a/etemplate/js/et2_widget_portlet.js b/etemplate/js/et2_widget_portlet.js index c56f33c896..3f609782b3 100644 --- a/etemplate/js/et2_widget_portlet.js +++ b/etemplate/js/et2_widget_portlet.js @@ -110,6 +110,7 @@ var et2_portlet = et2_valueWidget.extend( // Create DOM nodes this.div = $j(document.createElement("div")) + .addClass(this.options.class) .addClass("ui-widget ui-widget-content ui-corner-all") .addClass("et2_portlet") /* Gridster */ diff --git a/home/inc/class.home_link_portlet.inc.php b/home/inc/class.home_link_portlet.inc.php index dc8d7773cb..b89868b46b 100644 --- a/home/inc/class.home_link_portlet.inc.php +++ b/home/inc/class.home_link_portlet.inc.php @@ -11,6 +11,9 @@ * @version $Id$ */ + /** + * A single entry is displayed with its application icon and title + */ class home_link_portlet extends home_portlet { @@ -24,6 +27,12 @@ class home_link_portlet extends home_portlet */ protected $title = 'Link'; + /** + * Image shown. Leave at false to have it automatically set an icon based + * on the entry or customize it for the context. + */ + protected $image = false; + /** * Base name for template * @var string @@ -48,6 +57,8 @@ class home_link_portlet extends home_portlet { $this->title = $context['entry']['title'] = egw_link::title($context['entry']['app'], $context['entry']['id']); + // Reload to get the latest title + // TODO: This is a performance hit, it would be good to do this less $need_reload |= (boolean)$context['entry']['id']; } $this->context = $context; @@ -82,27 +93,79 @@ class home_link_portlet extends home_portlet public function exec($id = null, etemplate_new &$etemplate = null) { // Check for custom template for app + $custom_template = false; if($this->context && $this->context['entry'] && $this->context['entry']['app'] && $etemplate->read($this->context['entry']['app'] . '.' . $this->template_name)) { // No action needed, custom template loaded as side-effect + $custom_template = true; } else { $etemplate->read($this->template_name); } + + $etemplate->set_dom_id($id); + + $content = array( + 'image' => $this->image + ); + + // Try to load entry + if($this->context['entry'] && $this->context['entry']['app']) + { + try + { + $classname = $this->context['entry']['app'] . '_egw_record'; + if(class_exists($classname)) + { + $record = new $classname($this->context['entry']['id']); + if($record) + { + // If there's a custom template, send the full record + if($custom_template) + { + $content += $record->get_record_array(); + } + if($content['image'] == false) + { + $content['image'] = $record->get_icon(); + } + } + } + } + catch(Exception $e) + { + error_log("Problem loading record " . array2string($this->context['entry'])); + throw $e; + } + + // Set a fallback image + if($content['image'] == false) + { + if($this->context['entry'] && $this->context['entry']['app']) + { + $content['image'] = $this->context['entry']['app'] . '/navbar'; + } + else + { + $content['image'] = 'home'; + } + } + } + // Filemanager support - links need app = 'file' and type set if($this->context && $this->context['entry'] && $this->context['entry']['app'] == 'filemanager') { $this->context['entry']['app'] = 'file'; $this->context['entry']['path'] = $this->context['entry']['title'] = $this->context['entry']['id']; $this->context['entry']['type'] = egw_vfs::mime_content_type($this->context['entry']['id']); + $content['image'] = egw_framework::link('/etemplate/thumbnail.php',array('path' => $this->context['entry']['id'])); } - $etemplate->set_dom_id($id); - - $content = $this->context; + $content += $this->context; + if(!is_array($content['entry'])) { $content['entry'] = null; diff --git a/home/js/app.js b/home/js/app.js index 36937cc158..2a8557831a 100644 --- a/home/js/app.js +++ b/home/js/app.js @@ -171,7 +171,13 @@ app.classes.home = AppJS.extend( * Add a new portlet from the context menu */ add: function(action, source) { - var attrs = {id: this._create_id(), row: 1, col: 1}; + // Basic portlet attributes + var attrs = { + id: this._create_id(), + class: action.data.class, + width: this.DEFAULT.WIDTH, + height: this.DEFAULT.HEIGHT + }; // Try to put it about where the menu was opened if(action.menu_context) @@ -187,7 +193,7 @@ app.classes.home = AppJS.extend( portlet.loadingFinished(); // Get actual attributes & settings, since they're not available client side yet - portlet._process_edit(et2_dialog.OK_BUTTON, {class: action.id}); + portlet._process_edit(et2_dialog.OK_BUTTON, attrs); // Set up sorting/grid of new portlet var $portlet_container = $j(this.portlet_container.getDOMNode()); @@ -212,7 +218,12 @@ app.classes.home = AppJS.extend( var $portlet_container = $j(this.portlet_container.getDOMNode()); // Basic portlet attributes - var attrs = {id: this._create_id()}; + var attrs = { + id: this._create_id(), + class: action.data.class || action.id.substr(5), + width: this.DEFAULT.WIDTH, + height: this.DEFAULT.HEIGHT + }; // Try to find where the drop was if(action != null && action.ui && action.ui.position) @@ -239,7 +250,7 @@ app.classes.home = AppJS.extend( drop_data.push(source[i].data); } } - portlet._process_edit(et2_dialog.OK_BUTTON, {dropped_data: drop_data, class: action.data.class || action.id.substr(5)}); + 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( @@ -287,14 +298,6 @@ app.classes.home = AppJS.extend( egw().open(widget.options.settings.entry, "", 'edit'); }, - /** - * For list_portlet - adds a new link - * This is needed here so action system can find it - */ - add_link: function(action,source,target_action) { - this.List.add_link(action, source, target_action); - }, - /** * Set up the drag / drop / re-order of portlets */ @@ -395,118 +398,119 @@ app.classes.home = AppJS.extend( /** * Functions for the list portlet */ - List: - { - /** - * For list_portlet - opens a dialog to add a new entry to the list - */ - add_link: function(action, source, target_action) { - // Actions got confused drop vs popup - if(source[0].id == 'portlets') - { - return this.add_link(action); - } + /** + * 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: function(action, source, target_action) { + // Actions got confused drop vs popup + if(source[0].id == 'portlets') + { + return this.add_link(action); + } - // Get widget - var widget = null; - while(action.parent != null) + // Get widget + var widget = null; + while(action.parent != null) + { + if(action.data && action.data.widget) { - if(action.data && action.data.widget) - { - widget = action.data.widget; - break; - } - action = action.parent; + widget = action.data.widget; + break; } - if(target_action == null) - { - // use template base url from initial template, to continue using webdav, if that was loaded via webdav - var splitted = 'home.edit'.split('.'); - var path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" + - splitted.join('.')+ ".xet"; - var dialog = et2_createWidget("dialog",{ - callback: function(button_id, value) { - if(button_id == et2_dialog.CANCEL_BUTTON) return; - var new_list = widget.options.settings.list || []; - for(var 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 - widget.getWidgetById('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 - var new_list = widget.options.settings.list || []; - var changed = false; - for(var i = 0; i < new_list.length; i++) - { - // Avoid duplicates - for(var j = 0; j < source.length; j++) + action = action.parent; + } + if(target_action == null) + { + // use template base url from initial template, to continue using webdav, if that was loaded via webdav + var splitted = 'home.edit'.split('.'); + var path = app.home.portlet_container.getRoot()._inst.template_base_url + splitted.shift() + "/templates/default/" + + splitted.join('.')+ ".xet"; + var dialog = et2_createWidget("dialog",{ + callback: function(button_id, value) { + if(button_id == et2_dialog.CANCEL_BUTTON) return; + var new_list = widget.options.settings.list || []; + for(var i = 0; i < new_list.length; i++) { - if(!source[j].id || new_list[i].app+"::"+new_list[i].id == source[j].id) + if(new_list[i].app == value.add.app && new_list[i].id == value.add.id) { // Duplicate - skip it - source.splice(j,1); + return; } } - } - for(var i = 0; i < source.length; i++) + 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 + widget.getWidgetById('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 + var new_list = widget.options.settings.list || []; + var changed = false; + for(var i = 0; i < new_list.length; i++) + { + // Avoid duplicates + for(var j = 0; j < source.length; j++) { - var 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(var i = 0; i < new_list.length; i++) - { - if(new_list[i]['app'] == 'filemanager') + if(!source[j].id || new_list[i].app+"::"+new_list[i].id == source[j].id) { - new_list[i]['app'] = 'file'; - new_list[i]['path'] = new_list[i]['title'] = new_list[i]['icon'] = new_list[i]['id']; + // Duplicate - skip it + source.splice(j,1); } } - - widget.getWidgetById('list').set_value(new_list); - } - }, + for(var i = 0; i < source.length; i++) + { + var 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(var 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']; + } + } - /** - * Remove a link from the list - */ - link_change: function(list, link_id, row) { - // Quick response client side - row.slideUp(row.remove); + widget.getWidgetById('list').set_value(new_list); - // Actual removal - var portlet = list._parent._parent; - portlet.options.settings.list.splice(row.index(), 1); - portlet._process_edit(et2_dialog.OK_BUTTON,{list: portlet.options.settings.list || {}}); } }, + /** + * Remove a link from the list + */ + link_change: function(list, link_id, row) { + // Quick response client side + row.slideUp(row.remove); + + // Actual removal + var portlet = list._parent._parent; + portlet.options.settings.list.splice(row.index(), 1); + portlet._process_edit(et2_dialog.OK_BUTTON,{list: portlet.options.settings.list || {}}); + }, + /** * Functions for the note portlet */ @@ -543,5 +547,33 @@ app.classes.home = AppJS.extend( id: id, height: window_height - 70 }),'home_'+id, window_width+'x'+window_height,'home'); + }, + + /** + * Favorites / nextmatch + */ + /** + * Toggle the nextmatch header shown / hidden + * + * @param {Event} event + * @param {et2_button} widget + */ + nextmatch_toggle_header: function(event, widget) { + widget.set_image(widget.options.image == 'arrow_down' ? 'arrow_left' : 'arrow_down'); + // We operate on the DOM here, nm should be unaware of our fiddling + var nm = widget.getParent().getWidgetById('nm'); + if(!nm) return; + var header = nm.header; + var header_height = header.div.innerHeight(); + + // Hide header + nm.div.toggleClass('header_hidden'); + + header_height -= header.div.height(); + + // Grow row space - I have no idea why it needs to be 25 pixels instead of header_height + var scroll_height = $j('.egwGridView_scrollarea',nm.getDOMNode()).height(); + $j('.egwGridView_scrollarea',nm.getDOMNode()).height(scroll_height + (header_height > 0 ? 25 : -25)); + nm.resize(); } }); diff --git a/home/templates/default/app.css b/home/templates/default/app.css index ddc71ae928..8a83e7be2a 100644 --- a/home/templates/default/app.css +++ b/home/templates/default/app.css @@ -2,6 +2,9 @@ * Home CSS */ +/** + * Basic layout and structural CSS + */ #home-index_home-index { height:100%; } @@ -20,8 +23,8 @@ cursor: pointer; } -.et2_portlet.ui-widget-content { - overflow: hidden; +.et2_portlet.ui-widget-content > div { + } .et2_portlet.ui-widget-content > div:last-of-type { /* Allow space for header, as the whole portlet is sized by auto-generated css */ @@ -29,13 +32,11 @@ bottom: 0px; top: 20px; width: 100%; + overflow: hidden; } .et2_portlet .et2_container { height: 100%; } -.et2_portlet.ui-widget-content > div:last-of-type > div { - background: linear-gradient(to bottom, rgba(255,255,255,.9) 10%,rgba(255,255,255,.75) 90%) /* W3C */ -} /* Gridster */ #portlets { @@ -54,3 +55,65 @@ border: 1px solid silver; position: absolute; } + +/** + * Portlet styling (cosmetic) + */ +.et2_portlet.ui-widget-content > div:last-of-type > div { + background: linear-gradient(to bottom, rgba(255,255,255,.9) 10%,rgba(255,255,255,.75) 90%) /* W3C */ +} + +/* Single entry */ +.et2_portlet.home_link_portlet > .ui-widget-header { + display: none; + position: relative; + top: -20px; +} +.et2_portlet.home_link_portlet:hover > .ui-widget-header { + display: block; + z-index: 90; +} +.et2_portlet.home_link_portlet.ui-widget-content > div:last-of-type { + top: 0px +} +.et2_portlet.home_link_portlet > div:last-of-type > div { + padding: 10px; +} +/* Thumbnail / icon */ +.et2_portlet.home_link_portlet > div:last-of-type img:first-of-type { + float: left; + margin-right: 8px; + margin-bottom: 8px; + width: 32px; + height: 32px; +} + +/* Favorite / nextmatch */ +.et2_portlet .et2_container > div > .et2_button { + float: left; + margin-bottom: -16px; + min-height: 16px; + min-width: 16px; +} +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header { + min-height: 6px; +} +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header .ui-helper-reset{ + height: 0px; + padding: 0px; +} +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row .header_count { + top: -11px; + height: 14px; +} +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row .header_count span { + top: 0px; +} +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row div.search, +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row label, +.et2_portlet .et2_nextmatch.header_hidden .nextmatch_header div.nextmatch_header_row select { + display:none; +} +.et2_portlet .et2_nextmatch.header_hidden .egwGridView_outer:first-of-type { + position: absolute; +} \ No newline at end of file diff --git a/home/templates/default/favorite.xet b/home/templates/default/favorite.xet index 54a374b7b4..4db206f1f2 100644 --- a/home/templates/default/favorite.xet +++ b/home/templates/default/favorite.xet @@ -2,6 +2,7 @@ diff --git a/home/templates/default/index.xet b/home/templates/default/index.xet index fe9fef2c6e..11d4e97a5d 100644 --- a/home/templates/default/index.xet +++ b/home/templates/default/index.xet @@ -13,7 +13,7 @@ - + diff --git a/home/templates/default/link.xet b/home/templates/default/link.xet index 3c93690296..627ab98eea 100644 --- a/home/templates/default/link.xet +++ b/home/templates/default/link.xet @@ -1,6 +1,7 @@ \ No newline at end of file diff --git a/home/templates/default/list.xet b/home/templates/default/list.xet index 158e0bc0f3..b22a179472 100644 --- a/home/templates/default/list.xet +++ b/home/templates/default/list.xet @@ -2,6 +2,6 @@