@dataclouder/ngx-lessons 0.0.30 → 0.0.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/dataclouder-ngx-lessons.mjs +1170 -449
- package/fesm2022/dataclouder-ngx-lessons.mjs.map +1 -1
- package/lib/components/dc-lessons/dc-lesson-card/dc-lesson-card.component.d.ts +2 -2
- 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 +2 -4
- 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 +3 -3
- 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 +5 -5
- 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,34 +1,43 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ActivatedRoute, Router } from '@angular/router';
|
|
1
|
+
import { Component, ComponentRef, ElementRef, computed, effect, inject, signal, ViewChild, ViewContainerRef } from '@angular/core';
|
|
2
|
+
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
|
|
3
|
+
import { toSignal } from '@angular/core/rxjs-interop';
|
|
3
4
|
import { FormsModule } from '@angular/forms';
|
|
4
|
-
import {
|
|
5
|
+
import { map } from 'rxjs';
|
|
5
6
|
|
|
6
7
|
import BalloonEditor from '@ckeditor/ckeditor5-build-balloon-block';
|
|
7
8
|
import { CKEditorModule } from '@ckeditor/ckeditor5-angular';
|
|
8
9
|
// PrimeNG
|
|
9
|
-
|
|
10
|
-
import { SpeedDialModule } from 'primeng/speeddial';
|
|
10
|
+
// Removed DialogService, DynamicDialogRef, SpeedDialModule, MenuItem
|
|
11
11
|
import { ButtonModule } from 'primeng/button';
|
|
12
12
|
import { InputTextModule } from 'primeng/inputtext';
|
|
13
13
|
import { SplitterModule } from 'primeng/splitter';
|
|
14
14
|
import { TooltipModule } from 'primeng/tooltip';
|
|
15
15
|
// Dataclouder
|
|
16
|
-
import { AspectType,
|
|
16
|
+
import { AspectType, CropperComponentModal, ResolutionType, StorageImageSettings } from '@dataclouder/ngx-cloud-storage';
|
|
17
17
|
import { TOAST_ALERTS_TOKEN, ToastAlertsAbstractService } from '@dataclouder/ngx-core';
|
|
18
18
|
|
|
19
19
|
import { DCLessonRendererComponent } from '../dc-lesson-renderer/dc-lesson-renderer.component';
|
|
20
|
+
// Added DynamicContentComponent to the import below
|
|
20
21
|
import {
|
|
21
22
|
LessonComponentEnum,
|
|
22
23
|
LessonImage,
|
|
23
24
|
ILesson,
|
|
24
25
|
LessonComponentInterface,
|
|
25
26
|
LESSONS_TOKEN,
|
|
26
|
-
|
|
27
|
+
DynamicContentComponent,
|
|
28
|
+
LessonComponentConfiguration,
|
|
27
29
|
} from '../../lesson-mini-components/components/lessons.clases';
|
|
28
30
|
import { LessonComponentBuilders } from '../../lesson-mini-components/components/lessons.clases';
|
|
29
31
|
import { FlagLanguagePipe, LangDescTranslationPipe } from '../../../models/lessons.pipes';
|
|
30
|
-
|
|
31
|
-
import {
|
|
32
|
+
// Notion types might still be needed if passed/returned, but token injection removed
|
|
33
|
+
import { NotionExportType } from '../../../models/notion.models';
|
|
34
|
+
// Removed MenuItem import
|
|
35
|
+
import { LessonNotionService } from '../../../services/lesson-notion.service';
|
|
36
|
+
import { LessonUtilsService } from '../../../services/lesson-utils.service'; // Import the new utils service
|
|
37
|
+
import { DCLessonComponentAdderComponent } from '../dc-lesson-component-adder/dc-lesson-component-adder.component'; // Import the component adder
|
|
38
|
+
import { DCLessonMetadataEditorComponent } from '../dc-lesson-metadata-editor/dc-lesson-metadata-editor.component'; // Import the metadata editor
|
|
39
|
+
import { ComponentBuildData } from '../../lesson-mini-components/components/ComponentBuilder';
|
|
40
|
+
import { JsonPipe } from '@angular/common';
|
|
32
41
|
|
|
33
42
|
const GradientCss = 'linear-gradient(to bottom,var(--primary-color), rgba(213, 238, 239, 0.31))';
|
|
34
43
|
|
|
@@ -38,281 +47,310 @@ const GradientCss = 'linear-gradient(to bottom,var(--primary-color), rgba(213, 2
|
|
|
38
47
|
styleUrls: ['./dc-lesson-editor.component.css'],
|
|
39
48
|
standalone: true,
|
|
40
49
|
imports: [
|
|
41
|
-
|
|
50
|
+
ButtonModule,
|
|
42
51
|
CKEditorModule,
|
|
43
52
|
CropperComponentModal,
|
|
44
|
-
|
|
53
|
+
DCLessonRendererComponent,
|
|
54
|
+
FormsModule,
|
|
45
55
|
InputTextModule,
|
|
46
56
|
SplitterModule,
|
|
47
|
-
DCLessonRendererComponent,
|
|
48
|
-
LangDescTranslationPipe,
|
|
49
|
-
FlagLanguagePipe,
|
|
50
57
|
TooltipModule,
|
|
51
|
-
SpeedDialModule
|
|
58
|
+
// Removed SpeedDialModule
|
|
59
|
+
DCLessonComponentAdderComponent, // Add the component adder here
|
|
60
|
+
DCLessonMetadataEditorComponent, // Add the metadata editor here
|
|
61
|
+
JsonPipe,
|
|
52
62
|
],
|
|
63
|
+
providers: [LessonNotionService], // Provide the service here
|
|
53
64
|
})
|
|
54
|
-
export class DCLessonEditorComponent
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
65
|
+
export class DCLessonEditorComponent {
|
|
66
|
+
// Services
|
|
67
|
+
#activatedRoute = inject(ActivatedRoute); // Re-inject as it's needed for navigation
|
|
68
|
+
#lessonNotionService = inject(LessonNotionService);
|
|
69
|
+
#lessonUtilsService = inject(LessonUtilsService);
|
|
70
|
+
#router = inject(Router);
|
|
71
|
+
// Removed #dialogService injection
|
|
72
|
+
#lessonService = inject(LESSONS_TOKEN);
|
|
73
|
+
#toastService = inject(TOAST_ALERTS_TOKEN);
|
|
74
|
+
// ViewChild
|
|
61
75
|
@ViewChild('target', { read: ViewContainerRef }) target: ViewContainerRef;
|
|
62
76
|
@ViewChild('dhtml', { static: true }) dhtml: ElementRef;
|
|
77
|
+
// Signals States
|
|
78
|
+
readonly lessonId = toSignal(inject(ActivatedRoute).paramMap.pipe(map((params: ParamMap) => params.get('id'))));
|
|
79
|
+
lesson = signal<ILesson | undefined>(undefined); // Initialize as undefined
|
|
80
|
+
isCropperVisible = signal(false);
|
|
81
|
+
isLoadingLesson = signal(false);
|
|
82
|
+
// Removed items signal
|
|
83
|
+
// Computed Signals
|
|
84
|
+
coverBackground = computed(() => {
|
|
85
|
+
const currentLesson = this.lesson();
|
|
86
|
+
const coverImage = currentLesson?.media?.images?.find((img: any) => img.type === 'cover') as LessonImage | undefined;
|
|
87
|
+
const imageUrl = coverImage?.url || '/assets/images/default_banner.webp';
|
|
88
|
+
return `${GradientCss}, url("${imageUrl}")`;
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
// Computed signal to get dynamic components as an array for easier iteration in the template
|
|
92
|
+
readonly dynamicComponentsArray = computed(() => {
|
|
93
|
+
const currentLesson = this.lesson();
|
|
94
|
+
if (currentLesson?.dynamicComponents) {
|
|
95
|
+
return Object.values(currentLesson.dynamicComponents);
|
|
96
|
+
}
|
|
97
|
+
return []; // Return empty array if no lesson or no dynamic components
|
|
98
|
+
});
|
|
63
99
|
|
|
100
|
+
// States
|
|
101
|
+
public components: { [key: string]: ComponentRef<LessonComponentInterface> } = {}; // Current Dynamic components
|
|
64
102
|
public editor = BalloonEditor;
|
|
103
|
+
public lessonComponentEnum = LessonComponentEnum;
|
|
104
|
+
|
|
65
105
|
public coverStorageSettings: StorageImageSettings = {
|
|
66
106
|
path: 'lessons/covers',
|
|
67
107
|
fileName: 'cover',
|
|
68
108
|
cropSettings: { resizeToWidth: 850, aspectRatio: AspectType.RectangleLarge, resolutions: [ResolutionType.Medium] },
|
|
69
109
|
};
|
|
70
110
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
tooltipOptions: { tooltipLabel: 'Hablar: Para que una palabra o frase sea reproducible', tooltipPosition: 'bottom' },
|
|
106
|
-
icon: 'pi pi-megaphone',
|
|
107
|
-
command: () => this.openComponentBuilder('speaker'),
|
|
108
|
-
},
|
|
109
|
-
{
|
|
110
|
-
tooltipOptions: {
|
|
111
|
-
tooltipLabel: 'Entrada de texto: Escribe una respuesta en un cuadro de texto',
|
|
112
|
-
tooltipPosition: 'bottom',
|
|
113
|
-
},
|
|
114
|
-
icon: 'pi pi-pencil',
|
|
115
|
-
command: () => this.openComponentBuilder('textWriter'),
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
tooltipOptions: { tooltipLabel: 'Verbo: Para ver datos de un verbo', tooltipPosition: 'bottom' },
|
|
119
|
-
icon: 'pi pi-eye',
|
|
120
|
-
command: () => this.openComponentBuilder('verbSummary'),
|
|
121
|
-
},
|
|
122
|
-
{
|
|
123
|
-
tooltipOptions: { tooltipLabel: 'Palabra: Para ver datos de una palabra', tooltipPosition: 'bottom' },
|
|
124
|
-
icon: 'pi pi-file-word',
|
|
125
|
-
command: () => this.openComponentBuilder('wordSummary'),
|
|
126
|
-
},
|
|
127
|
-
];
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public async getLessonIfId(): Promise<void> {
|
|
131
|
-
if (!this.lessonId) {
|
|
132
|
-
this.cover = `${GradientCss}, url("/assets/images/default_banner.webp")`;
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
this.lesson = await this.lessonService.getLesson(this.lessonId);
|
|
136
|
-
console.log('lesson', this.lesson);
|
|
137
|
-
if (!this.lesson) {
|
|
138
|
-
this.toastService.warn({ title: 'No se encontró la lección', subtitle: 'Quiza el id es incorrecto' });
|
|
139
|
-
}
|
|
140
|
-
this.togleRender();
|
|
141
|
-
this.updateCover();
|
|
111
|
+
constructor() {
|
|
112
|
+
// Removed Speed Dial Items initialization
|
|
113
|
+
|
|
114
|
+
// Effect to fetch lesson data when ID changes
|
|
115
|
+
effect(async () => {
|
|
116
|
+
const id = this.lessonId();
|
|
117
|
+
console.log('Lesson ID Signal:', id);
|
|
118
|
+
if (id) {
|
|
119
|
+
this.isLoadingLesson.set(true); // Start loading
|
|
120
|
+
try {
|
|
121
|
+
const fetchedLesson = await this.#lessonService.getLesson(id);
|
|
122
|
+
if (fetchedLesson) {
|
|
123
|
+
this.lesson.set(fetchedLesson);
|
|
124
|
+
} else {
|
|
125
|
+
this.lesson.set(undefined); // Reset if not found
|
|
126
|
+
this.#toastService.warn({ title: 'No se encontró la lección', subtitle: 'Quizá el id es incorrecto' });
|
|
127
|
+
// Optional: Navigate away or show a specific "not found" state
|
|
128
|
+
// this.#router.navigate(['/path/to/lessons']);
|
|
129
|
+
}
|
|
130
|
+
} catch (error) {
|
|
131
|
+
console.error('Error fetching lesson:', error);
|
|
132
|
+
this.lesson.set(undefined); // Reset on error
|
|
133
|
+
this.#toastService.error({ title: 'Error al cargar la lección', subtitle: 'Intenta de nuevo más tarde' });
|
|
134
|
+
} finally {
|
|
135
|
+
this.isLoadingLesson.set(false); // Stop loading
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
// Handle case for new lesson (ID is null/undefined)
|
|
139
|
+
this.lesson.set({ textCoded: `<h1>Nueva lección </h1> <p> Texto aquí</p>`, tags: [] } as ILesson); // Set default new lesson structure
|
|
140
|
+
this.isLoadingLesson.set(false); // Ensure loading is off
|
|
141
|
+
}
|
|
142
|
+
});
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.
|
|
145
|
+
/**
|
|
146
|
+
* Updates a specific property on the lesson signal.
|
|
147
|
+
* Used for ngModelChange events to simulate two-way binding with signals.
|
|
148
|
+
* @param property The key of the ILesson property to update.
|
|
149
|
+
* @param value The new value for the property.
|
|
150
|
+
*/
|
|
151
|
+
public updateLessonProperty<K extends keyof ILesson>(property: K, value: ILesson[K]): void {
|
|
152
|
+
this.lesson.update((currentLesson) => {
|
|
153
|
+
if (!currentLesson) return undefined;
|
|
154
|
+
return { ...currentLesson, [property]: value };
|
|
155
|
+
});
|
|
152
156
|
}
|
|
153
157
|
|
|
154
158
|
public onTagRemove(tag: any): void {
|
|
155
|
-
this.lesson.
|
|
159
|
+
this.lesson.update((currentLesson) => {
|
|
160
|
+
if (!currentLesson) return undefined;
|
|
161
|
+
const updatedTags = currentLesson.tags.filter((text: string) => text !== tag.text);
|
|
162
|
+
return { ...currentLesson, tags: updatedTags };
|
|
163
|
+
});
|
|
156
164
|
}
|
|
157
165
|
|
|
158
166
|
public onTagAdd(tag: { value: string; input: any }): void {
|
|
159
167
|
if (tag.value) {
|
|
160
|
-
this.lesson.
|
|
168
|
+
this.lesson.update((currentLesson) => {
|
|
169
|
+
if (!currentLesson) return undefined;
|
|
170
|
+
// Avoid duplicate tags if necessary
|
|
171
|
+
if (currentLesson.tags.includes(tag.value)) {
|
|
172
|
+
return currentLesson;
|
|
173
|
+
}
|
|
174
|
+
const updatedTags = [...currentLesson.tags, tag.value];
|
|
175
|
+
return { ...currentLesson, tags: updatedTags };
|
|
176
|
+
});
|
|
161
177
|
}
|
|
162
|
-
tag.input.nativeElement.value = '';
|
|
178
|
+
tag.input.nativeElement.value = ''; // Clear input
|
|
163
179
|
}
|
|
164
180
|
|
|
165
|
-
public async saveLesson(event
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
this.toastService.success({ title: 'Se creó la lección', subtitle: 'Éxito' });
|
|
173
|
-
this.router.navigate([lesson.id], { relativeTo: this._activatedRoute });
|
|
174
|
-
} else {
|
|
175
|
-
this.toastService.info({ title: 'Se guadarón los cambios en la lección', subtitle: 'Guardado' });
|
|
176
|
-
this.lesson = lesson;
|
|
177
|
-
this.validateAudios();
|
|
181
|
+
public async saveLesson(event?: Event): Promise<ILesson | undefined> {
|
|
182
|
+
event?.preventDefault();
|
|
183
|
+
|
|
184
|
+
const currentLesson = this.lesson();
|
|
185
|
+
if (!currentLesson) {
|
|
186
|
+
this.#toastService.error({ title: 'Error', subtitle: 'No hay datos de lección para guardar' });
|
|
187
|
+
return undefined;
|
|
178
188
|
}
|
|
179
189
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
190
|
+
// Clean orphaned components before saving
|
|
191
|
+
const lessonToSave = this.#lessonUtilsService.cleanOrphanedComponents(currentLesson);
|
|
183
192
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
193
|
+
// TODO: Implement optimization for saving only changed data.
|
|
194
|
+
// This requires comparing lessonToSave with the initially fetched state.
|
|
195
|
+
this.isLoadingLesson.set(true); // Indicate saving
|
|
196
|
+
try {
|
|
197
|
+
// Use the cleaned lesson object for saving
|
|
198
|
+
const savedLesson = await this.#lessonService.postLesson(lessonToSave);
|
|
199
|
+
const currentId = this.lessonId();
|
|
200
|
+
|
|
201
|
+
if (!currentId) {
|
|
202
|
+
// It was a new lesson, now it has an ID. Navigate.
|
|
203
|
+
this.#toastService.success({ title: 'Se creó la lección', subtitle: 'Éxito' });
|
|
204
|
+
// The effect should automatically fetch the lesson again after navigation due to paramMap change.
|
|
205
|
+
this.#router.navigate(['../', savedLesson.id], { relativeTo: this.#activatedRoute });
|
|
206
|
+
} else {
|
|
207
|
+
// It was an existing lesson, update the signal with the potentially updated data from the backend.
|
|
208
|
+
this.lesson.set(savedLesson);
|
|
209
|
+
this.#toastService.info({ title: 'Se guardaron los cambios en la lección', subtitle: 'Guardado' });
|
|
210
|
+
// Call the service method for validation
|
|
211
|
+
this.#lessonUtilsService.validateAudios(this.lesson());
|
|
212
|
+
}
|
|
213
|
+
return savedLesson;
|
|
214
|
+
} catch (error: any) {
|
|
215
|
+
// Type error
|
|
216
|
+
console.error('Error saving lesson:', error);
|
|
217
|
+
this.#toastService.error({ title: 'Error al guardar', subtitle: 'No se pudieron guardar los cambios' });
|
|
218
|
+
return undefined;
|
|
219
|
+
} finally {
|
|
220
|
+
this.isLoadingLesson.set(false); // Finish saving indication
|
|
187
221
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
222
|
+
} // Add missing closing brace for saveLesson
|
|
223
|
+
|
|
224
|
+
// Removed openComponentBuilder method
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Handles the event emitted when a component is added via the adder component.
|
|
228
|
+
* @param result The configuration data returned from the component builder dialog. Expected format: { obj: LessonComponentConfiguration }
|
|
229
|
+
*/
|
|
230
|
+
public onComponentAdded(result: ComponentBuildData): void {
|
|
231
|
+
debugger;
|
|
232
|
+
// Check if result and result.obj.id exist
|
|
233
|
+
const newComponent = result?.obj;
|
|
234
|
+
if (newComponent?.id) {
|
|
235
|
+
console.log('Component builder closed, result received in editor:', newComponent);
|
|
236
|
+
|
|
237
|
+
// // Transform LessonComponentConfiguration to DynamicContentComponent
|
|
238
|
+
// const dynamicComponentToAdd: DynamicContentComponent = {
|
|
239
|
+
// id: componentConfig.id,
|
|
240
|
+
// component: componentConfig.component,
|
|
241
|
+
// inputs: {
|
|
242
|
+
// config: componentConfig, // Pass the original config object as an input named 'config'
|
|
243
|
+
// // Add other potential inputs if needed based on component type later
|
|
244
|
+
// },
|
|
245
|
+
// };
|
|
246
|
+
|
|
247
|
+
// Update the lesson signal, adding the transformed component to the dynamicComponents object
|
|
248
|
+
this.lesson.update((currentLesson: any) => {
|
|
249
|
+
if (!currentLesson) return undefined;
|
|
250
|
+
|
|
251
|
+
// Ensure dynamicComponents object exists, initialize if not
|
|
252
|
+
const currentDynamicComponents = currentLesson.dynamicComponents || {};
|
|
253
|
+
|
|
254
|
+
// Create the updated dynamicComponents object
|
|
255
|
+
const updatedDynamicComponents: Record<string, LessonComponentConfiguration<any>> = {
|
|
256
|
+
...currentDynamicComponents,
|
|
257
|
+
[newComponent.id]: newComponent, // Use component's id as the key
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// Return the updated lesson state
|
|
261
|
+
return { ...currentLesson, dynamicComponents: updatedDynamicComponents };
|
|
262
|
+
});
|
|
204
263
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
this.isRendered = true;
|
|
209
|
-
this.cdr.detectChanges();
|
|
210
|
-
}, 400);
|
|
211
|
-
this.cdr.detectChanges();
|
|
264
|
+
// Optionally save the lesson after adding the component
|
|
265
|
+
// this.saveLesson();
|
|
266
|
+
}
|
|
212
267
|
}
|
|
213
268
|
|
|
214
|
-
public
|
|
215
|
-
|
|
269
|
+
public openCropper(): void {
|
|
270
|
+
// Correctly define openCropper
|
|
271
|
+
this.isCropperVisible.set(true);
|
|
216
272
|
}
|
|
217
273
|
|
|
218
|
-
|
|
219
|
-
const image = { type: 'cover', ...imageUploaded };
|
|
220
|
-
|
|
221
|
-
if (!this.lesson.media) {
|
|
222
|
-
// puede que no exista media
|
|
223
|
-
this.lesson.media = {};
|
|
224
|
-
this.lesson.media.images = [image];
|
|
225
|
-
} else {
|
|
226
|
-
// solo sustituir el cover si ya existe, como siempre utilizo el mismo nombre, no tengo que eliminar la anterior si actualizo.
|
|
227
|
-
// const currentCover = this.lesson.media.images.find((img) => img.type === 'cover');
|
|
274
|
+
// isLoadingLesson signal is used directly
|
|
228
275
|
|
|
229
|
-
|
|
230
|
-
|
|
276
|
+
public async generateByAI(): Promise<void> {
|
|
277
|
+
const currentId = this.lessonId();
|
|
278
|
+
if (!currentId) {
|
|
279
|
+
this.#toastService.warn({ title: 'Guardar primero', subtitle: 'Guarda la lección antes de usar IA.' });
|
|
280
|
+
return;
|
|
231
281
|
}
|
|
232
|
-
this.updateCover();
|
|
233
|
-
this.saveLesson();
|
|
234
|
-
}
|
|
235
282
|
|
|
236
|
-
|
|
237
|
-
this.isCropperVisible = true;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
isLoadingLesson = false;
|
|
241
|
-
public async generateByAI() {
|
|
242
|
-
this.isLoadingLesson = true;
|
|
283
|
+
this.isLoadingLesson.set(true);
|
|
243
284
|
try {
|
|
244
|
-
|
|
245
|
-
await this.
|
|
246
|
-
|
|
285
|
+
// Ensure latest changes are saved before generating
|
|
286
|
+
const savedLesson = await this.saveLesson();
|
|
287
|
+
if (!savedLesson) {
|
|
288
|
+
// Handle save error - toast is shown in saveLesson
|
|
289
|
+
throw new Error('Failed to save before AI generation');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Call the service method
|
|
293
|
+
const updatedLesson = await this.#lessonUtilsService.generateByAI(currentId);
|
|
294
|
+
|
|
295
|
+
if (updatedLesson) {
|
|
296
|
+
this.lesson.set(updatedLesson); // Update the signal with AI changes from service
|
|
297
|
+
// Toast success is handled by the service
|
|
298
|
+
} else {
|
|
299
|
+
// Toast error is handled by the service
|
|
300
|
+
throw new Error('AI generation failed or lesson fetch failed after generation.');
|
|
301
|
+
}
|
|
302
|
+
} catch (error: any) {
|
|
303
|
+
// Type error
|
|
304
|
+
console.error('Error during AI generation process in component:', error);
|
|
305
|
+
// Service handles specific AI error toasts, maybe add a general one here if needed
|
|
306
|
+
// this.#toastService.error({ title: 'Error General', subtitle: 'Ocurrió un problema durante el proceso de IA.' });
|
|
247
307
|
} finally {
|
|
248
|
-
this.isLoadingLesson
|
|
308
|
+
this.isLoadingLesson.set(false); // Stop loading
|
|
249
309
|
}
|
|
250
310
|
}
|
|
251
311
|
|
|
252
|
-
|
|
253
|
-
|
|
312
|
+
/**
|
|
313
|
+
* Handles the image upload event, updates the lesson signal via the service, and saves.
|
|
314
|
+
* @param event The image upload event data.
|
|
315
|
+
*/
|
|
316
|
+
public async onImageUploaded(event: any): Promise<void> {
|
|
317
|
+
// Call the service to update the signal
|
|
318
|
+
this.#lessonUtilsService.uploadCover(this.lesson, event);
|
|
319
|
+
// The coverBackground computed signal will update automatically.
|
|
320
|
+
// Save the lesson after the signal has been updated
|
|
321
|
+
await this.saveLesson();
|
|
254
322
|
}
|
|
255
323
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
324
|
+
/**
|
|
325
|
+
* Imports lesson content from Notion using the LessonNotionService.
|
|
326
|
+
*/
|
|
327
|
+
public async importFromNotion(): Promise<void> {
|
|
328
|
+
// Use the service's loading state or manage locally
|
|
329
|
+
this.isLoadingLesson.set(true);
|
|
330
|
+
try {
|
|
331
|
+
const newContent = await this.#lessonNotionService.importAndLinkLessonFromNotion(this.lesson(), this.lessonId());
|
|
260
332
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (!response) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
notionPageId = this.lesson.extras?.notionPageId;
|
|
269
|
-
} else {
|
|
270
|
-
const response = prompt('Ingresa el url de notion para importar la lección, se guardará este id');
|
|
271
|
-
if (!response) {
|
|
272
|
-
return;
|
|
333
|
+
if (newContent !== null) {
|
|
334
|
+
// Update the lesson signal's textCoded property
|
|
335
|
+
this.updateLessonProperty('textCoded', newContent);
|
|
336
|
+
// Toast success is handled within the service now
|
|
273
337
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
this.toastService.info({ title: 'Importando lección...', subtitle: 'Espera unos segundos' });
|
|
280
|
-
|
|
281
|
-
const md = await this.notionService.getPageInSpecificFormat(notionPageId, NotionExportType.HTML);
|
|
282
|
-
console.log(md);
|
|
283
|
-
this.lesson.textCoded = md.content;
|
|
284
|
-
this.togleRender();
|
|
285
|
-
this.toastService.success({ title: 'Listo', subtitle: 'La lección se importó correctamente' });
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
extractNotionPageId(url: string) {
|
|
289
|
-
const notionIdRegex = /[a-f0-9]{32}(?=\?|$)/;
|
|
290
|
-
const match = url.match(notionIdRegex);
|
|
291
|
-
const notionId = match ? match[0] : null;
|
|
292
|
-
if (!notionId) {
|
|
293
|
-
this.toastService.error({
|
|
294
|
-
title: 'URL inválido',
|
|
295
|
-
subtitle: 'Por favor ingresa una URL válida de Notion',
|
|
296
|
-
});
|
|
338
|
+
// If newContent is null, the service handled errors/toasts
|
|
339
|
+
} finally {
|
|
340
|
+
// Ensure loading state is reset regardless of service outcome
|
|
341
|
+
// If observing service state: this.isLoadingLesson.set(this.#lessonNotionService.isLoading());
|
|
342
|
+
this.isLoadingLesson.set(false); // Keep local loading for now
|
|
297
343
|
}
|
|
298
|
-
return notionId;
|
|
299
344
|
}
|
|
300
345
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
await this.
|
|
306
|
-
this.toastService.success({ title: 'Listo', subtitle: 'Se enlazó la lección con Notion' });
|
|
346
|
+
/**
|
|
347
|
+
* Calls the LessonNotionService to improve the lesson using AI based on Notion content.
|
|
348
|
+
*/
|
|
349
|
+
public async improveNotionWithAI(): Promise<void> {
|
|
350
|
+
await this.#lessonNotionService.improveLessonWithNotionAI(this.lesson());
|
|
307
351
|
}
|
|
308
352
|
|
|
309
|
-
public
|
|
310
|
-
|
|
311
|
-
console.log(md);
|
|
312
|
-
|
|
313
|
-
// this.lesson.textCoded = md.content;
|
|
314
|
-
|
|
315
|
-
// this.togleRender();
|
|
316
|
-
// this.toastService.success({ title: 'Listo', subtitle: 'La lección se mejoró con AI' });
|
|
353
|
+
public showComponentDetails(data: any) {
|
|
354
|
+
alert('showComponentDetails' + JSON.stringify(data));
|
|
317
355
|
}
|
|
318
356
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/* Add any specific styles for the metadata editor here if needed */
|