Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(editors/binding): Allow filtering of subscribed/unsubscribed data in binding editors #1149

Merged
merged 21 commits into from
Mar 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 243 additions & 54 deletions src/editors/subscription/fcda-binding-list.ts
danyill marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,23 @@ import {
LitElement,
property,
PropertyValues,
query,
state,
TemplateResult,
} from 'lit-element';
import { nothing, SVGTemplateResult } from 'lit-html';
import { classMap } from 'lit-html/directives/class-map.js';
import { translate } from 'lit-translate';

import '@material/mwc-icon';
import '@material/mwc-list';
import '@material/mwc-list/mwc-list-item';
import '@material/mwc-list/mwc-check-list-item';
import '@material/mwc-menu';

import { Icon } from '@material/mwc-icon';
import { List } from '@material/mwc-list';
import { Menu } from '@material/mwc-menu';

import {
getDescriptionAttribute,
Expand Down Expand Up @@ -59,6 +67,64 @@ export class FcdaBindingList extends LitElement {
@state()
private extRefCounters = new Map();

@property({
type: Boolean,
hasChanged() {
return false;
},
})
get hideSubscribed(): boolean {
return (
localStorage.getItem(
`fcda-binding-list-${
this.includeLaterBinding ? 'later-binding' : 'data-binding'
}-${this.controlTag}$hideSubscribed`
) === 'true' ?? false
);
}

set hideSubscribed(value: boolean) {
const oldValue = this.hideSubscribed;
localStorage.setItem(
`fcda-binding-list-${
this.includeLaterBinding ? 'later-binding' : 'data-binding'
}-${this.controlTag}$hideSubscribed`,
`${value}`
);
this.requestUpdate('hideSubscribed', oldValue);
}

@property({
type: Boolean,
hasChanged() {
return false;
},
})
get hideNotSubscribed(): boolean {
return (
localStorage.getItem(
`fcda-binding-list-${
this.includeLaterBinding ? 'later-binding' : 'data-binding'
}-${this.controlTag}$hideNotSubscribed`
) === 'true' ?? false
);
}

set hideNotSubscribed(value: boolean) {
const oldValue = this.hideNotSubscribed;
localStorage.setItem(
`fcda-binding-list-${
this.includeLaterBinding ? 'later-binding' : 'data-binding'
}-${this.controlTag}$hideNotSubscribed`,
`${value}`
);
this.requestUpdate('hideNotSubscribed', oldValue);
}

@query('.actions-menu') actionsMenu!: Menu;
@query('.actions-menu-icon') actionsMenuIcon!: Icon;
@query('.control-block-list') controlBlockList!: List;

private iconControlLookup: iconLookup = {
SampledValueControl: smvIcon,
GSEControl: gooseIcon,
Expand Down Expand Up @@ -170,11 +236,18 @@ export class FcdaBindingList extends LitElement {

renderFCDA(controlElement: Element, fcdaElement: Element): TemplateResult {
const fcdaCount = this.getExtRefCount(fcdaElement, controlElement);

const filterClasses = {
subitem: true,
'show-subscribed': fcdaCount !== 0,
'show-not-subscribed': fcdaCount === 0,
};

return html`<mwc-list-item
graphic="large"
?hasMeta=${fcdaCount !== 0}
twoline
class="subitem"
class="${classMap(filterClasses)}"
@click=${() => this.onFcdaSelect(controlElement, fcdaElement)}
value="${identity(controlElement)}
${identity(fcdaElement)}"
Expand All @@ -186,63 +259,133 @@ export class FcdaBindingList extends LitElement {
</mwc-list-item>`;
}

render(): TemplateResult {
const controlElements = this.getControlElements();
return html` <section tabindex="0">
${controlElements.length > 0
? html`<h1>
${translate(
`subscription.${this.controlTag}.controlBlockList.title`
)}
</h1>
<filtered-list activatable>
${controlElements.map(controlElement => {
const fcdaElements = this.getFcdaElements(controlElement);
return html`
<mwc-list-item
noninteractive
graphic="icon"
twoline
hasMeta
value="
${identity(controlElement)}${fcdaElements
.map(
fcdaElement => `
updateBaseFilterState(): void {
!this.hideSubscribed
? this.controlBlockList!.classList.add('show-subscribed')
: this.controlBlockList!.classList.remove('show-subscribed');
!this.hideNotSubscribed
? this.controlBlockList!.classList.add('show-not-subscribed')
: this.controlBlockList!.classList.remove('show-not-subscribed');
}

protected firstUpdated(): void {
this.actionsMenu.anchor = <HTMLElement>this.actionsMenuIcon;

this.actionsMenu.addEventListener('closed', () => {
this.hideSubscribed = !(<Set<number>>this.actionsMenu.index).has(0);
this.hideNotSubscribed = !(<Set<number>>this.actionsMenu.index).has(1);
this.updateBaseFilterState();
});

this.updateBaseFilterState();
}

renderTitle(): TemplateResult {
const menuClasses = {
'filter-off': this.hideSubscribed || this.hideNotSubscribed,
};
return html`<h1>
${translate(`subscription.${this.controlTag}.controlBlockList.title`)}
<mwc-icon-button
class="actions-menu-icon ${classMap(menuClasses)}"
icon="filter_list"
@click=${() => {
if (!this.actionsMenu.open) this.actionsMenu.show();
else this.actionsMenu.close();
}}
></mwc-icon-button>
<mwc-menu
multi
class="actions-menu"
corner="BOTTOM_RIGHT"
menuCorner="END"
>
<mwc-check-list-item
class="filter-subscribed"
left
?selected=${!this.hideSubscribed}
>
<span>${translate('subscription.subscriber.subscribed')}</span>
</mwc-check-list-item>
<mwc-check-list-item
class="filter-not-subscribed"
left
?selected=${!this.hideNotSubscribed}
>
<span>${translate('subscription.subscriber.notSubscribed')}</span>
</mwc-check-list-item>
</mwc-menu>
</h1> `;
}

renderControls(controlElements: Element[]): TemplateResult {
return html`<filtered-list class="control-block-list" activatable>
${controlElements
.filter(controlElement => this.getFcdaElements(controlElement).length)
.map(controlElement => {
const fcdaElements = this.getFcdaElements(controlElement);
const showSubscribed = fcdaElements.some(
fcda => this.getExtRefCount(fcda, controlElement) !== 0
);
const showNotSubscribed = fcdaElements.some(
fcda => this.getExtRefCount(fcda, controlElement) === 0
);

const filterClasses = {
control: true,
'show-subscribed': showSubscribed,
'show-not-subscribed': showNotSubscribed,
};

return html`
<mwc-list-item
noninteractive
class="${classMap(filterClasses)}"
graphic="icon"
twoline
hasMeta
value="${identity(controlElement)}${fcdaElements
.map(
fcdaElement => `
${getFcdaTitleValue(fcdaElement)}
${getFcdaSubtitleValue(fcdaElement)}
${identity(fcdaElement)}`
)
.join('')}"
>
<mwc-icon-button
slot="meta"
icon="edit"
class="interactive"
@click=${() => this.openEditWizard(controlElement)}
></mwc-icon-button>
<span
>${getNameAttribute(controlElement)}
${getDescriptionAttribute(controlElement)
? html`${getDescriptionAttribute(controlElement)}`
: nothing}</span
>
<span slot="secondary">${identity(controlElement)}</span>
<mwc-icon slot="graphic"
>${this.iconControlLookup[this.controlTag]}</mwc-icon
>
</mwc-list-item>
<li divider role="separator"></li>
${fcdaElements.map(fcdaElement =>
this.renderFCDA(controlElement, fcdaElement)
)}
`;
})}
</filtered-list>`
: html`<h1>
${translate(
`subscription.${this.controlTag}.controlBlockList.noControlBlockFound`
)
.join('')}"
>
<mwc-icon-button
slot="meta"
icon="edit"
class="interactive"
@click=${() => this.openEditWizard(controlElement)}
></mwc-icon-button>
<span
>${getNameAttribute(controlElement)}
${getDescriptionAttribute(controlElement)
? html`${getDescriptionAttribute(controlElement)}`
: nothing}</span
>
<span slot="secondary">${identity(controlElement)}</span>
<mwc-icon slot="graphic"
>${this.iconControlLookup[this.controlTag]}</mwc-icon
>
</mwc-list-item>
<li divider role="separator"></li>
${fcdaElements.map(fcdaElement =>
this.renderFCDA(controlElement, fcdaElement)
)}
</h1>`}
`;
})}
</filtered-list>`;
}

render(): TemplateResult {
const controlElements = this.getControlElements();
return html`<section tabindex="0">
${this.renderTitle()}
${controlElements
? this.renderControls(controlElements)
: html`<h4>${translate('subscription.subscriber.notSubscribed')}</h4> `}
</section>`;
}

Expand All @@ -257,6 +400,52 @@ export class FcdaBindingList extends LitElement {
--mdc-list-item-meta-size: 48px;
}

section {
position: relative;
}

.actions-menu-icon {
float: right;
}

.actions-menu-icon.filter-off {
color: var(--secondary);
background-color: var(--mdc-theme-background);
}

/* Filtering rules for control blocks end up implementing logic to allow
very fast CSS response. The following rules appear to be minimal but can be
hard to understand intuitively for the multiple conditions. If modifying,
it is suggested to create a truth-table to check for side-effects */

/* remove all control blocks if no filters */
filtered-list.control-block-list:not(.show-subscribed, .show-not-subscribed)
mwc-list-item {
display: none;
}

/* remove control blocks taking care to respect multiple conditions */
filtered-list.control-block-list.show-not-subscribed:not(.show-subscribed)
mwc-list-item.control.show-subscribed:not(.show-not-subscribed) {
display: none;
}

filtered-list.control-block-list.show-subscribed:not(.show-not-subscribed)
mwc-list-item.control.show-not-subscribed:not(.show-subscribed) {
display: none;
}

/* remove fcdas if not part of filter */
filtered-list.control-block-list:not(.show-not-subscribed)
mwc-list-item.subitem.show-not-subscribed {
display: none;
}

filtered-list.control-block-list:not(.show-subscribed)
mwc-list-item.subitem.show-subscribed {
display: none;
}

.interactive {
pointer-events: all;
}
Expand Down
1 change: 1 addition & 0 deletions src/translations/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ export const de: Translations = {
disconnect: 'Daten-Attribute Verbindung gelöst',
subscriber: {
subscribed: 'Verbunden',
notSubscribed: 'Nicht Verbunden',
availableToSubscribe: 'Kann verbunden werden',
partiallySubscribed: 'Teilweise verbunden',
noControlBlockSelected: 'Keine Kontrollblock ausgewählt',
Expand Down
1 change: 1 addition & 0 deletions src/translations/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export const en = {
disconnect: 'Disconnect data attribute',
subscriber: {
subscribed: 'Subscribed',
notSubscribed: 'Not Subscribed',
availableToSubscribe: 'Available to subscribe',
partiallySubscribed: 'Partially subscribed',
noControlBlockSelected: 'No control block selected',
Expand Down
2 changes: 1 addition & 1 deletion test/integration/editors/test-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function selectFCDAItem(
fcdaIdentity: string
): void {
(<HTMLElement>Array.from(
listElement.shadowRoot!.querySelectorAll('mwc-list-item[class="subitem"]')
listElement.shadowRoot!.querySelectorAll('mwc-list-item.subitem')
).find(listItem => {
const value = listItem.getAttribute('value') ?? '';
return value.includes(controlIdentity) && value.includes(fcdaIdentity);
Expand Down
Loading