diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml
index 6f20a473e..63fe33d91 100644
--- a/.github/workflows/gradle.yml
+++ b/.github/workflows/gradle.yml
@@ -10,7 +10,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
- publish: ${{ steps.publish_vars.outputs.release != 'true' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/4.')) }}
+ publish: ${{ steps.publish_vars.outputs.release != 'true' && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/heads/4.') || startsWith(github.ref, 'refs/heads/epic-ai')) }}
repo: ${{ steps.publish_vars.outputs.repo }}
steps:
diff --git a/src/main/resources/assets/admin/common/icons/fonts/icomoon.svg b/src/main/resources/assets/admin/common/icons/fonts/icomoon.svg
index 781ce49c5..16c1f4a1d 100644
--- a/src/main/resources/assets/admin/common/icons/fonts/icomoon.svg
+++ b/src/main/resources/assets/admin/common/icons/fonts/icomoon.svg
@@ -38,6 +38,7 @@
+
@@ -65,4 +66,4 @@
-
+
\ No newline at end of file
diff --git a/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff b/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff
old mode 100755
new mode 100644
index d58b3faeb..0f2bf7b55
Binary files a/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff and b/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff differ
diff --git a/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff2 b/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff2
index dc22206f4..caf47e15d 100644
Binary files a/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff2 and b/src/main/resources/assets/admin/common/icons/fonts/icomoon.woff2 differ
diff --git a/src/main/resources/assets/admin/common/images/juke-eye-centered-animated.svg b/src/main/resources/assets/admin/common/images/juke-eye-centered-animated.svg
new file mode 100644
index 000000000..a9d87af86
--- /dev/null
+++ b/src/main/resources/assets/admin/common/images/juke-eye-centered-animated.svg
@@ -0,0 +1,20 @@
+
diff --git a/src/main/resources/assets/admin/common/images/juke-eye-centered.svg b/src/main/resources/assets/admin/common/images/juke-eye-centered.svg
new file mode 100644
index 000000000..d10b0af49
--- /dev/null
+++ b/src/main/resources/assets/admin/common/images/juke-eye-centered.svg
@@ -0,0 +1,16 @@
+
diff --git a/src/main/resources/assets/admin/common/js/ai/AiHelper.ts b/src/main/resources/assets/admin/common/js/ai/AiHelper.ts
new file mode 100644
index 000000000..60096182f
--- /dev/null
+++ b/src/main/resources/assets/admin/common/js/ai/AiHelper.ts
@@ -0,0 +1,120 @@
+import {PropertyPath} from '../data/PropertyPath';
+import {Element} from '../dom/Element';
+import {Store} from '../store/Store';
+import {i18n} from '../util/Messages';
+import {AiHelperState} from './AiHelperState';
+import {AiActionButton} from './ui/AiActionButton';
+
+export interface AiHelperConfig {
+ dataPathElement: Element;
+ getPathFunc: () => PropertyPath;
+ icon?: {
+ container: Element;
+ };
+ label?: string;
+ setValueFunc?: (value: string) => void;
+}
+
+const AI_HELPERS_KEY = 'AiHelpers';
+Store.instance().set(AI_HELPERS_KEY, []);
+
+export class AiHelper {
+
+ public static DATA_ATTR = 'data-path';
+
+ private readonly config: AiHelperConfig;
+
+ private readonly aiIcon?: AiActionButton;
+
+ private state: AiHelperState = AiHelperState.DEFAULT;
+
+ constructor(config: AiHelperConfig) {
+ this.config = config;
+
+ const updatePathCall = setInterval(() => {
+ this.updateInputElDataPath();
+ }, 1000);
+
+ this.config.dataPathElement.onRemoved(() => {
+ clearInterval(updatePathCall);
+ const helper: AiHelper[] = Store.instance().get(AI_HELPERS_KEY) ?? [];
+ Store.instance().set(AI_HELPERS_KEY, helper.filter(h => h !== this));
+ });
+
+ Store.instance().get(AI_HELPERS_KEY).push(this);
+
+ if (this.config.icon) {
+ this.aiIcon = new AiActionButton();
+ this.config.icon.container.appendChild(this.aiIcon);
+ }
+ }
+
+ private updateInputElDataPath(): void {
+ const dataPath = AiHelper.convertToPath(this.config.getPathFunc());
+ this.config.dataPathElement.getEl().setAttribute(AiHelper.DATA_ATTR, dataPath);
+ this.aiIcon?.setDataPath(dataPath);
+ }
+
+ setState(state: AiHelperState): this {
+ if (state === this.state) {
+ return this;
+ }
+
+ this.state = state;
+ this.aiIcon?.setState(state);
+
+ if (state === AiHelperState.COMPLETED || state === AiHelperState.FAILED) {
+ setTimeout(() => {
+ if (this.state === AiHelperState.COMPLETED || this.state === AiHelperState.FAILED) {
+ this.setState(AiHelperState.DEFAULT);
+ }
+ }, 1000);
+
+ this.config.dataPathElement.getEl().setDisabled(false);
+ this.config.dataPathElement.removeClass('ai-helper-mask');
+ this.resetTitle();
+ } else if (state === AiHelperState.PROCESSING) {
+ this.config.dataPathElement.getEl().setDisabled(true);
+ this.config.dataPathElement.addClass('ai-helper-mask');
+ this.updateTitle();
+ }
+
+ return this;
+ }
+
+ setValue(value: string): this {
+ this.config.setValueFunc?.(value);
+ return this;
+ }
+
+ getDataPath(): string {
+ return this.config.dataPathElement.getEl().getAttribute(AiHelper.DATA_ATTR);
+ }
+
+ public static convertToPath(path: PropertyPath): string {
+ return path?.toString().replace(/\./g, '/') || '';
+ }
+
+ public static getAiHelperByPath(dataPath: string): AiHelper | undefined {
+ return Store.instance().get(AI_HELPERS_KEY).find(helper => helper.getDataPath() === dataPath);
+ }
+
+ private updateTitle(): void {
+ const parent = this.config.dataPathElement.getEl().getParent();
+
+ if (parent.hasAttribute('title') && !parent.hasAttribute('data-title')) {
+ parent.setAttribute('data-title', parent.getTitle());
+ }
+
+ parent.setTitle(i18n('ai.field.processing', this.config.label));
+ }
+
+ private resetTitle(): void {
+ const parent = this.config.dataPathElement.getEl().getParent();
+ parent.removeAttribute('title');
+
+ if (parent.hasAttribute('data-title')) {
+ parent.setTitle(parent.getAttribute('data-title'));
+ }
+ }
+}
diff --git a/src/main/resources/assets/admin/common/js/ai/AiHelperState.ts b/src/main/resources/assets/admin/common/js/ai/AiHelperState.ts
new file mode 100644
index 000000000..762ef703d
--- /dev/null
+++ b/src/main/resources/assets/admin/common/js/ai/AiHelperState.ts
@@ -0,0 +1,6 @@
+export enum AiHelperState {
+ DEFAULT = 'default',
+ PROCESSING = 'processing',
+ COMPLETED = 'completed',
+ FAILED = 'failed',
+}
diff --git a/src/main/resources/assets/admin/common/js/ai/event/EnonicAiContentOperatorOpenDialogEvent.ts b/src/main/resources/assets/admin/common/js/ai/event/EnonicAiContentOperatorOpenDialogEvent.ts
new file mode 100644
index 000000000..7e5a0c0c9
--- /dev/null
+++ b/src/main/resources/assets/admin/common/js/ai/event/EnonicAiContentOperatorOpenDialogEvent.ts
@@ -0,0 +1,27 @@
+import {ClassHelper} from '../../ClassHelper';
+import {Event} from '../../event/Event';
+
+export class EnonicAiContentOperatorOpenDialogEvent
+ extends Event {
+
+ private readonly sourceDataPath?: string;
+
+ constructor(dataPath?: string) {
+ super();
+
+ this.sourceDataPath = dataPath;
+ }
+
+ getSourceDataPath(): string | undefined {
+ return this.sourceDataPath;
+ }
+
+ static on(handler: (event: EnonicAiContentOperatorOpenDialogEvent) => void) {
+ Event.bind(ClassHelper.getFullName(this), handler);
+ }
+
+ static un(handler?: (event: EnonicAiContentOperatorOpenDialogEvent) => void) {
+ Event.unbind(ClassHelper.getFullName(this), handler);
+ }
+
+}
diff --git a/src/main/resources/assets/admin/common/js/ai/ui/AiActionButton.ts b/src/main/resources/assets/admin/common/js/ai/ui/AiActionButton.ts
new file mode 100644
index 000000000..3be0bd2e3
--- /dev/null
+++ b/src/main/resources/assets/admin/common/js/ai/ui/AiActionButton.ts
@@ -0,0 +1,63 @@
+import * as Q from 'q';
+import {DivEl} from '../../dom/DivEl';
+import {Button} from '../../ui/button/Button';
+import {i18n} from '../../util/Messages';
+import {AiHelperState} from '../AiHelperState';
+import {EnonicAiContentOperatorOpenDialogEvent} from '../event/EnonicAiContentOperatorOpenDialogEvent';
+
+export class AiActionButton
+ extends DivEl {
+
+ private static readonly BASE_CLASS = 'ai-button-container';
+
+ private dataPath?: string;
+
+ private button: Button;
+
+ private loader: DivEl;
+
+ constructor() {
+ super();
+
+ this.initElements();
+ this.initListeners();
+ }
+
+ protected initElements(): void {
+ this.button = new Button().addClass(`${AiActionButton.BASE_CLASS}-icon ai-icon`) as Button;
+ this.loader = new DivEl(`${AiActionButton.BASE_CLASS}-loader`);
+ this.setTitle(i18n('ai.action.contentOperator.use'));
+ this.setState(AiHelperState.DEFAULT);
+ }
+
+ setState(state: AiHelperState): this {
+ this.setClass(`${AiActionButton.BASE_CLASS} ${state}`);
+
+ return this;
+ }
+
+ setDataPath(dataPath: string): AiActionButton {
+ this.dataPath = dataPath;
+ return this;
+ }
+
+ getDataPath(): string {
+ return this.dataPath;
+ }
+
+ protected initListeners(): void {
+ this.button.onClicked(() => {
+ if (this.dataPath) {
+ new EnonicAiContentOperatorOpenDialogEvent(this.dataPath).fire();
+ }
+ });
+ }
+
+ doRender(): Q.Promise {
+ return super.doRender().then((rendered: boolean) => {
+ this.appendChildren(this.loader, this.button);
+
+ return rendered;
+ });
+ }
+}
diff --git a/src/main/resources/assets/admin/common/js/form/FormContext.ts b/src/main/resources/assets/admin/common/js/form/FormContext.ts
index 974103844..c3b4e79e4 100644
--- a/src/main/resources/assets/admin/common/js/form/FormContext.ts
+++ b/src/main/resources/assets/admin/common/js/form/FormContext.ts
@@ -14,11 +14,14 @@ export class FormContext {
private validationErrors: ValidationError[];
+ private readonly aiEditable: boolean;
+
constructor(builder: FormContextBuilder) {
this.showEmptyFormItemSetOccurrences = builder.showEmptyFormItemSetOccurrences;
this.formState = builder.formState;
this.language = builder.language;
this.validationErrors = builder.validationErrors || [];
+ this.aiEditable = builder.aiEditable ?? false;
}
static create(): FormContextBuilder {
@@ -71,6 +74,11 @@ export class FormContext {
setLanguage(lang: string) {
this.language = lang;
}
+
+ isAiEditable(): boolean {
+ return this.aiEditable;
+ }
+
}
export class FormContextBuilder {
@@ -83,26 +91,33 @@ export class FormContextBuilder {
validationErrors: ValidationError[];
- public setShowEmptyFormItemSetOccurrences(value: boolean): FormContextBuilder {
+ aiEditable: boolean;
+
+ public setShowEmptyFormItemSetOccurrences(value: boolean): this {
this.showEmptyFormItemSetOccurrences = value;
return this;
}
- public setFormState(value: FormState): FormContextBuilder {
+ public setFormState(value: FormState): this {
this.formState = value;
return this;
}
- public setLanguage(lang: string): FormContextBuilder {
+ public setLanguage(lang: string): this {
this.language = lang;
return this;
}
- public setValidationErrors(value: ValidationError[]): FormContextBuilder {
+ public setValidationErrors(value: ValidationError[]): this {
this.validationErrors = value;
return this;
}
+ public setAiEditable(value: boolean): this {
+ this.aiEditable = value;
+ return this;
+ }
+
public build(): FormContext {
return new FormContext(this);
}
diff --git a/src/main/resources/assets/admin/common/js/form/FormItemOccurrenceView.ts b/src/main/resources/assets/admin/common/js/form/FormItemOccurrenceView.ts
index 19fb82bfc..c8a705498 100644
--- a/src/main/resources/assets/admin/common/js/form/FormItemOccurrenceView.ts
+++ b/src/main/resources/assets/admin/common/js/form/FormItemOccurrenceView.ts
@@ -1,22 +1,44 @@
import * as Q from 'q';
-import {DivEl} from '../dom/DivEl';
import {PropertyPath} from '../data/PropertyPath';
-import {InputValidationRecording} from './inputtype/InputValidationRecording';
+import {DivEl} from '../dom/DivEl';
import {FormItemOccurrence} from './FormItemOccurrence';
import {HelpTextContainer} from './HelpTextContainer';
import {RemoveButtonClickedEvent} from './RemoveButtonClickedEvent';
+export interface FormItemOccurrenceViewConfig {
+ className: string;
+ formItemOccurrence: FormItemOccurrence
+}
+
export abstract class FormItemOccurrenceView
extends DivEl {
protected formItemOccurrence: FormItemOccurrence;
protected helpText: HelpTextContainer;
+ protected readonly config: FormItemOccurrenceViewConfig;
private removeButtonClickedListeners: ((event: RemoveButtonClickedEvent) => void)[] = [];
private occurrenceChangedListeners: ((view: FormItemOccurrenceView) => void)[] = [];
- constructor(className: string, formItemOccurrence: FormItemOccurrence) {
- super(className);
- this.formItemOccurrence = formItemOccurrence;
+ protected constructor(config: FormItemOccurrenceViewConfig) {
+ super(config.className);
+
+ this.config = config;
+
+ this.initElements();
+ this.postInitElements();
+ this.initListeners();
+ }
+
+ protected initElements(): void {
+ this.formItemOccurrence = this.config.formItemOccurrence;
+ }
+
+ protected initListeners(): void {
+ //
+ }
+
+ protected postInitElements() {
+ //
}
isExpandable(): boolean {
@@ -110,4 +132,5 @@ export abstract class FormItemOccurrenceView
setEnabled(enable: boolean) {
//
}
+
}
diff --git a/src/main/resources/assets/admin/common/js/form/InputView.ts b/src/main/resources/assets/admin/common/js/form/InputView.ts
index e338e159c..cca6b36e8 100644
--- a/src/main/resources/assets/admin/common/js/form/InputView.ts
+++ b/src/main/resources/assets/admin/common/js/form/InputView.ts
@@ -1,31 +1,32 @@
import * as Q from 'q';
+import {Property} from '../data/Property';
import {PropertyArray} from '../data/PropertyArray';
import {PropertySet} from '../data/PropertySet';
-import {Property} from '../data/Property';
-import {BaseInputTypeNotManagingAdd} from './inputtype/support/BaseInputTypeNotManagingAdd';
-import {i18n} from '../util/Messages';
-import {StringHelper} from '../util/StringHelper';
-import {FormItemView, FormItemViewConfig} from './FormItemView';
-import {InputTypeView} from './inputtype/InputTypeView';
import {DivEl} from '../dom/DivEl';
+import {ObjectHelper} from '../ObjectHelper';
import {Button} from '../ui/button/Button';
-import {OccurrenceRemovedEvent} from './OccurrenceRemovedEvent';
-import {InputValidityChangedEvent} from './inputtype/InputValidityChangedEvent';
-import {InputTypeName} from './InputTypeName';
-import {InputValidationRecording} from './inputtype/InputValidationRecording';
+import {TogglerButton} from '../ui/button/TogglerButton';
+import {assertNotNull} from '../util/Assert';
+import {i18n} from '../util/Messages';
+import {StringHelper} from '../util/StringHelper';
import {FormContext} from './FormContext';
-import {Input} from './Input';
import {FormItemOccurrenceView} from './FormItemOccurrenceView';
-import {ValidationRecording} from './ValidationRecording';
-import {RecordingValidityChangedEvent} from './RecordingValidityChangedEvent';
+import {FormItemView, FormItemViewConfig} from './FormItemView';
import {HelpTextContainer} from './HelpTextContainer';
-import {assertNotNull} from '../util/Assert';
+import {Input} from './Input';
import {InputLabel} from './InputLabel';
import {InputTypeManager} from './inputtype/InputTypeManager';
-import {ValidationRecordingPath} from './ValidationRecordingPath';
-import {InputViewValidationViewer} from './InputViewValidationViewer';
-import {TogglerButton} from '../ui/button/TogglerButton';
+import {InputTypeView} from './inputtype/InputTypeView';
+import {InputValidationRecording} from './inputtype/InputValidationRecording';
+import {InputValidityChangedEvent} from './inputtype/InputValidityChangedEvent';
import {BaseInputType} from './inputtype/support/BaseInputType';
+import {BaseInputTypeNotManagingAdd} from './inputtype/support/BaseInputTypeNotManagingAdd';
+import {InputTypeName} from './InputTypeName';
+import {InputViewValidationViewer} from './InputViewValidationViewer';
+import {OccurrenceRemovedEvent} from './OccurrenceRemovedEvent';
+import {RecordingValidityChangedEvent} from './RecordingValidityChangedEvent';
+import {ValidationRecording} from './ValidationRecording';
+import {ValidationRecordingPath} from './ValidationRecordingPath';
export interface InputViewConfig {
@@ -44,7 +45,7 @@ export class InputView
public static debug: boolean = false;
private static ERROR_DETAILS_HIDDEN_CLS: string = 'error-details-hidden';
- private input: Input;
+ private readonly input: Input;
private parentPropertySet: PropertySet;
private propertyArray: PropertyArray;
private inputTypeView: InputTypeView;
@@ -82,9 +83,14 @@ export class InputView
}
}
- if (this.input.getHelpText()) {
- this.helpText = new HelpTextContainer(this.input.getHelpText());
+ this.inputTypeView = this.createInputTypeView();
+ const isAiEditable = ObjectHelper.iFrameSafeInstanceOf(this.inputTypeView, BaseInputType) &&
+ (this.inputTypeView as BaseInputType).isAiEditable();
+ this.toggleClass('ai-editable', isAiEditable);
+
+ if (this.input.getHelpText() && !isAiEditable) {
+ this.helpText = new HelpTextContainer(this.input.getHelpText());
this.appendChild(this.helpText.getToggler());
}
@@ -92,14 +98,12 @@ export class InputView
this.addClass('label-inline');
}
- this.inputTypeView = this.createInputTypeView();
-
this.propertyArray = this.getPropertyArray(this.parentPropertySet);
return this.inputTypeView.layout(this.input, this.propertyArray).then(() => {
this.appendChild(this.inputTypeView.getElement());
- if (!!this.helpText) {
+ if (this.helpText) {
this.appendChild(this.helpText.getHelpText());
}
@@ -281,9 +285,7 @@ export class InputView
}
toggleHelpText(show?: boolean) {
- if (!!this.helpText) {
- this.helpText.toggleHelpText(show);
- }
+ this.helpText?.toggleHelpText(show);
}
hasHelpText(): boolean {
diff --git a/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputType.ts b/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputType.ts
index 0cd023b71..3a5b1df44 100644
--- a/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputType.ts
+++ b/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputType.ts
@@ -1,16 +1,16 @@
-import {DivEl} from '../../../dom/DivEl';
-import {InputTypeView} from '../InputTypeView';
-import {Input} from '../../Input';
-import {InputValidityChangedEvent} from '../InputValidityChangedEvent';
-import {Element} from '../../../dom/Element';
-import {PropertyArray} from '../../../data/PropertyArray';
import * as Q from 'q';
-import {Value} from '../../../data/Value';
import {ClassHelper} from '../../../ClassHelper';
+import {PropertyArray} from '../../../data/PropertyArray';
+import {Value} from '../../../data/Value';
import {ValueType} from '../../../data/ValueType';
+import {DivEl} from '../../../dom/DivEl';
+import {Element} from '../../../dom/Element';
+import {Input} from '../../Input';
+import {InputTypeView} from '../InputTypeView';
+import {InputTypeViewContext} from '../InputTypeViewContext';
import {InputValidationRecording} from '../InputValidationRecording';
+import {InputValidityChangedEvent} from '../InputValidityChangedEvent';
import {ValueChangedEvent} from '../ValueChangedEvent';
-import {InputTypeViewContext} from '../InputTypeViewContext';
export abstract class BaseInputType extends DivEl
implements InputTypeView {
@@ -124,4 +124,7 @@ export abstract class BaseInputType extends DivEl
//
}
+ isAiEditable(): boolean {
+ return false;
+ }
}
diff --git a/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputTypeNotManagingAdd.ts b/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputTypeNotManagingAdd.ts
index 4ccd6984a..5390f7c6c 100644
--- a/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputTypeNotManagingAdd.ts
+++ b/src/main/resources/assets/admin/common/js/form/inputtype/support/BaseInputTypeNotManagingAdd.ts
@@ -1,35 +1,34 @@
import * as $ from 'jquery';
import * as Q from 'q';
+import {ClassHelper} from '../../../ClassHelper';
import {Property} from '../../../data/Property';
import {PropertyArray} from '../../../data/PropertyArray';
import {Value} from '../../../data/Value';
+import {Element} from '../../../dom/Element';
import {FormInputEl} from '../../../dom/FormInputEl';
-import {InputTypeViewContext} from '../InputTypeViewContext';
+import {ObjectHelper} from '../../../ObjectHelper';
+import {assertNotNull} from '../../../util/Assert';
+import {i18n} from '../../../util/Messages';
+import {ValidationError} from '../../../ValidationError';
+import {AdditionalValidationRecord} from '../../AdditionalValidationRecord';
import {Input} from '../../Input';
-import {InputValidityChangedEvent} from '../InputValidityChangedEvent';
-import {Element} from '../../../dom/Element';
-import {InputValidationRecording} from '../InputValidationRecording';
import {OccurrenceAddedEvent} from '../../OccurrenceAddedEvent';
-import {OccurrenceRenderedEvent} from '../../OccurrenceRenderedEvent';
import {OccurrenceRemovedEvent} from '../../OccurrenceRemovedEvent';
+import {OccurrenceRenderedEvent} from '../../OccurrenceRenderedEvent';
+import {InputTypeViewContext} from '../InputTypeViewContext';
+import {InputValidationRecording} from '../InputValidationRecording';
+import {InputValidityChangedEvent} from '../InputValidityChangedEvent';
import {ValueChangedEvent} from '../ValueChangedEvent';
-import {ClassHelper} from '../../../ClassHelper';
-import {ObjectHelper} from '../../../ObjectHelper';
-import {InputOccurrenceView} from './InputOccurrenceView';
+import {BaseInputType} from './BaseInputType';
import {InputOccurrences} from './InputOccurrences';
-import {assertNotNull} from '../../../util/Assert';
+import {InputOccurrenceView} from './InputOccurrenceView';
import {OccurrenceValidationRecord} from './OccurrenceValidationRecord';
-import {BaseInputType} from './BaseInputType';
-import {ValidationError} from '../../../ValidationError';
-import {AdditionalValidationRecord} from '../../AdditionalValidationRecord';
-import {i18n} from '../../../util/Messages';
export abstract class BaseInputTypeNotManagingAdd
extends BaseInputType {
public static debug: boolean = false;
protected propertyArray: PropertyArray;
- protected ignorePropertyChange: boolean;
protected occurrenceValidationState: Map = new Map();
private inputOccurrences: InputOccurrences;
private occurrenceValueChangedListeners: ((occurrence: Element, value: Value) => void)[] = [];
diff --git a/src/main/resources/assets/admin/common/js/form/inputtype/support/InputOccurrenceView.ts b/src/main/resources/assets/admin/common/js/form/inputtype/support/InputOccurrenceView.ts
index 2ad004259..15a501973 100644
--- a/src/main/resources/assets/admin/common/js/form/inputtype/support/InputOccurrenceView.ts
+++ b/src/main/resources/assets/admin/common/js/form/inputtype/support/InputOccurrenceView.ts
@@ -1,24 +1,32 @@
import * as Q from 'q';
+import {AiHelper} from '../../../ai/AiHelper';
import {Property} from '../../../data/Property';
+import {PropertyPath} from '../../../data/PropertyPath';
import {PropertyValueChangedEvent} from '../../../data/PropertyValueChangedEvent';
-import {FormItemOccurrenceView} from '../../FormItemOccurrenceView';
-import {Element} from '../../../dom/Element';
-import {DivEl} from '../../../dom/DivEl';
import {Value} from '../../../data/Value';
-import {PropertyPath} from '../../../data/PropertyPath';
-import {InputOccurrence} from './InputOccurrence';
-import {BaseInputTypeNotManagingAdd} from './BaseInputTypeNotManagingAdd';
import {ButtonEl} from '../../../dom/ButtonEl';
+import {DivEl} from '../../../dom/DivEl';
+import {Element} from '../../../dom/Element';
+import {FormItemOccurrenceView, FormItemOccurrenceViewConfig} from '../../FormItemOccurrenceView';
+import {BaseInputTypeNotManagingAdd} from './BaseInputTypeNotManagingAdd';
+import {InputOccurrence} from './InputOccurrence';
import {OccurrenceValidationRecord} from './OccurrenceValidationRecord';
+export interface InputOccurrenceViewConfig
+ extends FormItemOccurrenceViewConfig {
+ inputTypeView: BaseInputTypeNotManagingAdd;
+ property: Property;
+}
+
export class InputOccurrenceView
extends FormItemOccurrenceView {
public static debug: boolean = false;
- private inputOccurrence: InputOccurrence;
+ protected config: InputOccurrenceViewConfig;
private property: Property;
private inputTypeView: BaseInputTypeNotManagingAdd;
private inputElement: Element;
+ private inputWrapper: DivEl;
private removeButtonEl: ButtonEl;
private dragControl: DivEl;
private propertyValueChangedHandler: (event: PropertyValueChangedEvent) => void;
@@ -26,20 +34,24 @@ export class InputOccurrenceView
private validationErrorBlock: DivEl;
constructor(inputOccurrence: InputOccurrence, baseInputTypeView: BaseInputTypeNotManagingAdd, property: Property) {
- super('input-occurrence-view', inputOccurrence);
-
- this.inputTypeView = baseInputTypeView;
- this.inputOccurrence = inputOccurrence;
- this.property = property;
-
- this.initElements();
- this.initListeners();
+ super({
+ className: 'input-occurrence-view',
+ formItemOccurrence: inputOccurrence,
+ inputTypeView: baseInputTypeView,
+ property: property,
+ } as InputOccurrenceViewConfig);
this.refresh();
}
- private initElements() {
- this.inputElement = this.inputTypeView.createInputOccurrenceElement(this.inputOccurrence.getIndex(), this.property);
+ protected initElements(): void {
+ super.initElements();
+
+ this.inputTypeView = this.config.inputTypeView;
+ this.property = this.config.property;
+ this.inputWrapper = new DivEl('input-wrapper');
+
+ this.inputElement = this.inputTypeView.createInputOccurrenceElement(this.formItemOccurrence.getIndex(), this.property);
this.dragControl = new DivEl('drag-control');
this.validationErrorBlock = new DivEl('error-block');
this.removeButtonEl = new ButtonEl();
@@ -48,12 +60,11 @@ export class InputOccurrenceView
layout(_validate: boolean = true): Q.Promise {
return super.layout(_validate).then(() => {
const dataBlock: DivEl = new DivEl('data-block');
- const inputWrapper: DivEl = new DivEl('input-wrapper');
this.appendChild(dataBlock);
dataBlock.appendChild(this.dragControl);
- dataBlock.appendChild(inputWrapper);
- inputWrapper.appendChild(this.inputElement);
+ dataBlock.appendChild(this.inputWrapper);
+ this.inputWrapper.prependChild(this.inputElement);
dataBlock.appendChild(this.removeButtonEl);
this.appendChild(this.validationErrorBlock);
@@ -86,13 +97,13 @@ export class InputOccurrenceView
}
refresh() {
- if (this.inputOccurrence.oneAndOnly()) {
+ if (this.formItemOccurrence.oneAndOnly()) {
this.addClass('single-occurrence').removeClass('multiple-occurrence');
} else {
this.addClass('multiple-occurrence').removeClass('single-occurrence');
}
- this.removeButtonEl.setVisible(this.inputOccurrence.isRemoveButtonRequiredStrict());
+ this.removeButtonEl.setVisible(this.formItemOccurrence.isRemoveButtonRequiredStrict());
}
getDataPath(): PropertyPath {
@@ -100,7 +111,7 @@ export class InputOccurrenceView
}
getIndex(): number {
- return this.inputOccurrence.getIndex();
+ return this.formItemOccurrence.getIndex();
}
getInputElement(): Element {
@@ -131,7 +142,9 @@ export class InputOccurrenceView
this.inputElement.unBlur(listener);
}
- private initListeners() {
+ protected initListeners(): void {
+ super.initListeners();
+
let ignorePropertyChange: boolean = false;
this.occurrenceValueChangedHandler = (occurrence: Element, value: Value) => {
@@ -180,6 +193,21 @@ export class InputOccurrenceView
});
this.property.onPropertyValueChanged(this.propertyValueChangedHandler);
+
+ if (this.inputTypeView.isAiEditable()) {
+ new AiHelper({
+ dataPathElement: this.inputElement,
+ getPathFunc: () => this.getDataPath(),
+ icon: {
+ container: this.inputWrapper,
+ },
+ label: this.inputTypeView.getInput().getLabel(),
+ setValueFunc: (val: string) => {
+ this.property.setValue(this.inputTypeView.getValueType().newValue(val));
+ this.inputTypeView.updateInputOccurrenceElement(this.inputElement, this.property);
+ }
+ });
+ }
}
private registerProperty(property: Property) {
diff --git a/src/main/resources/assets/admin/common/js/form/inputtype/text/TextInputType.ts b/src/main/resources/assets/admin/common/js/form/inputtype/text/TextInputType.ts
index 5b9d4f62d..bd1b28959 100644
--- a/src/main/resources/assets/admin/common/js/form/inputtype/text/TextInputType.ts
+++ b/src/main/resources/assets/admin/common/js/form/inputtype/text/TextInputType.ts
@@ -1,21 +1,21 @@
-import {NumberHelper} from '../../../util/NumberHelper';
-import {FormInputEl} from '../../../dom/FormInputEl';
-import {ValueTypes} from '../../../data/ValueTypes';
-import {i18n} from '../../../util/Messages';
-import {BaseInputTypeNotManagingAdd} from '../support/BaseInputTypeNotManagingAdd';
-import {InputTypeViewContext} from '../InputTypeViewContext';
-import {Property} from '../../../data/Property';
-import {AdditionalValidationRecord} from '../../AdditionalValidationRecord';
-import {InputValueLengthCounterEl} from './InputValueLengthCounterEl';
import * as Q from 'q';
+import {Property} from '../../../data/Property';
import {Value} from '../../../data/Value';
-import {StringHelper} from '../../../util/StringHelper';
-import {Element, LangDirection} from '../../../dom/Element';
-import {ValueTypeConverter} from '../../../data/ValueTypeConverter';
-import {ValueChangedEvent} from '../../../ValueChangedEvent';
import {ValueType} from '../../../data/ValueType';
-import {TextInput} from '../../../ui/text/TextInput';
+import {ValueTypeConverter} from '../../../data/ValueTypeConverter';
+import {ValueTypes} from '../../../data/ValueTypes';
+import {Element, LangDirection} from '../../../dom/Element';
+import {FormInputEl} from '../../../dom/FormInputEl';
import {Locale} from '../../../locale/Locale';
+import {TextInput} from '../../../ui/text/TextInput';
+import {i18n} from '../../../util/Messages';
+import {NumberHelper} from '../../../util/NumberHelper';
+import {StringHelper} from '../../../util/StringHelper';
+import {ValueChangedEvent} from '../../../ValueChangedEvent';
+import {AdditionalValidationRecord} from '../../AdditionalValidationRecord';
+import {InputTypeViewContext} from '../InputTypeViewContext';
+import {BaseInputTypeNotManagingAdd} from '../support/BaseInputTypeNotManagingAdd';
+import {InputValueLengthCounterEl} from './InputValueLengthCounterEl';
export abstract class TextInputType
extends BaseInputTypeNotManagingAdd {
@@ -161,4 +161,8 @@ export abstract class TextInputType
return rendered;
});
}
+
+ isAiEditable(): boolean {
+ return this.getContext().formContext?.isAiEditable() === true;
+ }
}
diff --git a/src/main/resources/assets/admin/common/js/form/set/FormSetHeader.ts b/src/main/resources/assets/admin/common/js/form/set/FormSetHeader.ts
index 13ac1829b..70cb7e5c3 100644
--- a/src/main/resources/assets/admin/common/js/form/set/FormSetHeader.ts
+++ b/src/main/resources/assets/admin/common/js/form/set/FormSetHeader.ts
@@ -1,7 +1,7 @@
-import {H5El} from '../../dom/H5El';
-import {HelpTextContainer} from '../HelpTextContainer';
import {DivEl} from '../../dom/DivEl';
+import {H5El} from '../../dom/H5El';
import {SpanEl} from '../../dom/SpanEl';
+import {HelpTextContainer} from '../HelpTextContainer';
import {FormSet} from './FormSet';
export class FormSetHeader
@@ -26,8 +26,8 @@ export class FormSetHeader
return super.doRender().then(rendered => {
this.appendChild(this.title);
if (this.helpTextContainer) {
- this.prependChild(this.helpTextContainer.getToggler());
const helpTextDiv = this.helpTextContainer.getHelpText();
+ this.prependChild(this.helpTextContainer.getToggler());
if (helpTextDiv) {
this.appendChild(helpTextDiv);
}
diff --git a/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrenceView.ts b/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrenceView.ts
index b08bda447..ef10763d6 100644
--- a/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrenceView.ts
+++ b/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrenceView.ts
@@ -1,46 +1,47 @@
import * as Q from 'q';
-import {PropertySet} from '../../data/PropertySet';
+import {AiHelper} from '../../ai/AiHelper';
+import {Property} from '../../data/Property';
+import {PropertyAddedEvent} from '../../data/PropertyAddedEvent';
import {PropertyArray} from '../../data/PropertyArray';
+import {PropertyPath} from '../../data/PropertyPath';
+import {PropertyRemovedEvent} from '../../data/PropertyRemovedEvent';
+import {PropertySet} from '../../data/PropertySet';
import {PropertyValueChangedEvent} from '../../data/PropertyValueChangedEvent';
-import {i18n} from '../../util/Messages';
import {Value} from '../../data/Value';
+import {ValueType} from '../../data/ValueType';
+import {ValueTypes} from '../../data/ValueTypes';
import {DivEl} from '../../dom/DivEl';
-import {PropertyPath} from '../../data/PropertyPath';
-import {FormItemOccurrenceView} from '../FormItemOccurrenceView';
+import {Element} from '../../dom/Element';
+import {ObjectHelper} from '../../ObjectHelper';
+import {Action} from '../../ui/Action';
+import {MoreButton} from '../../ui/button/MoreButton';
+import {KeyBinding} from '../../ui/KeyBinding';
+import {KeyBindings} from '../../ui/KeyBindings';
+import {ConfirmationMask} from '../../ui/mask/ConfirmationMask';
+import {i18n} from '../../util/Messages';
+import {FormContext} from '../FormContext';
+import {FormItem} from '../FormItem';
+import {FormItemLayer} from '../FormItemLayer';
+import {FormItemOccurrence} from '../FormItemOccurrence';
+import {FormItemOccurrenceView, FormItemOccurrenceViewConfig} from '../FormItemOccurrenceView';
import {FormItemView} from '../FormItemView';
-import {RecordingValidityChangedEvent} from '../RecordingValidityChangedEvent';
import {FormOccurrenceDraggableLabel} from '../FormOccurrenceDraggableLabel';
+import {Input} from '../Input';
+import {RadioButton} from '../inputtype/radiobutton/RadioButton';
+import {RecordingValidityChangedEvent} from '../RecordingValidityChangedEvent';
import {ValidationRecording} from '../ValidationRecording';
-import {FormItemLayer} from '../FormItemLayer';
import {ValidationRecordingPath} from '../ValidationRecordingPath';
import {FormSet} from './FormSet';
-import {FormItem} from '../FormItem';
-import {FormContext} from '../FormContext';
-import {FormSetOccurrence} from './FormSetOccurrence';
-import {Action} from '../../ui/Action';
-import {MoreButton} from '../../ui/button/MoreButton';
-import {ConfirmationMask} from '../../ui/mask/ConfirmationMask';
-import {Element} from '../../dom/Element';
-import {KeyBindings} from '../../ui/KeyBindings';
-import {KeyBinding} from '../../ui/KeyBinding';
-import {Property} from '../../data/Property';
-import {ValueType} from '../../data/ValueType';
-import {ValueTypes} from '../../data/ValueTypes';
-import {PropertyAddedEvent} from '../../data/PropertyAddedEvent';
-import {PropertyRemovedEvent} from '../../data/PropertyRemovedEvent';
+import {FormItemSet} from './itemset/FormItemSet';
import {FormOptionSet} from './optionset/FormOptionSet';
import {FormOptionSetOption} from './optionset/FormOptionSetOption';
-import {FormItemSet} from './itemset/FormItemSet';
-import {Input} from '../Input';
-import {RadioButton} from '../inputtype/radiobutton/RadioButton';
-import {ObjectHelper} from '../../ObjectHelper';
export interface FormSetOccurrenceViewConfig {
context: FormContext;
layer: FormItemLayer;
- formSetOccurrence: FormSetOccurrence;
+ formItemOccurrence: FormItemOccurrence;
formSet: FormSet;
@@ -49,10 +50,14 @@ export interface FormSetOccurrenceViewConfig {
dataSet: PropertySet;
}
+export interface FormSetOccurrenceViewConfigExtended extends FormSetOccurrenceViewConfig, FormItemOccurrenceViewConfig {
+ classPrefix: string;
+}
+
export abstract class FormSetOccurrenceView
extends FormItemOccurrenceView {
- protected formItemViews: FormItemView[] = [];
+ protected formItemViews: FormItemView[];
protected validityChangedListeners: ((event: RecordingValidityChangedEvent) => void)[] = [];
@@ -72,6 +77,8 @@ export abstract class FormSetOccurrenceView
protected formSet: FormSet;
+ protected config: FormSetOccurrenceViewConfigExtended;
+
private dirtyFormItemViewsMap: object = {};
private deleteConfirmationMask: ConfirmationMask;
@@ -87,17 +94,12 @@ export abstract class FormSetOccurrenceView
private expandRequestedListeners: ((view: FormSetOccurrenceView) => void)[] = [];
protected constructor(classPrefix: string, config: FormSetOccurrenceViewConfig) {
- super(`${classPrefix}occurrence-view`, config.formSetOccurrence);
-
- this.occurrenceContainerClassName = `${classPrefix}occurrences-container`;
- this.formItemOccurrence = config.formSetOccurrence;
- this.formSet = config.formSet;
- this.propertySet = config.dataSet;
- this.formItemLayer = config.layer;
+ super({
+ ...config,
+ className: `${classPrefix}occurrence-view`,
+ classPrefix: classPrefix,
+ } as FormSetOccurrenceViewConfigExtended);
- this.initElements();
- this.postInitElements();
- this.initListeners();
this.layoutElements();
}
@@ -141,10 +143,17 @@ export abstract class FormSetOccurrenceView
this.refresh();
}
- protected initElements() {
+ protected initElements(): void {
+ super.initElements();
+
+ this.formItemViews = [];
+ this.occurrenceContainerClassName = `${this.config.classPrefix}occurrences-container`;
+ this.formSetOccurrencesContainer = new DivEl(this.occurrenceContainerClassName);
+ this.formSet = this.config.formSet;
+ this.propertySet = this.config.dataSet;
+ this.formItemLayer = this.config.layer;
this.moreButton = this.createMoreButton();
this.label = new FormOccurrenceDraggableLabel();
- this.formSetOccurrencesContainer = new DivEl(this.occurrenceContainerClassName);
this.formSetOccurrencesContainer.setVisible(false);
this.confirmDeleteAction = new Action(i18n('action.delete')).setClass('red large delete-button');
this.noAction = new Action(i18n('action.cancel')).setClass('black large');
@@ -157,7 +166,9 @@ export abstract class FormSetOccurrenceView
.build();
}
- protected postInitElements() {
+ protected postInitElements(): void {
+ super.postInitElements();
+
this.label.setExpandable(this.isExpandable());
if (!this.isExpandable()) {
@@ -166,6 +177,8 @@ export abstract class FormSetOccurrenceView
}
protected initListeners() {
+ super.initListeners();
+
this.label.onClicked(() => this.setContainerVisible(!this.isContainerVisible()));
this.confirmDeleteAction.onExecuted(() => {
@@ -222,6 +235,11 @@ export abstract class FormSetOccurrenceView
this.releasePropertySet(this.propertySet);
}
});
+
+ new AiHelper({
+ dataPathElement: this,
+ getPathFunc: () => this.getDataPath(),
+ });
}
private initOccurrencesContainer(): void {
@@ -698,4 +716,5 @@ export abstract class FormSetOccurrenceView
return new MoreButton([addAboveAction, addBelowAction, removeAction]);
}
+
}
diff --git a/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrences.ts b/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrences.ts
index 1505133c9..a37974b63 100644
--- a/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrences.ts
+++ b/src/main/resources/assets/admin/common/js/form/set/FormSetOccurrences.ts
@@ -69,7 +69,7 @@ export class FormSetOccurrences
return {
context: this.context,
layer: layer,
- formSetOccurrence: occurrence,
+ formItemOccurrence: occurrence,
formSet: this.formSet,
parent: this.parent,
dataSet: dataSet
diff --git a/src/main/resources/assets/admin/common/js/form/set/optionset/FormOptionSetOptionView.ts b/src/main/resources/assets/admin/common/js/form/set/optionset/FormOptionSetOptionView.ts
index 1d492ffd7..49eba7c3f 100644
--- a/src/main/resources/assets/admin/common/js/form/set/optionset/FormOptionSetOptionView.ts
+++ b/src/main/resources/assets/admin/common/js/form/set/optionset/FormOptionSetOptionView.ts
@@ -1,24 +1,26 @@
import * as $ from 'jquery';
import * as Q from 'q';
+import {AiHelper} from '../../../ai/AiHelper';
+import {PropertyPath, PropertyPathElement} from '../../../data/PropertyPath';
import {PropertySet} from '../../../data/PropertySet';
-import {Occurrences} from '../../Occurrences';
-import {Checkbox} from '../../../ui/Checkbox';
-import {NotificationDialog} from '../../../ui/dialog/NotificationDialog';
-import {i18n} from '../../../util/Messages';
-import {DivEl} from '../../../dom/DivEl';
import {DefaultErrorHandler} from '../../../DefaultErrorHandler';
+import {DivEl} from '../../../dom/DivEl';
import {Element} from '../../../dom/Element';
import {FormEl} from '../../../dom/FormEl';
-import {FormOptionSetOption} from './FormOptionSetOption';
-import {FormOptionSetOccurrenceView} from './FormOptionSetOccurrenceView';
+import {Checkbox} from '../../../ui/Checkbox';
+import {NotificationDialog} from '../../../ui/dialog/NotificationDialog';
+import {i18n} from '../../../util/Messages';
+import {FormItemLayer} from '../../FormItemLayer';
+import {CreatedFormItemLayerConfig, FormItemLayerFactory} from '../../FormItemLayerFactory';
+import {FormItemState} from '../../FormItemState';
import {FormItemView, FormItemViewConfig} from '../../FormItemView';
import {HelpTextContainer} from '../../HelpTextContainer';
-import {FormItemLayer} from '../../FormItemLayer';
-import {FormOptionSet} from './FormOptionSet';
+import {Occurrences} from '../../Occurrences';
import {RecordingValidityChangedEvent} from '../../RecordingValidityChangedEvent';
import {ValidationRecording} from '../../ValidationRecording';
-import {CreatedFormItemLayerConfig, FormItemLayerFactory} from '../../FormItemLayerFactory';
-import {FormItemState} from '../../FormItemState';
+import {FormOptionSet} from './FormOptionSet';
+import {FormOptionSetOccurrenceView} from './FormOptionSetOccurrenceView';
+import {FormOptionSetOption} from './FormOptionSetOption';
export interface FormOptionSetOptionViewConfig
extends CreatedFormItemLayerConfig {
@@ -41,7 +43,7 @@ export class FormOptionSetOptionView
private formItemLayer: FormItemLayer;
private selectionChangedListeners: ((isSelected: boolean) => void)[] = [];
private checkbox: Checkbox;
- private readonly isOptionSetExpandedByDefault: boolean;
+ private isOptionSetExpandedByDefault: boolean;
private formItemState: FormItemState;
private notificationDialog: NotificationDialog;
private isSelectedInitially: boolean;
@@ -54,17 +56,21 @@ export class FormOptionSetOptionView
parent: config.parent
} as FormItemViewConfig);
- this.formOptionSetOption = config.formOptionSetOption;
+ this.initElements(config);
+ }
+ private initElements(config: FormOptionSetOptionViewConfig): void {
+ this.formOptionSetOption = config.formOptionSetOption;
this.isOptionSetExpandedByDefault = (config.formOptionSetOption.getParent() as FormOptionSet).isExpanded();
-
this.formItemState = config.formItemState;
-
this.addClass(this.formOptionSetOption.getPath().getElements().length % 2 ? 'even' : 'odd');
-
this.formItemLayer = config.layerFactory.createLayer(config);
-
this.notificationDialog = new NotificationDialog(i18n('notify.optionset.notempty'));
+
+ new AiHelper({
+ dataPathElement: this,
+ getPathFunc: () => PropertyPath.fromParent(this.getParent().getDataPath(), new PropertyPathElement(this.getName(), 0)),
+ });
}
toggleHelpText(show?: boolean) {
@@ -95,9 +101,9 @@ export class FormOptionSetOptionView
const optionItemsPropertySet: PropertySet = this.parent.getOrPopulateOptionItemsPropertySet(this.getName());
- if (isDefaultAndNew) {
- this.parent.handleOptionSelected(this);
- }
+ if (isDefaultAndNew) {
+ this.parent.handleOptionSelected(this);
+ }
this.optionItemsContainer = new DivEl('option-items-container');
const isContainerVisibleByDefault = this.isOptionSetExpandedByDefault || isSingleSelection || this.isSelected();
diff --git a/src/main/resources/assets/admin/common/styles/api/form/inputtype/support/input-occurrence-view.less b/src/main/resources/assets/admin/common/styles/api/form/inputtype/support/input-occurrence-view.less
index 4ddd5b3ad..3b1f31ccc 100644
--- a/src/main/resources/assets/admin/common/styles/api/form/inputtype/support/input-occurrence-view.less
+++ b/src/main/resources/assets/admin/common/styles/api/form/inputtype/support/input-occurrence-view.less
@@ -1,6 +1,7 @@
.input-occurrence-view {
display: flex;
flex-direction: column;
+ position: relative; // to absolutely position elements within
.data-block {
.clearfix();
@@ -46,6 +47,114 @@
color: @input-red-text;
}
+ .ai-button-container {
+ width: 20px;
+ height: 20px;
+ border: none;
+ position: absolute;
+ z-index: 1;
+ top: 0;
+ right: 0;
+
+ &-icon {
+ .button(@admin-font-gray3, transparent, @admin-font-gray2);
+ padding: 2px;
+ width: 100%;
+ height: 100%;
+ font-size: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+
+ &-loader {
+ box-sizing: border-box;
+ position: absolute;
+ border: 2px dotted @admin-dark-gray-border;
+ border-radius: 50%;
+ width: 20px;
+ height: 20px;
+ animation: spin 3s linear infinite;
+ }
+
+ @keyframes spin {
+ 100% {
+ transform: rotateZ(360deg);
+ }
+ }
+
+ &:not(:hover):not(:focus):not(:focus-within).default {
+ display: none;
+ }
+
+ &.default,
+ &.processing {
+ .ai-icon::before {
+ content: '';
+ display: inline-block;
+ width: 1em;
+ height: 1em;
+ }
+ }
+
+ &.completed,
+ &.failed {
+ .ai-button-container-loader {
+ animation-play-state: paused;
+ }
+ }
+
+ &.default {
+ .ai-button-container-loader {
+ display: none;
+ }
+
+ .ai-icon::before {
+ background: url("../../../../../images/juke-eye-centered.svg") center / contain no-repeat;
+ }
+ }
+
+ &.processing {
+ .ai-button-container-loader {
+ border-color: #550072;
+ }
+
+ .ai-icon::before {
+ background: url("../../../../../images/juke-eye-centered-animated.svg") center / contain no-repeat;
+ }
+ }
+
+ &.completed {
+ .ai-button-container-loader {
+ border-color: @admin-green;
+ }
+
+ .ai-icon {
+ .icon-checkmark();
+
+ &::before {
+ color: @admin-green;
+ transform: scale(0.8);
+ }
+ }
+ }
+
+ &.failed {
+ .ai-button-container-loader {
+ border-color: @admin-red;
+ }
+
+ .ai-icon {
+ .icon-close();
+
+ &::before {
+ color: @admin-red;
+ transform: scale(0.9);
+ }
+ }
+ }
+ }
+
&.single-occurrence {
.data-block {
> .drag-control,
@@ -53,6 +162,10 @@
display: none;
}
}
+
+ .ai-button-container {
+ top: -21px;
+ }
}
&.multiple-occurrence {
@@ -72,5 +185,25 @@
.error-block {
padding: 0 0 10px 17px;
}
+
+ .ai-button-container {
+ top: 4px;
+ right: 4px;
+
+ &:not(.processing) {
+ background-color: @admin-white;
+
+ &:hover {
+ background-color: @admin-white;
+ }
+ }
+
+ }
}
}
+
+.ai-helper-mask {
+ background-color: @admin-bg-light-gray;
+ opacity: 0.75;
+ border-color: @admin-medium-gray-border !important;
+}
diff --git a/src/main/resources/assets/admin/common/styles/api/input-common.less b/src/main/resources/assets/admin/common/styles/api/input-common.less
index 117993dfa..a9bb99254 100644
--- a/src/main/resources/assets/admin/common/styles/api/input-common.less
+++ b/src/main/resources/assets/admin/common/styles/api/input-common.less
@@ -15,10 +15,22 @@
.input-glow();
}
- > .input-label {
- display: inline-block;
- max-width: calc(100% - 30px);
+ &:not(.ai-editable) {
+ > .input-label,
+ > label {
+ display: inline-block;
+ max-width: calc(100% - 18px);
+ .ellipsis();
+ }
+ }
+
+ &.ai-editable {
+ .help-text-toggler {
+ display: none;
+ }
+ }
+ > .input-label {
.@{_COMMON_PREFIX}wrapper {
display: flex;
white-space: nowrap;
@@ -35,13 +47,19 @@
color: @input-red-text;
}
}
+
+ &:focus-within, &:hover {
+ + .input-type-view {
+ > .input-occurrence-view:first-child {
+ .ai-button-container {
+ display: block; // showing AI icon for the first occurrence of the input on hover over label
+ }
+ }
+ }
+ }
}
> label {
- display: inline-block;
- max-width: calc(100% - 30px);
- .ellipsis();
-
&.required::after {
content: '\00a0*';
color: @input-red-text;
diff --git a/src/main/resources/i18n/common.properties b/src/main/resources/i18n/common.properties
index 32ce802f0..f92cf5b54 100644
--- a/src/main/resources/i18n/common.properties
+++ b/src/main/resources/i18n/common.properties
@@ -215,6 +215,12 @@ tooltip.header.collapse=Click to collapse
#
warning.optionsview.truncated = The list is truncated
+#
+# AI
+#
+ai.action.contentOperator.use=Use in Content Operator
+ai.field.processing={0} is being processed...
+
#
# Accessibility
#