@dataclouder/ngx-lessons 0.0.30 → 0.0.31

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/fesm2022/dataclouder-ngx-lessons.mjs +1170 -449
  2. package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
  3. package/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.d.ts +2 -2
  4. package/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.d.ts +11 -0
  5. package/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.d.ts +39 -38
  6. package/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.d.ts +22 -0
  7. package/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.d.ts +32 -37
  8. package/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.d.ts +2 -4
  9. package/lib/components/lesson-mini-components/components/ComponentBuilder.d.ts +7 -2
  10. package/lib/components/lesson-mini-components/components/lessons.clases.d.ts +17 -42
  11. package/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.d.ts +13 -0
  12. package/lib/components/lesson-mini-components/components/speaker/speaker.component.d.ts +12 -0
  13. package/lib/services/lesson-ai.service.d.ts +18 -0
  14. package/lib/services/lesson-notion.service.d.ts +35 -0
  15. package/lib/services/lesson-utils.service.d.ts +34 -0
  16. package/package.json +3 -2
  17. package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.html +40 -35
  18. package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.scss +15 -2
  19. package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.ts +3 -3
  20. package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.css +1 -0
  21. package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.html +46 -0
  22. package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.ts +52 -0
  23. package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.html +54 -92
  24. package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.ts +268 -230
  25. package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.css +1 -0
  26. package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.html +72 -0
  27. package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.ts +60 -0
  28. package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.html +23 -27
  29. package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.ts +247 -186
  30. package/src/lib/components/dc-lessons/lesson-form/lesson-form.component.ts +2 -2
  31. package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.html +3 -3
  32. package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.ts +5 -5
  33. package/src/lib/components/lesson-mini-components/components/ComponentBuilder.ts +23 -15
  34. package/src/lib/components/lesson-mini-components/components/lessons.clases.ts +32 -66
  35. package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.html +62 -58
  36. package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.ts +2 -2
  37. package/src/lib/components/lesson-mini-components/components/selector/selector.component.html +1 -2
  38. package/src/lib/components/lesson-mini-components/components/selector/selector.component.ts +2 -2
  39. package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.html +5 -27
  40. package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.ts +38 -25
  41. package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.html +9 -7
  42. package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.ts +30 -26
  43. package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcher.component.ts +2 -2
  44. package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcherBuilder/translationSwitcherBuilder.component.ts +2 -2
  45. package/src/lib/services/lesson-ai.service.ts +103 -0
  46. package/src/lib/services/lesson-notion.service.ts +161 -0
  47. package/src/lib/services/lesson-utils.service.ts +181 -0
  48. package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.spec.ts +0 -25
  49. package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.spec.ts +0 -25
  50. package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.spec.ts +0 -25
@@ -1,47 +1,49 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Component, Input, ChangeDetectionStrategy, InjectionToken, Pipe, EventEmitter, Output, Inject, ViewChildren, ViewChild, ViewContainerRef } from '@angular/core';
2
+ import { Component, Input, ChangeDetectionStrategy, signal, InjectionToken, Pipe, EventEmitter, Output, ViewChildren, Inject, inject, Injectable, input, Renderer2, ViewContainerRef, computed, effect, ViewChild } from '@angular/core';
3
3
  import * as i1 from '@angular/forms';
4
- import { FormControl, UntypedFormControl, FormsModule, ReactiveFormsModule, Validators, UntypedFormGroup } from '@angular/forms';
5
- import * as i2$3 from '@angular/common';
6
- import { NgFor, NgIf, CommonModule, DatePipe, NgComponentOutlet, KeyValuePipe } from '@angular/common';
4
+ import { FormControl, UntypedFormControl, FormsModule, ReactiveFormsModule, Validators, FormGroup } from '@angular/forms';
7
5
  import * as i4 from 'primeng/inputtext';
8
6
  import { InputTextModule } from 'primeng/inputtext';
9
- import * as i3 from 'primeng/button';
7
+ import * as i1$1 from 'primeng/button';
10
8
  import { ButtonModule } from 'primeng/button';
11
9
  import * as i5 from 'primeng/message';
12
10
  import { MessageModule } from 'primeng/message';
11
+ import { nanoid } from 'nanoid';
13
12
  import * as i2 from 'primeng/dynamicdialog';
14
- import * as i2$1 from 'primeng/dropdown';
13
+ import { DialogService } from 'primeng/dynamicdialog';
14
+ import * as i2$1 from 'primeng/select';
15
+ import { SelectModule } from 'primeng/select';
15
16
  import { DropdownModule } from 'primeng/dropdown';
16
- import * as i3$2 from 'primeng/paginator';
17
+ import { NgxTtsComponent } from '@dataclouder/ngx-tts';
18
+ import * as i2$3 from 'primeng/paginator';
17
19
  import { PaginatorModule } from 'primeng/paginator';
18
- import * as i1$1 from '@angular/router';
19
- import { RouterModule } from '@angular/router';
20
- import * as i5$1 from '@dataclouder/ngx-core';
20
+ import { DatePipe, NgComponentOutlet, KeyValuePipe, CommonModule, JsonPipe } from '@angular/common';
21
+ import * as i1$2 from '@angular/router';
22
+ import { RouterModule, ActivatedRoute, Router } from '@angular/router';
23
+ import * as i4$1 from '@dataclouder/ngx-core';
21
24
  import { PaginationBase, TOAST_ALERTS_TOKEN, DCFilterBarComponent, QuickTableComponent } from '@dataclouder/ngx-core';
22
25
  import { PopoverModule } from 'primeng/popover';
23
26
  import * as i2$2 from 'primeng/speeddial';
24
27
  import { SpeedDialModule } from 'primeng/speeddial';
25
- import * as i3$1 from 'primeng/card';
28
+ import * as i3 from 'primeng/card';
26
29
  import { CardModule } from 'primeng/card';
30
+ import { toSignal } from '@angular/core/rxjs-interop';
31
+ import { map } from 'rxjs';
27
32
  import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block';
28
- import * as i4$2 from '@ckeditor/ckeditor5-angular';
33
+ import * as i3$1 from '@ckeditor/ckeditor5-angular';
29
34
  import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
30
- import * as i8 from 'primeng/splitter';
35
+ import * as i5$1 from 'primeng/splitter';
31
36
  import { SplitterModule } from 'primeng/splitter';
32
- import * as i9 from 'primeng/tooltip';
37
+ import * as i2$5 from 'primeng/tooltip';
33
38
  import { TooltipModule } from 'primeng/tooltip';
34
- import { AspectType, ResolutionType, CropperComponentModal } from '@dataclouder/ngx-cloud-storage';
35
- import * as i2$4 from '@dataclouder/ngx-agent-cards';
36
- import { ConversationType, TextEngines, CONVERSATION_AI_TOKEN, DCChatComponent } from '@dataclouder/ngx-agent-cards';
37
- import * as i4$1 from 'primeng/drawer';
39
+ import { ResolutionType, AspectType, CropperComponentModal } from '@dataclouder/ngx-cloud-storage';
40
+ import { TextEngines, ConversationType, DCChatComponent } from '@dataclouder/ngx-agent-cards';
41
+ import * as i2$4 from 'primeng/drawer';
38
42
  import { DrawerModule } from 'primeng/drawer';
39
- import * as i6 from 'primeng/api';
43
+ import * as i2$6 from 'primeng/api';
40
44
 
41
45
  class ComponentBuilder {
42
- constructor(formBuilder,
43
- // protected toastrService: ToastService, // protected ref: NbDialogRef<ComponentBuilder>,
44
- ref) {
46
+ constructor(formBuilder, ref) {
45
47
  this.formBuilder = formBuilder;
46
48
  this.ref = ref;
47
49
  // Sobreescribir si es necesario
@@ -74,25 +76,30 @@ class ComponentBuilder {
74
76
  delete data[key];
75
77
  }
76
78
  });
77
- const id = Math.random().toString(36).substring(7);
78
- const code = { id: id, component: this.questionType, settings: { ...data } };
79
- return `~${JSON.stringify(code)}~`;
79
+ const code = { component: this.questionType, settings: { ...data } };
80
+ return code;
80
81
  }
81
82
  showCode() {
82
83
  const code = this.getCode();
83
- alert(code);
84
+ const stringCode = `~${JSON.stringify(code)}~`;
85
+ alert(stringCode);
84
86
  }
85
- async copyToClipboard() {
87
+ getComponentData() {
86
88
  const code = this.getCode();
87
- await navigator.clipboard.writeText(code);
88
- // this.toastrService.info('Control + V para pegar', 'Se copió al portapapeles');
89
- // this.ref.close();
90
- this.ref.close(code);
89
+ const id = nanoid().replace(/-/g, '_');
90
+ code.id = id;
91
+ return { str: `~${JSON.stringify(code)}~`, obj: code };
92
+ }
93
+ // TODO: for now copyToClipboard is the that that closes modal and return data.
94
+ async copyToClipboard() {
95
+ const data = this.getComponentData();
96
+ await navigator.clipboard.writeText(data.str);
97
+ this.ref.close(data);
91
98
  }
92
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ComponentBuilder, deps: [{ token: i1.FormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
93
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: ComponentBuilder, isStandalone: true, selector: "app-component-builder", ngImport: i0, template: '<div>no template</div>', isInline: true }); }
99
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ComponentBuilder, deps: [{ token: i1.FormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
100
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: ComponentBuilder, isStandalone: true, selector: "app-component-builder", ngImport: i0, template: '<div>no template</div>', isInline: true }); }
94
101
  }
95
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ComponentBuilder, decorators: [{
102
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ComponentBuilder, decorators: [{
96
103
  type: Component,
97
104
  args: [{
98
105
  selector: 'app-component-builder',
@@ -113,10 +120,10 @@ class ComponentWithForm {
113
120
  validate() {
114
121
  // TODO: generic method to evaluate
115
122
  }
116
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ComponentWithForm, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
117
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: ComponentWithForm, isStandalone: true, selector: "app-component-form", ngImport: i0, template: '<div>no template</div>', isInline: true }); }
123
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ComponentWithForm, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
124
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: ComponentWithForm, isStandalone: true, selector: "app-component-form", ngImport: i0, template: '<div>no template</div>', isInline: true }); }
118
125
  }
