Browse Source

Merge branch 'feature/html-container-widget' into lts-4.2

pull/15581/head
Igor Kulikov 5 months ago
parent
commit
2c554d4239
  1. 2
      application/src/main/data/json/system/widget_types/html_container.json
  2. 15
      ui-ngx/src/app/modules/home/components/widget/lib/html/html-container-widget.models.ts
  3. 162
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/html/html-container-settings.component.html
  4. 114
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/html/html-container-settings.component.scss
  5. 63
      ui-ngx/src/app/modules/home/components/widget/lib/settings/common/html/html-container-settings.component.ts

2
application/src/main/data/json/system/widget_types/html_container.json

@ -11,7 +11,7 @@
"resources": [], "resources": [],
"templateHtml": "<tb-html-container-widget \n [ctx]=\"ctx\">\n</tb-html-container-widget>", "templateHtml": "<tb-html-container-widget \n [ctx]=\"ctx\">\n</tb-html-container-widget>",
"templateCss": "", "templateCss": "",
"controllerScript": "self.onInit = function() {\n \n}\n", "controllerScript": "self.onInit = function() {\n \n}\n\nself.typeParameters = function() {\n return {\n previewWidth: '100%',\n previewHeight: '100%',\n overflowVisible: true\n };\n};\n",
"settingsDirective": "tb-html-container-widget-settings", "settingsDirective": "tb-html-container-widget-settings",
"hasBasicMode": true, "hasBasicMode": true,
"basicModeDirective": "tb-html-container-basic-config", "basicModeDirective": "tb-html-container-basic-config",

15
ui-ngx/src/app/modules/home/components/widget/lib/html/html-container-widget.models.ts

@ -51,13 +51,18 @@ const containerFunctionCompletions: TbEditorCompletions = {
type: widgetContextCompletions.ctx.type, type: widgetContextCompletions.ctx.type,
description: widgetContextCompletions.ctx.description, description: widgetContextCompletions.ctx.description,
children: widgetContextCompletions.ctx.children children: widgetContextCompletions.ctx.children
}, }
}
};
export const AngularContainerFunctionEditorCompleter = new TbEditorCompleter(containerFunctionCompletions);
export const HTMLContainerFunctionEditorCompleter = new TbEditorCompleter(
{...containerFunctionCompletions,
container: { container: {
meta: 'argument', meta: 'argument',
type: 'HTMLElement', type: 'HTMLElement',
description: 'Container element of the widget' description: 'Container element of the widget'
}, }}
} );
};
export const ContainerFunctionEditorCompleter = new TbEditorCompleter(containerFunctionCompletions);

162
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/html/html-container-settings.component.html

