import { ChangeDetectorRef, Component, OnDestroy } from "@angular/core";
import { getOrDefault, WidgetConfig } from "../widget.configuration";
import {
WidgetComponent,
WidgetConfiguration,
WidgetConfigure,
WidgetId,
WidgetInput,
} from "../widget.metadata";
import { ReplaySubject, Subject } from "rxjs";
import { distinctUntilChanged, filter, takeUntil } from "rxjs/operators";
import { NgUnsubscribe } from "../../ng-unsubscribe";
import {
DeletionMode,
Scope,
} from "../../components/local-storage/local-storage-constants";
import { LocalStorageEntryConfig } from "../../components/local-storage/local-storage.service";
import { Content } from "../../components/app-context/api";
import { BaseConfiguration } from "../widgetframe/widgetframe.component";
export const densityStorageConfig: LocalStorageEntryConfig = {
scope: Scope.UNSCOPED,
key: "nm-toolbox-table-density",
deletionMode: DeletionMode.NEVER,
};
export const fontSizeStorageConfig: LocalStorageEntryConfig = {
scope: Scope.UNSCOPED,
key: "nm-toolbox-table-fontsize",
deletionMode: DeletionMode.NEVER,
};
export interface EmptyWidgetConfiguration {
/**
* Sets the title shown in the empty widget header
*/
title: string;
/**
* Sets CSS class name for the embedded header component
*/
header: string;
/**
* Shows / Hides the border around the empty widget
*/
withBorder: boolean;
/**
* Sets the Info text
*/
infoText: string;
/**
* Sets the title shown for the info icon
*/
infoTitle: string;
/**
* Sets the link to external wiki system for more information
*/
wikiLink: string;
/**
* Shows / Hides the empty widget header @default(true)
*/
withHeader?: boolean;
/**
* Shows / Hides the empty widget @default(false)
*/
hideWidget?: boolean;
}
@WidgetComponent("nm-empty-widget")
@Component({
selector: "nm-empty-widget",
template: `
<nm-widgetframe
[header]="_widgetConfiguration.configuration['header']"
[toolbarInvisible]="!withHeader"
[visible]="!hideWidget"
[withBorder]="_widgetConfiguration.configuration.withBorder"
[infoText]="_widgetConfiguration.configuration['infoText']"
[infoTitle]="_widgetConfiguration.configuration['infoTitle']"
[infoWidth]="_widgetConfiguration.configuration['infoWidth']"
[infoHeight]="_widgetConfiguration.configuration['infoHeight']"
[title]="_widgetConfiguration.configuration.title | translate"
[configuration]="_widgetConfiguration"
>
<ng-container slot="title">
{{ _widgetConfiguration.configuration["title"] | translate }}
{{ widgetTitle | translate }}
</ng-container>
<nm-container
style="position: absolute; display: inline; right: 53px;"
class="header"
*ngIf="_widgetConfiguration && _widgetConfiguration.header"
[configuration]="
_widgetConfiguration.header | widgetFor: _widgetConfiguration
"
[parent]="id"
id="header"
>
</nm-container>
<nm-help-icon
*ngIf="_widgetConfiguration.configuration['infoText']"
style="position: absolute; display: inline; right: 47px; z-index: 666;"
[info-text]="_widgetConfiguration.configuration['infoText'] | translate"
[info-title]="_widgetConfiguration.configuration['title'] | translate"
[info-width]="_widgetConfiguration.configuration['infoWidth']"
[info-height]="_widgetConfiguration.configuration['infoHeight']"
[info-placement]="'left'"
[wiki-link]="_widgetConfiguration.configuration['wikiLink']"
></nm-help-icon>
</nm-widgetframe>
`,
})
export class EmptyWidgetComponent {
@WidgetId()
public id;
public withHeader: boolean = true;
@WidgetConfiguration()
public _widgetConfiguration: WidgetConfig<EmptyWidgetConfiguration>;
/**
* Shows / Hides the empty widget
*/
@WidgetInput("hide")
public hide: Subject<any> = new ReplaySubject<any>();
/**
* Appends text to the title shown in the empty widget header
*/
@WidgetInput("title")
public title: Subject<any> = new ReplaySubject<any>();
public hideWidget: boolean = false;
public widgetTitle: string;
private unsubscribe = NgUnsubscribe.create();
@WidgetConfigure()
protected configureWidget(
configuration: WidgetConfig<EmptyWidgetConfiguration>
) {
if (this._widgetConfiguration.configuration.withHeader != undefined) {
this.withHeader = this._widgetConfiguration.configuration.withHeader;
}
if (this._widgetConfiguration.configuration.hideWidget != undefined) {
this.hideWidget = this._widgetConfiguration.configuration.hideWidget;
}
this.hide
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((hide) => {
this.hideWidget = hide;
});
this.title
.asObservable()
.pipe(takeUntil(this.unsubscribe))
.subscribe((title) => {
this.widgetTitle = title;
});
}
}
@WidgetComponent("nm-switch-layout")
@Component({
selector: "nm-switch-layout",
template: `
<nm-container
*ngIf="!!component"
[configuration]="component | widgetFor: _widgetConfiguration"
[parent]="id"
[id]="component"
>
</nm-container>
`,
})
export class SwitchLayoutWidgetComponent implements OnDestroy {
@WidgetId()
public id;
@WidgetConfiguration()
public _widgetConfiguration: WidgetConfig;
/**
* The component id to display inside the placeholder widget
*/
@WidgetInput("active-component")
public activeComponent: Subject<string> = new ReplaySubject(1);
public component: string;
private unsubscribe = NgUnsubscribe.create();
constructor(private cdr: ChangeDetectorRef) {}
@WidgetConfigure()
public configureWidget(configuration: WidgetConfig) {
this.activeComponent
.pipe(
filter((component) => !!component),
distinctUntilChanged(),
takeUntil(this.unsubscribe)
)
.subscribe((component) => {
this.component = component;
this.cdr.markForCheck();
});
}
ngOnDestroy(): void {
this.unsubscribe.destroy();
}
}
export interface InteractionLayoutConfiguration extends BaseConfiguration {
/**
* If set to false, inactive interactions will not receive any events over the connected channels.
* Once the interaction is re-activated, all channels will be re-subscribed and emit their latest values.
*
* @default(true)
*/
"keep-attached": boolean;
}
/**
* layout implementation that allows the display and switching between multiple interactions.
*
* The widget input "interaction" can be used to assign individual interactions the layout.
* While only the latest interaction will actually be visible, _all_ interactions set via
* the input will be loaded but hidden.
*
* Each interaction (identified by "identifier" or its JSON representation) will only
* be loaded_once_.
*
* This allows the implementation of externally displayed tab components, without having to
* re-instantiate the interaction every time the active interaction is switched.
*
* To further reduce resources, interactions can be detached from the app-service, by setting the
* widget's "keep-attached" configuration property to false.
* When a detached interaction is re-activated (i.e., when the interaction is submitted using
* the "interaction" input), all connections are re-subscribed withint the app-service.
*/
@WidgetComponent("nm-interaction-layout")
@Component({
selector: "nm-interaction-layout",
template: `
<ng-container
*ngFor="let entry of interactions | keyvalue; trackBy: trackByFn"
>
<nm-interaction
[configuration]="entry.value"
[param]="param | async"
[is-attached]="keepAttached || entry.key == active"
[hidden]="entry.key != active"
>
</nm-interaction>
</ng-container>
`,
})
export class InteractionLayoutWidgetComponent {
public interactions: Map<string, Content> = new Map<string, Content>();
public active: string;
@WidgetId()
public id;
@WidgetInput("interaction")
public interaction: Subject<Content> = new ReplaySubject(1);
@WidgetInput("param")
public param: Subject<any> = new ReplaySubject(1);
public keepAttached: boolean;
constructor(private cdr: ChangeDetectorRef) {}
@WidgetConfigure()
public configureWidget(
configuration: WidgetConfig<InteractionLayoutConfiguration>
) {
this.keepAttached = getOrDefault(
configuration.configuration["keep-attached"],
true
);
this.interaction.subscribe((content) => {
let key = content.identifier || JSON.stringify(content);
this.active = key;
this.cdr.detectChanges();
if (!this.interactions.has(key)) {
this.interactions.set(key, content);
}
this.cdr.markForCheck();
});
}
public trackByFn(index, item) {
return item.key;
}
}