119
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: ComponentWithForm, decorators: [{
126
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: ComponentWithForm, decorators: [{
120
127
  type: Component,
121
128
  args: [{
122
129
  selector: 'app-component-form',
@@ -152,12 +159,12 @@ class SelectorComponent extends ComponentWithForm {
152
159
  }
153
160
  return true;
154
161
  }
155
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
156
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: SelectorComponent, isStandalone: true, selector: "app-selector", inputs: { config: "config" }, usesInheritance: true, ngImport: i0, template: "<p-dropdown [class]=\"status\" placeholder=\"Selecciona\" [options]=\"config.settings.options\" [formControl]=\"control\"\r\n [showClear]=\"true\"></p-dropdown>", styles: ["::ng-deep .comp-selector button{min-width:80px}.warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: i2$1.Dropdown, selector: "p-dropdown", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] }); }
162
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SelectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
163
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: SelectorComponent, isStandalone: true, selector: "app-selector", inputs: { config: "config" }, usesInheritance: true, ngImport: i0, template: "<p-select [class]=\"status\" placeholder=\"Selecciona\" [options]=\"config.settings.options\" [formControl]=\"control\"></p-select>\r\n", styles: ["::ng-deep .comp-selector button{min-width:80px}.warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: SelectModule }, { kind: "component", type: i2$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "name", "style", "panelStyle", "styleClass", "panelStyleClass", "readonly", "required", "editable", "appendTo", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "variant", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "autoDisplayFirst", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "size", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "maxlength", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "fluid", "disabled", "itemSize", "autoZIndex", "baseZIndex", "showTransitionOptions", "hideTransitionOptions", "filterValue", "options"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }] }); }
157
164
  }
158
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SelectorComponent, decorators: [{
165
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SelectorComponent, decorators: [{
159
166
  type: Component,
160
- args: [{ selector: 'app-selector', standalone: true, imports: [FormsModule, ReactiveFormsModule, DropdownModule], template: "<p-dropdown [class]=\"status\" placeholder=\"Selecciona\" [options]=\"config.settings.options\" [formControl]=\"control\"\r\n [showClear]=\"true\"></p-dropdown>", styles: ["::ng-deep .comp-selector button{min-width:80px}.warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"] }]
167
+ args: [{ selector: 'app-selector', standalone: true, imports: [FormsModule, ReactiveFormsModule, SelectModule], template: "<p-select [class]=\"status\" placeholder=\"Selecciona\" [options]=\"config.settings.options\" [formControl]=\"control\"></p-select>\r\n", styles: ["::ng-deep .comp-selector button{min-width:80px}.warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"] }]
161
168
  }], ctorParameters: () => [], propDecorators: { config: [{
162
169
  type: Input
163
170
  }] } });
@@ -203,12 +210,12 @@ class SelectorBuilderComponent extends ComponentBuilder {
203
210
  get optionsForm() {
204
211
  return this.formGroup.get('options');
205
212
  }
206
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SelectorBuilderComponent, deps: [{ token: i1.FormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
207
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: SelectorBuilderComponent, isStandalone: true, selector: "app-selector-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\r\n </div>\r\n\r\n <div>\r\n <span>En que a\u00F1o lleg\u00F3 cristobal colon a america?</span>\r\n <app-selector [config]=\"sampleConfig\"></app-selector>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div>\r\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\r\n <input class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\r\n <br />\r\n\r\n <input class=\"form-input\" type=\"\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\r\n\r\n <br />\r\n <input\r\n class=\"form-input\"\r\n type=\"text\"\r\n pInputText\r\n fullWidth\r\n formControlName=\"explanation\"\r\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\r\n\r\n <hr />\r\n <h6>Opciones</h6>\r\n\r\n <div class=\"form-group\" formArrayName=\"options\">\r\n <div\r\n style=\"display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-direction: column\"\r\n *ngFor=\"let item of optionsForm.controls; let i = index\">\r\n <div>\r\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\r\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\r\n </form>\r\n\r\n <!-- <button nbButton (click)=\"isRendered = !isRendered\"> Renderizar </button> -->\r\n\r\n <div *ngIf=\"isRendered\">\r\n <!-- TODO: probably i need to pass some params -->\r\n <app-selector></app-selector>\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: SelectorComponent, selector: "app-selector", inputs: ["config"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i5.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }] }); }
213
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SelectorBuilderComponent, deps: [{ token: i1.FormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
214
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: SelectorBuilderComponent, isStandalone: true, selector: "app-selector-builder", usesInheritance: true, ngImport: i0, template: "<div>\n <div>\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n </div>\n\n <div>\n <span>En que a\u00F1o lleg\u00F3 cristobal colon a america?</span>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n\n <hr />\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n <br />\n\n <input class=\"form-input\" type=\"\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n\n <br />\n <input\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div\n style=\"display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-direction: column\"\n >\n <div>\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n <!-- <button nbButton (click)=\"isRendered = !isRendered\"> Renderizar </button> -->\n\n @if (isRendered) {\n <div>\n <!-- TODO: probably i need to pass some params -->\n <app-selector></app-selector>\n </div>\n }\n </div>\n\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: i1.FormArrayName, selector: "[formArrayName]", inputs: ["formArrayName"] }, { kind: "component", type: SelectorComponent, selector: "app-selector", inputs: ["config"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: MessageModule }, { kind: "component", type: i5.Message, selector: "p-message", inputs: ["severity", "text", "escape", "style", "styleClass", "closable", "icon", "closeIcon", "life", "showTransitionOptions", "hideTransitionOptions", "size", "variant"], outputs: ["onClose"] }] }); }
208
215
  }
209
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: SelectorBuilderComponent, decorators: [{
216
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SelectorBuilderComponent, decorators: [{
210
217
  type: Component,
211
- args: [{ selector: 'app-selector-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, NgFor, NgIf, SelectorComponent, InputTextModule, ButtonModule, MessageModule], template: "<div>\r\n <div>\r\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\r\n </div>\r\n\r\n <div>\r\n <span>En que a\u00F1o lleg\u00F3 cristobal colon a america?</span>\r\n <app-selector [config]=\"sampleConfig\"></app-selector>\r\n </div>\r\n\r\n <hr />\r\n\r\n <div>\r\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\r\n <input class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\r\n <br />\r\n\r\n <input class=\"form-input\" type=\"\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\r\n\r\n <br />\r\n <input\r\n class=\"form-input\"\r\n type=\"text\"\r\n pInputText\r\n fullWidth\r\n formControlName=\"explanation\"\r\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\r\n\r\n <hr />\r\n <h6>Opciones</h6>\r\n\r\n <div class=\"form-group\" formArrayName=\"options\">\r\n <div\r\n style=\"display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-direction: column\"\r\n *ngFor=\"let item of optionsForm.controls; let i = index\">\r\n <div>\r\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\r\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\r\n </form>\r\n\r\n <!-- <button nbButton (click)=\"isRendered = !isRendered\"> Renderizar </button> -->\r\n\r\n <div *ngIf=\"isRendered\">\r\n <!-- TODO: probably i need to pass some params -->\r\n <app-selector></app-selector>\r\n </div>\r\n </div>\r\n\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"] }]
218
+ args: [{ selector: 'app-selector-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, SelectorComponent, InputTextModule, ButtonModule, MessageModule], template: "<div>\n <div>\n <p-message>Construcci\u00F3n del componente de Selecci\u00F3n, sirve para hacer una pregunta y mostrar varias opciones, ejemplo:</p-message>\n </div>\n\n <div>\n <span>En que a\u00F1o lleg\u00F3 cristobal colon a america?</span>\n <app-selector [config]=\"sampleConfig\"></app-selector>\n </div>\n\n <hr />\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input class=\"form-input\" type=\"text\" pInputText fullWidth formControlName=\"response\" placeholder=\"Respuesta Correcta...\" />\n <br />\n\n <input class=\"form-input\" type=\"\" pInputText fullWidth formControlName=\"hint\" placeholder=\"Escribe una pista para esta pregunta\" />\n\n <br />\n <input\n class=\"form-input\"\n type=\"text\"\n pInputText\n fullWidth\n formControlName=\"explanation\"\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\n\n <hr />\n <h6>Opciones</h6>\n\n <div class=\"form-group\" formArrayName=\"options\">\n @for (item of optionsForm.controls; track item; let i = $index) {\n <div\n style=\"display: flex; gap: 10px; align-items: center; justify-content: space-between; margin-bottom: 10px; flex-direction: column\"\n >\n <div>\n <input type=\"text\" pInputText fullWidth [formControlName]=\"i\" />\n <p-button (click)=\"deleteFormArrayByIndex('options', i)\" icon=\"pi pi-times\" severity=\"danger\"></p-button>\n </div>\n </div>\n }\n </div>\n\n <p-button (click)=\"pushControlToFormArray('options')\" label=\"Agregar Opci\u00F3n\" [text]=\"true\" severity=\"help\"></p-button>\n </form>\n\n <!-- <button nbButton (click)=\"isRendered = !isRendered\"> Renderizar </button> -->\n\n @if (isRendered) {\n <div>\n <!-- TODO: probably i need to pass some params -->\n <app-selector></app-selector>\n </div>\n }\n </div>\n\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n </div>\n", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"] }]
212
219
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.DynamicDialogRef }] });
213
220
 
214
221
  class TextWriterBuiderComponent extends ComponentBuilder {
@@ -221,10 +228,10 @@ class TextWriterBuiderComponent extends ComponentBuilder {
221
228
  this.formGroup = this.formBuilder.group({ response: ['', Validators.required], hint: [], explanation: [] });
222
229
  }
223
230
  ngOnInit() { }
224
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TextWriterBuiderComponent, deps: [{ token: i1.UntypedFormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
225
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: TextWriterBuiderComponent, isStandalone: true, selector: "app-text-writer-buider", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de formulario con texto</h5>\r\n </div>\r\n\r\n <div>\r\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\r\n <input pInputText type=\"text\" nbInput fullWidth formControlName=\"response\" placeholder=\"Respuesta\" />\r\n\r\n <input pInputText class=\"form-input\" type=\"\" nbInput fullWidth formControlName=\"hint\"\r\n placeholder=\"Escribe una pista para esta pregunta\" />\r\n\r\n <input pInputText class=\"form-input\" type=\"text\" nbInput fullWidth formControlName=\"explanation\"\r\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\r\n </form>\r\n </div>\r\n\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\"\r\n [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\"\r\n severity=\"secondary\"></p-button>\r\n </div>\r\n</div>", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
231
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextWriterBuiderComponent, deps: [{ token: i1.UntypedFormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
232
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: TextWriterBuiderComponent, isStandalone: true, selector: "app-text-writer-buider", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de formulario con texto</h5>\r\n </div>\r\n\r\n <div>\r\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\r\n <input pInputText type=\"text\" nbInput fullWidth formControlName=\"response\" placeholder=\"Respuesta\" />\r\n\r\n <input pInputText class=\"form-input\" type=\"\" nbInput fullWidth formControlName=\"hint\"\r\n placeholder=\"Escribe una pista para esta pregunta\" />\r\n\r\n <input pInputText class=\"form-input\" type=\"text\" nbInput fullWidth formControlName=\"explanation\"\r\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\r\n </form>\r\n </div>\r\n\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\"\r\n [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\"\r\n severity=\"secondary\"></p-button>\r\n </div>\r\n</div>", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
226
233
  }
227
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TextWriterBuiderComponent, decorators: [{
234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextWriterBuiderComponent, decorators: [{
228
235
  type: Component,
229
236
  args: [{ selector: 'app-text-writer-buider', standalone: true, imports: [FormsModule, ReactiveFormsModule, ButtonModule, InputTextModule], template: "<div>\r\n <div>\r\n <h5>Constructor de formulario con texto</h5>\r\n </div>\r\n\r\n <div>\r\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\r\n <input pInputText type=\"text\" nbInput fullWidth formControlName=\"response\" placeholder=\"Respuesta\" />\r\n\r\n <input pInputText class=\"form-input\" type=\"\" nbInput fullWidth formControlName=\"hint\"\r\n placeholder=\"Escribe una pista para esta pregunta\" />\r\n\r\n <input pInputText class=\"form-input\" type=\"text\" nbInput fullWidth formControlName=\"explanation\"\r\n placeholder=\"Excribe una explicaci\u00F3n para la respuesta\" />\r\n </form>\r\n </div>\r\n\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\"\r\n [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\"\r\n severity=\"secondary\"></p-button>\r\n </div>\r\n</div>", styles: ["nb-card{width:60vw}.builder-form{padding:5px}.form-input{margin-top:10px}.mar-top{margin:5px}\n"] }]
230
237
  }], ctorParameters: () => [{ type: i1.UntypedFormBuilder }, { type: i2.DynamicDialogRef }] });
@@ -264,10 +271,10 @@ class TextWriterComponent extends ComponentWithForm {
264
271
  }
265
272
  return true;
266
273
  }
267
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TextWriterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
268
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: TextWriterComponent, isStandalone: true, selector: "app-text-writer", inputs: { config: "config" }, usesInheritance: true, ngImport: i0, template: "<input [class]=\"this.status\" pInputText [formControl]=\"control\" fieldSize=\"small\" type=\"text\" placeholder=\"Respuesta\"\n [size]=\"size\" />\n\n<!-- [ngClass]=\"{ 'selected-radio': 'true']}\" -->", styles: [".warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
274
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextWriterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
275
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: TextWriterComponent, isStandalone: true, selector: "app-text-writer", inputs: { config: "config" }, usesInheritance: true, ngImport: i0, template: "<input [class]=\"this.status\" pInputText [formControl]=\"control\" fieldSize=\"small\" type=\"text\" placeholder=\"Respuesta\"\n [size]=\"size\" />\n\n<!-- [ngClass]=\"{ 'selected-radio': 'true']}\" -->", styles: [".warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }] }); }
269
276
  }
270
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TextWriterComponent, decorators: [{
277
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TextWriterComponent, decorators: [{
271
278
  type: Component,
272
279
  args: [{ selector: 'app-text-writer', standalone: true, imports: [FormsModule, ReactiveFormsModule, InputTextModule], template: "<input [class]=\"this.status\" pInputText [formControl]=\"control\" fieldSize=\"small\" type=\"text\" placeholder=\"Respuesta\"\n [size]=\"size\" />\n\n<!-- [ngClass]=\"{ 'selected-radio': 'true']}\" -->", styles: [".warning{border-color:#f0ad4e}.danger{border-color:#e1211b}\n"] }]
273
280
  }], ctorParameters: () => [], propDecorators: { config: [{
@@ -284,12 +291,12 @@ class TranslationSwitcherBuilderComponent extends ComponentBuilder {
284
291
  }
285
292
  // Este componente reutiliza completamente el form del padre y todos los métodos
286
293
  ngOnInit() { }
287
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslationSwitcherBuilderComponent, deps: [{ token: i1.FormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
288
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: TranslationSwitcherBuilderComponent, isStandalone: true, selector: "app-translation-switcher-builder", usesInheritance: true, ngImport: i0, template: "<div>\n <div>\n <h5>Constructor de translation switcher</h5>\n </div>\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input style=\"width: 100%\" pInputText type=\"text\" nbInput fullWidth formControlName=\"text\" placeholder=\"Texto para visualizar\" />\n\n <br /><br />\n\n <input\n style=\"width: 100%\"\n pInputText\n class=\"form-input\"\n type=\"\"\n nbInput\n fullWidth\n formControlName=\"response\"\n placeholder=\"Traducci\u00F3n al hacer clic\" />\n </form>\n </div>\n <br />\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
294
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslationSwitcherBuilderComponent, deps: [{ token: i1.FormBuilder }, { token: i2.DynamicDialogRef }], target: i0.ɵɵFactoryTarget.Component }); }
295
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: TranslationSwitcherBuilderComponent, isStandalone: true, selector: "app-translation-switcher-builder", usesInheritance: true, ngImport: i0, template: "<div>\n <div>\n <h5>Constructor de translation switcher</h5>\n </div>\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input style=\"width: 100%\" pInputText type=\"text\" nbInput fullWidth formControlName=\"text\" placeholder=\"Texto para visualizar\" />\n\n <br /><br />\n\n <input\n style=\"width: 100%\"\n pInputText\n class=\"form-input\"\n type=\"\"\n nbInput\n fullWidth\n formControlName=\"response\"\n placeholder=\"Traducci\u00F3n al hacer clic\" />\n </form>\n </div>\n <br />\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n</div>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
289
296
  }
290
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslationSwitcherBuilderComponent, decorators: [{
297
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslationSwitcherBuilderComponent, decorators: [{
291
298
  type: Component,
292
- args: [{ selector: 'app-translation-switcher-builder', standalone: true, imports: [CommonModule, FormsModule, ReactiveFormsModule, ButtonModule, InputTextModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\n <div>\n <h5>Constructor de translation switcher</h5>\n </div>\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input style=\"width: 100%\" pInputText type=\"text\" nbInput fullWidth formControlName=\"text\" placeholder=\"Texto para visualizar\" />\n\n <br /><br />\n\n <input\n style=\"width: 100%\"\n pInputText\n class=\"form-input\"\n type=\"\"\n nbInput\n fullWidth\n formControlName=\"response\"\n placeholder=\"Traducci\u00F3n al hacer clic\" />\n </form>\n </div>\n <br />\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n</div>\n", styles: [":host{display:block}\n"] }]
299
+ args: [{ selector: 'app-translation-switcher-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, ButtonModule, InputTextModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div>\n <div>\n <h5>Constructor de translation switcher</h5>\n </div>\n\n <div>\n <form class=\"builder-form\" [formGroup]=\"formGroup\">\n <input style=\"width: 100%\" pInputText type=\"text\" nbInput fullWidth formControlName=\"text\" placeholder=\"Texto para visualizar\" />\n\n <br /><br />\n\n <input\n style=\"width: 100%\"\n pInputText\n class=\"form-input\"\n type=\"\"\n nbInput\n fullWidth\n formControlName=\"response\"\n placeholder=\"Traducci\u00F3n al hacer clic\" />\n </form>\n </div>\n <br />\n <div>\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\n <p-button (click)=\"showCode()\" [disabled]=\"formGroup.invalid\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\n </div>\n</div>\n", styles: [":host{display:block}\n"] }]
293
300
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.DynamicDialogRef }] });
294
301
 
295
302
  class TranslationSwitcherComponent {
@@ -308,14 +315,75 @@ class TranslationSwitcherComponent {
308
315
  this.visibleText = this.config.settings.text;
309
316
  }
310
317
  }
311
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslationSwitcherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
312
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: TranslationSwitcherComponent, isStandalone: true, selector: "app-translation-switcher", inputs: { config: "config" }, ngImport: i0, template: "<button\n pButton\n style=\"padding: 0px 2px\"\n severity=\"help\"\n size=\"small\"\n (click)=\"switchTranslation()\"\n [label]=\"visibleText\"\n [text]=\"true\"\n [rounded]=\"true\"></button>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i3.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }], changeDetection: i0.ChangeDetectionStrategy.Default }); }
318
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslationSwitcherComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
319
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: TranslationSwitcherComponent, isStandalone: true, selector: "app-translation-switcher", inputs: { config: "config" }, ngImport: i0, template: "<button\n pButton\n style=\"padding: 0px 2px\"\n severity=\"help\"\n size=\"small\"\n (click)=\"switchTranslation()\"\n [label]=\"visibleText\"\n [text]=\"true\"\n [rounded]=\"true\"></button>\n", styles: [":host{display:block}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }], changeDetection: i0.ChangeDetectionStrategy.Default }); }
320
+ }
321
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslationSwitcherComponent, decorators: [{
322
+ type: Component,
323
+ args: [{ selector: 'app-translation-switcher', standalone: true, imports: [ButtonModule], changeDetection: ChangeDetectionStrategy.Default, template: "<button\n pButton\n style=\"padding: 0px 2px\"\n severity=\"help\"\n size=\"small\"\n (click)=\"switchTranslation()\"\n [label]=\"visibleText\"\n [text]=\"true\"\n [rounded]=\"true\"></button>\n", styles: [":host{display:block}\n"] }]
324
+ }], propDecorators: { config: [{
325
+ type: Input
326
+ }] } });
327
+
328
+ class SpeakerBuilderComponent extends ComponentBuilder {
329
+ constructor() {
330
+ super(...arguments);
331
+ this.tts = signal(undefined);
332
+ }
333
+ handleTtsGenerated(event) {
334
+ console.log('TTS generated:', event);
335
+ this.tts.set(event);
336
+ }
337
+ // Update return type and add settings object
338
+ getCode() {
339
+ const ttsValue = this.tts();
340
+ if (!ttsValue) {
341
+ // Handle the case where tts is undefined, maybe throw an error or return a default config
342
+ console.error('TTS data is not generated yet.');
343
+ // Depending on requirements, you might return a default/empty config or throw
344
+ return { component: this.questionType, inputs: {} }; // Example: return empty inputs
345
+ }
346
+ const code = {
347
+ component: this.questionType,
348
+ inputs: { tts: ttsValue },
349
+ };
350
+ return code;
351
+ }
352
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeakerBuilderComponent, deps: null, target: i0.ɵɵFactoryTarget.Component }); }
353
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: SpeakerBuilderComponent, isStandalone: true, selector: "app-speaker-builder", usesInheritance: true, ngImport: i0, template: "<div>\r\n <div>\r\n <h5>Constructor de Speaker</h5>\r\n </div>\r\n\r\n <lib-ngx-tts (ttsGenerated)=\"handleTtsGenerated($event)\"></lib-ngx-tts>\r\n\r\n <br />\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"!tts()\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n", styles: [""], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: DropdownModule }, { kind: "component", type: NgxTtsComponent, selector: "lib-ngx-tts", inputs: ["path"], outputs: ["ttsGenerated"] }] }); }
354
+ }
355
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeakerBuilderComponent, decorators: [{
356
+ type: Component,
357
+ args: [{ selector: 'app-speaker-builder', standalone: true, imports: [FormsModule, ReactiveFormsModule, InputTextModule, ButtonModule, DropdownModule, NgxTtsComponent], template: "<div>\r\n <div>\r\n <h5>Constructor de Speaker</h5>\r\n </div>\r\n\r\n <lib-ngx-tts (ttsGenerated)=\"handleTtsGenerated($event)\"></lib-ngx-tts>\r\n\r\n <br />\r\n <div>\r\n <p-button (click)=\"copyToClipboard()\" [disabled]=\"formGroup.invalid\" label=\"Copia C\u00F3digo\" [rounded]=\"true\"></p-button>\r\n <p-button (click)=\"showCode()\" [disabled]=\"!tts()\" label=\"Mostrar\" [rounded]=\"true\" severity=\"secondary\"></p-button>\r\n </div>\r\n</div>\r\n" }]
358
+ }] });
359
+
360
+ // ❌ can use this until i copy services to this lib
361
+ // import { CONVERSATION_AI_TOKEN } from '@dataclouder/ngx-agent-cards';
362
+ class SpeakerComponent {
363
+ ngOnInit() {
364
+ // throw new Error('Method not implemented.');
365
+ }
366
+ // voiceOptions = VoiceOptions;
367
+ // constructor(private speachService: SpeechService, private audioService: AudioService) {}
368
+ // ngOnInit(): void {}
369
+ speach() {
370
+ console.log('should speech but will do in next version');
371
+ // if (this.config.audio) {
372
+ // this.audioService.playAudio(this.config.audio.url);
373
+ // } else {
374
+ // this.speachService.speach(this.config.settings.text);
375
+ // }
376
+ }
377
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeakerComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
378
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: SpeakerComponent, isStandalone: true, selector: "app-speaker", inputs: { config: "config", tts: "tts" }, ngImport: i0, template: "<button\r\n pButton\r\n style=\"padding: 0px 2px\"\r\n severity=\"help\"\r\n size=\"small\"\r\n (click)=\"speach()\"\r\n [label]=\"config?.settings?.text\"\r\n [text]=\"true\"\r\n [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }] }); }
313
379
  }
314
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: TranslationSwitcherComponent, decorators: [{
380
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeakerComponent, decorators: [{
315
381
  type: Component,
316
- args: [{ selector: 'app-translation-switcher', standalone: true, imports: [CommonModule, ButtonModule], changeDetection: ChangeDetectionStrategy.Default, template: "<button\n pButton\n style=\"padding: 0px 2px\"\n severity=\"help\"\n size=\"small\"\n (click)=\"switchTranslation()\"\n [label]=\"visibleText\"\n [text]=\"true\"\n [rounded]=\"true\"></button>\n", styles: [":host{display:block}\n"] }]
382
+ args: [{ selector: 'app-speaker', standalone: true, imports: [ButtonModule], template: "<button\r\n pButton\r\n style=\"padding: 0px 2px\"\r\n severity=\"help\"\r\n size=\"small\"\r\n (click)=\"speach()\"\r\n [label]=\"config?.settings?.text\"\r\n [text]=\"true\"\r\n [rounded]=\"true\"></button>\r\n", styles: [".lisen{cursor:pointer}\n"] }]
317
383
  }], propDecorators: { config: [{
318
384
  type: Input
385
+ }], tts: [{
386
+ type: Input
319
387
  }] } });
320
388
 
321
389
  var LessonComponentEnum;
@@ -329,7 +397,7 @@ var LessonComponentEnum;
329
397
  })(LessonComponentEnum || (LessonComponentEnum = {}));
330
398
  const LessonComponentBuilders = {
331
399
  [LessonComponentEnum.Selector]: SelectorBuilderComponent,
332
- // [LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
400
+ [LessonComponentEnum.Speaker]: SpeakerBuilderComponent,
333
401
  [LessonComponentEnum.TextWriter]: TextWriterBuiderComponent,
334
402
  // [LessonComponentEnum.VerbSummary]: VerbSummaryBuilderComponent,
335
403
  // [LessonComponentEnum.WordSummary]: WordSummaryBuilderComponent,
@@ -337,7 +405,7 @@ const LessonComponentBuilders = {
337
405
  };
338
406
  const LessonComponents = {
339
407
  [LessonComponentEnum.Selector]: SelectorComponent,
340
- // [LessonComponentEnum.Speaker]: SpeakerComponent,
408
+ [LessonComponentEnum.Speaker]: SpeakerComponent,
341
409
  [LessonComponentEnum.TextWriter]: TextWriterComponent,
342
410
  // [LessonComponentEnum.VerbSummary]: VerbSummaryComponent,
343
411
  // [LessonComponentEnum.WordSummary]: WordSummaryComponent,
@@ -362,6 +430,16 @@ function provideLessonsService(serviceImplementation) {
362
430
  },
363
431
  ];
364
432
  }
433
+ // export type LessonComponentsType = 'selector' | 'speaker' | 'text-writer' | 'verb-summary' | 'word-summary';
434
+ // export function getLessonComponentClass(type: LessonComponentsType) {
435
+ // // return LessonComponents[type];
436
+ // return null;
437
+ // }
438
+ // Removed duplicate LessonComponentConfiguration definition
439
+ // Removed duplicate StorageFile definition
440
+ // Removed duplicate AudioStorage definition
441
+ // Removed duplicate SpeakerCompConfiguration definition
442
+ // Removed duplicate LessonComponentInterface definition
365
443
  // export const LessonComponents = {
366
444
  // [LessonComponentEnum.Selector]: 1,
367
445
  // [LessonComponentEnum.Speaker]: 2,
@@ -394,10 +472,10 @@ class LangDescTranslationPipe {
394
472
  return LangCodeDescription[value];
395
473
  }
396
474
  }
397
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: LangDescTranslationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
398
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: LangDescTranslationPipe, isStandalone: true, name: "langDesc" }); }
475
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LangDescTranslationPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
476
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: LangDescTranslationPipe, isStandalone: true, name: "langDesc" }); }
399
477
  }
400
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: LangDescTranslationPipe, decorators: [{
478
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LangDescTranslationPipe, decorators: [{
401
479
  type: Pipe,
402
480
  args: [{
403
481
  name: 'langDesc',
@@ -425,10 +503,10 @@ class FlagLanguagePipe {
425
503
  return '';
426
504
  }
427
505
  }
428
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: FlagLanguagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
429
- static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.1.1", ngImport: i0, type: FlagLanguagePipe, isStandalone: true, name: "flagEmoji" }); }
506
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: FlagLanguagePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); }
507
+ static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.4", ngImport: i0, type: FlagLanguagePipe, isStandalone: true, name: "flagEmoji" }); }
430
508
  }
431
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: FlagLanguagePipe, decorators: [{
509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: FlagLanguagePipe, decorators: [{
432
510
  type: Pipe,
433
511
  args: [{
434
512
  name: 'flagEmoji',
@@ -466,7 +544,7 @@ var EventCard;
466
544
  })(EventCard || (EventCard = {}));
467
545
  class DcLessonCardComponent {
468
546
  constructor() {
469
- this.isAdmin = true;
547
+ this.showOptions = true;
470
548
  this.onAction = new EventEmitter();
471
549
  this.coverUrl = 'assets/background/default-background.webp';
472
550
  this.eventType = EventCard;
@@ -515,15 +593,15 @@ class DcLessonCardComponent {
515
593
  break;
516
594
  }
517
595
  }
518
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
519
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", isAdmin: "isAdmin" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\r\n <p-speeddial\r\n class=\"dial-button\"\r\n [model]=\"items\"\r\n [radius]=\"70\"\r\n type=\"quarter-circle\"\r\n direction=\"down-left\"\r\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\r\n\r\n <p-card>\r\n <div class=\"lesson-card\">\r\n <div class=\"photo\">\r\n <img [src]=\"coverUrl\" alt=\"\" />\r\n </div>\r\n\r\n <span class=\"date\">{{ lesson.createdDate | date : 'dd/MM/yyyy' }}</span>\r\n\r\n <div class=\"description\">\r\n <h1>{{ lesson.title }}</h1>\r\n <p>{{ lesson.description }}</p>\r\n <div class=\"card-footer\">\r\n <div class=\"status-tags\">\r\n <span class=\"level-tag\">Nivel {{ lesson.level }}</span>\r\n <span *ngIf=\"lesson.taken?.status == 'passed'\" class=\"status-tag success\">Tomada</span>\r\n <span *ngIf=\"lesson.taken?.status == 'failed'\" class=\"status-tag danger\">Fallida</span>\r\n </div>\r\n\r\n <div class=\"actions\">\r\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </p-card>\r\n</div>\r\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.description{position:relative;z-index:2;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0006,#000c);height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem}.description p{margin-bottom:1rem;flex-grow:1}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}.actions ::ng-deep .p-button{font-size:.9rem}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$2.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }] }); }
596
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
597
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DcLessonCardComponent, isStandalone: true, selector: "dc-lesson-card", inputs: { lesson: "lesson", showOptions: "showOptions" }, outputs: { onAction: "onAction" }, ngImport: i0, template: "<div class=\"card-container\">\n @if(showOptions){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n }\n <p-card>\n <div class=\"lesson-card\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdDate | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson.title }}</h1>\n <p>{{ lesson.description }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n <span class=\"level-tag\">Nivel {{ lesson.level }}</span>\n @if (lesson.taken?.status == 'passed') {\n <span class=\"status-tag success\">Tomada</span>\n }\n @if (lesson.taken?.status == 'failed') {\n <span class=\"status-tag danger\">Fallida</span>\n }\n </div>\n\n <div class=\"actions\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n </div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem}.description p{margin-bottom:1rem;flex-grow:1}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}.actions ::ng-deep .p-button{font-size:.9rem}\n"], dependencies: [{ kind: "pipe", type: DatePipe, name: "date" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: PopoverModule }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$2.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }, { kind: "ngmodule", type: CardModule }, { kind: "component", type: i3.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }] }); }
520
598
  }
521
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DcLessonCardComponent, decorators: [{
599
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, decorators: [{
522
600
  type: Component,
523
- args: [{ selector: 'dc-lesson-card', standalone: true, imports: [NgFor, NgIf, DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule], template: "<div class=\"card-container\">\r\n <p-speeddial\r\n class=\"dial-button\"\r\n [model]=\"items\"\r\n [radius]=\"70\"\r\n type=\"quarter-circle\"\r\n direction=\"down-left\"\r\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\r\n\r\n <p-card>\r\n <div class=\"lesson-card\">\r\n <div class=\"photo\">\r\n <img [src]=\"coverUrl\" alt=\"\" />\r\n </div>\r\n\r\n <span class=\"date\">{{ lesson.createdDate | date : 'dd/MM/yyyy' }}</span>\r\n\r\n <div class=\"description\">\r\n <h1>{{ lesson.title }}</h1>\r\n <p>{{ lesson.description }}</p>\r\n <div class=\"card-footer\">\r\n <div class=\"status-tags\">\r\n <span class=\"level-tag\">Nivel {{ lesson.level }}</span>\r\n <span *ngIf=\"lesson.taken?.status == 'passed'\" class=\"status-tag success\">Tomada</span>\r\n <span *ngIf=\"lesson.taken?.status == 'failed'\" class=\"status-tag danger\">Fallida</span>\r\n </div>\r\n\r\n <div class=\"actions\">\r\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n </p-card>\r\n</div>\r\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.description{position:relative;z-index:2;padding:1.5rem;color:#fff;background:linear-gradient(to bottom,#0006,#000c);height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem}.description p{margin-bottom:1rem;flex-grow:1}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}.actions ::ng-deep .p-button{font-size:.9rem}\n"] }]
601
+ args: [{ selector: 'dc-lesson-card', standalone: true, imports: [DatePipe, ButtonModule, PopoverModule, SpeedDialModule, CardModule], template: "<div class=\"card-container\">\n @if(showOptions){\n <p-speeddial\n class=\"dial-button\"\n [model]=\"items\"\n [radius]=\"70\"\n type=\"quarter-circle\"\n direction=\"down-left\"\n [buttonProps]=\"{ severity: 'primary', rounded: true, outlined: true, raised: true }\" />\n }\n <p-card>\n <div class=\"lesson-card\">\n <div class=\"photo\">\n <img [src]=\"coverUrl\" alt=\"\" />\n </div>\n\n <span class=\"date\">{{ lesson.createdDate | date : 'dd/MM/yyyy' }}</span>\n\n <div class=\"description\">\n <h1>{{ lesson.title }}</h1>\n <p>{{ lesson.description }}</p>\n <div class=\"card-footer\">\n <div class=\"status-tags\">\n <span class=\"level-tag\">Nivel {{ lesson.level }}</span>\n @if (lesson.taken?.status == 'passed') {\n <span class=\"status-tag success\">Tomada</span>\n }\n @if (lesson.taken?.status == 'failed') {\n <span class=\"status-tag danger\">Fallida</span>\n }\n </div>\n\n <div class=\"actions\">\n <p-button label=\"Tomar lecci\u00F3n\" (onClick)=\"eventCard(eventType.Select)\" severity=\"primary\"> </p-button>\n </div>\n </div>\n </div>\n </div>\n </p-card>\n </div>\n", styles: [".card-container{position:relative;margin-bottom:20px;margin-left:10px}.dial-button{position:absolute;top:10px;right:20px;z-index:10}.lesson-card{border-radius:.5rem;height:100%}.lesson-card .photo{position:absolute;inset:0;z-index:1}.lesson-card .photo img{width:100%;height:100%;object-fit:cover;object-position:center}.lesson-card .photo:after{content:\"\";position:absolute;top:0;left:0;width:100%;height:100%;background:linear-gradient(to bottom,rgb(0,0,0) 50%,var(--p-primary-color) 100%);opacity:.4;z-index:2;pointer-events:none}.description{position:relative;z-index:2;color:#fff;height:100%;display:flex;flex-direction:column}.description h1{margin-top:0;margin-bottom:.5rem;font-size:1.5rem}.description p{margin-bottom:1rem;flex-grow:1}.date{position:absolute;top:1rem;left:1rem;z-index:3;background-color:#000000b3;color:#fff;padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:auto}.status-tags{display:flex;gap:.5rem}.status-tags .level-tag,.status-tags .status-tag{padding:.3rem .6rem;border-radius:.25rem;font-size:.8rem}.status-tags .level-tag{background-color:#fff3}.status-tags .status-tag.success{background-color:#28a745b3}.status-tags .status-tag.danger{background-color:#dc3545b3}.actions ::ng-deep .p-button{font-size:.9rem}\n"] }]
524
602
  }], propDecorators: { lesson: [{
525
603
  type: Input
526
- }], isAdmin: [{
604
+ }], showOptions: [{
527
605
  type: Input
528
606
  }], onAction: [{
529
607
  type: Output
@@ -549,10 +627,10 @@ class DCLessonListComponent extends PaginationBase {
549
627
  this.route = route;
550
628
  this.lessonsService = lessonsService;
551
629
  this.toastrService = toastrService;
552
- this.isAdmin = false;
630
+ this.showOptions = true;
553
631
  this.customFilters = [];
554
632
  this.viewType = 'cards';
555
- this.actions = TableViewActions;
633
+ // readonly actions = input<MenuItem[]>(TableViewActions);
556
634
  this.columns = tableViewColumns;
557
635
  this.cardComponent = null;
558
636
  this.cardEventSubs = [];
@@ -640,19 +718,19 @@ class DCLessonListComponent extends PaginationBase {
640
718
  this.onAction.emit(actionEvent);
641
719
  }
642
720
  }
643
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$1.Router }, { token: i1$1.ActivatedRoute }, { token: LESSONS_TOKEN }, { token: TOAST_ALERTS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
644
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCLessonListComponent, isStandalone: true, selector: "dc-lesson-list", inputs: { isAdmin: "isAdmin", customCardComponent: "customCardComponent", customFilters: "customFilters", viewType: "viewType", actions: "actions" }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" [actions]=\"actions\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n isAdmin: isAdmin\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1rem;flex:1;overflow-y:auto;min-height:0}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$3.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: RouterModule }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["isAdmin", "customFilters", "items"], outputs: ["onFilterAction", "onChangeSort"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i3$2.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["onlyView", "columns", "tableData", "actions"], outputs: ["onAction"] }] }); }
721
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonListComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i1$2.Router }, { token: i1$2.ActivatedRoute }, { token: LESSONS_TOKEN }, { token: TOAST_ALERTS_TOKEN }], target: i0.ɵɵFactoryTarget.Component }); }
722
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonListComponent, isStandalone: true, selector: "dc-lesson-list", inputs: { showOptions: "showOptions", customCardComponent: "customCardComponent", customFilters: "customFilters", viewType: "viewType" }, viewQueries: [{ propertyName: "outlets", predicate: ["outlet"], descendants: true }], usesInheritance: true, ngImport: i0, template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\" (onNew)=\"newLesson()\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: showOptions\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1rem;flex:1;overflow-y:auto;min-height:0}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"], dependencies: [{ kind: "ngmodule", type: RouterModule }, { kind: "component", type: DCFilterBarComponent, selector: "dc-filter-bar", inputs: ["isAdmin", "items", "customFilters"], outputs: ["onFilterAction", "onChangeSort", "onNew"] }, { kind: "ngmodule", type: PaginatorModule }, { kind: "component", type: i2$3.Paginator, selector: "p-paginator", inputs: ["pageLinkSize", "style", "styleClass", "alwaysShow", "dropdownAppendTo", "templateLeft", "templateRight", "appendTo", "dropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showFirstLastIcon", "totalRecords", "rows", "rowsPerPageOptions", "showJumpToPageDropdown", "showJumpToPageInput", "jumpToPageItemTemplate", "showPageLinks", "locale", "dropdownItemTemplate", "first"], outputs: ["onPageChange"] }, { kind: "directive", type: NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "component", type: QuickTableComponent, selector: "app-quick-table", inputs: ["columns", "tableData", "actions"], outputs: ["onAction"] }] }); }
645
723
  }
646
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonListComponent, decorators: [{
724
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonListComponent, decorators: [{
647
725
  type: Component,
648
- args: [{ selector: 'dc-lesson-list', standalone: true, imports: [CommonModule, RouterModule, DCFilterBarComponent, PaginatorModule, NgComponentOutlet, QuickTableComponent], template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" [actions]=\"actions\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n isAdmin: isAdmin\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1rem;flex:1;overflow-y:auto;min-height:0}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"] }]
649
- }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$1.Router }, { type: i1$1.ActivatedRoute }, { type: LessonsAbstractService, decorators: [{
726
+ args: [{ selector: 'dc-lesson-list', standalone: true, imports: [RouterModule, DCFilterBarComponent, PaginatorModule, NgComponentOutlet, QuickTableComponent], template: "<dc-filter-bar [customFilters]=\"customFilters\" (onFilterAction)=\"applyFilterBarEvent($event)\" (onNew)=\"newLesson()\"></dc-filter-bar>\n\n@if(viewType === 'table') {\n<app-quick-table [columns]=\"columns\" [tableData]=\"lessons\" (onAction)=\"doOrEmitAction($event)\"></app-quick-table>\n} @else {\n<div class=\"lesson-list-container\">\n @if (!isLoadingLessons && lessons?.length > 0) { @for (lesson of lessons; track lesson._id) {\n <ng-container\n #outlet=\"ngComponentOutlet\"\n [ngComponentOutlet]=\"cardComponent\"\n [ngComponentOutletInputs]=\"{\n lesson: lesson,\n showOptions: showOptions\n }\">\n </ng-container>\n } } @else {\n <p>No se encontraron lecciones disponibles</p>\n }\n</div>\n}\n\n<p-paginator\n currentPageReportTemplate=\"{{ totalRecords }} lecciones\"\n [showCurrentPageReport]=\"true\"\n (onPageChange)=\"onPageChange($event)\"\n [first]=\"paginatorFirst\"\n [rows]=\"paginatorRows\"\n [totalRecords]=\"totalRecords\"\n [rowsPerPageOptions]=\"[10, 20, 30]\">\n</p-paginator>\n", styles: [":host{display:flex;flex-direction:column;height:100%}.lesson-list-container{padding:1rem;flex:1;overflow-y:auto;min-height:0}p-paginator{margin-top:auto;padding:.5rem 1rem}\n"] }]
727
+ }], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$2.Router }, { type: i1$2.ActivatedRoute }, { type: LessonsAbstractService, decorators: [{
650
728
  type: Inject,
651
729
  args: [LESSONS_TOKEN]
652
- }] }, { type: i5$1.ToastAlertsAbstractService, decorators: [{
730
+ }] }, { type: i4$1.ToastAlertsAbstractService, decorators: [{
653
731
  type: Inject,
654
732
  args: [TOAST_ALERTS_TOKEN]
655
- }] }], propDecorators: { isAdmin: [{
733
+ }] }], propDecorators: { showOptions: [{
656
734
  type: Input
657
735
  }], customCardComponent: [{
658
736
  type: Input
@@ -660,24 +738,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
660
738
  type: Input
661
739
  }], viewType: [{
662
740
  type: Input
663
- }], actions: [{
664
- type: Input
665
741
  }], outlets: [{
666
742
  type: ViewChildren,
667
743
  args: ['outlet']
668
744
  }] } });