@ -17,82 +17,112 @@
--> -->
<ng-container [formGroup]="htmlContainerSettingsForm"> <ng-container [formGroup]="htmlContainerSettingsForm">
<div class="tb-form-panel no-padding no-border relative h-full"> <div class="tb-form-panel no-padding no-border relative h-full">
<div class="flex flex-row items-center gap-4"> <div class="flex flex-row items-center">
<div class="tb-form-panel-title" translate>widgets.html-container.container-type</div>
<tb-toggle-select formControlName="type"> <tb-toggle-select formControlName="type">
<tb-toggle-option [value]="HtmlContainerWidgetType.PLAIN">{{ 'widgets.html-container.type-plain' | translate }}</tb-toggle-option> <tb-toggle-option [value]="HtmlContainerWidgetType.PLAIN">{{ 'widgets.html-container.type-plain' | translate }}</tb-toggle-option>
<tb-toggle-option [value]="HtmlContainerWidgetType.ANGULAR">{{ 'widgets.html-container.type-angular' | translate }}</tb-toggle-option> <tb-toggle-option [value]="HtmlContainerWidgetType.ANGULAR">{{ 'widgets.html-container.type-angular' | translate }}</tb-toggle-option>
</tb-toggle-select> </tb-toggle-select>
</div> </div>
<mat-tab-group [mat-stretch-tabs]="false" selectedIndex="3" class="flex-1"> <div class="tb-html-container-settings-panel flex flex-1 flex-col" tb-fullscreen [fullscreen]="fullscreen">
<mat-tab #resourceTab="matTab"> <div class="tb-action-expand-button flex flex-row items-center justify-end">
<ng-template mat-tab-label> <button mat-stroked-button
<div [matBadge]="resourcesFormArray.length" [matBadgeHidden]="resourceTab.isActive || !resourcesFormArray.length" matTooltip="{{ 'widget.toggle-fullscreen' | translate }}"
matBadgeSize="small">{{ 'widgets.html-container.resources' | translate }}</div> matTooltipPosition="above"
</ng-template> (click)="toggleFullScreen()">
<div class="flex flex-col gap-2 pt-4"> <mat-icon>{{ fullscreen ? 'fullscreen_exit' : 'fullscreen' }}</mat-icon>
@if (resourcesFormArray.length) { <span>{{ (fullscreen ? 'fullscreen.exit' : 'fullscreen.fullscreen') | translate }}</span>
@for (resourceControl of resourcesControls; track resourceControl; let i = $index) { </button>
<div class="tb-form-row no-border no-padding" [formGroup]="resourceControl"> </div>
<tb-resource-autocomplete class="flex-1" <div class="flex flex-1">
formControlName="url" <mat-tab-group #leftPanel
inlineField [mat-stretch-tabs]="fullscreen"
hideRequiredMarker required [selectedIndex]="fullscreen ? 2 : 3"
[allowAutocomplete]="resourceControl.get('isModule').value && htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR" [animationDuration]="tabsAnimationDuration"
placeholder="{{ 'widget.resource-url' | translate }}"> [disablePagination]="fullscreen"
</tb-resource-autocomplete> class="tb-content"
@if (htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR) { [class.flex-1]="!fullscreen">
<mat-checkbox formControlName="isModule"> <mat-tab #resourceTab="matTab">
{{ 'widget.resource-is-extension' | translate }} <ng-template mat-tab-label>
</mat-checkbox> <div [matBadge]="resourcesFormArray.length" [matBadgeHidden]="resourceTab.isActive || !resourcesFormArray.length"
matBadgeSize="small">{{ 'widgets.html-container.resources' | translate }}</div>
</ng-template>
<div class="flex flex-col gap-2 pt-4" [class.px-2]="fullscreen">
@if (resourcesFormArray.length) {
@for (resourceControl of resourcesControls; track resourceControl; let i = $index) {
<div class="tb-form-row no-border no-padding" [formGroup]="resourceControl">
<tb-resource-autocomplete class="flex-1"
formControlName="url"
inlineField
hideRequiredMarker required
[allowAutocomplete]="resourceControl.get('isModule').value && htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR"
placeholder="{{ 'widget.resource-url' | translate }}">
</tb-resource-autocomplete>
@if (htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR) {
<mat-checkbox formControlName="isModule">
{{ 'widget.resource-is-extension' | translate }}
</mat-checkbox>
}
<button mat-icon-button color="primary"
(click)="removeResource(i)"
matTooltip="{{'widget.remove-resource' | translate}}"
matTooltipPosition="above">
<mat-icon>delete</mat-icon>
</button>
</div>
} }
<button mat-icon-button color="primary" } @else {
(click)="removeResource(i)" <span translate
matTooltip="{{'widget.remove-resource' | translate}}" class="tb-prompt flex items-center justify-center">widgets.html-container.no-resources</span>
}
<div>
<button mat-raised-button color="primary"
(click)="addResource()"
matTooltip="{{'widget.add-resource' | translate}}"
matTooltipPosition="above"> matTooltipPosition="above">
<mat-icon>delete</mat-icon> <span translate>action.add</span>
</button> </button>
</div> </div>
} </div>
} @else { </mat-tab>
<span translate <mat-tab label="{{ 'widgets.html-container.css' | translate }}">
class="tb-prompt flex items-center justify-center">widgets.html-container.no-resources</span> <tb-css class="flex-1"
[fillHeight]="true"
formControlName="css"
label="{{ 'widgets.html-container.css' | translate }}">
</tb-css>
</mat-tab>
<mat-tab label="{{ (htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR ? 'widgets.html-container.angular-html-template' : 'widgets.html-container.html') | translate }}">
<tb-html class="flex-1"
[fillHeight]="true"
formControlName="html"
label="{{ (htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR ? 'widgets.html-container.angular-html-template' : 'widgets.html-container.html') | translate }}">
</tb-html>
</mat-tab>
@if (!fullscreen) {
<mat-tab label="{{ 'widgets.html-container.java-script' | translate }}">
<ng-container *ngTemplateOutlet="javascript"></ng-container>
</mat-tab>
}
</mat-tab-group>
<div #rightPanel class="tb-content flex" [class.!hidden]="!fullscreen">
@if (fullscreen) {
<ng-container *ngTemplateOutlet="javascript"></ng-container>
} }
<div>
<button mat-raised-button color="primary"
(click)="addResource()"
matTooltip="{{'widget.add-resource' | translate}}"
matTooltipPosition="above">
<span translate>action.add</span>
</button>
</div>
</div> </div>
</mat-tab> </div>
<mat-tab label="{{ (htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR ? 'widgets.html-container.angular-html-template' : 'widgets.html-container.html') | translate }}"> </div>
<tb-html class="flex-1"
[fillHeight]="true"
formControlName="html"
label="{{ (htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR ? 'widgets.html-container.angular-html-template' : 'widgets.html-container.html') | translate }}">
</tb-html>
</mat-tab>
<mat-tab label="{{ 'widgets.html-container.css' | translate }}">
<tb-css class="flex-1"
[fillHeight]="true"
formControlName="css"
label="{{ 'widgets.html-container.css' | translate }}">
</tb-css>
</mat-tab>
<mat-tab label="{{ 'widgets.html-container.java-script' | translate }}">
<tb-js-func class="flex-1"
[fillHeight]="true"
formControlName="js"
[globalVariables]="functionScopeVariables"
[editorCompleter]="containerFunctionEditorCompleter"
[functionArgs]="htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR ? ['ctx'] : ['ctx', 'container']"
withModules
functionTitle="{{ 'widgets.html-container.js-function' | translate }}">
</tb-js-func>
</mat-tab>
</mat-tab-group>
</div> </div>
</ng-container> </ng-container>
<ng-template #javascript>
<ng-container [formGroup]="htmlContainerSettingsForm">
<tb-js-func class="flex-1"
[fillHeight]="true"
formControlName="js"
[globalVariables]="functionScopeVariables"
[editorCompleter]="containerFunctionEditorCompleter"
[functionArgs]="htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR ? ['ctx'] : ['ctx', 'container']"
withModules
functionTitle="{{ 'widgets.html-container.js-function' | translate }}">
</tb-js-func>
</ng-container>
</ng-template>

