2022-05-18 19:01:27 +02:00
|
|
|
/**
|
|
|
|
* EGroupware eTemplate2 - JS Favorite widget
|
|
|
|
*
|
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @package etemplate
|
|
|
|
* @subpackage api
|
|
|
|
* @link https://www.egroupware.org
|
|
|
|
* @author Nathan Gray
|
|
|
|
* @copyright Nathan Gray 2022
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {Et2DropdownButton} from "../Et2DropdownButton/Et2DropdownButton";
|
2023-09-13 19:55:33 +02:00
|
|
|
import {css, html, PropertyValues, TemplateResult} from "lit";
|
2022-05-18 19:01:27 +02:00
|
|
|
import {SelectOption} from "../Et2Select/FindSelectOptions";
|
|
|
|
import {et2_INextmatchHeader, et2_nextmatch} from "../et2_extension_nextmatch";
|
|
|
|
import {Et2Image} from "../Et2Image/Et2Image";
|
|
|
|
import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
|
|
|
|
import {SlMenuItem} from "@shoelace-style/shoelace";
|
2022-06-28 22:09:29 +02:00
|
|
|
import {cssImage} from "../Et2Widget/Et2Widget";
|
2024-06-12 19:48:50 +02:00
|
|
|
import {Favorite} from "./Favorite";
|
2022-05-18 19:01:27 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Favorites widget, designed for use in the nextmatch header
|
|
|
|
*
|
|
|
|
* The primary control is a split/dropdown button. Clicking on the left side of the button filters the
|
|
|
|
* nextmatch list by the user's default filter. The right side of the button gives a list of
|
|
|
|
* saved filters, pulled from preferences. Clicking a filter from the dropdown list sets the
|
|
|
|
* filters as saved.
|
|
|
|
*
|
|
|
|
* Favorites can also automatically be shown in the sidebox, using the special ID favorite_sidebox.
|
|
|
|
* Use the following code to generate the sidebox section:
|
|
|
|
* display_sidebox($appname,lang('Favorites'),array(
|
|
|
|
* array(
|
|
|
|
* 'no_lang' => true,
|
|
|
|
* 'text'=>'<span id="favorite_sidebox"/>',
|
|
|
|
* 'link'=>false,
|
|
|
|
* 'icon' => false
|
|
|
|
* )
|
|
|
|
* ));
|
|
|
|
* This sidebox list will be automatically generated and kept up to date.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* Favorites are implemented by saving the values for [column] filters. Filters are stored
|
|
|
|
* in preferences, with the name favorite_<name>. The favorite favorite used for clicking on
|
|
|
|
* the filter button is stored in nextmatch-<columnselection_pref>-favorite.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHeader
|
|
|
|
{
|
|
|
|
static get styles()
|
|
|
|
{
|
|
|
|
return [
|
|
|
|
...super.styles,
|
|
|
|
css`
|
2023-01-26 17:22:45 +01:00
|
|
|
:host {
|
2022-08-03 18:14:52 +02:00
|
|
|
min-width: 8ex;
|
2023-01-26 17:22:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
et2-image {
|
2023-12-04 10:31:05 +01:00
|
|
|
display: flex;
|
|
|
|
height: 100%;
|
|
|
|
width: ${egwIsMobile() ? css`4ex` : css`2ex`}
|
2023-01-26 17:22:45 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
et2-image[src="trash"] {
|
|
|
|
display: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
sl-menu {
|
2022-06-28 22:09:29 +02:00
|
|
|
min-width: 15em;
|
2023-01-26 17:22:45 +01:00
|
|
|
}
|
|
|
|
|
2024-01-08 23:24:09 +01:00
|
|
|
sl-menu-item:hover et2-image[src="trash"] {
|
2023-01-26 17:22:45 +01:00
|
|
|
display: initial;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Add star icons - radio button is already in prefix */
|
|
|
|
|
2024-01-08 23:24:09 +01:00
|
|
|
sl-menu-item::part(base) {
|
2022-06-28 22:09:29 +02:00
|
|
|
background-image: ${cssImage("fav_filter")};
|
|
|
|
background-repeat: no-repeat;
|
|
|
|
background-size: 16px 16px;
|
|
|
|
background-position: 5px center;
|
2023-01-26 17:22:45 +01:00
|
|
|
}
|
|
|
|
|
2024-01-08 23:24:09 +01:00
|
|
|
sl-menu-item[checked]::part(base) {
|
2022-06-28 22:09:29 +02:00
|
|
|
background-image: ${cssImage("favorites")};
|
2023-01-26 17:22:45 +01:00
|
|
|
}
|
|
|
|
|
2024-01-08 23:24:09 +01:00
|
|
|
sl-menu-item:last-child::part(base) {
|
2022-06-28 22:09:29 +02:00
|
|
|
background-image: none;
|
2023-01-26 17:22:45 +01:00
|
|
|
}
|
|
|
|
`,
|
2022-05-18 19:01:27 +02:00
|
|
|
];
|
|
|
|
}
|
|
|
|
|
|
|
|
static get properties()
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
...super.properties,
|
|
|
|
// Where we keep the "default" preference
|
2022-07-21 17:57:50 +02:00
|
|
|
defaultPref: {type: String},
|
2022-05-18 19:01:27 +02:00
|
|
|
// Application to show favorites for
|
|
|
|
app: {type: String},
|
|
|
|
// Extra filters to include in the saved favorite
|
|
|
|
filters: {type: Object}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Favorites are prefixed in preferences
|
|
|
|
public static readonly PREFIX = "favorite_";
|
2024-06-12 19:48:50 +02:00
|
|
|
static readonly ADD_VALUE = "~add~";
|
2022-05-18 19:01:27 +02:00
|
|
|
|
2022-08-04 09:08:46 +02:00
|
|
|
private favSortedList : any = [];
|
2022-05-18 19:01:27 +02:00
|
|
|
private _preferred : string;
|
|
|
|
private _nextmatch : et2_nextmatch;
|
|
|
|
|
|
|
|
constructor()
|
|
|
|
{
|
|
|
|
|
|
|
|
super();
|
|
|
|
this.__statustext = "Favorite queries";
|
|
|
|
this._handleRadio = this._handleRadio.bind(this);
|
|
|
|
this._handleDelete = this._handleDelete.bind(this);
|
|
|
|
}
|
|
|
|
|
|
|
|
connectedCallback()
|
|
|
|
{
|
|
|
|
super.connectedCallback();
|
|
|
|
if(!this.id)
|
|
|
|
{
|
|
|
|
this.id = "favorite";
|
|
|
|
}
|
|
|
|
|
2022-07-21 17:57:50 +02:00
|
|
|
this._preferred = <string>this.egw().preference(this.defaultPref, this.app);
|
2022-05-18 19:01:27 +02:00
|
|
|
|
|
|
|
// Need to wait until update is done and these exist
|
|
|
|
this.updateComplete.then(() =>
|
|
|
|
{
|
|
|
|
if(this.buttonNode)
|
|
|
|
{
|
|
|
|
let img = new Et2Image();
|
|
|
|
img.src = "fav_filter";
|
|
|
|
this.buttonNode.append(img);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
set select_options(_new_options : SelectOption[])
|
|
|
|
{
|
|
|
|
// We don't actually want your options, thanks.
|
|
|
|
}
|
|
|
|
|
|
|
|
get select_options() : SelectOption[]
|
|
|
|
{
|
|
|
|
if(this.__select_options.length)
|
|
|
|
{
|
|
|
|
return this.__select_options;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-10 00:47:59 +01:00
|
|
|
get preferred() : string
|
|
|
|
{
|
|
|
|
return this._preferred;
|
|
|
|
}
|
|
|
|
|
2022-05-18 19:01:27 +02:00
|
|
|
_optionTemplate(option : SelectOption) : TemplateResult
|
|
|
|
{
|
|
|
|
let radio = html`<input type="radio" slot="prefix" name="favorite" value="${option.value}"
|
|
|
|
?checked="${option.value == this._preferred}"
|
|
|
|
@change=${this._handleRadio}
|
|
|
|
title="${this.egw().lang('Set as default')}"/>`;
|
|
|
|
|
|
|
|
//@ts-ignore TS doesn't know about window.app
|
2022-06-28 22:09:29 +02:00
|
|
|
let is_admin = (typeof this.egw().app('admin') != "undefined");
|
2022-05-18 19:01:27 +02:00
|
|
|
//@ts-ignore option.group does not exist
|
2024-01-08 23:24:09 +01:00
|
|
|
let icon = (option.group !== false && !is_admin || ['blank', '~add~'].includes(option.value)) ? "" : html`
|
2022-05-18 19:01:27 +02:00
|
|
|
<et2-image slot="suffix" src=${"trash"} icon @click=${this._handleDelete}
|
|
|
|
statustext="${this.egw().lang("Delete")}"></et2-image>`;
|
|
|
|
|
|
|
|
return html`
|
2024-01-18 16:35:12 +01:00
|
|
|
<sl-menu-item value="${option.value}">
|
2022-05-18 19:01:27 +02:00
|
|
|
${option.value !== Et2Favorites.ADD_VALUE ? radio : ""}
|
|
|
|
${icon}
|
|
|
|
${option.label}
|
2024-01-08 23:24:09 +01:00
|
|
|
</sl-menu-item>`;
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2024-05-07 22:46:44 +02:00
|
|
|
/** @param changedProperties */
|
2022-05-18 19:01:27 +02:00
|
|
|
updated(changedProperties : PropertyValues)
|
|
|
|
{
|
|
|
|
super.updated(changedProperties);
|
|
|
|
if(changedProperties.has("app"))
|
|
|
|
{
|
2022-07-21 17:57:50 +02:00
|
|
|
this._preferred = <string>this.egw().preference(this.defaultPref, this.app);
|
2024-06-12 19:48:50 +02:00
|
|
|
this._load_favorites(this.app);
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Load favorites from preferences
|
|
|
|
*
|
|
|
|
* @param app String Load favorites from this application
|
|
|
|
*/
|
|
|
|
_load_favorites(app)
|
|
|
|
{
|
2024-06-12 19:48:50 +02:00
|
|
|
Favorite.load(this.egw(), app).then((favorites) =>
|
2022-05-18 19:01:27 +02:00
|
|
|
{
|
2024-06-12 19:48:50 +02:00
|
|
|
let options = [];
|
|
|
|
Object.keys(favorites).forEach((name) =>
|
2022-05-18 19:01:27 +02:00
|
|
|
{
|
2024-06-12 19:48:50 +02:00
|
|
|
options.push(Object.assign({value: name, label: favorites[name].name || name}, favorites[name]));
|
|
|
|
})
|
|
|
|
// Only add 'Add current' if we have a nextmatch
|
|
|
|
if(this._nextmatch)
|
2022-05-18 19:01:27 +02:00
|
|
|
{
|
2024-06-12 19:48:50 +02:00
|
|
|
options.push({value: Et2Favorites.ADD_VALUE, label: this.egw().lang('Add current')});
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
2024-06-12 19:48:50 +02:00
|
|
|
this.__select_options = options
|
|
|
|
this.requestUpdate("select_options");
|
|
|
|
});
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
|
|
|
|
2023-02-10 00:47:59 +01:00
|
|
|
public load_favorites(app)
|
|
|
|
{
|
2024-06-12 19:48:50 +02:00
|
|
|
this._load_favorites(app);
|
2023-02-10 00:47:59 +01:00
|
|
|
}
|
|
|
|
|
2022-05-18 19:01:27 +02:00
|
|
|
/**
|
|
|
|
* Add the current settings as a new favorite
|
|
|
|
*/
|
|
|
|
_add_current()
|
|
|
|
{
|
|
|
|
// Get current filters
|
|
|
|
let current_filters = Object.assign({}, this._nextmatch.activeFilters);
|
|
|
|
|
|
|
|
// Add in extras
|
|
|
|
for(let extra in this.filters)
|
|
|
|
{
|
|
|
|
// Don't overwrite what nm has, chances are nm has more up-to-date value
|
|
|
|
if(typeof current_filters == 'undefined')
|
|
|
|
{
|
|
|
|
// @ts-ignore
|
|
|
|
current_filters[extra] = this._nextmatch.options.settings[extra];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Skip columns
|
|
|
|
delete current_filters.selectcols;
|
|
|
|
|
|
|
|
// Add in application's settings
|
|
|
|
if(this.filters != true)
|
|
|
|
{
|
|
|
|
for(let i = 0; i < this.filters.length; i++)
|
|
|
|
{
|
|
|
|
current_filters[this.filters[i]] = this._nextmatch.options.settings[this.filters[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call framework
|
|
|
|
//@ts-ignore TS doesn't know about window.app
|
|
|
|
window.app[this.app].add_favorite(current_filters);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a favorite from the list by id
|
|
|
|
*/
|
|
|
|
favoriteByID(id : string) : any
|
|
|
|
{
|
|
|
|
if(!id)
|
|
|
|
{
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return this.__select_options.find(f => f.value == id)
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Clicked on an option
|
|
|
|
*
|
|
|
|
* @param ev
|
|
|
|
* @protected
|
|
|
|
*/
|
|
|
|
protected _handleSelect(ev)
|
|
|
|
{
|
|
|
|
if(ev.detail.item.value == Et2Favorites.ADD_VALUE)
|
|
|
|
{
|
|
|
|
return this._add_current();
|
|
|
|
}
|
|
|
|
this._value = ev.detail.item.value;
|
|
|
|
|
2024-06-12 19:48:50 +02:00
|
|
|
Favorite.applyFavorite(this.egw(), this.app, ev.detail.item.value);
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
|
|
|
|
2024-01-08 23:24:09 +01:00
|
|
|
/**
|
|
|
|
* Handle the click from the main button
|
|
|
|
*
|
|
|
|
* @param {MouseEvent} event
|
|
|
|
* @protected
|
|
|
|
*/
|
|
|
|
protected _handleClick(event : MouseEvent)
|
|
|
|
{
|
2024-06-12 19:48:50 +02:00
|
|
|
Favorite.applyFavorite(this.egw, this.app, this.preferred);
|
2024-01-08 23:24:09 +01:00
|
|
|
}
|
|
|
|
|
2022-05-18 19:01:27 +02:00
|
|
|
/**
|
|
|
|
* Clicked a radio button
|
|
|
|
*
|
|
|
|
* @param _ev
|
|
|
|
* @protected
|
|
|
|
*/
|
|
|
|
protected _handleRadio(_ev)
|
|
|
|
{
|
|
|
|
// Don't do the menu
|
|
|
|
_ev.stopImmediatePropagation();
|
|
|
|
|
|
|
|
// Save as default favorite - used when you click the button
|
|
|
|
let pref = _ev.target.value;
|
2022-07-21 17:57:50 +02:00
|
|
|
this.egw().set_preference(this.app, this.defaultPref, pref);
|
2022-05-18 19:01:27 +02:00
|
|
|
this._preferred = pref;
|
|
|
|
this.dropdownNode.hide();
|
2023-02-10 00:47:59 +01:00
|
|
|
this.requestUpdate("select_options");
|
|
|
|
|
|
|
|
this.dispatchEvent(new Event("change", {bubbles: true}));
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
_handleDelete(_ev : MouseEvent)
|
|
|
|
{
|
|
|
|
// Don't do the menu
|
|
|
|
_ev.stopImmediatePropagation();
|
|
|
|
|
|
|
|
let trash = <HTMLElement>(<HTMLElement>_ev.target).parentNode;
|
|
|
|
let line = <SlMenuItem>trash.parentNode;
|
|
|
|
let fav = this.favoriteByID(line.value);
|
|
|
|
line.classList.add("loading");
|
|
|
|
|
|
|
|
// Make sure first
|
|
|
|
let do_delete = function(button_id)
|
|
|
|
{
|
|
|
|
if(button_id != Et2Dialog.YES_BUTTON)
|
|
|
|
{
|
|
|
|
line.classList.remove('loading');
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hide the trash
|
|
|
|
trash.remove();
|
|
|
|
|
|
|
|
// Delete preference server side
|
2024-06-12 19:48:50 +02:00
|
|
|
Favorite.remove(this.egw(), this.app, line.value).then(response =>
|
2022-05-18 19:01:27 +02:00
|
|
|
{
|
|
|
|
line.classList.remove("loading");
|
|
|
|
|
|
|
|
let result = response.response.find(r => r.type == "data");
|
|
|
|
|
|
|
|
// Could not find the result we want
|
|
|
|
if(!result || result.type !== "data")
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(typeof result.data == 'boolean' && result.data)
|
|
|
|
{
|
|
|
|
// Remove line from list
|
|
|
|
line.remove();
|
|
|
|
|
|
|
|
// Remove favorite from options
|
|
|
|
this.__select_options = this.__select_options.filter(f => f.value != fav.value);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Something went wrong server side
|
|
|
|
line.classList.add('error');
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}.bind(this);
|
|
|
|
Et2Dialog.show_dialog(do_delete, (this.egw().lang("Delete") + " " + fav.name + "?"),
|
|
|
|
"Delete", null, Et2Dialog.BUTTONS_YES_NO, Et2Dialog.QUESTION_MESSAGE);
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set the nextmatch to filter
|
|
|
|
* From et2_INextmatchHeader interface
|
|
|
|
*
|
|
|
|
* @param {et2_nextmatch} nextmatch
|
|
|
|
*/
|
|
|
|
setNextmatch(nextmatch)
|
|
|
|
{
|
|
|
|
this._nextmatch = nextmatch;
|
|
|
|
|
|
|
|
if(this.nm_filter)
|
|
|
|
{
|
|
|
|
this.set_value(this.nm_filter);
|
|
|
|
this.nm_filter = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Re-generate filter list so we can add 'Add current'
|
2024-06-12 19:48:50 +02:00
|
|
|
this._load_favorites(this.app);
|
2022-05-18 19:01:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
customElements.define("et2-favorites", Et2Favorites);
|