@dataclouder/ngx-lessons 0.0.29 → 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.
- package/fesm2022/dataclouder-ngx-lessons.mjs +1205 -514
- package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
- package/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.d.ts +6 -8
- package/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.d.ts +11 -0
- package/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.d.ts +39 -38
- package/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.d.ts +22 -0
- package/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.d.ts +32 -37
- package/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.d.ts +7 -17
- package/lib/components/lesson-mini-components/components/ComponentBuilder.d.ts +7 -2
- package/lib/components/lesson-mini-components/components/lessons.clases.d.ts +17 -42
- package/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.d.ts +13 -0
- package/lib/components/lesson-mini-components/components/speaker/speaker.component.d.ts +12 -0
- package/lib/services/lesson-ai.service.d.ts +18 -0
- package/lib/services/lesson-notion.service.d.ts +35 -0
- package/lib/services/lesson-utils.service.d.ts +34 -0
- package/package.json +3 -2
- package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.html +40 -35
- package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.scss +15 -2
- package/src/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.ts +16 -18
- package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.css +1 -0
- package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.html +46 -0
- package/src/lib/components/dc-lessons/dc-lesson-component-adder/dc-lesson-component-adder.component.ts +52 -0
- package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.html +54 -92
- package/src/lib/components/dc-lessons/dc-lesson-editor/dc-lesson-editor.component.ts +268 -230
- package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.css +1 -0
- package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.html +72 -0
- package/src/lib/components/dc-lessons/dc-lesson-metadata-editor/dc-lesson-metadata-editor.component.ts +60 -0
- package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.html +23 -27
- package/src/lib/components/dc-lessons/dc-lesson-renderer/dc-lesson-renderer.component.ts +247 -186
- package/src/lib/components/dc-lessons/lesson-form/lesson-form.component.ts +2 -2
- package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.html +3 -3
- package/src/lib/components/dc-lessons/lesson-list/dc-lesson-list.component.ts +28 -46
- package/src/lib/components/lesson-mini-components/components/ComponentBuilder.ts +23 -15
- package/src/lib/components/lesson-mini-components/components/lessons.clases.ts +32 -66
- package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.html +62 -58
- package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.ts +2 -2
- package/src/lib/components/lesson-mini-components/components/selector/selector.component.html +1 -2
- package/src/lib/components/lesson-mini-components/components/selector/selector.component.ts +2 -2
- package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.html +5 -27
- package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.ts +38 -25
- package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.html +9 -7
- package/src/lib/components/lesson-mini-components/components/speaker/speaker.component.ts +30 -26
- package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcher.component.ts +2 -2
- package/src/lib/components/lesson-mini-components/components/translationSwitcher/translationSwitcherBuilder/translationSwitcherBuilder.component.ts +2 -2
- package/src/lib/services/lesson-ai.service.ts +103 -0
- package/src/lib/services/lesson-notion.service.ts +161 -0
- package/src/lib/services/lesson-utils.service.ts +181 -0
- package/src/lib/components/lesson-mini-components/components/selector/selector-builder/selector-builder.component.spec.ts +0 -25
- package/src/lib/components/lesson-mini-components/components/speaker/speaker-builder/speaker-builder.component.spec.ts +0 -25
- 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,
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
17
|
+
import { NgxTtsComponent } from '@dataclouder/ngx-tts';
|
|
18
|
+
import * as i2$3 from 'primeng/paginator';
|
|
17
19
|
import { PaginatorModule } from 'primeng/paginator';
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import
|
|
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
|
|
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
|
|
33
|
+
import * as i3$1 from '@ckeditor/ckeditor5-angular';
|
|
29
34
|
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
30
|
-
import * as
|
|
35
|
+
import * as i5$1 from 'primeng/splitter';
|
|
31
36
|
import { SplitterModule } from 'primeng/splitter';
|
|
32
|
-
import * as
|
|
37
|
+
import * as i2$5 from 'primeng/tooltip';
|
|
33
38
|
import { TooltipModule } from 'primeng/tooltip';
|
|
34
|
-
import {
|
|
35
|
-
import
|
|
36
|
-
import
|
|
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
|
|
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
|
|
78
|
-
|
|
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
|
-
|
|
84
|
+
const stringCode = `~${JSON.stringify(code)}~`;
|
|
85
|
+
alert(stringCode);
|
|
84
86
|
}
|
|
85
|
-
|
|
87
|
+
getComponentData() {
|
|
86
88
|
const code = this.getCode();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this.ref.close(code);
|
|
89
|
+
const id = nanoid().replace(/-/g, '_');
|
|
90
|
+
code.id = id;
|
|
91
|
+
return { str: `~${JSON.stringify(code)}~`, obj: code };
|
|
91
92
|
}
|
|
92
|
-
|
|
93
|
-
|
|
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);
|
|
98
|
+
}
|
|
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.
|
|
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.
|
|
117
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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.
|
|
156
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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,
|
|
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.
|
|
207
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
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.
|
|
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,
|
|
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.
|
|
225
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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.
|
|
268
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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.
|
|
288
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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: [
|
|
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,16 +315,77 @@ 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.
|
|
312
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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 }); }
|
|
313
320
|
}
|
|
314
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
321
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: TranslationSwitcherComponent, decorators: [{
|
|
315
322
|
type: Component,
|
|
316
|
-
args: [{ selector: 'app-translation-switcher', standalone: true, imports: [
|
|
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"] }]
|
|
317
324
|
}], propDecorators: { config: [{
|
|
318
325
|
type: Input
|
|
319
326
|
}] } });
|
|
320
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"] }] }); }
|
|
379
|
+
}
|
|
380
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: SpeakerComponent, decorators: [{
|
|
381
|
+
type: Component,
|
|
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"] }]
|
|
383
|
+
}], propDecorators: { config: [{
|
|
384
|
+
type: Input
|
|
385
|
+
}], tts: [{
|
|
386
|
+
type: Input
|
|
387
|
+
}] } });
|
|
388
|
+
|
|
321
389
|
var LessonComponentEnum;
|
|
322
390
|
(function (LessonComponentEnum) {
|
|
323
391
|
LessonComponentEnum["Selector"] = "selector";
|
|
@@ -329,7 +397,7 @@ var LessonComponentEnum;
|
|
|
329
397
|
})(LessonComponentEnum || (LessonComponentEnum = {}));
|
|
330
398
|
const LessonComponentBuilders = {
|
|
331
399
|
[LessonComponentEnum.Selector]: SelectorBuilderComponent,
|
|
332
|
-
|
|
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
|
-
|
|
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.
|
|
398
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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.
|
|
429
|
-
static { this.ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.
|
|
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.
|
|
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',
|
|
@@ -460,18 +538,14 @@ var NotionExportType;
|
|
|
460
538
|
var EventCard;
|
|
461
539
|
(function (EventCard) {
|
|
462
540
|
EventCard["Edit"] = "edit";
|
|
463
|
-
EventCard["
|
|
464
|
-
EventCard["
|
|
541
|
+
EventCard["Delete"] = "delete";
|
|
542
|
+
EventCard["Select"] = "select";
|
|
465
543
|
EventCard["Qr"] = "qr";
|
|
466
544
|
})(EventCard || (EventCard = {}));
|
|
467
545
|
class DcLessonCardComponent {
|
|
468
546
|
constructor() {
|
|
469
|
-
this.
|
|
470
|
-
|
|
471
|
-
this.take = new EventEmitter();
|
|
472
|
-
this.edit = new EventEmitter();
|
|
473
|
-
this.remove = new EventEmitter();
|
|
474
|
-
this.qr = new EventEmitter();
|
|
547
|
+
this.showOptions = true;
|
|
548
|
+
this.onAction = new EventEmitter();
|
|
475
549
|
this.coverUrl = 'assets/background/default-background.webp';
|
|
476
550
|
this.eventType = EventCard;
|
|
477
551
|
this.items = [
|
|
@@ -486,14 +560,14 @@ class DcLessonCardComponent {
|
|
|
486
560
|
label: 'Eliminar',
|
|
487
561
|
icon: 'pi pi-trash',
|
|
488
562
|
command: () => {
|
|
489
|
-
this.eventCard(EventCard.
|
|
563
|
+
this.eventCard(EventCard.Delete);
|
|
490
564
|
},
|
|
491
565
|
},
|
|
492
566
|
{
|
|
493
567
|
label: 'Tomar lección',
|
|
494
568
|
icon: 'pi pi-play',
|
|
495
569
|
command: () => {
|
|
496
|
-
this.eventCard(EventCard.
|
|
570
|
+
this.eventCard(EventCard.Select);
|
|
497
571
|
},
|
|
498
572
|
},
|
|
499
573
|
];
|
|
@@ -506,36 +580,30 @@ class DcLessonCardComponent {
|
|
|
506
580
|
eventCard(eventType) {
|
|
507
581
|
switch (eventType) {
|
|
508
582
|
case EventCard.Edit:
|
|
509
|
-
this.
|
|
583
|
+
this.onAction.emit({ action: 'edit', item: this.lesson });
|
|
510
584
|
break;
|
|
511
|
-
case EventCard.
|
|
512
|
-
this.
|
|
585
|
+
case EventCard.Delete:
|
|
586
|
+
this.onAction.emit({ action: 'delete', item: this.lesson });
|
|
513
587
|
break;
|
|
514
|
-
case EventCard.
|
|
515
|
-
this.
|
|
588
|
+
case EventCard.Select:
|
|
589
|
+
this.onAction.emit({ action: 'select', item: this.lesson });
|
|
516
590
|
break;
|
|
517
591
|
case EventCard.Qr:
|
|
518
|
-
this.
|
|
592
|
+
this.onAction.emit({ action: 'qr', item: this.lesson });
|
|
519
593
|
break;
|
|
520
594
|
}
|
|
521
595
|
}
|
|
522
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
523
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
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"] }] }); }
|
|
524
598
|
}
|
|
525
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
599
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DcLessonCardComponent, decorators: [{
|
|
526
600
|
type: Component,
|
|
527
|
-
args: [{ selector: 'dc-lesson-card', standalone: true, imports: [
|
|
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"] }]
|
|
528
602
|
}], propDecorators: { lesson: [{
|
|
529
603
|
type: Input
|
|
530
|
-
}],
|
|
604
|
+
}], showOptions: [{
|
|
531
605
|
type: Input
|
|
532
|
-
}],
|
|
533
|
-
type: Output
|
|
534
|
-
}], edit: [{
|
|
535
|
-
type: Output
|
|
536
|
-
}], remove: [{
|
|
537
|
-
type: Output
|
|
538
|
-
}], qr: [{
|
|
606
|
+
}], onAction: [{
|
|
539
607
|
type: Output
|
|
540
608
|
}] } });
|
|
541
609
|
|
|
@@ -559,15 +627,10 @@ class DCLessonListComponent extends PaginationBase {
|
|
|
559
627
|
this.route = route;
|
|
560
628
|
this.lessonsService = lessonsService;
|
|
561
629
|
this.toastrService = toastrService;
|
|
562
|
-
this.
|
|
630
|
+
this.showOptions = true;
|
|
563
631
|
this.customFilters = [];
|
|
564
|
-
this.viewType = '
|
|
565
|
-
|
|
566
|
-
this.onTakeLesson = new EventEmitter();
|
|
567
|
-
this.onEditLesson = new EventEmitter();
|
|
568
|
-
this.onRemoveLesson = new EventEmitter();
|
|
569
|
-
this.qr = new EventEmitter();
|
|
570
|
-
this.onNewLesson = new EventEmitter();
|
|
632
|
+
this.viewType = 'cards';
|
|
633
|
+
// readonly actions = input<MenuItem[]>(TableViewActions);
|
|
571
634
|
this.columns = tableViewColumns;
|
|
572
635
|
this.cardComponent = null;
|
|
573
636
|
this.cardEventSubs = [];
|
|
@@ -586,14 +649,8 @@ class DCLessonListComponent extends PaginationBase {
|
|
|
586
649
|
subscribeToCardEvents() {
|
|
587
650
|
this.outlets.forEach((outlet) => {
|
|
588
651
|
const instance = outlet.componentInstance;
|
|
589
|
-
this.cardEventSubs.push(instance.
|
|
590
|
-
this.
|
|
591
|
-
}), instance.edit.subscribe((lesson) => {
|
|
592
|
-
this.editLesson(lesson);
|
|
593
|
-
}), instance.remove.subscribe((lesson) => {
|
|
594
|
-
this.removeLesson(lesson);
|
|
595
|
-
}), instance.qr.subscribe((lesson) => {
|
|
596
|
-
this.generateQR(lesson);
|
|
652
|
+
this.cardEventSubs.push(instance.onAction.subscribe((lesson) => {
|
|
653
|
+
this.doOrEmitAction(lesson);
|
|
597
654
|
}));
|
|
598
655
|
});
|
|
599
656
|
}
|
|
@@ -622,23 +679,10 @@ class DCLessonListComponent extends PaginationBase {
|
|
|
622
679
|
this.cdr.detectChanges();
|
|
623
680
|
}
|
|
624
681
|
}
|
|
625
|
-
goToLessonDetails(id) {
|
|
626
|
-
// this.router.navigate([`${RouteNames.App}/${RouteNames.LessonsV2}/details/${id}`]);
|
|
627
|
-
alert('TODO: goToLessonDetails');
|
|
628
|
-
}
|
|
629
682
|
search(searchText) {
|
|
630
683
|
this.filterConfig['text'] = searchText;
|
|
631
684
|
this.getPaginatedLessons(this.filterConfig);
|
|
632
685
|
}
|
|
633
|
-
generateQR(lesson) {
|
|
634
|
-
this.qr.emit(lesson);
|
|
635
|
-
}
|
|
636
|
-
takeLesson(lesson) {
|
|
637
|
-
this.onTakeLesson.emit(lesson);
|
|
638
|
-
}
|
|
639
|
-
editLesson(lesson) {
|
|
640
|
-
this.onEditLesson.emit(lesson);
|
|
641
|
-
}
|
|
642
686
|
removeLesson(lesson) {
|
|
643
687
|
const response = confirm('¿Estás seguro de querer eliminar esta lección?');
|
|
644
688
|
if (response) {
|
|
@@ -649,30 +693,44 @@ class DCLessonListComponent extends PaginationBase {
|
|
|
649
693
|
}
|
|
650
694
|
}
|
|
651
695
|
newLesson() {
|
|
652
|
-
this.
|
|
696
|
+
this.onAction.emit({ action: 'new' });
|
|
653
697
|
}
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
698
|
+
applyFilterBarEvent(filterEvent) {
|
|
699
|
+
if (filterEvent.action == 'changeView') {
|
|
700
|
+
this.viewType = this.viewType === 'table' ? 'cards' : 'table';
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
console.log('filterChanged in dc-lesson-list', filterEvent);
|
|
705
|
+
this.filterConfig = filterEvent.item;
|
|
706
|
+
this.filterConfig.returnProps = returnProperties;
|
|
707
|
+
this.getPaginatedLessons(this.filterConfig);
|
|
708
|
+
}
|
|
659
709
|
}
|
|
660
710
|
loadData() {
|
|
661
711
|
return this.getPaginatedLessons(this.filterConfig);
|
|
662
712
|
}
|
|
663
|
-
|
|
664
|
-
|
|
713
|
+
doOrEmitAction(actionEvent) {
|
|
714
|
+
if (actionEvent.action === 'delete') {
|
|
715
|
+
this.removeLesson(actionEvent.item);
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
this.onAction.emit(actionEvent);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
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"] }] }); }
|
|
665
723
|
}
|
|
666
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
724
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonListComponent, decorators: [{
|
|
667
725
|
type: Component,
|
|
668
|
-
args: [{ selector: 'dc-lesson-list', standalone: true, imports: [
|
|
669
|
-
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i1$
|
|
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: [{
|
|
670
728
|
type: Inject,
|
|
671
729
|
args: [LESSONS_TOKEN]
|
|
672
|
-
}] }, { type:
|
|
730
|
+
}] }, { type: i4$1.ToastAlertsAbstractService, decorators: [{
|
|
673
731
|
type: Inject,
|
|
674
732
|
args: [TOAST_ALERTS_TOKEN]
|
|
675
|
-
}] }], propDecorators: {
|
|
733
|
+
}] }], propDecorators: { showOptions: [{
|
|
676
734
|
type: Input
|
|
677
735
|
}], customCardComponent: [{
|
|
678
736
|
type: Input
|
|
@@ -680,34 +738,22 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.1.1", ngImpor
|
|
|
680
738
|
type: Input
|
|
681
739
|
}], viewType: [{
|
|
682
740
|
type: Input
|
|
683
|
-
}], actions: [{
|
|
684
|
-
type: Input
|
|
685
|
-
}], onTakeLesson: [{
|
|
686
|
-
type: Output
|
|
687
|
-
}], onEditLesson: [{
|
|
688
|
-
type: Output
|
|
689
|
-
}], onRemoveLesson: [{
|
|
690
|
-
type: Output
|
|
691
|
-
}], qr: [{
|
|
692
|
-
type: Output
|
|
693
|
-
}], onNewLesson: [{
|
|
694
|
-
type: Output
|
|
695
741
|
}], outlets: [{
|
|
696
742
|
type: ViewChildren,
|
|
697
743
|
args: ['outlet']
|
|
698
744
|
}] } });
|
|
699
745
|
|
|
700
746
|
class DCLessonFormComponent {
|
|
701
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
702
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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 }] }); }
|
|
703
749
|
}
|
|
704
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
750
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonFormComponent, decorators: [{
|
|
705
751
|
type: Component,
|
|
706
|
-
args: [{ selector: 'dc-lesson-form', standalone: true, imports: [
|
|
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"] }]
|
|
707
753
|
}] });
|
|
708
754
|
|
|
709
|
-
//
|
|
710
|
-
const
|
|
755
|
+
// Re-define or import constants if they are not exported from the component file
|
|
756
|
+
const DEFAULT_LESSON_AGENT_CARD = {
|
|
711
757
|
conversationSettings: {
|
|
712
758
|
conversationType: ConversationType.General,
|
|
713
759
|
textEngine: TextEngines.SimpleText,
|
|
@@ -723,105 +769,270 @@ const DefaultLessonAgentCard = {
|
|
|
723
769
|
},
|
|
724
770
|
model: { provider: 'google' },
|
|
725
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
|
+
|
|
726
846
|
class DCLessonRendererComponent {
|
|
727
|
-
constructor(
|
|
728
|
-
|
|
729
|
-
this.
|
|
730
|
-
this.
|
|
731
|
-
this.
|
|
732
|
-
|
|
733
|
-
this.
|
|
734
|
-
this.
|
|
735
|
-
this.
|
|
736
|
-
this.
|
|
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 ---
|
|
737
866
|
this.components = {};
|
|
738
|
-
this.mainForm = new
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
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
|
+
});
|
|
747
909
|
}
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
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 = '';
|
|
754
920
|
}
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
//
|
|
764
|
-
|
|
765
|
-
//
|
|
766
|
-
|
|
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) {
|
|
767
937
|
const r1 = new RegExp('~(.+?)~', 'g');
|
|
768
938
|
let count = 0;
|
|
769
|
-
|
|
770
|
-
const
|
|
939
|
+
const createdComponents = {};
|
|
940
|
+
const htmlContent = lessonData.textCoded.replace(r1, (_matching, jsonCoded) => {
|
|
771
941
|
const componentName = `dynamicComp${count}`;
|
|
772
942
|
count++;
|
|
773
|
-
const componentRef = this.
|
|
943
|
+
const componentRef = this._createComponentReferenceWithJson(jsonCoded, lessonData);
|
|
774
944
|
if (!componentRef) {
|
|
775
|
-
|
|
945
|
+
console.error(`Failed to create component for: ${jsonCoded}`);
|
|
946
|
+
return '<!-- component creation failed -->'; // Placeholder in HTML
|
|
776
947
|
}
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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.`);
|
|
782
973
|
}
|
|
783
|
-
return `<span id="${componentName}"></span>`;
|
|
784
974
|
});
|
|
785
|
-
this.dynamicLesson.nativeElement.innerHTML = lessonHtml;
|
|
786
|
-
for (let compName of Object.keys(this.components)) {
|
|
787
|
-
// Interpolar componentes dentro del lugar correspondiente
|
|
788
|
-
const elementRef = document.getElementById(compName);
|
|
789
|
-
this.addComponentToNode(this.components[compName], elementRef);
|
|
790
|
-
}
|
|
791
975
|
}
|
|
792
|
-
|
|
976
|
+
_addComponentToNode(componentRef, nodeDOM) {
|
|
793
977
|
this.renderer.appendChild(nodeDOM, componentRef.location.nativeElement);
|
|
794
978
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
//
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
|
807
1015
|
}
|
|
808
|
-
const componentRef = this.viewContainerRef.createComponent(LessonClass);
|
|
809
|
-
componentRef.instance.config = componentConfig || lessonCodedConfig;
|
|
810
|
-
return componentRef;
|
|
811
1016
|
}
|
|
1017
|
+
// --- Evaluation Logic ---
|
|
812
1018
|
async evaluateForms() {
|
|
1019
|
+
this.mainForm.markAllAsTouched(); // Mark all controls for validation feedback
|
|
813
1020
|
if (!this.mainForm.valid) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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' });
|
|
818
1028
|
return;
|
|
819
1029
|
}
|
|
820
1030
|
const rates = { correct: 0, incorrect: 0, score: 0 };
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
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') {
|
|
825
1036
|
try {
|
|
826
1037
|
const result = instance.evaluate();
|
|
827
1038
|
if (result) {
|
|
@@ -832,350 +1043,830 @@ class DCLessonRendererComponent {
|
|
|
832
1043
|
}
|
|
833
1044
|
}
|
|
834
1045
|
catch (err) {
|
|
835
|
-
console.error('
|
|
1046
|
+
console.error('Error during evaluation for component:', controlName, instance, err);
|
|
1047
|
+
rates.incorrect++; // Count errors as incorrect
|
|
836
1048
|
}
|
|
837
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;
|
|
838
1056
|
}
|
|
839
|
-
|
|
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' });
|
|
840
1064
|
return;
|
|
841
1065
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
//
|
|
846
|
-
//
|
|
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
|
+
// }
|
|
847
1080
|
if (status === 'passed') {
|
|
848
|
-
this.toastrService.success({ subtitle:
|
|
1081
|
+
this.toastrService.success({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%.`, title: '¡Muy bien!' });
|
|
849
1082
|
}
|
|
850
1083
|
else {
|
|
851
|
-
this.toastrService.warn({ subtitle:
|
|
1084
|
+
this.toastrService.warn({ subtitle: `Calificación: ${Math.round(rates.score * 100)}%. Revisa tus respuestas.`, title: 'Casi lo logras' });
|
|
852
1085
|
}
|
|
853
1086
|
}
|
|
1087
|
+
// --- AI Chat Logic ---
|
|
854
1088
|
async startAI() {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
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' });
|
|
872
1113
|
}
|
|
873
|
-
console.log('lessonText', lessonText);
|
|
874
|
-
const lessonAgentCard = DefaultLessonAgentCard;
|
|
875
|
-
lessonAgentCard.characterCard.data.scenario = scenario;
|
|
876
|
-
lessonAgentCard.characterCard.data.post_history_instructions += `\n${userInformationPrompt}`;
|
|
877
|
-
this.agentMasterLesson = lessonAgentCard;
|
|
878
|
-
this.evaluatorAgentCard = this.getDefualtLessonEvaluatorAgentCard(lessonText);
|
|
879
|
-
this.chatVisible = true;
|
|
880
|
-
}
|
|
881
|
-
getDefualtLessonEvaluatorAgentCard(lessonText) {
|
|
882
|
-
return {
|
|
883
|
-
expectedResponseType: `interface EvalResult {
|
|
884
|
-
score: number; // Score of the user's response 0 to 3
|
|
885
|
-
feedback: string; // Feedback of the user's understanding of the conversation
|
|
886
|
-
}`,
|
|
887
|
-
messages: [],
|
|
888
|
-
model: { id: 'gpt-4o-mini', provider: 'openai' },
|
|
889
|
-
sources: [lessonText],
|
|
890
|
-
task: `User is reading a taking a lesson, now their are having a conversation,
|
|
891
|
-
you have to evaluate the current conversation, and give a feedback of the user understanding of the lesson,
|
|
892
|
-
this is the lesson: ${lessonText}`,
|
|
893
|
-
};
|
|
894
1114
|
}
|
|
895
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
896
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.
|
|
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"] }] }); }
|
|
897
1117
|
}
|
|
898
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1118
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonRendererComponent, decorators: [{
|
|
899
1119
|
type: Component,
|
|
900
|
-
args: [{ selector: 'dc-lesson-renderer', standalone: true, imports: [
|
|
901
|
-
}], ctorParameters: () => [
|
|
902
|
-
type: Inject,
|
|
903
|
-
args: [TOAST_ALERTS_TOKEN]
|
|
904
|
-
}] }, { type: LessonsAbstractService, decorators: [{
|
|
905
|
-
type: Inject,
|
|
906
|
-
args: [LESSONS_TOKEN]
|
|
907
|
-
}] }, { type: i2$4.AgentCardsAbstractService, decorators: [{
|
|
908
|
-
type: Inject,
|
|
909
|
-
args: [CONVERSATION_AI_TOKEN]
|
|
910
|
-
}] }, { type: i2$4.DCConversationPromptBuilderService }], propDecorators: { lesson: [{
|
|
911
|
-
type: Input
|
|
912
|
-
}], test: [{
|
|
913
|
-
type: Input
|
|
914
|
-
}], 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: [{
|
|
915
1122
|
type: ViewChild,
|
|
916
1123
|
args: ['dynamicLesson', { static: true }]
|
|
917
1124
|
}] } });
|
|
918
1125
|
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
this
|
|
923
|
-
this
|
|
924
|
-
|
|
925
|
-
this.
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
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;
|
|
943
1154
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
},
|
|
962
|
-
{
|
|
963
|
-
tooltipOptions: {
|
|
964
|
-
tooltipLabel: 'Entrada de texto: Escribe una respuesta en un cuadro de texto',
|
|
965
|
-
tooltipPosition: 'bottom',
|
|
966
|
-
},
|
|
967
|
-
icon: 'pi pi-pencil',
|
|
968
|
-
command: () => this.openComponentBuilder('textWriter'),
|
|
969
|
-
},
|
|
970
|
-
{
|
|
971
|
-
tooltipOptions: { tooltipLabel: 'Verbo: Para ver datos de un verbo', tooltipPosition: 'bottom' },
|
|
972
|
-
icon: 'pi pi-eye',
|
|
973
|
-
command: () => this.openComponentBuilder('verbSummary'),
|
|
974
|
-
},
|
|
975
|
-
{
|
|
976
|
-
tooltipOptions: { tooltipLabel: 'Palabra: Para ver datos de una palabra', tooltipPosition: 'bottom' },
|
|
977
|
-
icon: 'pi pi-file-word',
|
|
978
|
-
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,
|
|
979
1172
|
},
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
this.
|
|
985
|
-
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;
|
|
1179
|
+
}
|
|
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;
|
|
986
1185
|
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
if (!this.lesson) {
|
|
990
|
-
this.toastService.warn({ title: 'No se encontró la lección', subtitle: 'Quiza el id es incorrecto' });
|
|
1186
|
+
finally {
|
|
1187
|
+
this.isLoading.set(false);
|
|
991
1188
|
}
|
|
992
|
-
this.togleRender();
|
|
993
|
-
this.updateCover();
|
|
994
1189
|
}
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
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
|
+
}
|
|
999
1213
|
}
|
|
1000
1214
|
else {
|
|
1001
|
-
|
|
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
|
+
}
|
|
1002
1230
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
this.
|
|
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);
|
|
1011
1251
|
}
|
|
1012
|
-
tag.input.nativeElement.value = '';
|
|
1013
1252
|
}
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
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;
|
|
1017
1264
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
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).' });
|
|
1023
1274
|
}
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
this.
|
|
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);
|
|
1028
1282
|
}
|
|
1029
|
-
this.togleRender();
|
|
1030
|
-
return lesson;
|
|
1031
1283
|
}
|
|
1032
|
-
|
|
1033
|
-
|
|
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) {
|
|
1034
1303
|
return;
|
|
1035
1304
|
}
|
|
1036
|
-
//
|
|
1037
|
-
|
|
1038
|
-
//
|
|
1039
|
-
//
|
|
1040
|
-
//
|
|
1041
|
-
//
|
|
1042
|
-
//
|
|
1043
|
-
//
|
|
1044
|
-
//
|
|
1045
|
-
//
|
|
1046
|
-
//
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
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
|
+
};
|
|
1051
1449
|
}
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
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;
|
|
1059
1468
|
}
|
|
1469
|
+
// Services
|
|
1470
|
+
#dialogService;
|
|
1471
|
+
#toastService;
|
|
1472
|
+
// Moved logic from DCLessonEditorComponent
|
|
1060
1473
|
openComponentBuilder(type) {
|
|
1061
|
-
|
|
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
|
+
});
|
|
1492
|
+
}
|
|
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
|
+
});
|
|
1062
1671
|
}
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
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
|
+
});
|
|
1069
1684
|
}
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
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();
|
|
1075
1764
|
}
|
|
1076
|
-
this.updateCover();
|
|
1077
|
-
this.saveLesson();
|
|
1078
1765
|
}
|
|
1079
1766
|
openCropper() {
|
|
1080
|
-
|
|
1767
|
+
// Correctly define openCropper
|
|
1768
|
+
this.isCropperVisible.set(true);
|
|
1081
1769
|
}
|
|
1770
|
+
// isLoadingLesson signal is used directly
|
|
1082
1771
|
async generateByAI() {
|
|
1083
|
-
|
|
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);
|
|
1084
1778
|
try {
|
|
1085
|
-
|
|
1086
|
-
await this.
|
|
1087
|
-
|
|
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.' });
|
|
1088
1801
|
}
|
|
1089
1802
|
finally {
|
|
1090
|
-
this.isLoadingLesson
|
|
1803
|
+
this.isLoadingLesson.set(false); // Stop loading
|
|
1091
1804
|
}
|
|
1092
1805
|
}
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
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
|
+
*/
|
|
1096
1810
|
async onImageUploaded(event) {
|
|
1097
|
-
|
|
1098
|
-
this.
|
|
1099
|
-
|
|
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
|
+
*/
|
|
1100
1820
|
async importFromNotion() {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
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
|
|
1106
1829
|
}
|
|
1107
|
-
|
|
1830
|
+
// If newContent is null, the service handled errors/toasts
|
|
1108
1831
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
notionPageId = this.extractNotionPageId(response);
|
|
1115
|
-
if (!notionPageId)
|
|
1116
|
-
return;
|
|
1117
|
-
this.linkWithNotion(notionPageId);
|
|
1118
|
-
}
|
|
1119
|
-
this.toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
|
|
1120
|
-
const md = await this.notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
|
|
1121
|
-
console.log(md);
|
|
1122
|
-
this.lesson.textCoded = md.content;
|
|
1123
|
-
this.togleRender();
|
|
1124
|
-
this.toastService.success({ title: 'Listo', subtitle: 'La lección se importó correctamente' });
|
|
1125
|
-
}
|
|
1126
|
-
extractNotionPageId(url) {
|
|
1127
|
-
const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
|
|
1128
|
-
const match = url.match(notionIdRegex);
|
|
1129
|
-
const notionId = match ? match[0] : null;
|
|
1130
|
-
if (!notionId) {
|
|
1131
|
-
this.toastService.error({
|
|
1132
|
-
title: 'URL inválido',
|
|
1133
|
-
subtitle: 'Por favor ingresa una URL válida de Notion',
|
|
1134
|
-
});
|
|
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
|
|
1135
1836
|
}
|
|
1136
|
-
return notionId;
|
|
1137
|
-
}
|
|
1138
|
-
async linkWithNotion(notionPageId) {
|
|
1139
|
-
const extra = this.lesson.extras || {};
|
|
1140
|
-
extra.notionPageId = notionPageId;
|
|
1141
|
-
await this.lessonService.postLesson({ ...this.lesson, extras: extra });
|
|
1142
|
-
this.toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion' });
|
|
1143
1837
|
}
|
|
1838
|
+
/**
|
|
1839
|
+
* Calls the LessonNotionService to improve the lesson using AI based on Notion content.
|
|
1840
|
+
*/
|
|
1144
1841
|
async improveNotionWithAI() {
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1151
|
-
static { this.ɵ
|
|
1152
|
-
|
|
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"] }] }); }
|
|
1153
1852
|
}
|
|
1154
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1853
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: DCLessonEditorComponent, decorators: [{
|
|
1155
1854
|
type: Component,
|
|
1156
1855
|
args: [{ selector: 'dc-lesson-editor', standalone: true, imports: [
|
|
1157
|
-
|
|
1856
|
+
ButtonModule,
|
|
1158
1857
|
CKEditorModule,
|
|
1159
1858
|
CropperComponentModal,
|
|
1160
|
-
|
|
1859
|
+
DCLessonRendererComponent,
|
|
1860
|
+
FormsModule,
|
|
1161
1861
|
InputTextModule,
|
|
1162
1862
|
SplitterModule,
|
|
1163
|
-
DCLessonRendererComponent,
|
|
1164
|
-
LangDescTranslationPipe,
|
|
1165
|
-
FlagLanguagePipe,
|
|
1166
1863
|
TooltipModule,
|
|
1167
|
-
SpeedDialModule
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
type: Inject,
|
|
1174
|
-
args: [TOAST_ALERTS_TOKEN]
|
|
1175
|
-
}] }, { type: NotionAbstractService, decorators: [{
|
|
1176
|
-
type: Inject,
|
|
1177
|
-
args: [NOTION_SERVICE_TOKEN]
|
|
1178
|
-
}] }, { 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: [{
|
|
1179
1870
|
type: ViewChild,
|
|
1180
1871
|
args: ['target', { read: ViewContainerRef }]
|
|
1181
1872
|
}], dhtml: [{
|
|
@@ -1188,10 +1879,10 @@ class LessonDynamicComponent {
|
|
|
1188
1879
|
constructor() {
|
|
1189
1880
|
this.settings = {};
|
|
1190
1881
|
}
|
|
1191
|
-
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.
|
|
1192
|
-
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.
|
|
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 }); }
|
|
1193
1884
|
}
|
|
1194
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.
|
|
1885
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.4", ngImport: i0, type: LessonDynamicComponent, decorators: [{
|
|
1195
1886
|
type: Component,
|
|
1196
1887
|
args: [{
|
|
1197
1888
|
selector: 'app-lesson-component',
|