669
745
 
670
746
  class DCLessonFormComponent {
671
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
672
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: DCLessonFormComponent, isStandalone: true, selector: "dc-lesson-form", ngImport: i0, template: "<div class=\"lesson-form-container\">\n <h2>Lesson Form</h2>\n <p>Esto es dentro del componente de la leccion</p>\n <!-- Form implementation will go here -->\n</div>\n", styles: [".lesson-form-container{padding:1rem}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }] }); }
747
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
748
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: DCLessonFormComponent, isStandalone: true, selector: "dc-lesson-form", ngImport: i0, template: "<div class=\"lesson-form-container\">\n <h2>Lesson Form</h2>\n <p>Esto es dentro del componente de la leccion</p>\n <!-- Form implementation will go here -->\n</div>\n", styles: [".lesson-form-container{padding:1rem}\n"], dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }] }); }
673
749
  }
674
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonFormComponent, decorators: [{
750
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonFormComponent, decorators: [{
675
751
  type: Component,
676
- args: [{ selector: 'dc-lesson-form', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"lesson-form-container\">\n <h2>Lesson Form</h2>\n <p>Esto es dentro del componente de la leccion</p>\n <!-- Form implementation will go here -->\n</div>\n", styles: [".lesson-form-container{padding:1rem}\n"] }]
752
+ args: [{ selector: 'dc-lesson-form', standalone: true, imports: [ReactiveFormsModule], template: "<div class=\"lesson-form-container\">\n <h2>Lesson Form</h2>\n <p>Esto es dentro del componente de la leccion</p>\n <!-- Form implementation will go here -->\n</div>\n", styles: [".lesson-form-container{padding:1rem}\n"] }]
677
753
  }] });
678
754
 