114
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/html/html-container-settings.component.scss

@ -14,15 +14,115 @@
* limitations under the License. * limitations under the License.
*/ */
:host { .tb-html-container-settings {
&.tb-html-container-settings { height: 100%;
}
.tb-html-container-settings .tb-html-container-settings-panel, .tb-html-container-settings-panel {
position: relative;
background: #fff;
.mat-mdc-tab-body-wrapper {
position: relative;
top: 0;
flex: 1;
}
.tb-action-expand-button {
position: absolute;
top: 4px;
right: 0;
z-index: 2;
}
.gutter {
display: none;
background-color: #eee;
background-repeat: no-repeat;
background-position: 50%;
&.gutter-horizontal {
cursor: col-resize;
background-image: url("../../../../../../../../../assets/split.js/grips/vertical.png");
}
}
.tb-js-func {
&:not(.tb-fullscreen) {
&.tb-hide-brackets {
padding-bottom: 0;
}
}
}
.tb-html {
position: relative;
&:not(.tb-fullscreen) {
padding-bottom: 0;
}
.tb-html-toolbar {
position: absolute;
top: 0;
right: 8px;
z-index: 8;
.tb-title {
display: none;
}
}
.tb-html-content-panel {
border-top: none;
height: 100%;
}
}
.tb-css {
position: relative;
&:not(.tb-fullscreen) {
.tb-css-content-panel {
margin: 0;
}
}
.tb-css-toolbar {
position: absolute;
top: 0;
right: 8px;
z-index: 8;
.tb-title {
display: none;
}
}
.tb-css-content-panel {
border-top: none;
height: 100%; height: 100%;
::ng-deep { }
.mat-mdc-tab-body-wrapper { }
position: relative; &.tb-fullscreen {
top: 0; padding: 8px;
flex: 1; gap: 8px;
.tb-action-expand-button {
position: relative;
top: 0;
right: 0;
}
.gutter {
display: block;
}
.tb-content {
border: 1px solid #c0c0c0;
.tb-html {
.tb-html-content-panel {
border: none;
}
}
.tb-css {
.tb-css-content-panel {
border: none;
}
}
.tb-js-func {
padding-top: 8px;
.tb-js-func-toolbar {
padding: 0 5px;
}
.tb-js-func-panel {
border-left: none;
border-right: none;
border-bottom: none;
} }
} }
} }
}
} }

63
ui-ngx/src/app/modules/home/components/widget/lib/settings/common/html/html-container-settings.component.ts

