11import { ChangeDetectorRef , Component , ElementRef , NgZone , OnDestroy , OnInit , inject , input , output , signal , viewChild } from '@angular/core' ;
2- import { ApollonEditor , ApollonMode , Locale , UMLModel } from '@tumaet/apollon' ;
2+ import { ApollonEditor , ApollonMode , ApollonView , Locale , UMLModel , importDiagram } from '@tumaet/apollon' ;
33import { NgbModal , NgbModalRef , NgbTooltip } from '@ng-bootstrap/ng-bootstrap' ;
44import { convertRenderedSVGToPNG } from '../exercise-generation/svg-renderer' ;
55import { ApollonDiagramService } from 'app/quiz/manage/apollon-diagrams/services/apollon-diagram.service' ;
@@ -18,6 +18,7 @@ import { FormsModule, NgModel } from '@angular/forms';
1818import { TranslateDirective } from 'app/shared/language/translate.directive' ;
1919import { FaIconComponent } from '@fortawesome/angular-fontawesome' ;
2020import { ArtemisTranslatePipe } from 'app/shared/pipes/artemis-translate.pipe' ;
21+ import { hasQuizRelevantElements } from 'app/modeling/shared/apollon-model.util' ;
2122
2223@Component ( {
2324 selector : 'jhi-apollon-diagram-detail' ,
@@ -48,6 +49,7 @@ export class ApollonDiagramDetailComponent implements OnInit, OnDestroy {
4849
4950 apollonDiagram = signal < ApollonDiagram | undefined > ( undefined ) ;
5051 apollonEditor ?: ApollonEditor ;
52+ private lastSavedModelJson = '' ;
5153
5254 isSaved = true ;
5355
@@ -64,24 +66,7 @@ export class ApollonDiagramDetailComponent implements OnInit, OnDestroy {
6466 * v4 format: model.nodes/edges are arrays - in v4 ALL elements are considered interactive
6567 */
6668 get hasInteractive ( ) : boolean {
67- if ( ! this . apollonEditor ) {
68- return false ;
69- }
70- const model = this . apollonEditor . model as any ;
71-
72- // v3 format: check interactive.elements/relationships
73- if ( model . interactive ) {
74- const elements = model . interactive . elements ?? { } ;
75- const relationships = model . interactive . relationships ?? { } ;
76- return Object . values ( elements ) . some ( Boolean ) || Object . values ( relationships ) . some ( Boolean ) ;
77- }
78-
79- // v4 format: nodes and edges are ARRAYS - if there are any elements, they're interactive
80- if ( Array . isArray ( model . nodes ) ) {
81- return model . nodes . length > 0 || ( model . edges ?. length ?? 0 ) > 0 ;
82- }
83-
84- return false ;
69+ return hasQuizRelevantElements ( this . apollonEditor ?. model ) ;
8570 }
8671
8772 /** Whether some elements are selected in the apollon editor. */
@@ -114,7 +99,8 @@ export class ApollonDiagramDetailComponent implements OnInit, OnDestroy {
11499
115100 this . apollonDiagram . set ( diagram ) ;
116101
117- const model : UMLModel = diagram . jsonRepresentation ? JSON . parse ( diagram . jsonRepresentation ) : undefined ;
102+ const model : UMLModel | undefined = diagram . jsonRepresentation ? importDiagram ( JSON . parse ( diagram . jsonRepresentation ) ) : undefined ;
103+ this . lastSavedModelJson = model ? JSON . stringify ( model ) : '' ;
118104 this . initializeApollonEditor ( model ) ;
119105 this . setAutoSaveTimer ( ) ;
120106 } ,
@@ -141,26 +127,30 @@ export class ApollonDiagramDetailComponent implements OnInit, OnDestroy {
141127 * Initializes Apollon Editor with UML Model
142128 * @param initialModel
143129 */
144- initializeApollonEditor ( initialModel : UMLModel ) {
130+ initializeApollonEditor ( initialModel ? : UMLModel ) {
145131 if ( this . apollonEditor ) {
146132 this . apollonEditor . destroy ( ) ;
147133 }
148134
149135 const diagram = this . apollonDiagram ( ) ;
150- this . apollonEditor = new ApollonEditor ( this . editorContainer ( ) . nativeElement , {
136+ const editorOptions = {
151137 mode : ApollonMode . Modelling ,
138+ view : ApollonView . Modelling ,
139+ readonly : false ,
152140 model : initialModel ,
153141 type : diagram ?. diagramType ,
154142 locale : this . translateService . getCurrentLang ( ) as Locale ,
155- } ) ;
143+ availableViews : [ ApollonView . Modelling , ApollonView . Highlight ] ,
144+ } as ConstructorParameters < typeof ApollonEditor > [ 1 ] ;
145+ this . apollonEditor = new ApollonEditor ( this . editorContainer ( ) . nativeElement , editorOptions ) ;
156146 // Expose the ApollonEditor instance on the host DOM element for E2E test access.
157147 ( this . elementRef . nativeElement as any ) . __apollonEditor = this . apollonEditor ;
158148 // Wrap callback in NgZone.run() because Apollon's React/Zustand store fires outside Angular's zone.
159149 // Without this, programmatic model updates (e.g., from E2E tests) don't trigger change detection,
160150 // leaving template bindings like [disabled]="!hasInteractive" stale.
161151 this . apollonEditor . subscribeToModelChange ( ( newModel ) => {
162152 this . ngZone . run ( ( ) => {
163- this . isSaved = JSON . stringify ( newModel ) === this . apollonDiagram ( ) ?. jsonRepresentation ;
153+ this . isSaved = JSON . stringify ( newModel ) === this . lastSavedModelJson ;
164154 this . changeDetectorRef . markForCheck ( ) ;
165155 } ) ;
166156 } ) ;
@@ -181,6 +171,8 @@ export class ApollonDiagramDetailComponent implements OnInit, OnDestroy {
181171 const result = await lastValueFrom ( this . apollonDiagramService . update ( updatedDiagram , this . courseId ( ) ) ) ;
182172 if ( result ?. ok ) {
183173 this . alertService . success ( 'artemisApp.apollonDiagram.updated' , { title : this . apollonDiagram ( ) ?. title } ) ;
174+ this . lastSavedModelJson = JSON . stringify ( umlModel ) ;
175+ this . apollonDiagram . set ( updatedDiagram ) ;
184176 this . isSaved = true ;
185177 this . setAutoSaveTimer ( ) ;
186178 return true ;
@@ -273,6 +265,7 @@ export class ApollonDiagramDetailComponent implements OnInit, OnDestroy {
273265 const svg = await this . apollonEditor . exportAsSVG ( {
274266 keepOriginalSize : ! this . crop ,
275267 include : selection ,
268+ svgMode : 'compat' ,
276269 } ) ;
277270 const png = await convertRenderedSVGToPNG ( svg ) ;
278271 this . download ( png ) ;
0 commit comments