679
- // TODO: i need strategy to create prompt dinamically dpending on user language and level.
680
- const DefaultLessonAgentCard = {
755
+ // Re-define or import constants if they are not exported from the component file
756
+ const DEFAULT_LESSON_AGENT_CARD = {
681
757
  conversationSettings: {
682
758
  conversationType: ConversationType.General,
683
759
  textEngine: TextEngines.SimpleText,
@@ -693,105 +769,270 @@ const DefaultLessonAgentCard = {
693
769
  },
694
770
  model: { provider: 'google' },
695
771
  };
772
+ function getDefaultLessonEvaluatorAgentCard(lessonText) {
773
+ return {
774
+ expectedResponseType: `interface EvalResult {
775
+ score: number; // Score of the user's response 0 to 3
776
+ feedback: string; // Feedback of the user's understanding of the conversation
777
+ }`,
778
+ messages: [],
779
+ model: { id: 'gpt-4o-mini', provider: 'openai' },
780
+ sources: [lessonText],
781
+ task: `User is reading a taking a lesson, now their are having a conversation,
782
+ you have to evaluate the current conversation, and give a feedback of the user understanding of the lesson,
783
+ this is the lesson: ${lessonText}`,
784
+ };
785
+ }
786
+ class LessonAIService {
787
+ // TODO: Inject the application-level UserService
788
+ // private readonly userService = inject(UserService);
789
+ constructor() {
790
+ this.lessonService = inject(LESSONS_TOKEN);
791
+ }
792
+ /**
793
+ * Generates the necessary agent cards for a lesson chat session.
794
+ * @param lesson The lesson data.
795
+ * @returns An object containing the master agent card and the evaluator agent card.
796
+ */
797
+ async generateAgentCards(lesson) {
798
+ // TODO: Implement the logic moved from DCLessonRendererComponent.startAI here
799
+ // 1. Get user data using the injected UserService
800
+ // 2. Extract lesson text using lessonService
801
+ // 3. Build prompts (scenario, userInformationPrompt)
802
+ // 4. Configure and return the agent cards
803
+ alert('AI User data fetching needs refactoring into this service.');
804
+ // Placeholder for user data - replace with actual service call
805
+ const user = {
806
+ personalData: { firstname: 'Test', lastname: 'User' },
807
+ settings: { targetLanguage: 'en', baseLanguage: 'es' },
808
+ languageProgress: { en: { level: '1' } },
809
+ }; // Replace 'any' with your actual User type/interface
810
+ if (!user) {
811
+ console.error('User data not available to generate agent cards.');
812
+ // Handle error appropriately - maybe return null or throw?
813
+ return null;
814
+ }
815
+ const lessonText = this.lessonService.extractTextFromHtml(lesson.textCoded);
816
+ const scenario = `The user is reading lessons through this app interface. They will now talk with you, and you need to evaluate their understanding of the lesson.
817
+ Ask friendly questions throughout the conversation and help them learn English. Here is the lesson text the user just read:
818
+ ${lessonText}
819
+ In your next reply, start by greeting the user, asking something about the lesson, and then continue the conversation.`;
820
+ const targetLevel = parseInt(user.languageProgress[user.settings.targetLanguage]?.level ?? '1');
821
+ const langTargetDesc = LangCodeDescription[user.settings.targetLanguage] ?? user.settings.targetLanguage;
822
+ const langBaseDesc = LangCodeDescription[user.settings.baseLanguage] ?? user.settings.baseLanguage;
823
+ let userInformationPrompt = `
824
+ User information: user name is ${user.personalData.firstname} ${user.personalData.lastname}, their native language is ${langBaseDesc},
825
+ and right now is learning ${langTargetDesc}, their current level is ${targetLevel} out of 5.`;
826
+ if (targetLevel <= 2) {
827
+ userInformationPrompt += `\nUser is a beginner in ${langTargetDesc}, always reply mainly in ${langBaseDesc}, but during the conversation use simple words and phrases in ${langTargetDesc} to help them learn.`;
828
+ }
829
+ // Create a deep copy of the default card to avoid modifying the constant
830
+ const masterAgent = JSON.parse(JSON.stringify(DEFAULT_LESSON_AGENT_CARD));
831
+ masterAgent.characterCard.data.scenario = scenario;
832
+ masterAgent.characterCard.data.post_history_instructions += `\n${userInformationPrompt}`;
833
+ const evaluatorAgent = getDefaultLessonEvaluatorAgentCard(lessonText);
834
+ return { masterAgent, evaluatorAgent };
835
+ }
836
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonAIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
837
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonAIService, providedIn: 'root' }); }
838
+ }
839
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonAIService, decorators: [{
840
+ type: Injectable,
841
+ args: [{
842
+ providedIn: 'root', // Or provide appropriately if it's library-specific
843
+ }]
844
+ }], ctorParameters: () => [] });
845
+
696
846
  class DCLessonRendererComponent {
697
- constructor(renderer, viewContainerRef, activatedRoute, toastrService, lessonService, agentCardsService, conversationBuilder) {
698
- this.renderer = renderer;
699
- this.viewContainerRef = viewContainerRef;
700
- this.activatedRoute = activatedRoute;
701
- this.toastrService = toastrService;
702
- this.lessonService = lessonService;
703
- this.agentCardsService = agentCardsService;
704
- this.conversationBuilder = conversationBuilder;
705
- this.lessonId = this.activatedRoute.snapshot.paramMap.get('id');
706
- this.dataText = `<h1>Nueva lección </h1> <p> Texto aquí</p>`;
847
+ constructor() {
848
+ // --- Signal Inputs ---
849
+ this.lessonInput = input(); // Input signal for lesson object
850
+ this.lessonIdInput = input(); // Input signal for lesson ID
851
+ this.test = input(false);
852
+ // --- Injected Services (using inject function) ---
853
+ this.renderer = inject(Renderer2);
854
+ this.viewContainerRef = inject(ViewContainerRef);
855
+ this.toastrService = inject(TOAST_ALERTS_TOKEN);
856
+ this.lessonService = inject(LESSONS_TOKEN);
857
+ this.lessonAIService = inject(LessonAIService); // Inject the new service
858
+ // --- State Signals ---
859
+ this.lesson = signal(undefined); // Internal lesson state signal
860
+ this.chatVisible = signal(false); // Signal for chat visibility
861
+ this.agentMasterLesson = signal(undefined); // Signal for agent card
862
+ this.evaluatorAgentCard = signal(undefined); // Signal for evaluator card
863
+ // --- Computed Signals ---
864
+ this.imageCover = computed(() => this.lesson()?.media?.images?.find((img) => img.type === 'cover')?.url); // Computed signal for imageCover
865
+ // --- Properties ---
707
866
  this.components = {};
708
- this.mainForm = new UntypedFormGroup({});
709
- this.chatVisible = false;
710
- }
711
- ngOnInit() {
712
- this.initLesson();
713
- }
714
- async initLesson() {
715
- await this.getLessonIfId();
716
- this.renderLesson();
867
+ this.mainForm = new FormGroup({});
868
+ // Effect to fetch lesson data if ID is provided and lesson object isn't
869
+ effect(async () => {
870
+ const lessonInput = this.lessonInput();
871
+ const lessonId = this.lessonIdInput();
872
+ if (lessonInput) {
873
+ this.lesson.set(lessonInput); // Use input lesson directly
874
+ }
875
+ else if (lessonId && !this.lesson()) {
876
+ // Fetch only if ID exists and internal lesson is not set
877
+ console.log(`Fetching lesson with ID: ${lessonId}`);
878
+ try {
879
+ // Consider adding a loading state signal here
880
+ const fetchedLesson = await this.lessonService.getLesson(lessonId);
881
+ this.lesson.set(fetchedLesson);
882
+ console.log('Fetched lesson:', fetchedLesson);
883
+ }
884
+ catch (error) {
885
+ console.error(`Failed to fetch lesson with ID: ${lessonId}`, error);
886
+ this.toastrService.error({ subtitle: 'Failed to load lesson data.', title: 'Error' });
887
+ this.lesson.set(undefined); // Reset lesson on error
888
+ }
889
+ finally {
890
+ // Reset loading state signal here
891
+ }
892
+ }
893
+ else if (!lessonInput && !lessonId) {
894
+ // Handle case where neither input is provided, maybe clear the lesson?
895
+ this.lesson.set(undefined);
896
+ }
897
+ }, { allowSignalWrites: true }); // Allow signal writes inside effect
898
+ // Effect to render the lesson whenever the lesson signal changes
899
+ effect(() => {
900
+ const currentLesson = this.lesson();
901
+ if (currentLesson) {
902
+ this._renderLesson(currentLesson);
903
+ }
904
+ else {
905
+ // Clear previous rendering if lesson becomes undefined
906
+ this._clearLessonRendering();
907
+ }
908
+ });
717
909
  }
718
- async getLessonIfId() {
719
- if (this.lesson) {
720
- return;
721
- }
722
- if (!this.lessonId) {
723
- return;
910
+ // --- Rendering Logic ---
911
+ _clearLessonRendering() {
912
+ // Destroy previously created dynamic components
913
+ Object.values(this.components).forEach((compRef) => compRef.destroy());
914
+ this.components = {};
915
+ // Clear the form
916
+ this.mainForm = new FormGroup({});
917
+ // Clear the HTML content
918
+ if (this.dynamicLesson?.nativeElement) {
919
+ this.dynamicLesson.nativeElement.innerHTML = '';
724
920
  }
725
- this.dataText = `<h1>Cargando </h1> <p>...</p>`;
726
- this.lesson = await this.lessonService.getLesson(this.lessonId);
727
- console.log('lesson', this.lesson);
728
- }
729
- renderLesson() {
730
- this.imageCover = this.lesson.media?.images?.find((img) => img.type === 'cover')?.url;
731
- console.log('imageCover', this.imageCover);
732
- // Pasos para renderizar,
733
- // 0) Usar la expresión regular para sustitur los componentes por un span con un id
734
- // 1) En el mismo proceso de remplazar se contruyen dinamicamente los componentes
735
- // 2) Agregar componentes al DOM en su respectivo spanid
736
- // TODO Optimización, es posible agregar el componente al DOM en el mismo paso en que se crea el span?
921
+ }
922
+ _renderLesson(lessonData) {
923
+ this._clearLessonRendering(); // Clear previous state first
924
+ console.log('Rendering lesson:', lessonData.id);
925
+ // console.log('Image cover URL:', this.imageCover()); // Access computed signal
926
+ // 1) Parse textCoded, create components, and build HTML structure
927
+ const { htmlContent, components } = this._parseAndCreateComponents(lessonData);
928
+ this.components = components;
929
+ // 2) Aggregate form controls from created components
930
+ this._aggregateFormControls(this.components);
931
+ // 3) Set the innerHTML of the target element
932
+ this.dynamicLesson.nativeElement.innerHTML = htmlContent;
933
+ // 4) Inject the component views into the DOM
934
+ this._injectComponentsIntoDom(this.components);
935
+ }
936
+ _parseAndCreateComponents(lessonData) {
737
937
  const r1 = new RegExp('~(.+?)~', 'g');
738
938
  let count = 0;
739
- this.components = {}; //Si se destruye la lección, se destruyen todos los componentes dinamicis? tODO necesito reiniciar?
740
- const lessonHtml = this.lesson.textCoded.replace(r1, (_matching, jsonCoded) => {
939
+ const createdComponents = {};
940
+ const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
741
941
  const componentName = `dynamicComp${count}`;
742
942
  count++;
743
- const componentRef = this.createComponentReferenceWithJson(jsonCoded);
943
+ const componentRef = this._createComponentReferenceWithJson(jsonCoded, lessonData);
744
944
  if (!componentRef) {
745
- return null;
945
+ console.error(`Failed to create component for: ${jsonCoded}`);
946
+ return '<!-- component creation failed -->'; // Placeholder in HTML
746
947
  }
747
- this.components[componentName] = componentRef;
748
- if (componentRef.instance.control) {
749
- // Algunos componentes tienen formularios, estos se copian en mainForm como un unico control
750
- this.mainForm.addControl(componentName, componentRef.instance.control);
751
- this.mainForm.get(componentName).setValidators([Validators.required]);
948
+ createdComponents[componentName] = componentRef;
949
+ return `<span id="${componentName}"></span>`; // Return span placeholder
950
+ });
951
+ return { htmlContent, components: createdComponents };
952
+ }
953
+ _aggregateFormControls(components) {
954
+ const newFormControls = {};
955
+ Object.entries(components).forEach(([name, componentRef]) => {
956
+ // Check if the instance has a control property that is a FormControl
957
+ if (componentRef.instance?.control instanceof FormControl) {
958
+ newFormControls[name] = componentRef.instance.control;
959
+ // Add required validator (consider making this configurable via component config?)
960
+ newFormControls[name].addValidators(Validators.required);
961
+ }
962
+ });
963
+ this.mainForm = new FormGroup(newFormControls); // Create the typed FormGroup
964
+ }
965
+ _injectComponentsIntoDom(components) {
966
+ Object.entries(components).forEach(([name, componentRef]) => {
967
+ const elementRef = document.getElementById(name); // Find the placeholder span
968
+ if (elementRef) {
969
+ this._addComponentToNode(componentRef, elementRef);
970
+ }
971
+ else {
972
+ console.warn(`Placeholder element with ID '${name}' not found in the DOM.`);
752
973
  }
753
- return `<span id="${componentName}"></span>`;
754
974
  });
755
- this.dynamicLesson.nativeElement.innerHTML = lessonHtml;
756
- for (let compName of Object.keys(this.components)) {
757
- // Interpolar componentes dentro del lugar correspondiente
758
- const elementRef = document.getElementById(compName);
759
- this.addComponentToNode(this.components[compName], elementRef);
760
- }
761
975
  }
762
- addComponentToNode(componentRef, nodeDOM) {
976
+ _addComponentToNode(componentRef, nodeDOM) {
763
977
  this.renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
764
978
  }
765
- createComponentReferenceWithJson(json) {
766
- const lessonCodedConfig = JSON.parse(json);
767
- let componentConfig;
768
- if (this.lesson.components && lessonCodedConfig.id) {
769
- // TODO: retomar, Creo que esta linea es para reutilziar los componentes por id, pero si no tiene,
770
- componentConfig = this.lesson.components.find((comp) => comp.id === lessonCodedConfig.id);
771
- }
772
- const LessonClass = getLessonComponentClass(lessonCodedConfig.component);
773
- if (!LessonClass) {
774
- console.error('No existe este componente, revisa el codigo insertado' + json);
775
- // throw new Error('No existe este com revisa el codigo insertado');
776
- return null;
979
+ _createComponentReferenceWithJson(json, currentLesson) {
980
+ try {
981
+ let lessonCodedConfig = JSON.parse(json);
982
+ debugger;
983
+ // Attempt to find pre-configured component data in the lesson object by ID
984
+ if (lessonCodedConfig.id && currentLesson?.dynamicComponents[lessonCodedConfig.id]) {
985
+ const foundConfig = currentLesson.dynamicComponents[lessonCodedConfig.id];
986
+ if (foundConfig) {
987
+ // Merge configurations: Start with coded, override/add with found lesson config
988
+ lessonCodedConfig = { ...lessonCodedConfig, ...foundConfig };
989
+ }
990
+ }
991
+ const LessonClass = getLessonComponentClass(lessonCodedConfig.component);
992
+ if (!LessonClass) {
993
+ console.error(`Component class not found for type: ${lessonCodedConfig.component}. JSON: ${json}`);
994
+ return null; // Return null if class doesn't exist
995
+ }
996
+ // Create component instance
997
+ const componentRef = this.viewContainerRef.createComponent(LessonClass);
998
+ if (lessonCodedConfig.inputs) {
999
+ for (const key in lessonCodedConfig.inputs) {
1000
+ if (lessonCodedConfig.inputs.hasOwnProperty(key)) {
1001
+ componentRef.instance[key] = lessonCodedConfig.inputs[key];
1002
+ }
1003
+ }
1004
+ }
1005
+ // i think i can improve this to pass only settings not all config, Assign the configuration to the component instance
1006
+ // settings data i defined in form interface, config will be all the component data including id and component type
1007
+ if (lessonCodedConfig.settings) {
1008
+ componentRef.instance.config = lessonCodedConfig;
1009
+ }
1010
+ return componentRef;
1011
+ }
1012
+ catch (error) {
1013
+ console.error(`Error processing component JSON: ${json}`, error);
1014
+ return null; // Return null on JSON parsing or other errors
777
1015
  }
778
- const componentRef = this.viewContainerRef.createComponent(LessonClass);
779
- componentRef.instance.config = componentConfig || lessonCodedConfig;
780
- return componentRef;
781
1016
  }
1017
+ // --- Evaluation Logic ---
782
1018
  async evaluateForms() {
1019
+ this.mainForm.markAllAsTouched(); // Mark all controls for validation feedback
783
1020
  if (!this.mainForm.valid) {
784
- for (let controlName of Object.keys(this.mainForm.controls)) {
785
- this.components[controlName].instance.validate();
786
- }
787
- this.toastrService.warn({ subtitle: 'Por favor completa todos los formularios', title: 'Espera' });
1021
+ // Trigger validation feedback on individual components visually if needed
1022
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
1023
+ if (this.components[controlName]?.instance?.validate) {
1024
+ this.components[controlName].instance.validate(); // Assuming validate method handles visual feedback
1025
+ }
1026
+ });
1027
+ this.toastrService.warn({ subtitle: 'Por favor completa todos los ejercicios', title: 'Incompleto' });
788
1028
  return;
789
1029
  }
790
1030
  const rates = { correct: 0, incorrect: 0, score: 0 };
791
- for (let controlName of Object.keys(this.mainForm.controls)) {
792
- const instance = this.components[controlName].instance;
793
- if (instance instanceof ComponentWithForm) {
794
- // TODO: quizá no necesito validar instanceof ComponentWithForm ya que se garantiza que todo en mainform tiene un control.
1031
+ // Evaluate each component associated with a form control
1032
+ Object.keys(this.mainForm.controls).forEach((controlName) => {
1033
+ const instance = this.components[controlName]?.instance;
1034
+ // Check if the instance has an evaluate method (duck typing is okay here)
1035
+ if (instance && typeof instance.evaluate === 'function') {
795
1036
  try {
796
1037
  const result = instance.evaluate();
797
1038
  if (result) {
@@ -802,350 +1043,830 @@ class DCLessonRendererComponent {
802
1043
  }
803
1044
  }
804
1045
  catch (err) {
805
- console.error('Validation method for instance is failing', instance);
1046
+ console.error('Error during evaluation for component:', controlName, instance, err);
1047
+ rates.incorrect++; // Count errors as incorrect
806
1048
  }
807
1049
  }
1050
+ });
1051
+ if (this.test()) {
1052
+ // Access signal value
1053
+ console.log('Test mode: Evaluation skipped saving.');
1054
+ this.toastrService.info({ subtitle: `Test Results: ${rates.correct} correct, ${rates.incorrect} incorrect`, title: 'Test Mode' });
1055
+ return;
808
1056
  }
809
- if (this.test) {
1057
+ const totalQuestions = rates.correct + rates.incorrect;
1058
+ rates.score = totalQuestions > 0 ? rates.correct / totalQuestions : 0; // Avoid division by zero
1059
+ const status = rates.score >= 0.7 ? 'passed' : 'failed'; // Use >= for threshold
1060
+ const currentLesson = this.lesson();
1061
+ if (!currentLesson) {
1062
+ console.error('Cannot save result, lesson data is missing.');
1063
+ this.toastrService.error({ subtitle: 'Cannot save result, lesson data missing.', title: 'Error' });
810
1064
  return;
811
1065
  }
812
- rates.score = rates.correct / (rates.correct + rates.incorrect);
813
- const status = rates.score > 0.7 ? 'passed' : 'failed';
814
- const takenLesson = { lessonId: this.lesson.id, status: status, score: rates.score };
815
- // TODO: implementar el servicio para guardar la lección tomada
816
- // await this.lessonService.saveTakenLesson(takenLesson);
1066
+ const takenLesson = { lessonId: currentLesson.id, status: status, score: rates.score };
1067
+ console.log('Lesson evaluation result:', takenLesson);
1068
+ // TODO: Re-implement saving the taken lesson status via lessonService
1069
+ // try {
1070
+ // await this.lessonService.saveTakenLesson(takenLesson);
1071
+ // if (status === 'passed') {
1072
+ // this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Se guardó tu progreso.`, title: '¡Muy bien!' });
1073
+ // } else {
1074
+ // this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
1075
+ // }
1076
+ // } catch (error) {
1077
+ // console.error('Failed to save taken lesson', error);
1078
+ // this.toastrService.error({ subtitle: 'No se pudo guardar tu progreso.', title: 'Error' });
1079
+ // }
817
1080
  if (status === 'passed') {
818
- this.toastrService.success({ subtitle: 'Aprobaste, se guardó tu calificación', title: 'Muy bien' });
1081
+ this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
819
1082
  }
820
1083
  else {
821
- this.toastrService.warn({ subtitle: 'Revisa tus respuestas', title: 'Algunas preguntas no son correctas' });
1084
+ this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
822
1085
  }
823
1086
  }
1087
+ // --- AI Chat Logic ---
824
1088
  async startAI() {
825
- // TODO: para refactorizar y utilizar las lecciones en otro componente, voy a necesitar pasar esto al servicio abstracto asi cada aplicación lo implementa.
826
- const lessonText = this.lessonService.extractTextFromHtml(this.lesson.textCoded);
827
- const scenario = `The user is reading lessons through this app interface. They will now talk with you, and you need to evaluate their understanding of the lesson.
828
- Ask friendly questions throughout the conversation and help them learn English. Here is the lesson text the user just read:
829
- ${lessonText}
830
- In your next reply, start by greeting the user, asking something about the lesson, and then continue the conversation.`;
831
- // const user = this.userService.getUserSnapshot();
832
- alert('i need to fix this, need to find a way to pass user throug the service not the library, i think i already started something.');
833
- const user = {};
834
- const targetLevel = parseInt(user.languageProgress[user.settings.targetLanguage].level);
835
- const langTargetDesc = LangCodeDescription[user.settings.targetLanguage];
836
- const langBaseDesc = LangCodeDescription[user.settings.baseLanguage];
837
- let userInformationPrompt = `
838
- User information: user name is ${user.personalData.firstname} ${user.personalData.lastname}, their native language is ${langBaseDesc},
839
- and right now is learning ${langTargetDesc}, their current level is ${user.languageProgress[user.settings.targetLanguage].level} out of 5.`;
840
- if (targetLevel <= 2) {
841
- userInformationPrompt += `\nUser is a beginner in english, always reply in ${langBaseDesc}, but during the conversation use simple words in ${langTargetDesc}`;
1089
+ const currentLesson = this.lesson();
1090
+ if (!currentLesson) {
1091
+ console.error('Cannot start AI without a lesson.');
1092
+ this.toastrService.error({ subtitle: 'Lesson data not available.', title: 'Cannot Start Chat' });
1093
+ return;
1094
+ }
1095
+ console.log('Requesting agent cards from LessonAIService...');
1096
+ try {
1097
+ // Call the service to get the agent cards
1098
+ const agentCards = await this.lessonAIService.generateAgentCards(currentLesson);
1099
+ if (agentCards) {
1100
+ this.agentMasterLesson.set(agentCards.masterAgent);
1101
+ this.evaluatorAgentCard.set(agentCards.evaluatorAgent);
1102
+ this.chatVisible.set(true);
1103
+ console.log('Agent cards received and set.');
1104
+ }
1105
+ else {
1106
+ console.error('Failed to generate agent cards (service returned null).');
1107
+ this.toastrService.error({ subtitle: 'Could not prepare the AI chat session.', title: 'Error' });
1108
+ }
1109
+ }
1110
+ catch (error) {
1111
+ console.error('Error generating agent cards:', error);
1112
+ this.toastrService.error({ subtitle: 'An error occurred while preparing the AI chat.', title: 'Error' });
842
1113
  }
843
- console.log('lessonText', lessonText);
844
- const lessonAgentCard = DefaultLessonAgentCard;
845
- lessonAgentCard.characterCard.data.scenario = scenario;
846
- lessonAgentCard.characterCard.data.post_history_instructions += `\n${userInformationPrompt}`;
847
- this.agentMasterLesson = lessonAgentCard;
848
- this.evaluatorAgentCard = this.getDefualtLessonEvaluatorAgentCard(lessonText);
849
- this.chatVisible = true;
850
- }
851
- getDefualtLessonEvaluatorAgentCard(lessonText) {
852
- return {
853
- expectedResponseType: `interface EvalResult {
854
- score: number; // Score of the user's response 0 to 3
855
- feedback: string; // Feedback of the user's understanding of the conversation
856
- }`,
857
- messages: [],
858
- model: { id: 'gpt-4o-mini', provider: 'openai' },
859
- sources: [lessonText],
860
- task: `User is reading a taking a lesson, now their are having a conversation,
861
- you have to evaluate the current conversation, and give a feedback of the user understanding of the lesson,
862
- this is the lesson: ${lessonText}`,
863
- };
864
1114
  }
865
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonRendererComponent, deps: [{ token: i0.Renderer2 }, { token: i0.ViewContainerRef }, { token: i1$1.ActivatedRoute }, { token: TOAST_ALERTS_TOKEN }, { token: LESSONS_TOKEN }, { token: CONVERSATION_AI_TOKEN }, { token: i2$4.DCConversationPromptBuilderService }], target: i0.ɵɵFactoryTarget.Component }); }
866
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCLessonRendererComponent, isStandalone: true, selector: "dc-lesson-renderer", inputs: { lesson: "lesson", test: "test" }, viewQueries: [{ propertyName: "dynamicLesson", first: true, predicate: ["dynamicLesson"], descendants: true, static: true }], ngImport: i0, template: "<div>\r\n <!-- <div *ngIf=\"lesson\">\r\n {lesson.title }} \r\n <br>\r\n <img [src]=\"imageCover\" alt=\"\">\r\n </div> -->\r\n\r\n <div #dynamicLesson class=\"targetclass\">\r\n <ng-template #target></ng-template>\r\n </div>\r\n</div>\r\n\r\n<br />\r\n<div style=\"display: flex; gap: 10px\">\r\n <div *ngIf=\"(mainForm.controls | keyvalue)?.length\">\r\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\r\n </div>\r\n\r\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\r\n</div>\r\n<br /><br />\r\n\r\n@if(chatVisible) {\r\n<p-drawer header=\"Conversation\" [(visible)]=\"chatVisible\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\r\n <dc-chat [agentCard]=\"agentMasterLesson\" [evaluatorAgentCard]=\"evaluatorAgentCard\"></dc-chat>\r\n</p-drawer>\r\n}\r\n", styles: [".evaluate{float:right}\n"], dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "agentCard", "evaluatorAgentCard", "parseDict"], outputs: ["sendMessage"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i4$1.Drawer, selector: "p-drawer", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }] }); }
1115
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1116
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonRendererComponent, isStandalone: true, selector: "dc-lesson-renderer", inputs: { lessonInput: { classPropertyName: "lessonInput", publicName: "lessonInput", isSignal: true, isRequired: false, transformFunction: null }, lessonIdInput: { classPropertyName: "lessonIdInput", publicName: "lessonIdInput", isSignal: true, isRequired: false, transformFunction: null }, test: { classPropertyName: "test", publicName: "test", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "dynamicLesson", first: true, predicate: ["dynamicLesson"], descendants: true, static: true }], ngImport: i0, template: "<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\n\n<br />\n<div style=\"display: flex; gap: 10px\">\n @if ((mainForm.controls | keyvalue)?.length) {\n <div>\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\n </div>\n }\n\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\n</div>\n<br /><br />\n\n@if(chatVisible()) {\n<p-drawer header=\"Conversation\" [visible]=\"chatVisible()\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\n <dc-chat [agentCard]=\"agentMasterLesson()\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"], dependencies: [{ kind: "pipe", type: KeyValuePipe, name: "keyvalue" }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: DCChatComponent, selector: "dc-chat", inputs: ["chatUserSettings", "conversationSettings", "agentCard", "evaluatorAgentCard", "parseDict"], outputs: ["sendMessage", "goalCompleted"] }, { kind: "ngmodule", type: DrawerModule }, { kind: "component", type: i2$4.Drawer, selector: "p-drawer", inputs: ["appendTo", "blockScroll", "style", "styleClass", "ariaCloseLabel", "autoZIndex", "baseZIndex", "modal", "closeButtonProps", "dismissible", "showCloseIcon", "closeOnEscape", "transitionOptions", "visible", "position", "fullScreen", "header", "maskStyle", "closable"], outputs: ["onShow", "onHide", "visibleChange"] }] }); }
867
1117
  }