@ -14,7 +14,18 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { Component, DestroyRef, forwardRef, HostBinding, Input, OnInit } from '@angular/core'; import {
AfterViewInit,
Component,
DestroyRef,
ElementRef,
forwardRef,
HostBinding,
Input,
OnInit,
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { WidgetResource } from '@shared/models/widget.models'; import { WidgetResource } from '@shared/models/widget.models';
import { import {
ControlValueAccessor, ControlValueAccessor,
@ -28,7 +39,8 @@ import {
Validators Validators
} from '@angular/forms'; } from '@angular/forms';
import { import {
ContainerFunctionEditorCompleter, AngularContainerFunctionEditorCompleter,
HTMLContainerFunctionEditorCompleter,
HtmlContainerWidgetSettings, HtmlContainerWidgetSettings,
HtmlContainerWidgetType HtmlContainerWidgetType
} from '@home/components/widget/lib/html/html-container-widget.models'; } from '@home/components/widget/lib/html/html-container-widget.models';
@ -52,23 +64,39 @@ import { WidgetService } from '@core/http/widget.service';
multi: true, multi: true,
} }
], ],
encapsulation: ViewEncapsulation.None,
standalone: false standalone: false
}) })
export class HtmlContainerSettingsComponent implements OnInit, ControlValueAccessor, Validator { export class HtmlContainerSettingsComponent implements OnInit, AfterViewInit, ControlValueAccessor, Validator {
HtmlContainerWidgetType = HtmlContainerWidgetType; HtmlContainerWidgetType = HtmlContainerWidgetType;
functionScopeVariables = this.widgetService.getWidgetScopeVariables(); functionScopeVariables = this.widgetService.getWidgetScopeVariables();
containerFunctionEditorCompleter = ContainerFunctionEditorCompleter; get containerFunctionEditorCompleter() {
return this.htmlContainerSettingsForm.get('type').value === HtmlContainerWidgetType.ANGULAR
? AngularContainerFunctionEditorCompleter
: HTMLContainerFunctionEditorCompleter;
}
@HostBinding('class') @HostBinding('class')
hostClass = 'tb-html-container-settings'; hostClass = 'tb-html-container-settings';
@ViewChild('leftPanel', { read: ElementRef })
leftPanelElmRef!: ElementRef;
@ViewChild('rightPanel', { read: ElementRef })
rightPanelElmRef!: ElementRef;
@Input() @Input()
disabled: boolean; disabled: boolean;
fullscreen = false;
tabsAnimationDuration = '500ms';
htmlContainerSettingsForm: UntypedFormGroup; htmlContainerSettingsForm: UntypedFormGroup;
private modelValue: HtmlContainerWidgetSettings; private modelValue: HtmlContainerWidgetSettings;
constructor(private fb: UntypedFormBuilder, constructor(private fb: UntypedFormBuilder,
@ -102,11 +130,26 @@ export class HtmlContainerSettingsComponent implements OnInit, ControlValueAcces
}); });
} }
ngAfterViewInit(): void {
if (this.leftPanelElmRef && this.rightPanelElmRef) {
this.initSplitLayout(this.leftPanelElmRef.nativeElement,
this.rightPanelElmRef.nativeElement);
}
}
private initSplitLayout(leftPanel: any, rightPanel: any) {
Split([leftPanel, rightPanel], {
sizes: [50, 50],
gutterSize: 8,
cursor: 'col-resize'
});
}
registerOnChange(fn: any): void { registerOnChange(fn: any): void {
this.propagateChange = fn; this.propagateChange = fn;
} }
registerOnTouched(fn: any): void { registerOnTouched(_fn: any): void {
} }
setDisabledState(isDisabled: boolean): void { setDisabledState(isDisabled: boolean): void {
@ -150,7 +193,15 @@ export class HtmlContainerSettingsComponent implements OnInit, ControlValueAcces
this.resourcesFormArray.removeAt(index); this.resourcesFormArray.removeAt(index);
} }
private propagateChange = (v: any) => { }; toggleFullScreen(): void {
this.fullscreen = !this.fullscreen;
this.tabsAnimationDuration = '0ms';
setTimeout(() => {
this.tabsAnimationDuration = '500ms';
});
}
private propagateChange = (_v: any) => { };
private updateModel() { private updateModel() {
this.modelValue = this.htmlContainerSettingsForm.value; this.modelValue = this.htmlContainerSettingsForm.value;

Loading…
Cancel
Save