Implement ability to move widget types between widget bundles. Ability to deprecate widget type.
This commit is contained in:
parent
e83aebead8
commit
acdf5ad48c
@ -80,7 +80,7 @@ public class WidgetTypeController extends AutoCommitController {
|
||||
"The newly created Widget Type Id will be present in the response. " +
|
||||
"Specify existing Widget Type id to update the Widget Type. " +
|
||||
"Referencing non-existing Widget Type Id will cause 'Not Found' error." +
|
||||
"\n\nWidget Type alias is unique in the scope of Widget Bundle. " +
|
||||
"\n\nWidget Type fqn is unique in the scope of System or Tenant. " +
|
||||
"Special Tenant Id '13814000-1dd2-11b2-8080-808080808080' is automatically used if the create request is sent by user with 'SYS_ADMIN' authority." +
|
||||
"Remove 'id', 'tenantId' rom the request body example (below) to create new Widget Type entity." +
|
||||
SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
|
||||
@ -243,8 +243,8 @@ public class WidgetTypeController extends AutoCommitController {
|
||||
notes = "Set Widget Type deprecated flag. Referencing non-existing Widget Type Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
|
||||
@RequestMapping(value = "/widgetType/{widgetTypeId}/deprecate/{deprecated}", method = RequestMethod.POST)
|
||||
@ResponseStatus(value = HttpStatus.OK)
|
||||
public void setWidgetTypeDeprecated(
|
||||
@ResponseBody
|
||||
public WidgetTypeDetails setWidgetTypeDeprecated(
|
||||
@ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
|
||||
@PathVariable("widgetTypeId") String strWidgetTypeId,
|
||||
@PathVariable("deprecated") boolean deprecated) throws Exception {
|
||||
@ -252,14 +252,45 @@ public class WidgetTypeController extends AutoCommitController {
|
||||
var currentUser = getCurrentUser();
|
||||
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
|
||||
WidgetTypeDetails wtd = checkWidgetTypeId(widgetTypeId, Operation.WRITE);
|
||||
widgetTypeService.setWidgetTypeDeprecated(currentUser.getTenantId(), widgetTypeId, deprecated);
|
||||
|
||||
if (wtd != null && !Authority.SYS_ADMIN.equals(currentUser.getAuthority())) {
|
||||
WidgetTypeDetails updated = widgetTypeService.setWidgetTypeDeprecated(currentUser.getTenantId(), widgetTypeId, deprecated);
|
||||
if (!Authority.SYS_ADMIN.equals(currentUser.getAuthority())) {
|
||||
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(wtd.getTenantId(), wtd.getBundleAlias());
|
||||
if (widgetsBundle != null) {
|
||||
autoCommit(currentUser, widgetsBundle.getId());
|
||||
}
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
@ApiOperation(value = "Move widget type to target widgets bundle (moveWidgetType)",
|
||||
notes = "Move Widget Type to target Widgets Bundle. Referencing non-existing Widget Type Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
|
||||
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
|
||||
@RequestMapping(value = "/widgetType/{widgetTypeId}/move", params = {"targetBundleAlias"}, method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public WidgetTypeDetails moveWidgetType(
|
||||
@ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
|
||||
@PathVariable("widgetTypeId") String strWidgetTypeId,
|
||||
@ApiParam(value = "Target Widget Bundle alias", required = true)
|
||||
@RequestParam String targetBundleAlias) throws Exception {
|
||||
checkParameter("widgetTypeId", strWidgetTypeId);
|
||||
checkParameter("targetBundleAlias", targetBundleAlias);
|
||||
var currentUser = getCurrentUser();
|
||||
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
|
||||
WidgetTypeDetails wtd = checkWidgetTypeId(widgetTypeId, Operation.WRITE);
|
||||
if (!wtd.getBundleAlias().equals(targetBundleAlias)) {
|
||||
wtd = widgetTypeService.moveWidgetType(currentUser.getTenantId(), widgetTypeId, targetBundleAlias);
|
||||
if (!Authority.SYS_ADMIN.equals(currentUser.getAuthority())) {
|
||||
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(wtd.getTenantId(), wtd.getBundleAlias());
|
||||
if (widgetsBundle != null) {
|
||||
autoCommit(currentUser, widgetsBundle.getId());
|
||||
}
|
||||
WidgetsBundle targetWidgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(currentUser.getTenantId(), targetBundleAlias);
|
||||
if (targetWidgetsBundle != null) {
|
||||
autoCommit(currentUser, targetWidgetsBundle.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
return wtd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -191,10 +191,30 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
|
||||
widgetType.setName("Widget Type");
|
||||
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
|
||||
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
|
||||
|
||||
WidgetsBundle widgetsBundle2 = new WidgetsBundle();
|
||||
widgetsBundle2.setTitle("My widgets bundle 2");
|
||||
WidgetsBundle savedWidgetsBundle2 = doPost("/api/widgetsBundle", widgetsBundle2, WidgetsBundle.class);
|
||||
savedWidgetType.setBundleAlias(savedWidgetsBundle2.getAlias());
|
||||
|
||||
doPost("/api/widgetType", savedWidgetType);
|
||||
|
||||
WidgetTypeDetails foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetTypeDetails.class);
|
||||
Assert.assertEquals(savedWidgetsBundle2.getAlias(), foundWidgetType.getBundleAlias());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateWidgetTypeBundleAliasToNonExistent() throws Exception {
|
||||
WidgetTypeDetails widgetType = new WidgetTypeDetails();
|
||||
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
|
||||
widgetType.setName("Widget Type");
|
||||
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
|
||||
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
|
||||
savedWidgetType.setBundleAlias("some_alias");
|
||||
doPost("/api/widgetType", savedWidgetType)
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(statusReason(containsString("Update of widget type bundle alias is prohibited")));
|
||||
.andExpect(statusReason(containsString("Widget type is referencing to non-existent widgets bundle")));
|
||||
|
||||
}
|
||||
|
||||
@ -212,6 +232,51 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeprecateWidgetType() throws Exception {
|
||||
WidgetTypeDetails widgetType = new WidgetTypeDetails();
|
||||
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
|
||||
widgetType.setName("Widget Type");
|
||||
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
|
||||
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
|
||||
doPost("/api/widgetType/"+savedWidgetType.getId().getId().toString() + "/deprecate/true")
|
||||
.andExpect(status().isOk());
|
||||
WidgetTypeDetails foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetTypeDetails.class);
|
||||
Assert.assertTrue(foundWidgetType.isDeprecated());
|
||||
doPost("/api/widgetType/"+savedWidgetType.getId().getId().toString() + "/deprecate/false")
|
||||
.andExpect(status().isOk());
|
||||
foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetTypeDetails.class);
|
||||
Assert.assertFalse(foundWidgetType.isDeprecated());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveWidgetType() throws Exception {
|
||||
WidgetTypeDetails widgetType = new WidgetTypeDetails();
|
||||
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
|
||||
widgetType.setName("Widget Type");
|
||||
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
|
||||
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
|
||||
WidgetsBundle widgetsBundle2 = new WidgetsBundle();
|
||||
widgetsBundle2.setTitle("My widgets bundle 2");
|
||||
WidgetsBundle savedWidgetsBundle2 = doPost("/api/widgetsBundle", widgetsBundle2, WidgetsBundle.class);
|
||||
doPost("/api/widgetType/"+savedWidgetType.getId().getId().toString() + "/move?targetBundleAlias=" + savedWidgetsBundle2.getAlias())
|
||||
.andExpect(status().isOk());
|
||||
WidgetTypeDetails foundWidgetType = doGet("/api/widgetType/" + savedWidgetType.getId().getId().toString(), WidgetTypeDetails.class);
|
||||
Assert.assertEquals(savedWidgetsBundle2.getAlias(), foundWidgetType.getBundleAlias());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMoveWidgetTypeToNonExistentBundle() throws Exception {
|
||||
WidgetTypeDetails widgetType = new WidgetTypeDetails();
|
||||
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
|
||||
widgetType.setName("Widget Type");
|
||||
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
|
||||
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
|
||||
doPost("/api/widgetType/"+savedWidgetType.getId().getId().toString() + "/move?targetBundleAlias=some_alias")
|
||||
.andExpect(status().isBadRequest())
|
||||
.andExpect(statusReason(containsString("Widget type is referencing to non-existent widgets bundle")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetBundleWidgetTypes() throws Exception {
|
||||
List<WidgetType> widgetTypes = new ArrayList<>();
|
||||
|
||||
@ -33,9 +33,11 @@ public interface WidgetTypeService extends EntityDaoService {
|
||||
|
||||
WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetType);
|
||||
|
||||
WidgetTypeDetails moveWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId, String targetBundleAlias);
|
||||
|
||||
void deleteWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId);
|
||||
|
||||
void setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated);
|
||||
WidgetTypeDetails setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated);
|
||||
|
||||
List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias);
|
||||
|
||||
|
||||
@ -87,7 +87,10 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
|
||||
throw new DataValidationException("Can't move existing widget type to different tenant!");
|
||||
}
|
||||
if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) {
|
||||
throw new DataValidationException("Update of widget type bundle alias is prohibited!");
|
||||
WidgetsBundle widgetsBundle = widgetsBundleDao.findWidgetsBundleByTenantIdAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias());
|
||||
if (widgetsBundle == null) {
|
||||
throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!");
|
||||
}
|
||||
}
|
||||
if (!storedWidgetType.getFqn().equals(widgetTypeDetails.getFqn())) {
|
||||
throw new DataValidationException("Update of widget type fqn is prohibited!");
|
||||
|
||||
@ -84,6 +84,21 @@ public class WidgetTypeServiceImpl extends AbstractEntityService implements Widg
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public WidgetTypeDetails moveWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId, String targetBundleAlias) {
|
||||
log.trace("Executing moveWidgetType, widgetTypeId [{}], targetBundleAlias [{}]", widgetTypeId, targetBundleAlias);
|
||||
Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
|
||||
WidgetTypeDetails widgetTypeDetails = widgetTypeDao.findById(tenantId, widgetTypeId.getId());
|
||||
if (widgetTypeDetails != null && !widgetTypeDetails.getBundleAlias().equals(targetBundleAlias)) {
|
||||
widgetTypeDetails.setBundleAlias(targetBundleAlias);
|
||||
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
|
||||
widgetTypeDetails = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
|
||||
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(widgetTypeDetails.getTenantId())
|
||||
.entityId(widgetTypeDetails.getId()).added(widgetTypeDetails.getId() == null).build());
|
||||
}
|
||||
return widgetTypeDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId) {
|
||||
log.trace("Executing deleteWidgetType [{}]", widgetTypeId);
|
||||
@ -93,14 +108,15 @@ public class WidgetTypeServiceImpl extends AbstractEntityService implements Widg
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated) {
|
||||
public WidgetTypeDetails setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated) {
|
||||
log.trace("Executing setWidgetTypeDeprecated, widgetTypeId [{}], deprecated [{}]", widgetTypeId, deprecated);
|
||||
Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
|
||||
WidgetTypeDetails widgetTypeDetails = widgetTypeDao.findById(tenantId, widgetTypeId.getId());
|
||||
if (widgetTypeDetails.isDeprecated() != deprecated) {
|
||||
widgetTypeDetails.setDeprecated(deprecated);
|
||||
widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
|
||||
widgetTypeDetails = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
|
||||
}
|
||||
return widgetTypeDetails;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -117,6 +133,7 @@ public class WidgetTypeServiceImpl extends AbstractEntityService implements Widg
|
||||
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
|
||||
Validator.validateString(bundleAlias, INCORRECT_BUNDLE_ALIAS + bundleAlias);
|
||||
return widgetTypeDao.findWidgetTypesDetailsByTenantIdAndBundleAlias(tenantId.getId(), bundleAlias);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@ -152,9 +152,12 @@ export class WidgetService {
|
||||
return this.getBundleWidgetTypes(bundleAlias, isSystem, config).pipe(
|
||||
map((types) => {
|
||||
types = types.sort((a, b) => {
|
||||
let result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
|
||||
let result = (a.deprecated ? 1 : 0) - (b.deprecated ? 1 : 0);
|
||||
if (result === 0) {
|
||||
result = b.createdTime - a.createdTime;
|
||||
result = widgetType[b.descriptor.type].localeCompare(widgetType[a.descriptor.type]);
|
||||
if (result === 0) {
|
||||
result = b.createdTime - a.createdTime;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
@ -180,6 +183,9 @@ export class WidgetService {
|
||||
};
|
||||
|
||||
widget.config.title = widgetTypeInfo.widgetName;
|
||||
if (type.deprecated) {
|
||||
widget.config.title += ` (${this.translate.instant('widget.deprecated')})`;
|
||||
}
|
||||
|
||||
widgetTypes.push(widget);
|
||||
top += sizeY;
|
||||
@ -216,6 +222,22 @@ export class WidgetService {
|
||||
}));
|
||||
}
|
||||
|
||||
public setWidgetTypeDeprecated(widgetTypeId: string, deprecated: boolean, config?: RequestConfig): Observable<WidgetTypeDetails> {
|
||||
return this.http.post<WidgetTypeDetails>(`/api/widgetType/${widgetTypeId}/deprecate/${deprecated}`,
|
||||
defaultHttpOptionsFromConfig(config)).pipe(
|
||||
tap((savedWidgetType) => {
|
||||
this.widgetTypeUpdated(savedWidgetType);
|
||||
}));
|
||||
}
|
||||
|
||||
public moveWidgetType(widgetTypeId: string, targetBundleAlias: string, config?: RequestConfig): Observable<WidgetTypeDetails> {
|
||||
return this.http.post<WidgetTypeDetails>(`/api/widgetType/${widgetTypeId}/move?targetBundleAlias=${targetBundleAlias}`,
|
||||
defaultHttpOptionsFromConfig(config)).pipe(
|
||||
tap((savedWidgetType) => {
|
||||
this.widgetTypeUpdated(savedWidgetType);
|
||||
}));
|
||||
}
|
||||
|
||||
public saveImportedWidgetTypeDetails(widgetTypeDetails: WidgetTypeDetails,
|
||||
config?: RequestConfig): Observable<WidgetTypeDetails> {
|
||||
return this.http.post<WidgetTypeDetails>('/api/widgetType', widgetTypeDetails,
|
||||
|
||||
@ -408,7 +408,7 @@
|
||||
(ngModelChange)="searchBundle = $event">
|
||||
</tb-widgets-bundle-search>
|
||||
</div>
|
||||
<div class="details-buttons" *ngIf="isAddingWidget">
|
||||
<div class="details-buttons" *ngIf="isAddingWidget" fxLayout="row" fxLayoutAlign="start center">
|
||||
<button mat-button type="button" (click)="importWidget($event)"
|
||||
*ngIf="!dashboardWidgetSelectComponent?.widgetsBundle">
|
||||
<mat-icon>file_upload</mat-icon>{{ 'dashboard.import-widget' | translate }}</button>
|
||||
@ -419,6 +419,15 @@
|
||||
matTooltipPosition="above">
|
||||
<mat-icon>filter_list</mat-icon>
|
||||
</button>
|
||||
<tb-toggle-select *ngIf="dashboardWidgetSelectComponent?.hasDeprecated"
|
||||
appearance="fill-invert"
|
||||
disablePagination
|
||||
selectMediaBreakpoint="xs"
|
||||
[(ngModel)]="dashboardWidgetSelectComponent.widgetsListMode">
|
||||
<tb-toggle-option value="all">{{ 'widget.all' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="actual">{{ 'widget.actual' | translate }}</tb-toggle-option>
|
||||
<tb-toggle-option value="deprecated">{{ 'widget.deprecated' | translate }}</tb-toggle-option>
|
||||
</tb-toggle-select>
|
||||
</div>
|
||||
<tb-dashboard-widget-select #dashboardWidgetSelect
|
||||
*ngIf="isAddingWidget"
|
||||
|
||||
@ -24,7 +24,7 @@
|
||||
<img class="preview" [src]="getPreviewImage(widget.image)" alt="{{ widget.title }}">
|
||||
</div>
|
||||
<div fxFlex fxLayout="column">
|
||||
<mat-card-title>{{widget.title}}</mat-card-title>
|
||||
<mat-card-title>{{widget.title}}<div *ngIf="widget.deprecated" class="tb-deprecated" translate>widget.deprecated</div></mat-card-title>
|
||||
<mat-card-subtitle>{{ 'widget.' + widget.type | translate }}</mat-card-subtitle>
|
||||
<mat-card-content *ngIf="widget.description">
|
||||
{{ widget.description }}
|
||||
|
||||
@ -54,6 +54,10 @@
|
||||
font-size: 20px;
|
||||
line-height: normal;
|
||||
margin-bottom: 8px;
|
||||
.tb-deprecated {
|
||||
font-size: 14px;
|
||||
color: rgba(209, 39, 48, 0.87);
|
||||
}
|
||||
}
|
||||
|
||||
.mat-mdc-card-subtitle {
|
||||
|
||||
@ -25,6 +25,8 @@ import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
|
||||
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
|
||||
import { isDefinedAndNotNull } from '@core/utils';
|
||||
|
||||
type widgetsListMode = 'all' | 'actual' | 'deprecated';
|
||||
|
||||
@Component({
|
||||
selector: 'tb-dashboard-widget-select',
|
||||
templateUrl: './dashboard-widget-select.component.html',
|
||||
@ -34,9 +36,11 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
|
||||
private search$ = new BehaviorSubject<string>('');
|
||||
private filterWidgetTypes$ = new BehaviorSubject<Array<widgetType>>(null);
|
||||
private widgetsListMode$ = new BehaviorSubject<widgetsListMode>('actual');
|
||||
private widgetsInfo: Observable<Array<WidgetInfo>>;
|
||||
private widgetsBundleValue: WidgetsBundle;
|
||||
widgetTypes = new Set<widgetType>();
|
||||
hasDeprecated = false;
|
||||
|
||||
widgets$: Observable<Array<WidgetInfo>>;
|
||||
loadingWidgetsSubject: BehaviorSubject<boolean> = new BehaviorSubject(false);
|
||||
@ -54,8 +58,10 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
this.widgetsBundleValue = widgetBundle;
|
||||
if (widgetBundle === null) {
|
||||
this.widgetTypes.clear();
|
||||
this.hasDeprecated = false;
|
||||
}
|
||||
this.filterWidgetTypes$.next(null);
|
||||
this.widgetsListMode$.next('actual');
|
||||
this.widgetsInfo = null;
|
||||
}
|
||||
}
|
||||
@ -81,6 +87,15 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
return this.filterWidgetTypes$.value;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set widgetsListMode(mode: widgetsListMode) {
|
||||
this.widgetsListMode$.next(mode);
|
||||
}
|
||||
|
||||
get widgetsListMode(): widgetsListMode {
|
||||
return this.widgetsListMode$.value;
|
||||
}
|
||||
|
||||
@Output()
|
||||
widgetSelected: EventEmitter<WidgetInfo> = new EventEmitter<WidgetInfo>();
|
||||
|
||||
@ -94,7 +109,7 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
distinctUntilChanged(),
|
||||
switchMap(search => this.fetchWidgetBundle(search))
|
||||
);
|
||||
this.widgets$ = combineLatest([this.search$.asObservable(), this.filterWidgetTypes$.asObservable()]).pipe(
|
||||
this.widgets$ = combineLatest([this.search$.asObservable(), this.filterWidgetTypes$.asObservable(), this.widgetsListMode$]).pipe(
|
||||
distinctUntilChanged((oldValue, newValue) => JSON.stringify(oldValue) === JSON.stringify(newValue)),
|
||||
switchMap(search => this.fetchWidget(...search))
|
||||
);
|
||||
@ -113,6 +128,7 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
map(widgets => {
|
||||
widgets = widgets.sort((a, b) => b.createdTime - a.createdTime);
|
||||
const widgetTypes = new Set<widgetType>();
|
||||
const hasDeprecated = widgets.some(w => w.deprecated);
|
||||
const widgetInfos = widgets.map((widgetTypeInfo) => {
|
||||
widgetTypes.add(widgetTypeInfo.widgetType);
|
||||
const widget: WidgetInfo = {
|
||||
@ -120,13 +136,15 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
type: widgetTypeInfo.widgetType,
|
||||
title: widgetTypeInfo.name,
|
||||
image: widgetTypeInfo.image,
|
||||
description: widgetTypeInfo.description
|
||||
description: widgetTypeInfo.description,
|
||||
deprecated: widgetTypeInfo.deprecated
|
||||
};
|
||||
return widget;
|
||||
}
|
||||
);
|
||||
setTimeout(() => {
|
||||
this.widgetTypes = widgetTypes;
|
||||
this.hasDeprecated = hasDeprecated;
|
||||
this.cd.markForCheck();
|
||||
});
|
||||
return widgetInfos;
|
||||
@ -185,8 +203,10 @@ export class DashboardWidgetSelectComponent implements OnInit {
|
||||
);
|
||||
}
|
||||
|
||||
private fetchWidget(search: string, filter: widgetType[]): Observable<Array<WidgetInfo>> {
|
||||
private fetchWidget(search: string, filter: widgetType[], listMode: widgetsListMode): Observable<Array<WidgetInfo>> {
|
||||
return this.getWidgets().pipe(
|
||||
map(widgets => (listMode && listMode !== 'all') ?
|
||||
widgets.filter((widget) => listMode === 'actual' ? !widget.deprecated : widget.deprecated) : widgets),
|
||||
map(widgets => filter ? widgets.filter((widget) => filter.includes(widget.type)) : widgets),
|
||||
map(widgets => search ? widgets.filter(
|
||||
widget => (
|
||||
|
||||
@ -0,0 +1,56 @@
|
||||
<!--
|
||||
|
||||
Copyright © 2016-2023 The Thingsboard Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
-->
|
||||
<form [formGroup]="moveWidgetTypeFormGroup" (ngSubmit)="move()">
|
||||
<mat-toolbar color="primary">
|
||||
<h2 translate>widget.move-widget-type</h2>
|
||||
<span fxFlex></span>
|
||||
<button mat-icon-button
|
||||
(click)="cancel()"
|
||||
type="button">
|
||||
<mat-icon class="material-icons">close</mat-icon>
|
||||
</button>
|
||||
</mat-toolbar>
|
||||
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
|
||||
</mat-progress-bar>
|
||||
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
|
||||
<div mat-dialog-content>
|
||||
<fieldset>
|
||||
<span translate>widget.move-widget-type-text</span>
|
||||
<tb-widgets-bundle-select fxFlex
|
||||
formControlName="widgetsBundle"
|
||||
required
|
||||
[excludeBundleIds]="[data.currentBundleId]"
|
||||
bundlesScope="{{bundlesScope}}">
|
||||
</tb-widgets-bundle-select>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div mat-dialog-actions fxLayoutAlign="end center">
|
||||
<button mat-button color="primary"
|
||||
type="button"
|
||||
[disabled]="(isLoading$ | async)"
|
||||
(click)="cancel()" cdkFocusInitial>
|
||||
{{ 'action.cancel' | translate }}
|
||||
</button>
|
||||
<button mat-raised-button color="primary"
|
||||
type="submit"
|
||||
[disabled]="(isLoading$ | async) || moveWidgetTypeFormGroup.invalid
|
||||
|| !moveWidgetTypeFormGroup.dirty">
|
||||
{{ 'action.move' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
@ -0,0 +1,82 @@
|
||||
///
|
||||
/// Copyright © 2016-2023 The Thingsboard Authors
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
///
|
||||
|
||||
import { Component, Inject, OnInit } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { DialogComponent } from '@shared/components/dialog.component';
|
||||
import { Router } from '@angular/router';
|
||||
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
|
||||
import { getCurrentAuthUser } from '@core/auth/auth.selectors';
|
||||
import { Authority } from '@shared/models/authority.enum';
|
||||
|
||||
export interface MoveWidgetTypeDialogResult {
|
||||
bundleId: string;
|
||||
bundleAlias: string;
|
||||
}
|
||||
|
||||
export interface MoveWidgetTypeDialogData {
|
||||
currentBundleId: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'tb-move-widget-type-dialog',
|
||||
templateUrl: './move-widget-type-dialog.component.html',
|
||||
styleUrls: []
|
||||
})
|
||||
export class MoveWidgetTypeDialogComponent extends
|
||||
DialogComponent<MoveWidgetTypeDialogComponent, MoveWidgetTypeDialogResult> implements OnInit {
|
||||
|
||||
moveWidgetTypeFormGroup: UntypedFormGroup;
|
||||
|
||||
bundlesScope: string;
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
protected router: Router,
|
||||
@Inject(MAT_DIALOG_DATA) public data: MoveWidgetTypeDialogData,
|
||||
public dialogRef: MatDialogRef<MoveWidgetTypeDialogComponent, MoveWidgetTypeDialogResult>,
|
||||
public fb: UntypedFormBuilder) {
|
||||
super(store, router, dialogRef);
|
||||
|
||||
const authUser = getCurrentAuthUser(store);
|
||||
if (authUser.authority === Authority.TENANT_ADMIN) {
|
||||
this.bundlesScope = 'tenant';
|
||||
} else {
|
||||
this.bundlesScope = 'system';
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.moveWidgetTypeFormGroup = this.fb.group({
|
||||
widgetsBundle: [null, [Validators.required]]
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this.dialogRef.close(null);
|
||||
}
|
||||
|
||||
move(): void {
|
||||
const widgetsBundle: WidgetsBundle = this.moveWidgetTypeFormGroup.get('widgetsBundle').value;
|
||||
const result: MoveWidgetTypeDialogResult = {
|
||||
bundleId: widgetsBundle.id.id,
|
||||
bundleAlias: widgetsBundle.alias
|
||||
};
|
||||
this.dialogRef.close(result);
|
||||
}
|
||||
}
|
||||
@ -63,9 +63,18 @@
|
||||
[tb-circular-progress]="saveWidgetAsPending"
|
||||
matTooltip="{{ 'widget.saveAs' | translate }} (Shift + CTRL + S)"
|
||||
matTooltipPosition="below">
|
||||
<mat-icon>save</mat-icon>
|
||||
<mat-icon>save_as</mat-icon>
|
||||
<span translate>action.saveAs</span>
|
||||
</button>
|
||||
<button mat-raised-button
|
||||
fxHide.lt-md [disabled]="(isLoading$ | async) || moveDisabled()"
|
||||
(click)="moveWidget()"
|
||||
[tb-circular-progress]="moveWidgetPending"
|
||||
matTooltip="{{ 'widget.move' | translate }} (Shift + CTRL + M)"
|
||||
matTooltipPosition="below">
|
||||
<tb-icon matButtonIcon>mdi:content-save-move</tb-icon>
|
||||
<span translate>action.move</span>
|
||||
</button>
|
||||
<button mat-button
|
||||
fxHide.lt-lg
|
||||
(click)="fullscreen = !fullscreen"
|
||||
@ -106,9 +115,15 @@
|
||||
<button mat-menu-item
|
||||
[disabled]="(isLoading$ | async) || saveAsDisabled()"
|
||||
(click)="saveWidgetAs()">
|
||||
<mat-icon>save</mat-icon>
|
||||
<mat-icon>save_as</mat-icon>
|
||||
<span translate>action.saveAs</span>
|
||||
</button>
|
||||
<button mat-menu-item
|
||||
[disabled]="(isLoading$ | async) || moveDisabled()"
|
||||
(click)="moveWidget()">
|
||||
<tb-icon matMenuItemIcon>mdi:content-save-move</tb-icon>
|
||||
<span translate>action.move</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</mat-toolbar>
|
||||
<div fxFlex style="position: relative;">
|
||||
@ -253,6 +268,11 @@
|
||||
rows="2" maxlength="255"></textarea>
|
||||
<mat-hint align="end">{{descriptionInput.value?.length || 0}}/255</mat-hint>
|
||||
</mat-form-field>
|
||||
<mat-slide-toggle class="mat-block" style="padding-bottom: 16px;"
|
||||
[(ngModel)]="widget.deprecated"
|
||||
(ngModelChange)="isDirty = true">
|
||||
{{ 'widget.deprecated' | translate }}
|
||||
</mat-slide-toggle>
|
||||
<mat-form-field class="mat-block">
|
||||
<mat-label translate>widget.settings-form-selector</mat-label>
|
||||
<input matInput
|
||||
|
||||
@ -15,20 +15,22 @@
|
||||
///
|
||||
|
||||
import { PageComponent } from '@shared/components/page.component';
|
||||
import { Component, ElementRef, Inject, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
|
||||
import {
|
||||
Component,
|
||||
ElementRef,
|
||||
EventEmitter,
|
||||
Inject,
|
||||
OnDestroy,
|
||||
OnInit,
|
||||
ViewChild,
|
||||
ViewEncapsulation
|
||||
} from '@angular/core';
|
||||
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { AppState } from '@core/core.state';
|
||||
import { WidgetService } from '@core/http/widget.service';
|
||||
import { detailsToWidgetInfo, toWidgetInfo, WidgetInfo } from '@home/models/widget-component.models';
|
||||
import {
|
||||
Widget,
|
||||
WidgetConfig,
|
||||
WidgetType,
|
||||
widgetType,
|
||||
WidgetTypeDetails,
|
||||
widgetTypesData
|
||||
} from '@shared/models/widget.models';
|
||||
import { detailsToWidgetInfo, WidgetInfo } from '@home/models/widget-component.models';
|
||||
import { Widget, WidgetConfig, widgetType, WidgetTypeDetails, widgetTypesData } from '@shared/models/widget.models';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { deepClone } from '@core/utils';
|
||||
import { HasDirtyFlag } from '@core/guards/confirm-on-exit.guard';
|
||||
@ -51,13 +53,18 @@ import {
|
||||
SaveWidgetTypeAsDialogComponent,
|
||||
SaveWidgetTypeAsDialogResult
|
||||
} from '@home/pages/widget/save-widget-type-as-dialog.component';
|
||||
import { forkJoin, from, Subscription } from 'rxjs';
|
||||
import { forkJoin, Subscription } from 'rxjs';
|
||||
import { ResizeObserver } from '@juggle/resize-observer';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
import { widgetEditorCompleter } from '@home/pages/widget/widget-editor.models';
|
||||
import { Observable } from 'rxjs/internal/Observable';
|
||||
import { map, tap } from 'rxjs/operators';
|
||||
import { beautifyCss, beautifyHtml, beautifyJs } from '@shared/models/beautify.models';
|
||||
import {
|
||||
MoveWidgetTypeDialogComponent,
|
||||
MoveWidgetTypeDialogData,
|
||||
MoveWidgetTypeDialogResult
|
||||
} from '@home/pages/widget/move-widget-type-dialog.component';
|
||||
import Timeout = NodeJS.Timeout;
|
||||
|
||||
// @dynamic
|
||||
@Component({
|
||||
@ -148,6 +155,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
iframeWidgetEditModeInited = false;
|
||||
saveWidgetPending = false;
|
||||
saveWidgetAsPending = false;
|
||||
moveWidgetPending = false;
|
||||
|
||||
gotError = false;
|
||||
errorMarkers: number[] = [];
|
||||
@ -157,6 +165,8 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
|
||||
hotKeys: Hotkey[] = [];
|
||||
|
||||
updateBreadcrumbs = new EventEmitter();
|
||||
|
||||
private rxSubscriptions = new Array<Subscription>();
|
||||
|
||||
constructor(protected store: Store<AppState>,
|
||||
@ -249,6 +259,16 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
}, ['INPUT', 'SELECT', 'TEXTAREA'],
|
||||
this.translate.instant('widget.saveAs'))
|
||||
);
|
||||
this.hotKeys.push(
|
||||
new Hotkey('shift+ctrl+m', (event: KeyboardEvent) => {
|
||||
if (!getCurrentIsLoading(this.store) && !this.moveDisabled()) {
|
||||
event.preventDefault();
|
||||
this.moveWidget();
|
||||
}
|
||||
return false;
|
||||
}, ['INPUT', 'SELECT', 'TEXTAREA'],
|
||||
this.translate.instant('widget.move'))
|
||||
);
|
||||
this.hotKeys.push(
|
||||
new Hotkey('shift+ctrl+f', (event: KeyboardEvent) => {
|
||||
event.preventDefault();
|
||||
@ -542,17 +562,22 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
private commitSaveWidget() {
|
||||
const id = (this.widgetTypeDetails && this.widgetTypeDetails.id) ? this.widgetTypeDetails.id : undefined;
|
||||
const createdTime = (this.widgetTypeDetails && this.widgetTypeDetails.createdTime) ? this.widgetTypeDetails.createdTime : undefined;
|
||||
this.widgetService.saveWidgetTypeDetails(this.widget, id, this.widgetsBundle.alias, createdTime).subscribe(
|
||||
(widgetTypeDetails) => {
|
||||
this.setWidgetTypeDetails(widgetTypeDetails);
|
||||
this.widgetService.saveWidgetTypeDetails(this.widget, id, this.widgetsBundle.alias, createdTime).subscribe({
|
||||
next: (widgetTypeDetails) => {
|
||||
this.saveWidgetPending = false;
|
||||
if (!this.widgetTypeDetails?.id) {
|
||||
this.isDirty = false;
|
||||
this.router.navigate(['..', widgetTypeDetails.id.id], {relativeTo: this.route});
|
||||
} else {
|
||||
this.setWidgetTypeDetails(widgetTypeDetails);
|
||||
}
|
||||
this.store.dispatch(new ActionNotificationShow(
|
||||
{message: this.translate.instant('widget.widget-saved'), type: 'success', duration: 500}));
|
||||
},
|
||||
() => {
|
||||
error: () => {
|
||||
this.saveWidgetPending = false;
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private commitSaveWidgetAs() {
|
||||
@ -570,12 +595,18 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
this.widget.defaultConfig = JSON.stringify(config);
|
||||
this.isDirty = false;
|
||||
this.widgetService.saveWidgetTypeDetails(this.widget, undefined, saveWidgetAsData.bundleAlias, undefined).subscribe(
|
||||
(widgetTypeDetails) => {
|
||||
this.router.navigateByUrl(`/widgets-bundles/${saveWidgetAsData.bundleId}/widgetTypes/${widgetTypeDetails.id.id}`);
|
||||
}
|
||||
);
|
||||
{
|
||||
next: (widgetTypeDetails) => {
|
||||
this.saveWidgetAsPending = false;
|
||||
this.router.navigateByUrl(`/widgets-bundles/${saveWidgetAsData.bundleId}/widgetTypes/${widgetTypeDetails.id.id}`);
|
||||
},
|
||||
error: () => {
|
||||
this.saveWidgetAsPending = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.saveWidgetAsPending = false;
|
||||
}
|
||||
this.saveWidgetAsPending = false;
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -587,6 +618,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
this.widget.defaultConfig = JSON.stringify(config);
|
||||
this.origWidget = deepClone(this.widget);
|
||||
this.isDirty = false;
|
||||
this.updateBreadcrumbs.emit();
|
||||
}
|
||||
|
||||
applyWidgetScript(): void {
|
||||
@ -623,6 +655,34 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
this.applyWidgetScript();
|
||||
}
|
||||
|
||||
moveWidget() {
|
||||
this.moveWidgetPending = true;
|
||||
this.dialog.open<MoveWidgetTypeDialogComponent, MoveWidgetTypeDialogData,
|
||||
MoveWidgetTypeDialogResult>(MoveWidgetTypeDialogComponent, {
|
||||
disableClose: true,
|
||||
data: {
|
||||
currentBundleId: this.widgetsBundle.id.id
|
||||
},
|
||||
panelClass: ['tb-dialog', 'tb-fullscreen-dialog']
|
||||
}).afterClosed().subscribe(
|
||||
(moveWidgetTypeData) => {
|
||||
if (moveWidgetTypeData) {
|
||||
this.widgetService.moveWidgetType(this.widgetTypeDetails.id.id, moveWidgetTypeData.bundleAlias).subscribe({
|
||||
next: (widgetTypeDetails) => {
|
||||
this.moveWidgetPending = false;
|
||||
this.router.navigateByUrl(`/widgets-bundles/${moveWidgetTypeData.bundleId}/widgetTypes/${widgetTypeDetails.id.id}`);
|
||||
},
|
||||
error: () => {
|
||||
this.moveWidgetPending = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.moveWidgetPending = false;
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
undoDisabled(): boolean {
|
||||
return !this.isDirty
|
||||
|| !this.iframeWidgetEditModeInited
|
||||
@ -644,6 +704,15 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
|
||||
|| this.saveWidgetAsPending;
|
||||
}
|
||||
|
||||
moveDisabled(): boolean {
|
||||
return this.isReadOnly
|
||||
|| !this.widgetTypeDetails?.id
|
||||
|| !this.iframeWidgetEditModeInited
|
||||
|| this.saveWidgetPending
|
||||
|| this.saveWidgetAsPending
|
||||
|| this.moveWidgetPending;
|
||||
}
|
||||
|
||||
beautifyCss(): void {
|
||||
beautifyCss(this.widget.templateCss, {indent_size: 4}).subscribe(
|
||||
(res) => {
|
||||
|
||||
@ -78,35 +78,28 @@ export class WidgetEditorDataResolver implements Resolve<WidgetEditorData> {
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> {
|
||||
const widgetTypeId = route.params.widgetTypeId;
|
||||
return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe(
|
||||
map((result) => ({
|
||||
if (!widgetTypeId || Object.keys(widgetType).includes(widgetTypeId)) {
|
||||
let widgetTypeParam = widgetTypeId as widgetType;
|
||||
if (!widgetTypeParam) {
|
||||
widgetTypeParam = widgetType.timeseries;
|
||||
}
|
||||
return this.widgetsService.getWidgetTemplate(widgetTypeParam).pipe(
|
||||
map((widget) => {
|
||||
widget.widgetName = null;
|
||||
return {
|
||||
widgetTypeDetails: null,
|
||||
widget
|
||||
};
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe(
|
||||
map((result) => ({
|
||||
widgetTypeDetails: result,
|
||||
widget: detailsToWidgetInfo(result)
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WidgetEditorAddDataResolver implements Resolve<WidgetEditorData> {
|
||||
|
||||
constructor(private widgetsService: WidgetService) {
|
||||
}
|
||||
|
||||
resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> {
|
||||
let widgetTypeParam = route.params.widgetType as widgetType;
|
||||
if (!widgetTypeParam) {
|
||||
widgetTypeParam = widgetType.timeseries;
|
||||
);
|
||||
}
|
||||
return this.widgetsService.getWidgetTemplate(widgetTypeParam).pipe(
|
||||
map((widget) => {
|
||||
widget.widgetName = null;
|
||||
return {
|
||||
widgetTypeDetails: null,
|
||||
widget
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -114,7 +107,9 @@ export const widgetTypesBreadcumbLabelFunction: BreadCrumbLabelFunction<any> = (
|
||||
route.data.widgetsBundle.title);
|
||||
|
||||
export const widgetEditorBreadcumbLabelFunction: BreadCrumbLabelFunction<WidgetEditorComponent> =
|
||||
((route, translate, component) => component ? component.widget.widgetName : '');
|
||||
((route, translate, component) =>
|
||||
component?.widget?.widgetName ?
|
||||
(component.widget.widgetName + (component.widget.deprecated ? ` (${translate.instant('widget.deprecated')})` : '')) : '');
|
||||
|
||||
export const widgetsBundlesRoutes: Routes = [
|
||||
{
|
||||
@ -175,22 +170,6 @@ export const widgetsBundlesRoutes: Routes = [
|
||||
resolve: {
|
||||
widgetEditorData: WidgetEditorDataResolver
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'add/:widgetType',
|
||||
component: WidgetEditorComponent,
|
||||
canDeactivate: [ConfirmOnExitGuard],
|
||||
data: {
|
||||
auth: [Authority.SYS_ADMIN, Authority.TENANT_ADMIN],
|
||||
title: 'widget.editor',
|
||||
breadcrumb: {
|
||||
labelFunction: widgetEditorBreadcumbLabelFunction,
|
||||
icon: 'insert_chart'
|
||||
} as BreadCrumbConfig<WidgetEditorComponent>
|
||||
},
|
||||
resolve: {
|
||||
widgetEditorData: WidgetEditorAddDataResolver
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -216,7 +195,11 @@ const routes: Routes = [
|
||||
},
|
||||
{
|
||||
path: 'widgets-bundles/:widgetsBundleId/widgetTypes/add/:widgetType',
|
||||
redirectTo: '/resources/widgets-bundles/:widgetsBundleId/widgetTypes/add/:widgetType',
|
||||
redirectTo: '/resources/widgets-bundles/:widgetsBundleId/widgetTypes/:widgetType',
|
||||
},
|
||||
{
|
||||
path: 'resources/widgets-bundles/:widgetsBundleId/widgetTypes/add/:widgetType',
|
||||
redirectTo: '/resources/widgets-bundles/:widgetsBundleId/widgetTypes/:widgetType',
|
||||
}
|
||||
];
|
||||
|
||||
@ -228,8 +211,7 @@ const routes: Routes = [
|
||||
WidgetsBundlesTableConfigResolver,
|
||||
WidgetsBundleResolver,
|
||||
WidgetsTypesDataResolver,
|
||||
WidgetEditorDataResolver,
|
||||
WidgetEditorAddDataResolver
|
||||
WidgetEditorDataResolver
|
||||
]
|
||||
})
|
||||
export class WidgetLibraryRoutingModule { }
|
||||
|
||||
@ -160,7 +160,7 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
|
||||
}).afterClosed().subscribe(
|
||||
(type) => {
|
||||
if (type) {
|
||||
this.router.navigate(['add', type], {relativeTo: this.route});
|
||||
this.router.navigate([type], {relativeTo: this.route});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@ -25,6 +25,7 @@ import { WidgetEditorComponent } from '@home/pages/widget/widget-editor.componen
|
||||
import { SelectWidgetTypeDialogComponent } from '@home/pages/widget/select-widget-type-dialog.component';
|
||||
import { SaveWidgetTypeAsDialogComponent } from './save-widget-type-as-dialog.component';
|
||||
import { WidgetsBundleTabsComponent } from '@home/pages/widget/widgets-bundle-tabs.component';
|
||||
import { MoveWidgetTypeDialogComponent } from '@home/pages/widget/move-widget-type-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
@ -33,6 +34,7 @@ import { WidgetsBundleTabsComponent } from '@home/pages/widget/widgets-bundle-ta
|
||||
WidgetEditorComponent,
|
||||
SelectWidgetTypeDialogComponent,
|
||||
SaveWidgetTypeAsDialogComponent,
|
||||
MoveWidgetTypeDialogComponent,
|
||||
WidgetsBundleTabsComponent
|
||||
],
|
||||
imports: [
|
||||
|
||||
@ -61,6 +61,9 @@ export class WidgetsBundleSelectComponent implements ControlValueAccessor, OnIni
|
||||
@Input()
|
||||
disabled: boolean;
|
||||
|
||||
@Input()
|
||||
excludeBundleIds: Array<string>;
|
||||
|
||||
widgetsBundles$: Observable<Array<WidgetsBundle>>;
|
||||
|
||||
widgetsBundles: Array<WidgetsBundle>;
|
||||
@ -161,6 +164,12 @@ export class WidgetsBundleSelectComponent implements ControlValueAccessor, OnIni
|
||||
} else {
|
||||
widgetsBundlesObservable = this.widgetService.getAllWidgetsBundles();
|
||||
}
|
||||
if (this.excludeBundleIds && this.excludeBundleIds.length) {
|
||||
widgetsBundlesObservable = widgetsBundlesObservable.pipe(
|
||||
map((widgetBundles) =>
|
||||
widgetBundles.filter(w => !this.excludeBundleIds.includes(w.id.id)))
|
||||
);
|
||||
}
|
||||
return widgetsBundlesObservable;
|
||||
}
|
||||
|
||||
|
||||
@ -698,6 +698,7 @@ export interface WidgetInfo {
|
||||
title: string;
|
||||
image?: string;
|
||||
description?: string;
|
||||
deprecated?: boolean;
|
||||
}
|
||||
|
||||
export interface GroupInfo {
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
"suspend": "Suspend",
|
||||
"save": "Save",
|
||||
"saveAs": "Save as",
|
||||
"move": "Move",
|
||||
"cancel": "Cancel",
|
||||
"ok": "OK",
|
||||
"delete": "Delete",
|
||||
@ -4548,8 +4549,11 @@
|
||||
"unable-to-save-widget-error": "Unable to save widget! Widget has errors!",
|
||||
"save": "Save widget",
|
||||
"saveAs": "Save widget as",
|
||||
"move": "Move widget",
|
||||
"save-widget-type-as": "Save widget type as",
|
||||
"save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle",
|
||||
"move-widget-type": "Move widget type",
|
||||
"move-widget-type-text": "Please select target widgets bundle",
|
||||
"toggle-fullscreen": "Toggle fullscreen",
|
||||
"run": "Run widget",
|
||||
"title": "Widget title",
|
||||
@ -4572,6 +4576,9 @@
|
||||
"settings-form-selector": "Settings form selector",
|
||||
"data-key-settings-form-selector": "Data key settings form selector",
|
||||
"latest-data-key-settings-form-selector": "Latest data key settings form selector",
|
||||
"all": "All",
|
||||
"actual": "Actual",
|
||||
"deprecated": "Deprecated",
|
||||
"has-basic-mode": "Has basic mode",
|
||||
"basic-mode-form-selector": "Basic mode form selector",
|
||||
"basic-mode": "Basic",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user