868
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
1118
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
869
1119
  type: Component,
870
- args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [NgIf, KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule], template: "<div>\r\n <!-- <div *ngIf=\"lesson\">\r\n {lesson.title }} \r\n <br>\r\n <img [src]=\"imageCover\" alt=\"\">\r\n </div> -->\r\n\r\n <div #dynamicLesson class=\"targetclass\">\r\n <ng-template #target></ng-template>\r\n </div>\r\n</div>\r\n\r\n<br />\r\n<div style=\"display: flex; gap: 10px\">\r\n <div *ngIf=\"(mainForm.controls | keyvalue)?.length\">\r\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\r\n </div>\r\n\r\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\r\n</div>\r\n<br /><br />\r\n\r\n@if(chatVisible) {\r\n<p-drawer header=\"Conversation\" [(visible)]=\"chatVisible\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\r\n <dc-chat [agentCard]=\"agentMasterLesson\" [evaluatorAgentCard]=\"evaluatorAgentCard\"></dc-chat>\r\n</p-drawer>\r\n}\r\n", styles: [".evaluate{float:right}\n"] }]
871
- }], ctorParameters: () => [{ type: i0.Renderer2 }, { type: i0.ViewContainerRef }, { type: i1$1.ActivatedRoute }, { type: i5$1.ToastAlertsAbstractService, decorators: [{
872
- type: Inject,
873
- args: [TOAST_ALERTS_TOKEN]
874
- }] }, { type: LessonsAbstractService, decorators: [{
875
- type: Inject,
876
- args: [LESSONS_TOKEN]
877
- }] }, { type: i2$4.AgentCardsAbstractService, decorators: [{
878
- type: Inject,
879
- args: [CONVERSATION_AI_TOKEN]
880
- }] }, { type: i2$4.DCConversationPromptBuilderService }], propDecorators: { lesson: [{
881
- type: Input
882
- }], test: [{
883
- type: Input
884
- }], dynamicLesson: [{
1120
+ args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [KeyValuePipe, ButtonModule, DCChatComponent, DrawerModule], template: "<div>\n <div #dynamicLesson class=\"targetclass\">\n <ng-template #target></ng-template>\n </div>\n</div>\n\n<br />\n<div style=\"display: flex; gap: 10px\">\n @if ((mainForm.controls | keyvalue)?.length) {\n <div>\n <p-button label=\"Calificar Lecci\u00F3n\" icon=\"pi pi-check-circle\" (click)=\"evaluateForms()\" [rounded]=\"true\"></p-button>\n </div>\n }\n\n <p-button icon=\"pi pi-verified\" [rounded]=\"true\" (click)=\"startAI()\" label=\"Repasar con IA\" />\n</div>\n<br /><br />\n\n@if(chatVisible()) {\n<p-drawer header=\"Conversation\" [visible]=\"chatVisible()\" position=\"bottom\" styleClass=\"app-bottom-overlay\">\n <dc-chat [agentCard]=\"agentMasterLesson()\" [evaluatorAgentCard]=\"evaluatorAgentCard()\"></dc-chat>\n</p-drawer>\n}\n", styles: [".evaluate{float:right}\n"] }]
1121
+ }], ctorParameters: () => [], propDecorators: { dynamicLesson: [{
885
1122
  type: ViewChild,
886
1123
  args: ['dynamicLesson', { static: true }]
887
1124
  }] } });
888
1125
 
