mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-11-28 10:53:39 +01:00
Refactor Favorites UI
- Move common stuff into Favorite.ts - New widget Et2FavoritesMenu that's just a menu - Et2Favorite unchanged, still dependent on nextmatch
This commit is contained in:
parent
16c727b84a
commit
95f1034abd
@ -17,6 +17,7 @@ import {Et2Image} from "../Et2Image/Et2Image";
|
|||||||
import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
|
import {Et2Dialog} from "../Et2Dialog/Et2Dialog";
|
||||||
import {SlMenuItem} from "@shoelace-style/shoelace";
|
import {SlMenuItem} from "@shoelace-style/shoelace";
|
||||||
import {cssImage} from "../Et2Widget/Et2Widget";
|
import {cssImage} from "../Et2Widget/Et2Widget";
|
||||||
|
import {Favorite} from "./Favorite";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Favorites widget, designed for use in the nextmatch header
|
* Favorites widget, designed for use in the nextmatch header
|
||||||
@ -108,7 +109,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
|
|
||||||
// Favorites are prefixed in preferences
|
// Favorites are prefixed in preferences
|
||||||
public static readonly PREFIX = "favorite_";
|
public static readonly PREFIX = "favorite_";
|
||||||
protected static readonly ADD_VALUE = "~add~";
|
static readonly ADD_VALUE = "~add~";
|
||||||
|
|
||||||
private favSortedList : any = [];
|
private favSortedList : any = [];
|
||||||
private _preferred : string;
|
private _preferred : string;
|
||||||
@ -193,8 +194,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
if(changedProperties.has("app"))
|
if(changedProperties.has("app"))
|
||||||
{
|
{
|
||||||
this._preferred = <string>this.egw().preference(this.defaultPref, this.app);
|
this._preferred = <string>this.egw().preference(this.defaultPref, this.app);
|
||||||
this.__select_options = this._load_favorites(this.app);
|
this._load_favorites(this.app);
|
||||||
this.requestUpdate("select_options");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,68 +205,8 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
*/
|
*/
|
||||||
_load_favorites(app)
|
_load_favorites(app)
|
||||||
{
|
{
|
||||||
|
Favorite.load(this.egw(), app).then((favorites) =>
|
||||||
// Default blank filter
|
|
||||||
let favorites : any = {
|
|
||||||
'blank': {
|
|
||||||
name: this.egw().lang("No filters"),
|
|
||||||
state: {}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load saved favorites
|
|
||||||
this.favSortedList = [];
|
|
||||||
let preferences : any = this.egw().preference("*", app);
|
|
||||||
for(let pref_name in preferences)
|
|
||||||
{
|
{
|
||||||
if(pref_name.indexOf(Et2Favorites.PREFIX) == 0 && typeof preferences[pref_name] == 'object')
|
|
||||||
{
|
|
||||||
let name = pref_name.substr(Et2Favorites.PREFIX.length);
|
|
||||||
favorites[name] = preferences[pref_name];
|
|
||||||
// Keep older favorites working - they used to store nm filters in 'filters',not state
|
|
||||||
if(preferences[pref_name]["filters"])
|
|
||||||
{
|
|
||||||
favorites[pref_name]["state"] = preferences[pref_name]["filters"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(pref_name == 'fav_sort_pref')
|
|
||||||
{
|
|
||||||
this.favSortedList = preferences[pref_name];
|
|
||||||
//Make sure sorted list is always an array, seems some old fav are not array
|
|
||||||
if(!Array.isArray(this.favSortedList))
|
|
||||||
{
|
|
||||||
this.favSortedList = this.favSortedList.split(',');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for(let name in favorites)
|
|
||||||
{
|
|
||||||
if(this.favSortedList.indexOf(name) < 0)
|
|
||||||
{
|
|
||||||
this.favSortedList.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.egw().set_preference(this.app, 'fav_sort_pref', this.favSortedList);
|
|
||||||
if(this.favSortedList.length > 0)
|
|
||||||
{
|
|
||||||
let sortedListObj = {};
|
|
||||||
|
|
||||||
for(let i = 0; i < this.favSortedList.length; i++)
|
|
||||||
{
|
|
||||||
if(typeof favorites[this.favSortedList[i]] != 'undefined')
|
|
||||||
{
|
|
||||||
sortedListObj[this.favSortedList[i]] = favorites[this.favSortedList[i]];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
this.favSortedList.splice(i, 1);
|
|
||||||
this.egw().set_preference(this.app, 'fav_sort_pref', this.favSortedList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
favorites = Object.assign(sortedListObj, favorites);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = [];
|
let options = [];
|
||||||
Object.keys(favorites).forEach((name) =>
|
Object.keys(favorites).forEach((name) =>
|
||||||
{
|
{
|
||||||
@ -277,15 +217,14 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
{
|
{
|
||||||
options.push({value: Et2Favorites.ADD_VALUE, label: this.egw().lang('Add current')});
|
options.push({value: Et2Favorites.ADD_VALUE, label: this.egw().lang('Add current')});
|
||||||
}
|
}
|
||||||
|
this.__select_options = options
|
||||||
this.requestUpdate("select_options");
|
this.requestUpdate("select_options");
|
||||||
return options;
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public load_favorites(app)
|
public load_favorites(app)
|
||||||
{
|
{
|
||||||
this.__select_options = this._load_favorites(app);
|
this._load_favorites(app);
|
||||||
this.requestUpdate("select_options");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,7 +288,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
}
|
}
|
||||||
this._value = ev.detail.item.value;
|
this._value = ev.detail.item.value;
|
||||||
|
|
||||||
this._apply_favorite(ev.detail.item.value);
|
Favorite.applyFavorite(this.egw(), this.app, ev.detail.item.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -360,7 +299,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
*/
|
*/
|
||||||
protected _handleClick(event : MouseEvent)
|
protected _handleClick(event : MouseEvent)
|
||||||
{
|
{
|
||||||
this._apply_favorite(this.preferred);
|
Favorite.applyFavorite(this.egw, this.app, this.preferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -407,9 +346,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
trash.remove();
|
trash.remove();
|
||||||
|
|
||||||
// Delete preference server side
|
// Delete preference server side
|
||||||
let request = this.egw().json("EGroupware\\Api\\Framework::ajax_set_favorite",
|
Favorite.remove(this.egw(), this.app, line.value).then(response =>
|
||||||
[this.app, fav.name, "delete", "" + fav.group, '']).sendRequest();
|
|
||||||
request.then(response =>
|
|
||||||
{
|
{
|
||||||
line.classList.remove("loading");
|
line.classList.remove("loading");
|
||||||
|
|
||||||
@ -442,24 +379,6 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Apply a favorite to the app or nextmatch
|
|
||||||
*
|
|
||||||
* @param {string} favorite_name
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected _apply_favorite(favorite_name : string)
|
|
||||||
{
|
|
||||||
let fav = favorite_name == "blank" ? {} : this.favoriteByID(favorite_name);
|
|
||||||
// use app[appname].setState if available to allow app to overwrite it (eg. change to non-listview in calendar)
|
|
||||||
//@ts-ignore TS doesn't know about window.app
|
|
||||||
if(typeof window.app[this.app] != 'undefined')
|
|
||||||
{
|
|
||||||
//@ts-ignore TS doesn't know about window.app
|
|
||||||
window.app[this.app].setState(fav);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the nextmatch to filter
|
* Set the nextmatch to filter
|
||||||
* From et2_INextmatchHeader interface
|
* From et2_INextmatchHeader interface
|
||||||
@ -477,8 +396,7 @@ export class Et2Favorites extends Et2DropdownButton implements et2_INextmatchHea
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Re-generate filter list so we can add 'Add current'
|
// Re-generate filter list so we can add 'Add current'
|
||||||
this.__select_options = this._load_favorites(this.app);
|
this._load_favorites(this.app);
|
||||||
this.requestUpdate("select_options");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
115
api/js/etemplate/Et2Favorites/Et2FavoritesMenu.ts
Normal file
115
api/js/etemplate/Et2Favorites/Et2FavoritesMenu.ts
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
import {css, html, LitElement, TemplateResult} from "lit";
|
||||||
|
import {customElement} from "lit/decorators/custom-element.js";
|
||||||
|
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
||||||
|
import {Favorite} from "./Favorite";
|
||||||
|
import {property} from "lit/decorators/property.js";
|
||||||
|
import {until} from "lit/directives/until.js";
|
||||||
|
import {repeat} from "lit/directives/repeat.js";
|
||||||
|
|
||||||
|
@customElement("et2-favorites-menu")
|
||||||
|
export class Et2FavoritesMenu extends Et2Widget(LitElement)
|
||||||
|
{
|
||||||
|
|
||||||
|
static get styles()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
super.styles,
|
||||||
|
css`
|
||||||
|
:host {
|
||||||
|
min-width: 15em;
|
||||||
|
}
|
||||||
|
|
||||||
|
et2-image[src="trash"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
sl-menu-item:hover et2-image[src="trash"] {
|
||||||
|
display: initial;
|
||||||
|
}`
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
@property()
|
||||||
|
application : string;
|
||||||
|
|
||||||
|
private favorites : { [name : string] : Favorite }
|
||||||
|
private loadingPromise = Promise.resolve();
|
||||||
|
|
||||||
|
connectedCallback()
|
||||||
|
{
|
||||||
|
super.connectedCallback();
|
||||||
|
|
||||||
|
if(this.application)
|
||||||
|
{
|
||||||
|
this.loadingPromise = Favorite.load(this.egw(), this.application).then((favorites) =>
|
||||||
|
{
|
||||||
|
this.favorites = favorites;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSelect(event)
|
||||||
|
{
|
||||||
|
Favorite.applyFavorite(this.egw(), this.application, event.detail.item.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDelete(event)
|
||||||
|
{
|
||||||
|
// Don't trigger click
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
const menuItem = event.target.closest("sl-menu-item");
|
||||||
|
menuItem.setAttribute("loading", "");
|
||||||
|
|
||||||
|
const favoriteName = menuItem.value;
|
||||||
|
|
||||||
|
// Remove from server
|
||||||
|
Favorite.remove(this.egw(), this.application, favoriteName).then(() =>
|
||||||
|
{
|
||||||
|
// Remove from widget
|
||||||
|
delete this.favorites[favoriteName];
|
||||||
|
this.requestUpdate();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected menuItemTemplate(name : string, favorite : Favorite) : TemplateResult
|
||||||
|
{
|
||||||
|
let is_admin = (typeof this.egw().app('admin') != "undefined");
|
||||||
|
|
||||||
|
//@ts-ignore option.group does not exist
|
||||||
|
let icon = (favorite.group !== false && !is_admin || ['blank', '~add~'].includes(name)) ? "" : html`
|
||||||
|
<et2-image slot="suffix" src="trash" icon @click=${this.handleDelete}
|
||||||
|
statustext="${this.egw().lang("Delete")}"></et2-image>`;
|
||||||
|
|
||||||
|
return html`
|
||||||
|
<sl-menu-item value="${name}">
|
||||||
|
${icon}
|
||||||
|
${favorite.name}
|
||||||
|
</sl-menu-item>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected loadingTemplate()
|
||||||
|
{
|
||||||
|
return html`
|
||||||
|
<sl-menu-item loading>${this.egw().lang("Loading")}</sl-menu-item>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
render()
|
||||||
|
{
|
||||||
|
let content = this.loadingPromise.then(() =>
|
||||||
|
{
|
||||||
|
return html`
|
||||||
|
<sl-menu
|
||||||
|
@sl-select=${this.handleSelect}
|
||||||
|
>
|
||||||
|
${repeat(Object.keys(this.favorites), (i) => this.menuItemTemplate(i, this.favorites[i]))}
|
||||||
|
</sl-menu>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
return html`
|
||||||
|
${until(content, this.loadingTemplate())}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
109
api/js/etemplate/Et2Favorites/Favorite.ts
Normal file
109
api/js/etemplate/Et2Favorites/Favorite.ts
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
export class Favorite
|
||||||
|
{
|
||||||
|
name : string
|
||||||
|
state : object
|
||||||
|
group : number | false
|
||||||
|
|
||||||
|
// Favorites are prefixed in preferences
|
||||||
|
public static readonly PREFIX = "favorite_";
|
||||||
|
public static readonly ADD_VALUE = "~add~";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load favorites from preferences
|
||||||
|
*
|
||||||
|
* @param app String Load favorites from this application
|
||||||
|
*/
|
||||||
|
static async load(egw, app : string) : Promise<{ [name : string] : Favorite }>
|
||||||
|
{
|
||||||
|
// Default blank filter
|
||||||
|
let favorites : { [name : string] : Favorite } = {
|
||||||
|
'blank': {
|
||||||
|
name: egw.lang("No filters"),
|
||||||
|
state: {},
|
||||||
|
group: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load saved favorites
|
||||||
|
let sortedList = [];
|
||||||
|
let preferences : any = await egw.preference("*", app, true);
|
||||||
|
for(let pref_name in preferences)
|
||||||
|
{
|
||||||
|
if(pref_name.indexOf(Favorite.PREFIX) == 0 && typeof preferences[pref_name] == 'object')
|
||||||
|
{
|
||||||
|
let name = pref_name.substr(Favorite.PREFIX.length);
|
||||||
|
favorites[name] = preferences[pref_name];
|
||||||
|
// Keep older favorites working - they used to store nm filters in 'filters',not state
|
||||||
|
if(preferences[pref_name]["filters"])
|
||||||
|
{
|
||||||
|
favorites[pref_name]["state"] = preferences[pref_name]["filters"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(pref_name == 'fav_sort_pref')
|
||||||
|
{
|
||||||
|
sortedList = preferences[pref_name];
|
||||||
|
//Make sure sorted list is always an array, seems some old fav are not array
|
||||||
|
if(!Array.isArray(sortedList) && typeof sortedList == "string")
|
||||||
|
{
|
||||||
|
// @ts-ignore What's the point of a typecheck if IDE still errors
|
||||||
|
sortedList = sortedList.split(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(let name in favorites)
|
||||||
|
{
|
||||||
|
if(sortedList.indexOf(name) < 0)
|
||||||
|
{
|
||||||
|
sortedList.push(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
egw.set_preference(app, 'fav_sort_pref', sortedList);
|
||||||
|
if(sortedList.length > 0)
|
||||||
|
{
|
||||||
|
let sortedListObj = {};
|
||||||
|
|
||||||
|
for(let i = 0; i < sortedList.length; i++)
|
||||||
|
{
|
||||||
|
if(typeof favorites[sortedList[i]] != 'undefined')
|
||||||
|
{
|
||||||
|
sortedListObj[sortedList[i]] = favorites[sortedList[i]];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
sortedList.splice(i, 1);
|
||||||
|
egw.set_preference(app, 'fav_sort_pref', sortedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
favorites = Object.assign(sortedListObj, favorites);
|
||||||
|
}
|
||||||
|
|
||||||
|
return favorites;
|
||||||
|
}
|
||||||
|
|
||||||
|
static async applyFavorite(egw, app : string, favoriteName : string)
|
||||||
|
{
|
||||||
|
const favorites = await Favorite.load(egw, app);
|
||||||
|
let fav = favoriteName == "blank" ? {} : favorites[favoriteName] ?? {};
|
||||||
|
// use app[appname].setState if available to allow app to overwrite it (eg. change to non-listview in calendar)
|
||||||
|
//@ts-ignore TS doesn't know about window.app
|
||||||
|
if(typeof window.app[app] != 'undefined')
|
||||||
|
{
|
||||||
|
//@ts-ignore TS doesn't know about window.app
|
||||||
|
window.app[app].setState(fav);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static async remove(egw, app, favoriteName)
|
||||||
|
{
|
||||||
|
const favorites = await Favorite.load(egw, app);
|
||||||
|
let fav = favorites[favoriteName];
|
||||||
|
if(!fav)
|
||||||
|
{
|
||||||
|
return Promise.reject("No such favorite");
|
||||||
|
}
|
||||||
|
|
||||||
|
return egw.request("EGroupware\\Api\\Framework::ajax_set_favorite",
|
||||||
|
[app, favoriteName, "delete", "" + fav.group, '']);
|
||||||
|
}
|
||||||
|
}
|
@ -55,6 +55,7 @@ import './Et2Email/Et2Email';
|
|||||||
import './Expose/Et2ImageExpose';
|
import './Expose/Et2ImageExpose';
|
||||||
import './Expose/Et2DescriptionExpose';
|
import './Expose/Et2DescriptionExpose';
|
||||||
import './Et2Favorites/Et2Favorites';
|
import './Et2Favorites/Et2Favorites';
|
||||||
|
import './Et2Favorites/Et2FavoritesMenu';
|
||||||
import './Et2Image/Et2Image';
|
import './Et2Image/Et2Image';
|
||||||
import './Et2Image/Et2AppIcon';
|
import './Et2Image/Et2AppIcon';
|
||||||
import './Et2Avatar/Et2LAvatar';
|
import './Et2Avatar/Et2LAvatar';
|
||||||
|
@ -582,7 +582,13 @@ export class EgwFrameworkApp extends LitElement
|
|||||||
// No favorites here
|
// No favorites here
|
||||||
if(menu["title"] == "Favorites" || menu["title"] == this.egw.lang("favorites"))
|
if(menu["title"] == "Favorites" || menu["title"] == this.egw.lang("favorites"))
|
||||||
{
|
{
|
||||||
return nothing;
|
return html`
|
||||||
|
<sl-menu-item>
|
||||||
|
<et2-image style="width:1em;" src="fav_filter" slot="prefix"></et2-image>
|
||||||
|
${menu["title"]}
|
||||||
|
<et2-favorites-menu slot="submenu" application="${this.appName}"></et2-favorites-menu>
|
||||||
|
</sl-menu-item>
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
// Just one thing, don't bother with submenu
|
// Just one thing, don't bother with submenu
|
||||||
if(menu["entries"].length == 1)
|
if(menu["entries"].length == 1)
|
||||||
|
Loading…
Reference in New Issue
Block a user