mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-22 14:41:29 +01:00
Et2LinkString & Et2LinkList performance enhancements
- Limit server fetch to a certain amount of links (default 20) - Load more button for Et2LinkList, More... for Et2LinkString - Defer server fetch during initial loading for faster initial load - Use repeat() so LitElement can do node caching magic
This commit is contained in:
parent
24ccfbf3ab
commit
58763707fd
@ -10,7 +10,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {css, html, TemplateResult} from "lit";
|
import {css, html, nothing, TemplateResult} from "lit";
|
||||||
import {repeat} from "lit/directives/repeat.js";
|
import {repeat} from "lit/directives/repeat.js";
|
||||||
import {LinkInfo} from "./Et2Link";
|
import {LinkInfo} from "./Et2Link";
|
||||||
import {egw} from "../../jsapi/egw_global";
|
import {egw} from "../../jsapi/egw_global";
|
||||||
@ -46,58 +46,69 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
return [
|
return [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
column-gap: 10px;
|
column-gap: 10px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
div {
|
div {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
div:hover {
|
div:hover {
|
||||||
background-color: var(--highlight-background-color);
|
background-color: var(--highlight-background-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
div.zip_highlight {
|
div.zip_highlight {
|
||||||
animation-name: new_entry_pulse, new_entry_clear;
|
animation-name: new_entry_pulse, new_entry_clear;
|
||||||
animation-duration: 5s;
|
animation-duration: 5s;
|
||||||
animation-delay: 0s, 30s;
|
animation-delay: 0s, 30s;
|
||||||
animation-fill-mode: forwards;
|
animation-fill-mode: forwards;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CSS for child elements */
|
/* CSS for child elements */
|
||||||
|
|
||||||
::slotted(*):after {
|
|
||||||
/* Reset from Et2LinkString */
|
|
||||||
content: initial;
|
|
||||||
}
|
|
||||||
|
|
||||||
::slotted(*)::part(icon) {
|
et2-link::part(title):after {
|
||||||
width: 1rem;
|
/* Reset from Et2LinkString */
|
||||||
}
|
content: initial;
|
||||||
|
}
|
||||||
|
|
||||||
::slotted(et2-link) {
|
et2-link::part(icon) {
|
||||||
flex: 1 1 auto;
|
width: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(.remark) {
|
et2-link {
|
||||||
flex: 1 1 auto;
|
display: block;
|
||||||
width: 20%;
|
flex: 1 1 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(.delete_button) {
|
et2-link:hover {
|
||||||
visibility: hidden;
|
text-decoration: none;
|
||||||
width: 16px;
|
}
|
||||||
order: 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
div:hover ::slotted(.delete_button) {
|
et2-link::part(base) {
|
||||||
visibility: initial;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.remark {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
div et2-image[part=delete-button] {
|
||||||
|
visibility: hidden;
|
||||||
|
width: 16px;
|
||||||
|
order: 5;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
div:hover et2-image[part=delete-button] {
|
||||||
|
visibility: initial;
|
||||||
|
}
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -164,7 +175,35 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
${repeat(this._link_list,
|
${repeat(this._link_list,
|
||||||
(link) => link.app + ":" + link.id,
|
(link) => link.app + ":" + link.id,
|
||||||
(link) => this._rowTemplate(link))
|
(link) => this._rowTemplate(link))
|
||||||
}`;
|
}
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async moreResultsTemplate()
|
||||||
|
{
|
||||||
|
if(this._totalResults <= 0 || !this._loadingPromise)
|
||||||
|
{
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return this._loadingPromise.then(() =>
|
||||||
|
{
|
||||||
|
const moreCount = this._totalResults - this._link_list.length;
|
||||||
|
const more = this.egw().lang("%1 more...", moreCount);
|
||||||
|
return html`${moreCount > 0 ? html`
|
||||||
|
<et2-button image="box-arrow-down" label="${more}" noSubmit="true"
|
||||||
|
._parent=${this}
|
||||||
|
@click="${(e) =>
|
||||||
|
{
|
||||||
|
// Change icon for some feedback
|
||||||
|
e.target.querySelectorAll("[slot=prefix]").forEach(n => n.remove());
|
||||||
|
e.target.append(Object.assign(document.createElement("sl-spinner"), {slot: "prefix"}));
|
||||||
|
|
||||||
|
// Get the next batch
|
||||||
|
const start = this._link_list.filter(l => l.app !== "file").length;
|
||||||
|
this.get_links([], start);
|
||||||
|
}}"
|
||||||
|
></et2-button>` : nothing}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,7 +217,6 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
{
|
{
|
||||||
this._link_list = links;
|
this._link_list = links;
|
||||||
this.requestUpdate();
|
this.requestUpdate();
|
||||||
this.updateComplete.then(() => super._addLinks(links));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -193,7 +231,7 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
{
|
{
|
||||||
const id = typeof link.id === "string" ? link.id : link.link_id;
|
const id = typeof link.id === "string" ? link.id : link.link_id;
|
||||||
return html`
|
return html`
|
||||||
<et2-link slot="${this._get_row_id(link)}" app="${link.app}" entryId="${id}" statustext="${link.title}"
|
<et2-link app="${link.app}" entryId="${id}" statustext="${link.title}"
|
||||||
._parent=${this}
|
._parent=${this}
|
||||||
.value=${link}></et2-link>
|
.value=${link}></et2-link>
|
||||||
${this._deleteButtonTemplate(link)}
|
${this._deleteButtonTemplate(link)}
|
||||||
@ -214,7 +252,7 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
return html`
|
return html`
|
||||||
<div id="${this._get_row_id(link)}"
|
<div id="${this._get_row_id(link)}"
|
||||||
@contextmenu=${this._handleRowContext}>
|
@contextmenu=${this._handleRowContext}>
|
||||||
<slot name="${this._get_row_id(link)}"></slot>
|
${this._linkTemplate(link)}
|
||||||
</div>`;
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +280,24 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
{
|
{
|
||||||
if(_ev && typeof _ev.currentTarget)
|
if(_ev && typeof _ev.currentTarget)
|
||||||
{
|
{
|
||||||
this.get_links(_ev.detail || []);
|
// Add in new links from LinkTo
|
||||||
|
for(let link of <LinkInfo[]>Object.values(_ev.detail || []))
|
||||||
|
{
|
||||||
|
if(!this._link_list.some(l => l.app == link.app && l.id == link.id))
|
||||||
|
{
|
||||||
|
this._link_list.unshift(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// No need to ask server if we got it in the event
|
||||||
|
if(_ev.detail.length)
|
||||||
|
{
|
||||||
|
this.requestUpdate();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Event didn't have it, need to ask
|
||||||
|
this.get_links();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,22 +345,27 @@ export class Et2LinkList extends Et2LinkString
|
|||||||
*/
|
*/
|
||||||
protected _delete_link(link : LinkInfo)
|
protected _delete_link(link : LinkInfo)
|
||||||
{
|
{
|
||||||
let link_element = <HTMLElement>this.querySelector("et2-link[slot='" + this._get_row_id(link) + "']");
|
let link_element = <HTMLElement>this.shadowRoot.querySelector("[id='" + this._get_row_id(link) + "']");
|
||||||
link_element.classList.add("loading");
|
link_element.classList.add("loading");
|
||||||
|
|
||||||
this.dispatchEvent(new CustomEvent("et2-before-delete", {detail: link}));
|
this.dispatchEvent(new CustomEvent("et2-before-delete", {detail: link}));
|
||||||
|
|
||||||
let removeLink = () =>
|
let removeLink = () =>
|
||||||
{
|
{
|
||||||
this.querySelectorAll("[slot='" + this._get_row_id(link) + "']").forEach(e => e.remove());
|
this.shadowRoot.querySelectorAll("[id='" + this._get_row_id(link) + "']").forEach(e => e.remove());
|
||||||
if(this._link_list.indexOf(link) != -1)
|
if(this._link_list.indexOf(link) != -1)
|
||||||
{
|
{
|
||||||
this._link_list.splice(this._link_list.indexOf(link), 1);
|
this._link_list.splice(this._link_list.indexOf(link), 1);
|
||||||
|
this._totalResults--;
|
||||||
}
|
}
|
||||||
this.dispatchEvent(new CustomEvent("et2-delete", {bubbles: true, detail: link}));
|
this.requestUpdate();
|
||||||
let change = new Event("change", {bubbles: true});
|
this.updateComplete.then(() =>
|
||||||
change['data'] = link;
|
{
|
||||||
this.dispatchEvent(change);
|
this.dispatchEvent(new CustomEvent("et2-delete", {bubbles: true, detail: link}));
|
||||||
|
let change = new Event("change", {bubbles: true});
|
||||||
|
change['data'] = link;
|
||||||
|
this.dispatchEvent(change);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// Unsaved entry, had no ID yet
|
// Unsaved entry, had no ID yet
|
||||||
|
@ -10,13 +10,14 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import {css, html, LitElement, PropertyValues, render, TemplateResult} from "lit";
|
import {css, html, LitElement, nothing, PropertyValues, render, TemplateResult} from "lit";
|
||||||
import {until} from "lit/directives/until.js";
|
import {until} from "lit/directives/until.js";
|
||||||
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
import {Et2Widget} from "../Et2Widget/Et2Widget";
|
||||||
import {LinkInfo} from "./Et2Link";
|
import {LinkInfo} from "./Et2Link";
|
||||||
import {et2_IDetachedDOM} from "../et2_core_interfaces";
|
import {et2_IDetachedDOM} from "../et2_core_interfaces";
|
||||||
import {property} from "lit/decorators/property.js";
|
import {property} from "lit/decorators/property.js";
|
||||||
import {customElement} from "lit/decorators/custom-element.js";
|
import {customElement} from "lit/decorators/custom-element.js";
|
||||||
|
import {repeat} from "lit/directives/repeat.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a list of entries in a comma separated list
|
* Display a list of entries in a comma separated list
|
||||||
@ -36,30 +37,30 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
return [
|
return [
|
||||||
...super.styles,
|
...super.styles,
|
||||||
css`
|
css`
|
||||||
:host {
|
:host {
|
||||||
list-style-type: none;
|
list-style-type: none;
|
||||||
display: inline;
|
display: inline;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(*) {
|
et2-link, et2-link::part(base) {
|
||||||
display: inline;
|
display: inline;
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(*):hover {
|
et2-link:hover {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* CSS for child elements */
|
/* CSS for child elements */
|
||||||
|
|
||||||
::slotted(*):after {
|
et2-link::part(title):after {
|
||||||
content: ", "
|
content: ", "
|
||||||
}
|
}
|
||||||
|
|
||||||
::slotted(*:last-child):after {
|
et2-link:last-child::part(title):after {
|
||||||
content: initial;
|
content: initial;
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -112,13 +113,14 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
@property({type: Number})
|
@property({type: Number})
|
||||||
limit = 20;
|
limit = 20;
|
||||||
|
|
||||||
protected _link_list : LinkInfo[];
|
protected _totalResults : number = 0;
|
||||||
protected _loadingPromise : Promise<LinkInfo[]>;
|
protected _link_list : LinkInfo[] = [];
|
||||||
|
protected _loadingPromise : Promise<LinkInfo[]> = Promise.resolve([]);
|
||||||
|
protected _loading = false;
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
{
|
{
|
||||||
super();
|
super();
|
||||||
this._link_list = []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUpdateComplete()
|
async getUpdateComplete()
|
||||||
@ -144,6 +146,12 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
public set_value(_value : string | { to_app : string, to_id : string } | LinkInfo[])
|
public set_value(_value : string | { to_app : string, to_id : string } | LinkInfo[])
|
||||||
{
|
{
|
||||||
this._link_list = [];
|
this._link_list = [];
|
||||||
|
if(typeof _value["total"] !== "undefined")
|
||||||
|
{
|
||||||
|
this._totalResults = _value["total"];
|
||||||
|
delete _value["total"];
|
||||||
|
}
|
||||||
|
|
||||||
if(typeof _value == "object" && !Array.isArray(_value) && !_value.to_app && this.application)
|
if(typeof _value == "object" && !Array.isArray(_value) && !_value.to_app && this.application)
|
||||||
{
|
{
|
||||||
_value.to_app = this.application;
|
_value.to_app = this.application;
|
||||||
@ -155,7 +163,12 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
{
|
{
|
||||||
this.application = _value.to_app;
|
this.application = _value.to_app;
|
||||||
this.entryId = _value.to_id;
|
this.entryId = _value.to_id;
|
||||||
this.get_links();
|
|
||||||
|
// Let update complete finish first, if it's not done yet
|
||||||
|
this.updateComplete.then(() =>
|
||||||
|
{
|
||||||
|
this.get_links();
|
||||||
|
})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,9 +179,9 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
ids.forEach((id) => (<LinkInfo[]>this._link_list).push(<LinkInfo>{app: this.application, id: id}));
|
ids.forEach((id) => (<LinkInfo[]>this._link_list).push(<LinkInfo>{app: this.application, id: id}));
|
||||||
}
|
}
|
||||||
// List of LinkInfo
|
// List of LinkInfo
|
||||||
else if(Array.isArray(_value))
|
else if(Array.isArray(_value) || typeof _value[0] == "object")
|
||||||
{
|
{
|
||||||
this._link_list = _value;
|
this._link_list = <LinkInfo[]>Object.values(_value);
|
||||||
}
|
}
|
||||||
// List of LinkInfo stuffed into to_id - entry is not yet saved
|
// List of LinkInfo stuffed into to_id - entry is not yet saved
|
||||||
else if(_value.to_id && typeof _value.to_id !== "string")
|
else if(_value.to_id && typeof _value.to_id !== "string")
|
||||||
@ -179,8 +192,7 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
this._link_list.push(<LinkInfo>_value.to_id[key]);
|
this._link_list.push(<LinkInfo>_value.to_id[key]);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this._addLinks(this._link_list);
|
this.requestUpdate();
|
||||||
super.requestUpdate();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public updated(changedProperties : PropertyValues)
|
public updated(changedProperties : PropertyValues)
|
||||||
@ -200,20 +212,21 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
{
|
{
|
||||||
// This shows loading template until loadingPromise resolves, then shows _listTemplate
|
// This shows loading template until loadingPromise resolves, then shows _listTemplate
|
||||||
return html`
|
return html`
|
||||||
${this._loadingPromise ? until(
|
${until(this._loadingPromise?.then(res =>
|
||||||
this._loadingPromise?.then(res =>
|
|
||||||
{
|
{
|
||||||
this._listTemplate();
|
return this._listTemplate();
|
||||||
}),
|
}),
|
||||||
this._loadingTemplate()
|
this._loadingTemplate()
|
||||||
) : this._listTemplate()}
|
)}
|
||||||
|
${until(this.moreResultsTemplate(), nothing)}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected _listTemplate()
|
protected _listTemplate()
|
||||||
{
|
{
|
||||||
return html`
|
return html`
|
||||||
<slot></slot>`;
|
${repeat(this._link_list, l => l.link_id, this._linkTemplate)}
|
||||||
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -227,22 +240,9 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
{
|
{
|
||||||
const id = typeof link.id === "string" ? link.id : link.link_id;
|
const id = typeof link.id === "string" ? link.id : link.link_id;
|
||||||
return html`
|
return html`
|
||||||
<et2-link app="${link.app}" entryId="${id}" .value=${link} ._parent=${this}></et2-link>`;
|
<et2-link part="link" class="et2_link"
|
||||||
}
|
app="${link.app}" entryId="${id}" .value=${link} ._parent=${this}
|
||||||
|
></et2-link>`;
|
||||||
/**
|
|
||||||
* Render "more links available"
|
|
||||||
*
|
|
||||||
* @param link
|
|
||||||
* @returns {TemplateResult}
|
|
||||||
* @protected
|
|
||||||
*/
|
|
||||||
protected _moreAvailableTemplate(link : LinkInfo) : TemplateResult
|
|
||||||
{
|
|
||||||
return html`
|
|
||||||
<et2-button image="${link.icon}" label="${link.title}" .onclick="${() => {
|
|
||||||
this.get_links();
|
|
||||||
}}" ._parent=${this} slot="link_exceeded"></et2-button>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -252,7 +252,25 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
*/
|
*/
|
||||||
protected _loadingTemplate() : TemplateResult
|
protected _loadingTemplate() : TemplateResult
|
||||||
{
|
{
|
||||||
return html`loading...`;
|
return html`
|
||||||
|
<div class="search__loading">
|
||||||
|
<sl-spinner></sl-spinner>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async moreResultsTemplate()
|
||||||
|
{
|
||||||
|
if(this._totalResults <= 0 || !this._loadingPromise)
|
||||||
|
{
|
||||||
|
return nothing;
|
||||||
|
}
|
||||||
|
return this._loadingPromise.then(() =>
|
||||||
|
{
|
||||||
|
const moreCount = this._totalResults - this._link_list.length;
|
||||||
|
const more = this.egw().lang("%1 more...", moreCount);
|
||||||
|
return html`${moreCount > 0 ? more : nothing}`;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -264,6 +282,7 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
*/
|
*/
|
||||||
protected _addLinks(links : LinkInfo[])
|
protected _addLinks(links : LinkInfo[])
|
||||||
{
|
{
|
||||||
|
return;
|
||||||
// Remove anything there right now
|
// Remove anything there right now
|
||||||
while(this.lastChild)
|
while(this.lastChild)
|
||||||
{
|
{
|
||||||
@ -273,7 +292,7 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
links.forEach((link) =>
|
links.forEach((link) =>
|
||||||
{
|
{
|
||||||
let temp = document.createElement("div");
|
let temp = document.createElement("div");
|
||||||
render(link.app === 'exceeded' ? this._moreAvailableTemplate(link) : this._linkTemplate(link), temp);
|
render(this._linkTemplate(link), temp);
|
||||||
temp.childNodes.forEach((node) => this.appendChild(node));
|
temp.childNodes.forEach((node) => this.appendChild(node));
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -297,8 +316,15 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
* Called internally to fetch the list. May be called externally to trigger a refresh if a link is added.
|
* Called internally to fetch the list. May be called externally to trigger a refresh if a link is added.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public get_links(not_saved_links? : LinkInfo[])
|
public get_links(not_saved_links? : LinkInfo[], offset = 0)
|
||||||
{
|
{
|
||||||
|
if(this._loading)
|
||||||
|
{
|
||||||
|
// Already waiting
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._loading = true;
|
||||||
|
|
||||||
if(typeof not_saved_links === "undefined")
|
if(typeof not_saved_links === "undefined")
|
||||||
{
|
{
|
||||||
not_saved_links = [];
|
not_saved_links = [];
|
||||||
@ -308,31 +334,31 @@ export class Et2LinkString extends Et2Widget(LitElement) implements et2_IDetache
|
|||||||
to_id: this.entryId,
|
to_id: this.entryId,
|
||||||
only_app: this.onlyApp,
|
only_app: this.onlyApp,
|
||||||
show_deleted: this.showDeleted,
|
show_deleted: this.showDeleted,
|
||||||
limit: this.limit
|
limit: [offset, /* num_rows: */this.limit]
|
||||||
};
|
};
|
||||||
this.limit *= 2; // double number of loaded links on next call
|
|
||||||
|
|
||||||
if(this._loadingPromise)
|
|
||||||
{
|
|
||||||
// Already waiting
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._loadingPromise = <Promise<LinkInfo[]>>(this.egw().jsonq('EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_list', [_value]))
|
this._loadingPromise = <Promise<LinkInfo[]>>(this.egw().jsonq('EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_list', [_value]))
|
||||||
.then(_value =>
|
.then(_value =>
|
||||||
{
|
{
|
||||||
if(_value && Array.isArray(_value))
|
if(typeof _value.total)
|
||||||
{
|
{
|
||||||
for(let link of <LinkInfo[]>_value)
|
this._totalResults = _value.total;
|
||||||
|
delete _value.total;
|
||||||
|
}
|
||||||
|
if(_value)
|
||||||
|
{
|
||||||
|
for(let link of <LinkInfo[]>Object.values(_value))
|
||||||
{
|
{
|
||||||
if(!not_saved_links.some(l => l.app == link.app && l.id == link.id))
|
// Avoid duplicates, files are always sent
|
||||||
|
if(!not_saved_links.some(l => l.app == link.app && l.id == link.id) &&
|
||||||
|
!this._link_list.some(l => l.app == link.app && l.id == link.id))
|
||||||
{
|
{
|
||||||
not_saved_links.push(link);
|
this._link_list.push(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._addLinks(not_saved_links);
|
this._loading = false;
|
||||||
this._loadingPromise = null;
|
this.requestUpdate();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -201,8 +201,8 @@ class Link extends Etemplate\Widget
|
|||||||
$app = $value['to_app'];
|
$app = $value['to_app'];
|
||||||
$id = $value['to_id'];
|
$id = $value['to_id'];
|
||||||
|
|
||||||
$links = Api\Link::get_links($app, $id, $value['only_app'] ?? '', 'link_lastmod DESC', true, $value['show_deleted'], $value['limit'] ?? null);
|
$links = Api\Link::get_links($app, $id, $value['only_app'], 'link_lastmod DESC, link_id DESC', true, $value['show_deleted'], $value['limit']);
|
||||||
$limit_exceeded = !empty($value['limit']) && Api\Link::$limit_exceeded;
|
|
||||||
$only_links = [];
|
$only_links = [];
|
||||||
if($value['only_app'])
|
if($value['only_app'])
|
||||||
{
|
{
|
||||||
@ -244,19 +244,12 @@ class Link extends Etemplate\Widget
|
|||||||
$link['help'] = lang('Remove this link (not the entry itself)');
|
$link['help'] = lang('Remove this link (not the entry itself)');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($limit_exceeded)
|
|
||||||
{
|
|
||||||
$links[] = [
|
|
||||||
'app' => 'exceeded',
|
|
||||||
'id' => 'exceeded',
|
|
||||||
'title' => lang('Load more links ...'),
|
|
||||||
'icon' => 'box-arrow-down',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = Api\Json\Response::get();
|
$response = Api\Json\Response::get();
|
||||||
// Strip keys, unneeded and cause index problems on the client side
|
// Strip keys, unneeded and cause index problems on the client side
|
||||||
$response->data(array_values($links));
|
$result = array_values($links);
|
||||||
|
$result['total'] = Api\Link\Storage::$row_count;
|
||||||
|
$response->data($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -497,7 +497,7 @@ class Link extends Link\Storage
|
|||||||
* @param int $limit =null number of entries to return, only affects links, attachments are allways reported!
|
* @param int $limit =null number of entries to return, only affects links, attachments are allways reported!
|
||||||
* @return array id => links pairs if $id is an array or just the links (only_app: ids) or empty array if no matching links found
|
* @return array id => links pairs if $id is an array or just the links (only_app: ids) or empty array if no matching links found
|
||||||
*/
|
*/
|
||||||
static function get_links($app, $id, $only_app='', $order='link_lastmod DESC',$cache_titles=false, $deleted=false, $limit=null)
|
static function get_links($app, $id, $only_app = '', $order = 'link_lastmod DESC, link_id DESC', $cache_titles = false, $deleted = false, $limit = null)
|
||||||
{
|
{
|
||||||
if (self::DEBUG) echo "<p>Link::get_links(app='$app',id='$id',only_app='$only_app',order='$order',deleted='$deleted')</p>\n";
|
if (self::DEBUG) echo "<p>Link::get_links(app='$app',id='$id',only_app='$only_app',order='$order',deleted='$deleted')</p>\n";
|
||||||
|
|
||||||
@ -529,6 +529,7 @@ class Link extends Link\Storage
|
|||||||
if (($vfs_ids = self::list_attached($app,$id)))
|
if (($vfs_ids = self::list_attached($app,$id)))
|
||||||
{
|
{
|
||||||
$ids += $vfs_ids;
|
$ids += $vfs_ids;
|
||||||
|
self::$row_count += count($vfs_ids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//echo "ids=<pre>"; print_r($ids); echo "</pre>\n";
|
//echo "ids=<pre>"; print_r($ids); echo "</pre>\n";
|
||||||
@ -550,6 +551,7 @@ class Link extends Link\Storage
|
|||||||
if(!self::title(is_array($link) ? $link['app'] : $only_app, is_array($link) ? $link['id'] : $link))
|
if(!self::title(is_array($link) ? $link['app'] : $only_app, is_array($link) ? $link['id'] : $link))
|
||||||
{
|
{
|
||||||
unset($ids[$key]);
|
unset($ids[$key]);
|
||||||
|
self::$row_count--;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
reset($ids);
|
reset($ids);
|
||||||
|
@ -47,6 +47,7 @@ class Storage
|
|||||||
* True if call to get_links or get_3links exceeded limit (contains not all rows)
|
* True if call to get_links or get_3links exceeded limit (contains not all rows)
|
||||||
*/
|
*/
|
||||||
public static $limit_exceeded = false;
|
public static $limit_exceeded = false;
|
||||||
|
public static int $row_count = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* creates a link between $app1,$id1 and $app2,$id2
|
* creates a link between $app1,$id1 and $app2,$id2
|
||||||
@ -122,7 +123,7 @@ class Storage
|
|||||||
* @param int|array $limit =null number of entries to return, default null = all or array(offset, num_rows) to return num_rows starting from offset
|
* @param int|array $limit =null number of entries to return, default null = all or array(offset, num_rows) to return num_rows starting from offset
|
||||||
* @return array id => links pairs if $id is an array or just the links (only_app: ids) or empty array if no matching links found
|
* @return array id => links pairs if $id is an array or just the links (only_app: ids) or empty array if no matching links found
|
||||||
*/
|
*/
|
||||||
static function get_links($app, $id, $only_app='', $order='link_lastmod DESC', $deleted=false, $limit=null)
|
static function get_links($app, $id, $only_app = '', $order = 'link_lastmod DESC', $deleted = false, $limit = null)
|
||||||
{
|
{
|
||||||
if (self::DEBUG)
|
if (self::DEBUG)
|
||||||
{
|
{
|
||||||
@ -132,7 +133,14 @@ class Storage
|
|||||||
{
|
{
|
||||||
$only_app = substr($only_app,1);
|
$only_app = substr($only_app,1);
|
||||||
}
|
}
|
||||||
|
$query = self::$db->expression(
|
||||||
|
self::TABLE, '((',
|
||||||
|
self::encodeRow(['link_app1' => $app, 'link_id1' => $id,]),
|
||||||
|
') OR (',
|
||||||
|
self::encodeRow(['link_app2' => $app, 'link_id2' => $id,]),
|
||||||
|
'))',
|
||||||
|
$deleted ? '' : ' AND deleted IS NULL'
|
||||||
|
);
|
||||||
$offset = false;
|
$offset = false;
|
||||||
if (is_array($limit))
|
if (is_array($limit))
|
||||||
{
|
{
|
||||||
@ -142,18 +150,30 @@ class Storage
|
|||||||
{
|
{
|
||||||
$offset = 0;
|
$offset = 0;
|
||||||
}
|
}
|
||||||
|
if($offset !== false) // need to get the total too
|
||||||
|
{
|
||||||
|
if(self::$db->Type == 'mysql' && (float)self::$db->ServerInfo['version'] >= 4.0)
|
||||||
|
{
|
||||||
|
$mysql_calc_rows = 'SQL_CALC_FOUND_ROWS ';
|
||||||
|
}
|
||||||
|
else // can't do a count, have to run the query without limit
|
||||||
|
{
|
||||||
|
self::$row_count = self::$db->select(
|
||||||
|
self::$table_name, '*', $query, __LINE__, __FILE__, false, $order ? " ORDER BY $order" : '', false, 0
|
||||||
|
)->NumRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$links = array();
|
$links = array();
|
||||||
try {
|
try {
|
||||||
foreach(self::$db->select(self::TABLE, '*', self::$db->expression(self::TABLE, '((', self::encodeRow([
|
$rs = self::$db->select(self::TABLE, ($mysql_calc_rows ?? '') . '*', $query,
|
||||||
'link_app1' => $app,
|
__LINE__, __FILE__, $offset, $order ? " ORDER BY $order" : '', 'phpgwapi', $limit
|
||||||
'link_id1' => $id,
|
);
|
||||||
]), ') OR (', self::encodeRow([
|
if(!empty($mysql_calc_rows))
|
||||||
'link_app2' => $app,
|
{
|
||||||
'link_id2' => $id,
|
self::$row_count = self::$db->query('SELECT FOUND_ROWS()')->fetchColumn();
|
||||||
]), '))',
|
}
|
||||||
$deleted ? '' : ' AND deleted IS NULL'
|
foreach($rs as $row)
|
||||||
), __LINE__, __FILE__, $offset, $order ? " ORDER BY $order" : '', 'phpgwapi', $limit) as $row)
|
|
||||||
{
|
{
|
||||||
$row = self::decodeRow($row);
|
$row = self::decodeRow($row);
|
||||||
|
|
||||||
|
@ -1274,7 +1274,7 @@ div.et2_link_entry input.ui-autocomplete-input {
|
|||||||
padding-right: 0;
|
padding-right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.et2_link, et2-link {
|
.et2_link, et2-link, et2-link-string {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #003075;
|
color: #003075;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
Loading…
Reference in New Issue
Block a user