889
- const GradientCss = 'linear-gradient(to bottom,var(--primary-color), rgba(213, 238, 239, 0.31))';
890
- class DCLessonEditorComponent {
891
- constructor(lessonService, toastService, notionService, _activatedRoute, router, cdr, dialogService) {
892
- this.lessonService = lessonService;
893
- this.toastService = toastService;
894
- this.notionService = notionService;
895
- this._activatedRoute = _activatedRoute;
896
- this.router = router;
897
- this.cdr = cdr;
898
- this.dialogService = dialogService;
899
- this.editor = BalloonEditor;
900
- this.coverStorageSettings = {
901
- path: 'lessons/covers',
902
- fileName: 'cover',
903
- cropSettings: { resizeToWidth: 850, aspectRatio: AspectType.RectangleLarge, resolutions: [ResolutionType.Medium] },
904
- };
905
- this.lessonComponentEnum = LessonComponentEnum;
906
- this.lessonId = this._activatedRoute.snapshot.paramMap.get('id');
907
- this.lesson = { textCoded: `<h1>Nueva lección </h1> <p> Texto aquí</p>`, tags: [] };
908
- this.components = {};
909
- this.isRendered = false;
910
- this.cover = '';
911
- this.isCropperVisible = false;
912
- this.isLoadingLesson = false;
1126
+ class LessonNotionService {
1127
+ constructor() {
1128
+ this.#notionService = inject(NOTION_SERVICE_TOKEN);
1129
+ this.#lessonService = inject(LESSONS_TOKEN);
1130
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1131
+ // Keep track of loading state specific to Notion operations
1132
+ this.isLoading = signal(false);
1133
+ }
1134
+ #notionService;
1135
+ #lessonService;
1136
+ #toastService;
1137
+ /**
1138
+ * Extracts the Notion Page ID from a URL.
1139
+ * @param url The Notion page URL.
1140
+ * @returns The extracted page ID or null if invalid.
1141
+ */
1142
+ extractNotionPageId(url) {
1143
+ const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
1144
+ const match = url.match(notionIdRegex);
1145
+ const notionId = match ? match[0] : null;
1146
+ if (!notionId) {
1147
+ this.#toastService.error({
1148
+ title: 'URL inválido',
1149
+ subtitle: 'Por favor ingresa una URL válida de Notion.',
1150
+ });
1151
+ return null;
1152
+ }
1153
+ return notionId;
913
1154
  }
914
- ngOnInit() {
915
- this.getLessonIfId();
916
- this.items = [
917
- {
918
- tooltipOptions: { tooltipLabel: 'Reparar con IA: Repara la lección con IA', tooltipPosition: 'bottom' },
919
- icon: 'pi pi-magic',
920
- command: () => this.generateByAI(),
921
- },
922
- {
923
- tooltipOptions: { tooltipLabel: 'Selector: Agrega un selector con multiples opciones', tooltipPosition: 'bottom' },
924
- icon: 'pi pi-caret-down',
925
- command: () => this.openComponentBuilder('selector'),
926
- },
927
- {
928
- tooltipOptions: { tooltipLabel: 'Hablar: Para que una palabra o frase sea reproducible', tooltipPosition: 'bottom' },
929
- icon: 'pi pi-megaphone',
930
- command: () => this.openComponentBuilder('speaker'),
931
- },
932
- {
933
- tooltipOptions: {
934
- tooltipLabel: 'Entrada de texto: Escribe una respuesta en un cuadro de texto',
935
- tooltipPosition: 'bottom',
936
- },
937
- icon: 'pi pi-pencil',
938
- command: () => this.openComponentBuilder('textWriter'),
939
- },
940
- {
941
- tooltipOptions: { tooltipLabel: 'Verbo: Para ver datos de un verbo', tooltipPosition: 'bottom' },
942
- icon: 'pi pi-eye',
943
- command: () => this.openComponentBuilder('verbSummary'),
944
- },
945
- {
946
- tooltipOptions: { tooltipLabel: 'Palabra: Para ver datos de una palabra', tooltipPosition: 'bottom' },
947
- icon: 'pi pi-file-word',
948
- command: () => this.openComponentBuilder('wordSummary'),
1155
+ /**
1156
+ * Links an existing lesson with a Notion page ID by updating the lesson's extras.
1157
+ * @param lesson The current lesson data.
1158
+ * @param notionPageId The Notion page ID to link.
1159
+ * @returns The updated lesson data from the backend or undefined on failure.
1160
+ */
1161
+ async linkLessonWithNotion(lesson, notionPageId) {
1162
+ if (!lesson || !lesson.id) {
1163
+ // Ensure lesson exists and has an ID
1164
+ this.#toastService.warn({ title: 'No se puede enlazar', subtitle: 'La lección debe existir y tener un ID.' });
1165
+ return undefined;
1166
+ }
1167
+ const updatedLesson = {
1168
+ ...lesson,
1169
+ extras: {
1170
+ ...(lesson.extras || {}),
1171
+ notionPageId: notionPageId,
949
1172
  },
950
- ];
951
- }
952
- async getLessonIfId() {
953
- if (!this.lessonId) {
954
- this.cover = `${GradientCss}, url("/assets/images/default_banner.webp")`;
955
- return;
1173
+ };
1174
+ this.isLoading.set(true);
1175
+ try {
1176
+ const savedLesson = await this.#lessonService.postLesson(updatedLesson);
1177
+ this.#toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion.' });
1178
+ return savedLesson;
956
1179
  }
957
- this.lesson = await this.lessonService.getLesson(this.lessonId);
958
- console.log('lesson', this.lesson);
959
- if (!this.lesson) {
960
- this.toastService.warn({ title: 'No se encontró la lección', subtitle: 'Quiza el id es incorrecto' });
1180
+ catch (error) {
1181
+ // Remove explicit type, rely on tsconfig setting
1182
+ console.error('Error linking with Notion:', error);
1183
+ this.#toastService.error({ title: 'Error al enlazar', subtitle: 'Ocurrió un error inesperado.' });
1184
+ return undefined;
1185
+ }
1186
+ finally {
1187
+ this.isLoading.set(false);
961
1188
  }
962
- this.togleRender();
963
- this.updateCover();
964
1189
  }
965
- updateCover() {
966
- const cover = this.lesson.media?.images?.find((img) => img.type === 'cover');
967
- if (cover) {
968
- this.cover = `${GradientCss}, url('${cover.url}')`;
1190
+ /**
1191
+ * Handles the process of importing lesson content from Notion.
1192
+ * It prompts the user for a URL if necessary, extracts the ID, fetches content,
1193
+ * and potentially links the lesson if it's an existing one.
1194
+ * @param currentLesson The current lesson data (can be a new or existing lesson).
1195
+ * @param lessonId The current lesson ID (null if it's a new lesson).
1196
+ * @returns The fetched HTML content from Notion, or null if the process fails.
1197
+ */
1198
+ async importAndLinkLessonFromNotion(currentLesson, lessonId) {
1199
+ if (!currentLesson)
1200
+ return null;
1201
+ let notionPageId = null;
1202
+ if (currentLesson.extras?.notionPageId) {
1203
+ const useExisting = confirm(`Ya tenemos el id ${currentLesson.extras.notionPageId} ¿Quieres usar este id para importar?`);
1204
+ if (useExisting) {
1205
+ notionPageId = currentLesson.extras.notionPageId;
1206
+ }
1207
+ else {
1208
+ const inputUrl = prompt('Ingresa la NUEVA URL de Notion para importar (este ID NO se guardará automáticamente si la lección ya existe)');
1209
+ if (!inputUrl)
1210
+ return null; // User cancelled
1211
+ notionPageId = this.extractNotionPageId(inputUrl);
1212
+ }
969
1213
  }
970
1214
  else {
971
- this.cover = `${GradientCss}, url("/assets/images/default_banner.webp")`;
1215
+ const inputUrl = prompt('Ingresa el URL de Notion para importar la lección (se enlazará si la lección ya existe)');
1216
+ if (!inputUrl)
1217
+ return null; // User cancelled
1218
+ notionPageId = this.extractNotionPageId(inputUrl);
1219
+ // Link automatically only if we got a valid Notion ID AND the lesson already exists (has an ID)
1220
+ if (notionPageId && lessonId) {
1221
+ const linkedLesson = await this.linkLessonWithNotion(currentLesson, notionPageId);
1222
+ if (!linkedLesson) {
1223
+ // Linking failed, maybe stop the import? Or proceed without linking?
1224
+ // For now, let's stop.
1225
+ this.#toastService.error({ title: 'Error de Enlace', subtitle: 'No se pudo enlazar con Notion antes de importar.' });
1226
+ return null;
1227
+ }
1228
+ // If linking succeeded, the lesson object might have changed, but we proceed with the import using the notionPageId.
1229
+ }
972
1230
  }
973
- this.cdr.detectChanges();
974
- }
975
- onTagRemove(tag) {
976
- this.lesson.tags = this.lesson.tags.filter((text) => text !== tag.text);
977
- }
978
- onTagAdd(tag) {
979
- if (tag.value) {
980
- this.lesson.tags.push(tag.value);
1231
+ if (!notionPageId) {
1232
+ this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'No se proporcionó un ID de Notion válido para importar.' });
1233
+ return null;
1234
+ }
1235
+ this.isLoading.set(true);
1236
+ try {
1237
+ this.#toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
1238
+ const md = await this.#notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
1239
+ console.log('Imported MD/HTML:', md);
1240
+ this.#toastService.success({ title: 'Contenido Importado', subtitle: 'Contenido de Notion obtenido.' });
1241
+ return md.content; // Return the fetched content
1242
+ }
1243
+ catch (error) {
1244
+ // Remove explicit type, rely on tsconfig setting
1245
+ console.error('Error importing from Notion:', error);
1246
+ this.#toastService.error({ title: 'Error de importación', subtitle: 'Ocurrió un error inesperado.' });
1247
+ return null; // Return null on failure
1248
+ }
1249
+ finally {
1250
+ this.isLoading.set(false);
981
1251
  }
982
- tag.input.nativeElement.value = '';
983
1252
  }
984
- async saveLesson(event = null) {
985
- if (event) {
986
- event.preventDefault();
1253
+ /**
1254
+ * Fetches content from the linked Notion page for AI improvement (placeholder).
1255
+ * @param lesson The current lesson data.
1256
+ */
1257
+ async improveLessonWithNotionAI(lesson) {
1258
+ if (!lesson)
1259
+ return;
1260
+ const notionId = lesson.extras?.notionPageId;
1261
+ if (!notionId) {
1262
+ this.#toastService.warn({ title: 'Sin ID de Notion', subtitle: 'Enlaza la lección con Notion primero.' });
1263
+ return;
987
1264
  }
988
- // TODO: Optimización importante, guardar una lección guarda todos los datos de nuevo, y no solo los que se modificaron, encontrar una forma de optimizar.
989
- const lesson = await this.lessonService.postLesson(this.lesson);
990
- if (!this.lessonId) {
991
- this.toastService.success({ title: 'Se creó la lección', subtitle: 'Éxito' });
992
- this.router.navigate([lesson.id], { relativeTo: this._activatedRoute });
1265
+ this.isLoading.set(true);
1266
+ try {
1267
+ this.#toastService.info({ title: 'Mejorando con IA...', subtitle: 'Obteniendo contenido de Notion.' });
1268
+ const md = await this.#notionService.getPageInSpecificFormat(notionId, NotionExportType.HTML);
1269
+ console.log('Content to improve:', md);
1270
+ // TODO: Add actual AI improvement logic here
1271
+ // e.g., call another service: await this.aiImprovementService.improve(md.content);
1272
+ // Then potentially update the lesson via lessonService or return data
1273
+ this.#toastService.success({ title: 'Contenido Obtenido', subtitle: 'Listo para mejorar con IA (lógica no implementada).' });
993
1274
  }
994
- else {
995
- this.toastService.info({ title: 'Se guadarón los cambios en la lección', subtitle: 'Guardado' });
996
- this.lesson = lesson;
997
- this.validateAudios();
1275
+ catch (error) {
1276
+ // Remove explicit type, rely on tsconfig setting
1277
+ console.error('Error improving with AI:', error);
1278
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'Ocurrió un error inesperado.' });
1279
+ }
1280
+ finally {
1281
+ this.isLoading.set(false);
998
1282
  }
999
- this.togleRender();
1000
- return lesson;
1001
1283
  }
1002
- async validateAudios() {
1003
- if (!this.lesson.components) {
1284
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonNotionService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1285
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonNotionService }); }
1286
+ }
1287
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonNotionService, decorators: [{
1288
+ type: Injectable
1289
+ }] });
1290
+
1291
+ class LessonUtilsService {
1292
+ #lessonService = inject(LESSONS_TOKEN);
1293
+ #toastService = inject(TOAST_ALERTS_TOKEN);
1294
+ constructor() { }
1295
+ /**
1296
+ * Validates if audios need generation for the given lesson.
1297
+ * Currently logs a placeholder message.
1298
+ * @param lesson The lesson object (or signal) to validate.
1299
+ */
1300
+ validateAudios(lesson) {
1301
+ // Access lesson data directly
1302
+ if (!lesson?.components) {
1004
1303
  return;
1005
1304
  }
1006
- // al menos un audio sin generar.
1007
- // agregar más condigciones después
1008
- // this.lesson.components.forEach((component: SpeakerCompConfiguration) => {
1009
- // if (component.component === 'speaker') {
1010
- // if (component.settings.voice && !component.audio) {
1011
- // // this.toastrService.warn('Se encontraron audios por generar', 'Será rápido');
1012
- // // TODO call backend.
1013
- // this.lessonService.generateAudiosForLesson(this.lesson.id).then((res) => {
1014
- // // this.toastrService.success('Se generaron los audios', 'Listo');
1015
- // console.log(res);
1016
- // });
1017
- // return;
1018
- // }
1019
- // }
1020
- // });
1305
+ // Placeholder logic - adapt as needed from original component
1306
+ console.log('Validating audios for lesson:', lesson.id);
1307
+ // Original logic commented out - requires SpeakerCompConfiguration type
1308
+ // const needsGeneration = lesson.components.some((component: any) => // Use 'any' or define SpeakerCompConfiguration
1309
+ // component.component === 'speaker' && component.settings?.voice && !component.audio
1310
+ // );
1311
+ // if (needsGeneration) {
1312
+ // this.#toastService.warn({ title: 'Audios por generar', subtitle: 'Se encontraron audios pendientes' });
1313
+ // // Consider calling generation service here or returning a flag
1314
+ // // this.#lessonService.generateAudiosForLesson(lesson.id).then(res => { ... });
1315
+ // }
1316
+ }
1317
+ /**
1318
+ * Updates the lesson signal with a new cover image.
1319
+ * @param lessonSignal The signal holding the lesson data.
1320
+ * @param imageUploaded The image data object from the upload event.
1321
+ */
1322
+ uploadCover(lessonSignal, imageUploaded) {
1323
+ // Use 'any' for now, refine if event structure is known
1324
+ const newImage = {
1325
+ type: 'cover',
1326
+ url: imageUploaded?.url,
1327
+ path: imageUploaded?.path,
1328
+ fullPath: imageUploaded?.fullPath,
1329
+ resolutions: imageUploaded?.resolutions ?? [],
1330
+ resolution: imageUploaded?.resolution,
1331
+ bucket: imageUploaded?.bucket,
1332
+ // Add any other required fields from LessonImage or ImgStorageData
1333
+ };
1334
+ lessonSignal.update((currentLesson) => {
1335
+ if (!currentLesson)
1336
+ return undefined;
1337
+ let updatedImages;
1338
+ const existingMedia = currentLesson.media;
1339
+ if (!existingMedia || !existingMedia.images) {
1340
+ updatedImages = [newImage];
1341
+ }
1342
+ else {
1343
+ const filteredImages = existingMedia.images.filter((img) => img.type !== 'cover');
1344
+ const mappedExistingImages = filteredImages.map((img) => ({
1345
+ type: img.type,
1346
+ url: img.url,
1347
+ path: img.path,
1348
+ fullPath: img.fullPath,
1349
+ resolutions: img.resolutions ?? [],
1350
+ resolution: img.resolution,
1351
+ bucket: img.bucket,
1352
+ }));
1353
+ updatedImages = [...mappedExistingImages, newImage];
1354
+ }
1355
+ return {
1356
+ ...currentLesson,
1357
+ media: {
1358
+ ...(existingMedia || {}),
1359
+ images: updatedImages,
1360
+ },
1361
+ };
1362
+ });
1363
+ // Note: Saving the lesson should be triggered from the component after calling this.
1364
+ }
1365
+ /**
1366
+ * Generates lesson content using AI. Assumes the lesson is already saved.
1367
+ * @param lessonId The ID of the lesson to generate content for.
1368
+ * @returns The updated lesson object after AI generation, or null on failure.
1369
+ */
1370
+ async generateByAI(lessonId) {
1371
+ if (!lessonId) {
1372
+ this.#toastService.warn({ title: 'ID Requerido', subtitle: 'Se necesita un ID de lección para usar IA.' });
1373
+ return null;
1374
+ }
1375
+ // No need to save here, component should ensure it's saved before calling.
1376
+ try {
1377
+ await this.#lessonService.postGenerateByAI(lessonId);
1378
+ // Re-fetch the lesson data to get AI updates
1379
+ const updatedLesson = await this.#lessonService.getLesson(lessonId);
1380
+ if (updatedLesson) {
1381
+ this.#toastService.success({ title: 'IA completada', subtitle: 'Lección actualizada con IA.' });
1382
+ return updatedLesson;
1383
+ }
1384
+ else {
1385
+ throw new Error('Failed to fetch lesson after AI generation');
1386
+ }
1387
+ }
1388
+ catch (error) {
1389
+ // Type the error object
1390
+ console.error('Error during AI generation in service:', error);
1391
+ this.#toastService.error({ title: 'Error de IA', subtitle: 'No se pudo generar la lección con IA.' });
1392
+ return null; // Return null in catch block
1393
+ }
1394
+ // Loading state should be managed by the component calling this service.
1395
+ }
1396
+ /**
1397
+ * Cleans orphaned components from the lesson's dynamicComponents.
1398
+ * An orphaned component is one present in dynamicComponents but its ID is not found in the textCoded HTML.
1399
+ * @param lesson The lesson object to clean.
1400
+ * @returns A new lesson object with orphaned components removed from dynamicComponents.
1401
+ */
1402
+ cleanOrphanedComponents(lesson) {
1403
+ if (!lesson || !lesson.textCoded || !lesson.dynamicComponents) {
1404
+ // Return the original lesson if essential parts are missing
1405
+ return lesson;
1406
+ }
1407
+ const textCoded = lesson.textCoded;
1408
+ const existingComponents = lesson.dynamicComponents;
1409
+ const existingComponentIds = new Set(Object.keys(existingComponents));
1410
+ // Regex to find "id":"<component_id>" within the textCoded string
1411
+ const idRegex = /"id":"([^"]+)"/g;
1412
+ const foundIdsInText = new Set();
1413
+ let match;
1414
+ while ((match = idRegex.exec(textCoded)) !== null) {
1415
+ const potentialId = match[1];
1416
+ // Check if the found ID actually exists in our dynamic components map
1417
+ // This ensures we only count IDs that correspond to known components.
1418
+ if (existingComponentIds.has(potentialId)) {
1419
+ foundIdsInText.add(potentialId);
1420
+ }
1421
+ }
1422
+ const orphanedIds = [];
1423
+ const cleanedDynamicComponents = {};
1424
+ // Iterate through existing component IDs
1425
+ for (const componentId of existingComponentIds) {
1426
+ if (foundIdsInText.has(componentId)) {
1427
+ // Keep the component if its ID was found in the text
1428
+ cleanedDynamicComponents[componentId] = existingComponents[componentId];
1429
+ }
1430
+ else {
1431
+ // Mark as orphaned if not found
1432
+ orphanedIds.push(componentId);
1433
+ }
1434
+ }
1435
+ // Log a warning if any orphaned components were found
1436
+ if (orphanedIds.length > 0) {
1437
+ console.warn(`[LessonUtilsService] Orphaned components detected and will be removed from lesson data (IDs not found in textCoded):`, orphanedIds);
1438
+ this.#toastService.warn({
1439
+ title: 'Componentes Huérfanos Detectados',
1440
+ subtitle: `Se removerán ${orphanedIds.length} componentes no usados del editor.`,
1441
+ // life: 5000, // Removed 'life' property as it might not be supported by ToastData
1442
+ });
1443
+ }
1444
+ // Return a new lesson object with the cleaned components
1445
+ return {
1446
+ ...lesson,
1447
+ dynamicComponents: cleanedDynamicComponents,
1448
+ };
1021
1449
  }
1022
- togleRender(_event = null) {
1023
- this.isRendered = false;
1024
- setTimeout(() => {
1025
- this.isRendered = true;
1026
- this.cdr.detectChanges();
1027
- }, 400);
1028
- this.cdr.detectChanges();
1450
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonUtilsService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
1451
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonUtilsService, providedIn: 'root' }); }
1452
+ }
1453
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonUtilsService, decorators: [{
1454
+ type: Injectable,
1455
+ args: [{
1456
+ providedIn: 'root', // Provide globally or in a specific module if preferred
1457
+ }]
1458
+ }], ctorParameters: () => [] });
1459
+
1460
+ class DCLessonComponentAdderComponent {
1461
+ constructor() {
1462
+ // Services
1463
+ this.#dialogService = inject(DialogService);
1464
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1465
+ this.componentAdded = new EventEmitter(); // Changed Output name and type
1466
+ // Expose enum to the template
1467
+ this.lessonComponentEnum = LessonComponentEnum;
1029
1468
  }
1469
+ // Services
1470
+ #dialogService;
1471
+ #toastService;
1472
+ // Moved logic from DCLessonEditorComponent
1030
1473
  openComponentBuilder(type) {
1031
- return this.dialogService.open(LessonComponentBuilders[type], { width: '550px', header: 'Agregar componente', closable: true }).onClose.pipe();
1474
+ const componentToBuild = LessonComponentBuilders[type];
1475
+ if (!componentToBuild) {
1476
+ console.error(`No component builder found for type: ${type}`);
1477
+ this.#toastService.error({ title: 'Error', subtitle: `Componente desconocido: ${type}` });
1478
+ return;
1479
+ }
1480
+ const dialogRef = this.#dialogService.open(componentToBuild, {
1481
+ width: '80vw',
1482
+ header: 'Agregar componente',
1483
+ closable: true,
1484
+ });
1485
+ // Handle the result and emit the new event
1486
+ dialogRef.onClose.subscribe((result) => {
1487
+ if (result) {
1488
+ console.log('Component builder closed:', result);
1489
+ this.componentAdded.emit(result); // Emit the result when dialog closes successfully
1490
+ }
1491
+ });
1032
1492
  }
1033
- uploadCover(imageUploaded) {
1034
- const image = { type: 'cover', ...imageUploaded };
1035
- if (!this.lesson.media) {
1036
- // puede que no exista media
1037
- this.lesson.media = {};
1038
- this.lesson.media.images = [image];
1493
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonComponentAdderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1494
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: DCLessonComponentAdderComponent, isStandalone: true, selector: "dc-lesson-component-adder", outputs: { componentAdded: "componentAdded" }, providers: [DialogService], ngImport: i0, template: "<span>Componentes: </span>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap\">\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Selector)\"\n pTooltip=\"Agrega un selector con multiples opciones\"\n tooltipPosition=\"bottom\">\n Selector\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Speaker)\"\n pTooltip=\"Para que una palabra o frase sea reproducible\"\n tooltipPosition=\"bottom\">\n Speaker\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\"\n pTooltip=\"Escribe una respuesta en un cuadro de texto\"\n tooltipPosition=\"bottom\">\n Text\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de un verbo\"\n tooltipPosition=\"bottom\">\n Verb\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de una palabra\"\n tooltipPosition=\"bottom\">\n Palabra\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Traducci\u00F3n\n </p-button>\n <!-- Add other buttons here if needed, following the same pattern -->\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2$5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }] }); }
1495
+ }
1496
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonComponentAdderComponent, decorators: [{
1497
+ type: Component,
1498
+ args: [{ selector: 'dc-lesson-component-adder', standalone: true, imports: [CommonModule, ButtonModule, TooltipModule], providers: [DialogService], template: "<span>Componentes: </span>\n<div style=\"display: flex; gap: 10px; flex-wrap: wrap\">\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Selector)\"\n pTooltip=\"Agrega un selector con multiples opciones\"\n tooltipPosition=\"bottom\">\n Selector\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.Speaker)\"\n pTooltip=\"Para que una palabra o frase sea reproducible\"\n tooltipPosition=\"bottom\">\n Speaker\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\"\n pTooltip=\"Escribe una respuesta en un cuadro de texto\"\n tooltipPosition=\"bottom\">\n Text\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de un verbo\"\n tooltipPosition=\"bottom\">\n Verb\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\"\n pTooltip=\"Muestra la informaci\u00F3n de una palabra\"\n tooltipPosition=\"bottom\">\n Palabra\n </p-button>\n <p-button\n severity=\"info\"\n (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\"\n pTooltip=\"Muestra el texto pero al pica cambia de idioma\"\n tooltipPosition=\"bottom\">\n Traducci\u00F3n\n </p-button>\n <!-- Add other buttons here if needed, following the same pattern -->\n</div>\n" }]
1499
+ }], propDecorators: { componentAdded: [{
1500
+ type: Output
1501
+ }] } });
1502
+
1503
+ class DCLessonMetadataEditorComponent {
1504
+ constructor() {
1505
+ // Use signal for input lesson data
1506
+ this.lesson = signal(undefined);
1507
+ this.isLoadingLesson = signal(false);
1508
+ // Outputs for actions
1509
+ this.saveRequest = new EventEmitter();
1510
+ this.importNotionRequest = new EventEmitter();
1511
+ this.improveNotionRequest = new EventEmitter();
1512
+ this.generateAIRequest = new EventEmitter();
1513
+ // Output for property changes
1514
+ this.propertyChange = new EventEmitter();
1515
+ }
1516
+ // Method to emit property changes, called by ngModelChange in the template
1517
+ onPropertyChange(property, value) {
1518
+ this.propertyChange.emit({ property, value });
1519
+ }
1520
+ // Methods to emit action requests
1521
+ emitSaveRequest() {
1522
+ this.saveRequest.emit();
1523
+ }
1524
+ emitImportNotionRequest() {
1525
+ this.importNotionRequest.emit();
1526
+ }
1527
+ emitImproveNotionRequest() {
1528
+ this.improveNotionRequest.emit();
1529
+ }
1530
+ emitGenerateAIRequest() {
1531
+ this.generateAIRequest.emit();
1532
+ }
1533
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonMetadataEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1534
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonMetadataEditorComponent, isStandalone: true, selector: "dc-lesson-metadata-editor", inputs: { lesson: "lesson", isLoadingLesson: "isLoadingLesson" }, outputs: { saveRequest: "saveRequest", importNotionRequest: "importNotionRequest", improveNotionRequest: "improveNotionRequest", generateAIRequest: "generateAIRequest", propertyChange: "propertyChange" }, ngImport: i0, template: "@if (lesson(); as currentLesson) {\n<div>\n <div>\n <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div>\n\n <!-- Use one-way binding and ngModelChange for signals -->\n <div>\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.title\"\n (ngModelChange)=\"onPropertyChange('title', $event)\"\n type=\"text\"\n placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.description\"\n (ngModelChange)=\"onPropertyChange('description', $event)\"\n type=\"text\"\n placeholder=\"Agrega una descripci\u00F3n\" />\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [ngModel]=\"currentLesson.prompt\"\n (ngModelChange)=\"onPropertyChange('prompt', $event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" [disabled]=\"isLoadingLesson()\" (click)=\"emitGenerateAIRequest()\"> Generar con IA </p-button>\n </div>\n\n <div style=\"margin-top: 10px\">\n <label class=\"checkbox-container\" style=\"margin-right: 15px\">\n <input\n type=\"checkbox\"\n [ngModel]=\"currentLesson.isPublished\"\n (ngModelChange)=\"onPropertyChange('isPublished', $event)\"\n title=\"Cuando termines la edici\u00F3n marca esta casilla\" />\n <span class=\"checkmark\"></span>\n Publicada\n </label>\n\n <input\n pInputText\n [ngModel]=\"currentLesson.level\"\n (ngModelChange)=\"onPropertyChange('level', $event)\"\n type=\"number\"\n placeholder=\"Nivel\"\n style=\"width: 80px\" />\n </div>\n\n <!-- Access signal values -->\n <div style=\"margin-top: 10px; font-size: 0.9em; color: var(--text-color-secondary)\">\n {{ currentLesson.baseLang | flagEmoji }} -> {{ currentLesson.targetLang | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ currentLesson.baseLang | langDesc : 'es' }} que aprenden\n {{ currentLesson.targetLang | langDesc : 'es' }}\n </div>\n </div>\n</div>\n} @else {\n<!-- Optional: Show a loading state or placeholder if lesson is undefined -->\n<p>Cargando datos de la lecci\u00F3n...</p>\n}\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "pipe", type: // Added TooltipModule
1535
+ FlagLanguagePipe, name: "flagEmoji" }, { kind: "pipe", type: // Added Pipe
1536
+ LangDescTranslationPipe, name: "langDesc" }] }); }
1537
+ }
1538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonMetadataEditorComponent, decorators: [{
1539
+ type: Component,
1540
+ args: [{ selector: 'dc-lesson-metadata-editor', standalone: true, imports: [
1541
+ CommonModule,
1542
+ FormsModule,
1543
+ ButtonModule,
1544
+ InputTextModule,
1545
+ TooltipModule, // Added TooltipModule
1546
+ FlagLanguagePipe, // Added Pipe
1547
+ LangDescTranslationPipe, // Added Pipe
1548
+ ], template: "@if (lesson(); as currentLesson) {\n<div>\n <div>\n <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"emitSaveRequest()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"emitImportNotionRequest()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"emitImproveNotionRequest()\" />\n </div>\n\n <!-- Use one-way binding and ngModelChange for signals -->\n <div>\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.title\"\n (ngModelChange)=\"onPropertyChange('title', $event)\"\n type=\"text\"\n placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <input\n pInputText\n style=\"width: 100%\"\n [ngModel]=\"currentLesson.description\"\n (ngModelChange)=\"onPropertyChange('description', $event)\"\n type=\"text\"\n placeholder=\"Agrega una descripci\u00F3n\" />\n </div>\n\n <div style=\"display: flex; align-items: center; margin-top: 10px\">\n <input\n pInputText\n style=\"flex: auto; margin-right: 5px\"\n [ngModel]=\"currentLesson.prompt\"\n (ngModelChange)=\"onPropertyChange('prompt', $event)\"\n type=\"text\"\n placeholder=\"Prompt para IA (opcional)\" />\n <p-button severity=\"primary\" [disabled]=\"isLoadingLesson()\" (click)=\"emitGenerateAIRequest()\"> Generar con IA </p-button>\n </div>\n\n <div style=\"margin-top: 10px\">\n <label class=\"checkbox-container\" style=\"margin-right: 15px\">\n <input\n type=\"checkbox\"\n [ngModel]=\"currentLesson.isPublished\"\n (ngModelChange)=\"onPropertyChange('isPublished', $event)\"\n title=\"Cuando termines la edici\u00F3n marca esta casilla\" />\n <span class=\"checkmark\"></span>\n Publicada\n </label>\n\n <input\n pInputText\n [ngModel]=\"currentLesson.level\"\n (ngModelChange)=\"onPropertyChange('level', $event)\"\n type=\"number\"\n placeholder=\"Nivel\"\n style=\"width: 80px\" />\n </div>\n\n <!-- Access signal values -->\n <div style=\"margin-top: 10px; font-size: 0.9em; color: var(--text-color-secondary)\">\n {{ currentLesson.baseLang | flagEmoji }} -> {{ currentLesson.targetLang | flagEmoji }} Lecci\u00F3n para hablantes de\n {{ currentLesson.baseLang | langDesc : 'es' }} que aprenden\n {{ currentLesson.targetLang | langDesc : 'es' }}\n </div>\n </div>\n</div>\n} @else {\n<!-- Optional: Show a loading state or placeholder if lesson is undefined -->\n<p>Cargando datos de la lecci\u00F3n...</p>\n}\n" }]
1549
+ }], propDecorators: { lesson: [{
1550
+ type: Input,
1551
+ args: [{ required: true }]
1552
+ }], isLoadingLesson: [{
1553
+ type: Input,
1554
+ args: [{ required: true }]
1555
+ }], saveRequest: [{
1556
+ type: Output
1557
+ }], importNotionRequest: [{
1558
+ type: Output
1559
+ }], improveNotionRequest: [{
1560
+ type: Output
1561
+ }], generateAIRequest: [{
1562
+ type: Output
1563
+ }], propertyChange: [{
1564
+ type: Output
1565
+ }] } });
1566
+
1567
+ const GradientCss = 'linear-gradient(to bottom,var(--primary-color), rgba(213, 238, 239, 0.31))';
1568
+ class DCLessonEditorComponent {
1569
+ // Services
1570
+ #activatedRoute; // Re-inject as it's needed for navigation
1571
+ #lessonNotionService;
1572
+ #lessonUtilsService;
1573
+ #router;
1574
+ // Removed #dialogService injection
1575
+ #lessonService;
1576
+ #toastService;
1577
+ constructor() {
1578
+ // Removed Speed Dial Items initialization
1579
+ // Services
1580
+ this.#activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
1581
+ this.#lessonNotionService = inject(LessonNotionService);
1582
+ this.#lessonUtilsService = inject(LessonUtilsService);
1583
+ this.#router = inject(Router);
1584
+ // Removed #dialogService injection
1585
+ this.#lessonService = inject(LESSONS_TOKEN);
1586
+ this.#toastService = inject(TOAST_ALERTS_TOKEN);
1587
+ // Signals States
1588
+ this.lessonId = toSignal(inject(ActivatedRoute).paramMap.pipe(map((params) => params.get('id'))));
1589
+ this.lesson = signal(undefined); // Initialize as undefined
1590
+ this.isCropperVisible = signal(false);
1591
+ this.isLoadingLesson = signal(false);
1592
+ // Removed items signal
1593
+ // Computed Signals
1594
+ this.coverBackground = computed(() => {
1595
+ const currentLesson = this.lesson();
1596
+ const coverImage = currentLesson?.media?.images?.find((img) => img.type === 'cover');
1597
+ const imageUrl = coverImage?.url || '/assets/images/default_banner.webp';
1598
+ return `${GradientCss}, url("${imageUrl}")`;
1599
+ });
1600
+ // Computed signal to get dynamic components as an array for easier iteration in the template
1601
+ this.dynamicComponentsArray = computed(() => {
1602
+ const currentLesson = this.lesson();
1603
+ if (currentLesson?.dynamicComponents) {
1604
+ return Object.values(currentLesson.dynamicComponents);
1605
+ }
1606
+ return []; // Return empty array if no lesson or no dynamic components
1607
+ });
1608
+ // States
1609
+ this.components = {}; // Current Dynamic components
1610
+ this.editor = BalloonEditor;
1611
+ this.lessonComponentEnum = LessonComponentEnum;
1612
+ this.coverStorageSettings = {
1613
+ path: 'lessons/covers',
1614
+ fileName: 'cover',
1615
+ cropSettings: { resizeToWidth: 850, aspectRatio: AspectType.RectangleLarge, resolutions: [ResolutionType.Medium] },
1616
+ };
1617
+ // Effect to fetch lesson data when ID changes
1618
+ effect(async () => {
1619
+ const id = this.lessonId();
1620
+ console.log('Lesson ID Signal:', id);
1621
+ if (id) {
1622
+ this.isLoadingLesson.set(true); // Start loading
1623
+ try {
1624
+ const fetchedLesson = await this.#lessonService.getLesson(id);
1625
+ if (fetchedLesson) {
1626
+ this.lesson.set(fetchedLesson);
1627
+ }
1628
+ else {
1629
+ this.lesson.set(undefined); // Reset if not found
1630
+ this.#toastService.warn({ title: 'No se encontró la lección', subtitle: 'Quizá el id es incorrecto' });
1631
+ // Optional: Navigate away or show a specific "not found" state
1632
+ // this.#router.navigate(['/path/to/lessons']);
1633
+ }
1634
+ }
1635
+ catch (error) {
1636
+ console.error('Error fetching lesson:', error);
1637
+ this.lesson.set(undefined); // Reset on error
1638
+ this.#toastService.error({ title: 'Error al cargar la lección', subtitle: 'Intenta de nuevo más tarde' });
1639
+ }
1640
+ finally {
1641
+ this.isLoadingLesson.set(false); // Stop loading
1642
+ }
1643
+ }
1644
+ else {
1645
+ // Handle case for new lesson (ID is null/undefined)
1646
+ this.lesson.set({ textCoded: `<h1>Nueva lección </h1> <p> Texto aquí</p>`, tags: [] }); // Set default new lesson structure
1647
+ this.isLoadingLesson.set(false); // Ensure loading is off
1648
+ }
1649
+ });
1650
+ }
1651
+ /**
1652
+ * Updates a specific property on the lesson signal.
1653
+ * Used for ngModelChange events to simulate two-way binding with signals.
1654
+ * @param property The key of the ILesson property to update.
1655
+ * @param value The new value for the property.
1656
+ */
1657
+ updateLessonProperty(property, value) {
1658
+ this.lesson.update((currentLesson) => {
1659
+ if (!currentLesson)
1660
+ return undefined;
1661
+ return { ...currentLesson, [property]: value };
1662
+ });
1663
+ }
1664
+ onTagRemove(tag) {
1665
+ this.lesson.update((currentLesson) => {
1666
+ if (!currentLesson)
1667
+ return undefined;
1668
+ const updatedTags = currentLesson.tags.filter((text) => text !== tag.text);
1669
+ return { ...currentLesson, tags: updatedTags };
1670
+ });
1671
+ }
1672
+ onTagAdd(tag) {
1673
+ if (tag.value) {
1674
+ this.lesson.update((currentLesson) => {
1675
+ if (!currentLesson)
1676
+ return undefined;
1677
+ // Avoid duplicate tags if necessary
1678
+ if (currentLesson.tags.includes(tag.value)) {
1679
+ return currentLesson;
1680
+ }
1681
+ const updatedTags = [...currentLesson.tags, tag.value];
1682
+ return { ...currentLesson, tags: updatedTags };
1683
+ });
1039
1684
  }
1040
- else {
1041
- // solo sustituir el cover si ya existe, como siempre utilizo el mismo nombre, no tengo que eliminar la anterior si actualizo.
1042
- // const currentCover = this.lesson.media.images.find((img) => img.type === 'cover');
1043
- this.lesson.media.images = this.lesson.media.images.filter((img) => img.type !== 'cover');
1044
- this.lesson.media.images.push(image);
1685
+ tag.input.nativeElement.value = ''; // Clear input
1686
+ }
1687
+ async saveLesson(event) {
1688
+ event?.preventDefault();
1689
+ const currentLesson = this.lesson();
1690
+ if (!currentLesson) {
1691
+ this.#toastService.error({ title: 'Error', subtitle: 'No hay datos de lección para guardar' });
1692
+ return undefined;
1693
+ }
1694
+ // Clean orphaned components before saving
1695
+ const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
1696
+ // TODO: Implement optimization for saving only changed data.
1697
+ // This requires comparing lessonToSave with the initially fetched state.
1698
+ this.isLoadingLesson.set(true); // Indicate saving
1699
+ try {
1700
+ // Use the cleaned lesson object for saving
1701
+ const savedLesson = await this.#lessonService.postLesson(lessonToSave);
1702
+ const currentId = this.lessonId();
1703
+ if (!currentId) {
1704
+ // It was a new lesson, now it has an ID. Navigate.
1705
+ this.#toastService.success({ title: 'Se creó la lección', subtitle: 'Éxito' });
1706
+ // The effect should automatically fetch the lesson again after navigation due to paramMap change.
1707
+ this.#router.navigate(['../', savedLesson.id], { relativeTo: this.#activatedRoute });
1708
+ }
1709
+ else {
1710
+ // It was an existing lesson, update the signal with the potentially updated data from the backend.
1711
+ this.lesson.set(savedLesson);
1712
+ this.#toastService.info({ title: 'Se guardaron los cambios en la lección', subtitle: 'Guardado' });
1713
+ // Call the service method for validation
1714
+ this.#lessonUtilsService.validateAudios(this.lesson());
1715
+ }
1716
+ return savedLesson;
1717
+ }
1718
+ catch (error) {
1719
+ // Type error
1720
+ console.error('Error saving lesson:', error);
1721
+ this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudieron guardar los cambios' });
1722
+ return undefined;
1723
+ }
1724
+ finally {
1725
+ this.isLoadingLesson.set(false); // Finish saving indication
1726
+ }
1727
+ } // Add missing closing brace for saveLesson
1728
+ // Removed openComponentBuilder method
1729
+ /**
1730
+ * Handles the event emitted when a component is added via the adder component.
1731
+ * @param result The configuration data returned from the component builder dialog. Expected format: { obj: LessonComponentConfiguration }
1732
+ */
1733
+ onComponentAdded(result) {
1734
+ debugger;
1735
+ // Check if result and result.obj.id exist
1736
+ const newComponent = result?.obj;
1737
+ if (newComponent?.id) {
1738
+ console.log('Component builder closed, result received in editor:', newComponent);
1739
+ // // Transform LessonComponentConfiguration to DynamicContentComponent
1740
+ // const dynamicComponentToAdd: DynamicContentComponent = {
1741
+ // id: componentConfig.id,
1742
+ // component: componentConfig.component,
1743
+ // inputs: {
1744
+ // config: componentConfig, // Pass the original config object as an input named 'config'
1745
+ // // Add other potential inputs if needed based on component type later
1746
+ // },
1747
+ // };
1748
+ // Update the lesson signal, adding the transformed component to the dynamicComponents object
1749
+ this.lesson.update((currentLesson) => {
1750
+ if (!currentLesson)
1751
+ return undefined;
1752
+ // Ensure dynamicComponents object exists, initialize if not
1753
+ const currentDynamicComponents = currentLesson.dynamicComponents || {};
1754
+ // Create the updated dynamicComponents object
1755
+ const updatedDynamicComponents = {
1756
+ ...currentDynamicComponents,
1757
+ [newComponent.id]: newComponent, // Use component's id as the key
1758
+ };
1759
+ // Return the updated lesson state
1760
+ return { ...currentLesson, dynamicComponents: updatedDynamicComponents };
1761
+ });
1762
+ // Optionally save the lesson after adding the component
1763
+ // this.saveLesson();
1045
1764
  }
1046
- this.updateCover();
1047
- this.saveLesson();
1048
1765
  }
1049
1766
  openCropper() {
1050
- this.isCropperVisible = true;
1767
+ // Correctly define openCropper
1768
+ this.isCropperVisible.set(true);
1051
1769
  }
1770
+ // isLoadingLesson signal is used directly
1052
1771
  async generateByAI() {
1053
- this.isLoadingLesson = true;
1772
+ const currentId = this.lessonId();
1773
+ if (!currentId) {
1774
+ this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
1775
+ return;
1776
+ }
1777
+ this.isLoadingLesson.set(true);
1054
1778
  try {
1055
- await this.saveLesson();
1056
- await this.lessonService.postGenerateByAI(this.lesson.id);
1057
- await this.getLessonIfId();
1779
+ // Ensure latest changes are saved before generating
1780
+ const savedLesson = await this.saveLesson();
1781
+ if (!savedLesson) {
1782
+ // Handle save error - toast is shown in saveLesson
1783
+ throw new Error('Failed to save before AI generation');
1784
+ }
1785
+ // Call the service method
1786
+ const updatedLesson = await this.#lessonUtilsService.generateByAI(currentId);
1787
+ if (updatedLesson) {
1788
+ this.lesson.set(updatedLesson); // Update the signal with AI changes from service
1789
+ // Toast success is handled by the service
1790
+ }
1791
+ else {
1792
+ // Toast error is handled by the service
1793
+ throw new Error('AI generation failed or lesson fetch failed after generation.');
1794
+ }
1795
+ }
1796
+ catch (error) {
1797
+ // Type error
1798
+ console.error('Error during AI generation process in component:', error);
1799
+ // Service handles specific AI error toasts, maybe add a general one here if needed
1800
+ // this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
1058
1801
  }
1059
1802
  finally {
1060
- this.isLoadingLesson = false;
1803
+ this.isLoadingLesson.set(false); // Stop loading
1061
1804
  }
1062
1805
  }
1063
- onImageSelected(event) {
1064
- console.log(event);
1065
- }
1806
+ /**
1807
+ * Handles the image upload event, updates the lesson signal via the service, and saves.
1808
+ * @param event The image upload event data.
1809
+ */
1066
1810
  async onImageUploaded(event) {
1067
- this.uploadCover(event);
1068
- this.cdr.detectChanges();
1069
- }
1811
+ // Call the service to update the signal
1812
+ this.#lessonUtilsService.uploadCover(this.lesson, event);
1813
+ // The coverBackground computed signal will update automatically.
1814
+ // Save the lesson after the signal has been updated
1815
+ await this.saveLesson();
1816
+ }
1817
+ /**
1818
+ * Imports lesson content from Notion using the LessonNotionService.
1819
+ */
1070
1820
  async importFromNotion() {
1071
- let notionPageId;
1072
- if (this.lesson?.extras?.notionPageId) {
1073
- const response = confirm(`Ya tenemos el id ${this.lesson.extras?.notionPageId} ¿Quieres usar este id?`);
1074
- if (!response) {
1075
- return;
1821
+ // Use the service's loading state or manage locally
1822
+ this.isLoadingLesson.set(true);
1823
+ try {
1824
+ const newContent = await this.#lessonNotionService.importAndLinkLessonFromNotion(this.lesson(), this.lessonId());
1825
+ if (newContent !== null) {
1826
+ // Update the lesson signal's textCoded property
1827
+ this.updateLessonProperty('textCoded', newContent);
1828
+ // Toast success is handled within the service now
1076
1829
  }
1077
- notionPageId = this.lesson.extras?.notionPageId;
1830
+ // If newContent is null, the service handled errors/toasts
1078
1831
  }
1079
- else {
1080
- const response = prompt('Ingresa el url de notion para importar la lección, se guardará este id');
1081
- if (!response) {
1082
- return;
1083
- }
1084
- notionPageId = this.extractNotionPageId(response);
1085
- if (!notionPageId)
1086
- return;
1087
- this.linkWithNotion(notionPageId);
1088
- }
1089
- this.toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
1090
- const md = await this.notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
1091
- console.log(md);
1092
- this.lesson.textCoded = md.content;
1093
- this.togleRender();
1094
- this.toastService.success({ title: 'Listo', subtitle: 'La lección se importó correctamente' });
1095
- }
1096
- extractNotionPageId(url) {
1097
- const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
1098
- const match = url.match(notionIdRegex);
1099
- const notionId = match ? match[0] : null;
1100
- if (!notionId) {
1101
- this.toastService.error({
1102
- title: 'URL inválido',
1103
- subtitle: 'Por favor ingresa una URL válida de Notion',
1104
- });
1832
+ finally {
1833
+ // Ensure loading state is reset regardless of service outcome
1834
+ // If observing service state: this.isLoadingLesson.set(this.#lessonNotionService.isLoading());
1835
+ this.isLoadingLesson.set(false); // Keep local loading for now
1105
1836
  }
1106
- return notionId;
1107
- }
1108
- async linkWithNotion(notionPageId) {
1109
- const extra = this.lesson.extras || {};
1110
- extra.notionPageId = notionPageId;
1111
- await this.lessonService.postLesson({ ...this.lesson, extras: extra });
1112
- this.toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion' });
1113
1837
  }
1838
+ /**
1839
+ * Calls the LessonNotionService to improve the lesson using AI based on Notion content.
1840
+ */
1114
1841
  async improveNotionWithAI() {
1115
- const md = await this.notionService.getPageInSpecificFormat(this.lesson.extras.notionPageId, NotionExportType.HTML);
1116
- console.log(md);
1117
- // this.lesson.textCoded = md.content;
1118
- // this.togleRender();
1119
- // this.toastService.success({ title: 'Listo', subtitle: 'La lección se mejoró con AI' });
1120
- }
1121
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonEditorComponent, deps: [{ token: LESSONS_TOKEN }, { token: TOAST_ALERTS_TOKEN }, { token: NOTION_SERVICE_TOKEN }, { token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: i0.ChangeDetectorRef }, { token: i2.DialogService }], target: i0.ɵɵFactoryTarget.Component }); }
1122
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.1.1", type: DCLessonEditorComponent, isStandalone: true, selector: "dc-lesson-editor", viewQueries: [{ propertyName: "target", first: true, predicate: ["target"], descendants: true, read: ViewContainerRef }, { propertyName: "dhtml", first: true, predicate: ["dhtml"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"position: relative; margin-bottom: 20px\">\n <div class=\"header-cover\" [style.background-image]=\"cover\"></div>\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n</div>\n\n<br />\n\n<div>\n <div>\n <div>\n <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"saveLesson()\" />\n\n <p-button label=\"Previsualizar\" severity=\"secondary\" (click)=\"togleRender()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"importFromNotion()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"improveNotionWithAI()\" />\n </div>\n\n <div>\n <input pInputText style=\"width: 100%\" [(ngModel)]=\"lesson.title\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <input pInputText style=\"width: 100%\" [(ngModel)]=\"lesson.description\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n </div>\n\n <div style=\"display: flex; align-items: center\">\n <br />\n <br />\n <input pInputText style=\"flex: auto\" [(ngModel)]=\"lesson.prompt\" type=\"text\" placeholder=\"Prompt\" />\n <p-button severity=\"primary\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\"> Generar </p-button>\n </div>\n\n <div>\n <label class=\"checkbox-container\">\n <input type=\"checkbox\" [(ngModel)]=\"lesson.isPublished\" title=\"Cuando termines la edici\u00F3n marca esta casilla\" />\n <span class=\"checkmark\"></span>\n Lecci\u00F3n publicada\n </label>\n <br />\n\n <input pInputText [(ngModel)]=\"lesson.level\" type=\"number\" placeholder=\"Level\" />\n </div>\n\n <div>\n {{ lesson.baseLang | flagEmoji }} -> {{ lesson.targetLang | flagEmoji }} Lecci\u00F3n para hablantes de {{ lesson.baseLang | langDesc : 'es' }} que aprenden\n {{ lesson.targetLang | langDesc : 'es' }}\n </div>\n </div>\n </div>\n\n <hr />\n <span>Componentes: </span>\n <div style=\"display: flex; gap: 10px\">\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.Selector)\" pTooltip=\"Agrega un selector con multiples opciones\">\n Selector\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.Speaker)\" pTooltip=\"Para que una palabra o frase sea reproducible\">\n Speaker\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\" pTooltip=\"Escribe una respuesta en un cuadro de texto\">\n Text\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\" pTooltip=\"Muestra la informaci\u00F3n de un verbo\"> Text </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\" pTooltip=\"Muestra la informaci\u00F3n de un verbo\"> Verb </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\" pTooltip=\"Muestra la informaci\u00F3n de una palabra\">\n Palabra\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\" pTooltip=\"Muestra el texto pero al pica cambia de idioma\">\n Traducci\u00F3n\n </p-button>\n </div>\n\n <hr />\n\n <p-splitter [style]=\"{ height: '80vh' }\" styleClass=\"mb-8\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n (keydown.control.space)=\"togleRender($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [(ngModel)]=\"lesson.textCoded\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n @if (isRendered) {\n <dc-lesson-renderer class=\"text-editor\" [lesson]=\"lesson\" [test]=\"true\"></dc-lesson-renderer>\n }\n </ng-template>\n </p-splitter>\n\n <div class=\"float-button\">\n <p-speeddial [model]=\"items\" [radius]=\"120\" type=\"quarter-circle\" direction=\"up-left\" />\n\n <p-button icon=\"pi pi-save\" (click)=\"saveLesson()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n\n <hr />\n</div>\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i4$2.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "ngmodule", type: ButtonModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i6.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["variant", "fluid", "pSize"] }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i8.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "style", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "component", type: DCLessonRendererComponent, selector: "dc-lesson-renderer", inputs: ["lesson", "test"] }, { kind: "pipe", type: LangDescTranslationPipe, name: "langDesc" }, { kind: "pipe", type: FlagLanguagePipe, name: "flagEmoji" }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i9.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "ngmodule", type: SpeedDialModule }, { kind: "component", type: i2$2.SpeedDial, selector: "p-speeddial, p-speedDial, p-speed-dial", inputs: ["id", "model", "visible", "style", "className", "direction", "transitionDelay", "type", "radius", "mask", "disabled", "hideOnClickOutside", "buttonStyle", "buttonClassName", "maskStyle", "maskClassName", "showIcon", "hideIcon", "rotateAnimation", "ariaLabel", "ariaLabelledBy", "tooltipOptions", "buttonProps"], outputs: ["onVisibleChange", "visibleChange", "onClick", "onShow", "onHide"] }] }); }
1842
+ await this.#lessonNotionService.improveLessonWithNotionAI(this.lesson());
1843
+ }
1844
+ showComponentDetails(data) {
1845
+ alert('showComponentDetails' + JSON.stringify(data));
1846
+ }
1847
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1848
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.4", type: DCLessonEditorComponent, isStandalone: true, selector: "dc-lesson-editor", providers: [LessonNotionService], viewQueries: [{ propertyName: "target", first: true, predicate: ["target"], descendants: true, read: ViewContainerRef }, { propertyName: "dhtml", first: true, predicate: ["dhtml"], descendants: true, static: true }], ngImport: i0, template: "<div style=\"position: relative; margin-bottom: 20px\">\n <div class=\"header-cover\" [style.background-image]=\"coverBackground()\"></div>\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n</div>\n\n<br />\n\n<!-- Lesson Metadata Editor -->\n<dc-lesson-metadata-editor\n [lesson]=\"lesson\"\n [isLoadingLesson]=\"isLoadingLesson\"\n (saveRequest)=\"saveLesson()\"\n (importNotionRequest)=\"importFromNotion()\"\n (improveNotionRequest)=\"improveNotionWithAI()\"\n (generateAIRequest)=\"generateByAI()\"\n (propertyChange)=\"updateLessonProperty($event.property, $event.value)\">\n</dc-lesson-metadata-editor>\n\n<hr />\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\"></dc-lesson-component-adder>\n\n<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li>ID: {{ comp.id }} - Tipo: {{ comp.component }}<button pButton icon=\"pi pi-info\" (click)=\"showComponentDetails(comp)\"></button></li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<p-splitter [style]=\"{ height: '80vh' }\" styleClass=\"mb-8\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [ngModel]=\"lesson()?.textCoded\"\n (ngModelChange)=\"updateLessonProperty('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"lesson()\" [test]=\"true\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n\n<div class=\"float-button\">\n <!-- Removed p-speeddial -->\n <p-button icon=\"pi pi-save\" (click)=\"saveLesson()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n\n<hr />\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}\n"], dependencies: [{ kind: "ngmodule", type: ButtonModule }, { kind: "directive", type: i1$1.ButtonDirective, selector: "[pButton]", inputs: ["iconPos", "loadingIcon", "loading", "severity", "raised", "rounded", "text", "outlined", "size", "plain", "fluid", "label", "icon", "buttonProps"] }, { kind: "component", type: i1$1.Button, selector: "p-button", inputs: ["type", "iconPos", "icon", "badge", "label", "disabled", "loading", "loadingIcon", "raised", "rounded", "text", "plain", "severity", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "fluid", "buttonProps"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i2$6.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "ngmodule", type: CKEditorModule }, { kind: "component", type: i3$1.CKEditorComponent, selector: "ckeditor", inputs: ["editor", "config", "data", "tagName", "watchdog", "editorWatchdogConfig", "disableTwoWayDataBinding", "disabled"], outputs: ["ready", "change", "blur", "focus", "error"] }, { kind: "component", type: CropperComponentModal, selector: "dc-cropper-modal", inputs: ["imgStorageSettings", "buttonLabel", "currentStorage"], outputs: ["imageUploaded", "onImageCropped", "onFileSelected"] }, { kind: "component", type: DCLessonRendererComponent, selector: "dc-lesson-renderer", inputs: ["lessonInput", "lessonIdInput", "test"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: InputTextModule }, { kind: "ngmodule", type: SplitterModule }, { kind: "component", type: i5$1.Splitter, selector: "p-splitter", inputs: ["styleClass", "panelStyleClass", "style", "panelStyle", "stateStorage", "stateKey", "layout", "gutterSize", "step", "minSizes", "panelSizes"], outputs: ["onResizeEnd", "onResizeStart"] }, { kind: "ngmodule", type: TooltipModule }, { kind: "directive", type: i2$5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "appendTo", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "pTooltip", "tooltipDisabled", "tooltipOptions"] }, { kind: "component", type:
1849
+ // Removed SpeedDialModule
1850
+ DCLessonComponentAdderComponent, selector: "dc-lesson-component-adder", outputs: ["componentAdded"] }, { kind: "component", type: // Add the component adder here
1851
+ DCLessonMetadataEditorComponent, selector: "dc-lesson-metadata-editor", inputs: ["lesson", "isLoadingLesson"], outputs: ["saveRequest", "importNotionRequest", "improveNotionRequest", "generateAIRequest", "propertyChange"] }] }); }
1123
1852
  }
1124
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
1853
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
1125
1854
  type: Component,
1126
1855
  args: [{ selector: 'dc-lesson-editor', standalone: true, imports: [
1127
- FormsModule,
1856
+ ButtonModule,
1128
1857
  CKEditorModule,
1129
1858
  CropperComponentModal,
1130
- ButtonModule,
1859
+ DCLessonRendererComponent,
1860
+ FormsModule,
1131
1861
  InputTextModule,
1132
1862
  SplitterModule,
1133
- DCLessonRendererComponent,
1134
- LangDescTranslationPipe,
1135
- FlagLanguagePipe,
1136
1863
  TooltipModule,
1137
- SpeedDialModule,
1138
- ], template: "<div style=\"position: relative; margin-bottom: 20px\">\n <div class=\"header-cover\" [style.background-image]=\"cover\"></div>\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n</div>\n\n<br />\n\n<div>\n <div>\n <div>\n <div style=\"display: flex; gap: 10px; padding: 10px\">\n <p-button label=\"Guardar\" severity=\"primary\" (click)=\"saveLesson()\" />\n\n <p-button label=\"Previsualizar\" severity=\"secondary\" (click)=\"togleRender()\" />\n <p-button label=\"Importar de Notion\" severity=\"help\" (click)=\"importFromNotion()\" />\n <p-button label=\"Mejorar Notion con AI\" severity=\"help\" (click)=\"improveNotionWithAI()\" />\n </div>\n\n <div>\n <input pInputText style=\"width: 100%\" [(ngModel)]=\"lesson.title\" type=\"text\" placeholder=\"Agrega un t\u00EDtulo\" />\n </div>\n <div style=\"margin-top: 4px\">\n <input pInputText style=\"width: 100%\" [(ngModel)]=\"lesson.description\" type=\"text\" placeholder=\"Agrega una descripci\u00F3n\" />\n </div>\n\n <div style=\"display: flex; align-items: center\">\n <br />\n <br />\n <input pInputText style=\"flex: auto\" [(ngModel)]=\"lesson.prompt\" type=\"text\" placeholder=\"Prompt\" />\n <p-button severity=\"primary\" [disabled]=\"isLoadingLesson\" (click)=\"generateByAI()\"> Generar </p-button>\n </div>\n\n <div>\n <label class=\"checkbox-container\">\n <input type=\"checkbox\" [(ngModel)]=\"lesson.isPublished\" title=\"Cuando termines la edici\u00F3n marca esta casilla\" />\n <span class=\"checkmark\"></span>\n Lecci\u00F3n publicada\n </label>\n <br />\n\n <input pInputText [(ngModel)]=\"lesson.level\" type=\"number\" placeholder=\"Level\" />\n </div>\n\n <div>\n {{ lesson.baseLang | flagEmoji }} -> {{ lesson.targetLang | flagEmoji }} Lecci\u00F3n para hablantes de {{ lesson.baseLang | langDesc : 'es' }} que aprenden\n {{ lesson.targetLang | langDesc : 'es' }}\n </div>\n </div>\n </div>\n\n <hr />\n <span>Componentes: </span>\n <div style=\"display: flex; gap: 10px\">\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.Selector)\" pTooltip=\"Agrega un selector con multiples opciones\">\n Selector\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.Speaker)\" pTooltip=\"Para que una palabra o frase sea reproducible\">\n Speaker\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.TextWriter)\" pTooltip=\"Escribe una respuesta en un cuadro de texto\">\n Text\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\" pTooltip=\"Muestra la informaci\u00F3n de un verbo\"> Text </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.VerbSummary)\" pTooltip=\"Muestra la informaci\u00F3n de un verbo\"> Verb </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.WordSummary)\" pTooltip=\"Muestra la informaci\u00F3n de una palabra\">\n Palabra\n </p-button>\n <p-button severity=\"info\" (click)=\"openComponentBuilder(lessonComponentEnum.TranslationSwitcher)\" pTooltip=\"Muestra el texto pero al pica cambia de idioma\">\n Traducci\u00F3n\n </p-button>\n </div>\n\n <hr />\n\n <p-splitter [style]=\"{ height: '80vh' }\" styleClass=\"mb-8\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n (keydown.control.space)=\"togleRender($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [(ngModel)]=\"lesson.textCoded\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n @if (isRendered) {\n <dc-lesson-renderer class=\"text-editor\" [lesson]=\"lesson\" [test]=\"true\"></dc-lesson-renderer>\n }\n </ng-template>\n </p-splitter>\n\n <div class=\"float-button\">\n <p-speeddial [model]=\"items\" [radius]=\"120\" type=\"quarter-circle\" direction=\"up-left\" />\n\n <p-button icon=\"pi pi-save\" (click)=\"saveLesson()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n </div>\n\n <hr />\n</div>\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}\n"] }]
1139
- }], ctorParameters: () => [{ type: LessonsAbstractService, decorators: [{
1140
- type: Inject,
1141
- args: [LESSONS_TOKEN]
1142
- }] }, { type: i5$1.ToastAlertsAbstractService, decorators: [{
1143
- type: Inject,
1144
- args: [TOAST_ALERTS_TOKEN]
1145
- }] }, { type: NotionAbstractService, decorators: [{
1146
- type: Inject,
1147
- args: [NOTION_SERVICE_TOKEN]
1148
- }] }, { type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: i0.ChangeDetectorRef }, { type: i2.DialogService }], propDecorators: { target: [{
1864
+ // Removed SpeedDialModule
1865
+ DCLessonComponentAdderComponent, // Add the component adder here
1866
+ DCLessonMetadataEditorComponent, // Add the metadata editor here
1867
+ JsonPipe,
1868
+ ], providers: [LessonNotionService], template: "<div style=\"position: relative; margin-bottom: 20px\">\n <div class=\"header-cover\" [style.background-image]=\"coverBackground()\"></div>\n\n <dc-cropper-modal\n style=\"position: absolute; top: 10px; left: 20px\"\n [buttonLabel]=\"'Carga una portada'\"\n [imgStorageSettings]=\"coverStorageSettings\"\n (imageUploaded)=\"onImageUploaded($event)\"></dc-cropper-modal>\n</div>\n\n<br />\n\n<!-- Lesson Metadata Editor -->\n<dc-lesson-metadata-editor\n [lesson]=\"lesson\"\n [isLoadingLesson]=\"isLoadingLesson\"\n (saveRequest)=\"saveLesson()\"\n (importNotionRequest)=\"importFromNotion()\"\n (improveNotionRequest)=\"improveNotionWithAI()\"\n (generateAIRequest)=\"generateByAI()\"\n (propertyChange)=\"updateLessonProperty($event.property, $event.value)\">\n</dc-lesson-metadata-editor>\n\n<hr />\n\n<!-- Component Adder -->\n<dc-lesson-component-adder (componentAdded)=\"onComponentAdded($event)\"></dc-lesson-component-adder>\n\n<!-- Display Added Components -->\n<div class=\"added-components-list\" style=\"margin-top: 15px; margin-bottom: 15px\">\n <h4>Componentes Agregados:</h4>\n @if (dynamicComponentsArray().length > 0) {\n <ul>\n @for (comp of dynamicComponentsArray(); track comp.id) {\n <li>ID: {{ comp.id }} - Tipo: {{ comp.component }}<button pButton icon=\"pi pi-info\" (click)=\"showComponentDetails(comp)\"></button></li>\n }\n </ul>\n } @else {\n <p>A\u00FAn no se han agregado componentes.</p>\n }\n</div>\n\n<hr />\n\n<!-- Text Editor and Renderer -->\n<p-splitter [style]=\"{ height: '80vh' }\" styleClass=\"mb-8\">\n <ng-template pTemplate>\n <ckeditor\n (keydown.control.s)=\"saveLesson($event)\"\n class=\"text-editor\"\n [editor]=\"editor\"\n [ngModel]=\"lesson()?.textCoded\"\n (ngModelChange)=\"updateLessonProperty('textCoded', $event)\">\n </ckeditor>\n </ng-template>\n\n <ng-template pTemplate>\n <dc-lesson-renderer class=\"text-editor\" [lessonInput]=\"lesson()\" [test]=\"true\"></dc-lesson-renderer>\n </ng-template>\n</p-splitter>\n\n<div class=\"float-button\">\n <!-- Removed p-speeddial -->\n <p-button icon=\"pi pi-save\" (click)=\"saveLesson()\" severity=\"primary\" [rounded]=\"true\" [raised]=\"true\" pTooltip=\"Guardar (Ctrl + S)\"> </p-button>\n</div>\n\n<hr />\n", styles: [".btn{padding:.5rem 1rem;border-radius:4px;border:1px solid transparent;cursor:pointer}.btn-primary{background-color:#007bff;color:#fff}.btn-outline-primary{border-color:#007bff;color:#007bff}.btn-secondary{background-color:#6c757d;color:#fff}.btn-outline-secondary{border-color:#6c757d;color:#6c757d}.btn-rounded{border-radius:50%}.form-control{padding:.375rem .75rem;border:1px solid #ced4da;border-radius:.25rem}.splitter{display:flex;gap:1rem}.splitter-panel{flex:1}.checkbox-container{display:inline-flex;align-items:center;gap:.5rem;cursor:pointer}.mr-2{margin-right:.5rem}.header-cover{width:100%;height:250px;background-position:center;background-repeat:no-repeat;background-size:cover;position:relative}.float-button{position:fixed;bottom:3.5rem;right:2rem;z-index:1000;display:flex;gap:1px}.float-button :host ::ng-deep .p-button{width:4rem;height:4rem;border-radius:50%}.text-editor{width:-webkit-fill-available;overflow-y:auto}:host ::ng-deep .p-inputtext{background:#fff3}\n"] }]
1869
+ }], ctorParameters: () => [], propDecorators: { target: [{
1149
1870
  type: ViewChild,
1150
1871
  args: ['target', { read: ViewContainerRef }]
1151
1872
  }], dhtml: [{
@@ -1158,10 +1879,10 @@ class LessonDynamicComponent {
1158
1879
  constructor() {
1159
1880
  this.settings = {};
1160
1881
  }
1161
- static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: LessonDynamicComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1162
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.1.1", type: LessonDynamicComponent, isStandalone: true, selector: "app-lesson-component", inputs: { settings: "settings" }, ngImport: i0, template: '<div>no template</div>', isInline: true }); }
1882
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonDynamicComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
1883
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.4", type: LessonDynamicComponent, isStandalone: true, selector: "app-lesson-component", inputs: { settings: "settings" }, ngImport: i0, template: '<div>no template</div>', isInline: true }); }
1163
1884
  }
1164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImport: i0, type: LessonDynamicComponent, decorators: [{
1885
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonDynamicComponent, decorators: [{
1165
1886
  type: Component,
1166
1887
  args: [{
1167
1888
  selector: 'app-lesson-component',