pyarchinit-mini 1.9.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (281) hide show
  1. pyarchinit_mini/__init__.py +40 -0
  2. pyarchinit_mini/api/__init__.py +92 -0
  3. pyarchinit_mini/api/auth.py +312 -0
  4. pyarchinit_mini/api/dependencies.py +63 -0
  5. pyarchinit_mini/api/graphml.py +160 -0
  6. pyarchinit_mini/api/inventario.py +107 -0
  7. pyarchinit_mini/api/schemas.py +186 -0
  8. pyarchinit_mini/api/site.py +220 -0
  9. pyarchinit_mini/api/us.py +107 -0
  10. pyarchinit_mini/cli/__init__.py +7 -0
  11. pyarchinit_mini/cli/export_import_cli.py +289 -0
  12. pyarchinit_mini/cli/extended_matrix_import.py +101 -0
  13. pyarchinit_mini/cli/graphml_cli.py +219 -0
  14. pyarchinit_mini/cli/harris_import.py +574 -0
  15. pyarchinit_mini/cli/harris_template.py +323 -0
  16. pyarchinit_mini/cli/migrate.py +202 -0
  17. pyarchinit_mini/cli/pyarchinit_import_export_cli.py +428 -0
  18. pyarchinit_mini/cli_interface/__init__.py +1 -0
  19. pyarchinit_mini/cli_interface/cli_app.py +529 -0
  20. pyarchinit_mini/config/__init__.py +0 -0
  21. pyarchinit_mini/config/connection_manager.py +286 -0
  22. pyarchinit_mini/config/em_node_config_manager.py +452 -0
  23. pyarchinit_mini/config/em_node_types.yaml +349 -0
  24. pyarchinit_mini/database/__init__.py +13 -0
  25. pyarchinit_mini/database/connection.py +168 -0
  26. pyarchinit_mini/database/database_creator.py +290 -0
  27. pyarchinit_mini/database/manager.py +231 -0
  28. pyarchinit_mini/database/migration_scripts/__init__.py +7 -0
  29. pyarchinit_mini/database/migration_scripts/add_file_path.py +152 -0
  30. pyarchinit_mini/database/migration_scripts/add_i18n_columns.py +312 -0
  31. pyarchinit_mini/database/migration_scripts/add_tipo_documento.py +154 -0
  32. pyarchinit_mini/database/migration_scripts/change_us_area_to_text.py +321 -0
  33. pyarchinit_mini/database/migration_scripts/change_us_area_to_text_simple.py +230 -0
  34. pyarchinit_mini/database/migrations.py +232 -0
  35. pyarchinit_mini/database/postgres_installer.py +375 -0
  36. pyarchinit_mini/database/schemas.py +177 -0
  37. pyarchinit_mini/desktop_gui/__init__.py +51 -0
  38. pyarchinit_mini/desktop_gui/analytics_dialog.py +228 -0
  39. pyarchinit_mini/desktop_gui/assets/logo.png +0 -0
  40. pyarchinit_mini/desktop_gui/dialogs.py +1984 -0
  41. pyarchinit_mini/desktop_gui/excel_import_dialog.py +380 -0
  42. pyarchinit_mini/desktop_gui/export_import_dialog.py +313 -0
  43. pyarchinit_mini/desktop_gui/graphml_export_dialog.py +221 -0
  44. pyarchinit_mini/desktop_gui/gui_app.py +106 -0
  45. pyarchinit_mini/desktop_gui/harris_matrix_editor.py +970 -0
  46. pyarchinit_mini/desktop_gui/i18n.py +191 -0
  47. pyarchinit_mini/desktop_gui/icons.py +70 -0
  48. pyarchinit_mini/desktop_gui/inventario_dialog_extended.py +966 -0
  49. pyarchinit_mini/desktop_gui/main_window.py +1826 -0
  50. pyarchinit_mini/desktop_gui/media_manager_advanced.py +141 -0
  51. pyarchinit_mini/desktop_gui/postgres_installer_dialog.py +357 -0
  52. pyarchinit_mini/desktop_gui/pyarchinit_import_export_dialog.py +585 -0
  53. pyarchinit_mini/desktop_gui/thesaurus_dialog.py +463 -0
  54. pyarchinit_mini/desktop_gui/us_dialog_extended.py +2122 -0
  55. pyarchinit_mini/dto/__init__.py +16 -0
  56. pyarchinit_mini/dto/inventario_dto.py +57 -0
  57. pyarchinit_mini/dto/site_dto.py +62 -0
  58. pyarchinit_mini/dto/us_dto.py +237 -0
  59. pyarchinit_mini/exceptions.py +23 -0
  60. pyarchinit_mini/graphml_converter/__init__.py +47 -0
  61. pyarchinit_mini/graphml_converter/converter.py +343 -0
  62. pyarchinit_mini/graphml_converter/dot_parser.py +1331 -0
  63. pyarchinit_mini/graphml_converter/em_palette.py +355 -0
  64. pyarchinit_mini/graphml_converter/graphml_builder.py +657 -0
  65. pyarchinit_mini/graphml_converter/graphml_exporter.py +1048 -0
  66. pyarchinit_mini/graphml_converter/pure_networkx_exporter.py +719 -0
  67. pyarchinit_mini/graphml_converter/svg_resources.py +244 -0
  68. pyarchinit_mini/graphml_converter/templates/EM_palette.graphml +658 -0
  69. pyarchinit_mini/graphml_converter/x11_colors.py +678 -0
  70. pyarchinit_mini/graphml_converter/yed_template.py +365 -0
  71. pyarchinit_mini/harris_matrix/__init__.py +11 -0
  72. pyarchinit_mini/harris_matrix/enhanced_visualizer.py +465 -0
  73. pyarchinit_mini/harris_matrix/matrix_generator.py +1244 -0
  74. pyarchinit_mini/harris_matrix/matrix_visualizer.py +368 -0
  75. pyarchinit_mini/harris_matrix/pyarchinit_visualizer.py +581 -0
  76. pyarchinit_mini/i18n/__init__.py +15 -0
  77. pyarchinit_mini/i18n/flask_babel_config.py +94 -0
  78. pyarchinit_mini/i18n/locale_manager.py +142 -0
  79. pyarchinit_mini/mcp_server/__init__.py +22 -0
  80. pyarchinit_mini/mcp_server/__main__.py +45 -0
  81. pyarchinit_mini/mcp_server/blender_client.py +525 -0
  82. pyarchinit_mini/mcp_server/config.py +181 -0
  83. pyarchinit_mini/mcp_server/event_stream.py +282 -0
  84. pyarchinit_mini/mcp_server/graphml_parser.py +612 -0
  85. pyarchinit_mini/mcp_server/http_server.py +501 -0
  86. pyarchinit_mini/mcp_server/period_filter.py +362 -0
  87. pyarchinit_mini/mcp_server/prompts/__init__.py +18 -0
  88. pyarchinit_mini/mcp_server/prompts/base_prompt.py +23 -0
  89. pyarchinit_mini/mcp_server/prompts/period_visualization_prompt.py +25 -0
  90. pyarchinit_mini/mcp_server/prompts/stratigraphic_model_prompt.py +25 -0
  91. pyarchinit_mini/mcp_server/prompts/us_description_prompt.py +25 -0
  92. pyarchinit_mini/mcp_server/proxy_generator.py +470 -0
  93. pyarchinit_mini/mcp_server/resources/__init__.py +24 -0
  94. pyarchinit_mini/mcp_server/resources/base_resource.py +79 -0
  95. pyarchinit_mini/mcp_server/resources/graphml_resource.py +382 -0
  96. pyarchinit_mini/mcp_server/resources/periods_resource.py +52 -0
  97. pyarchinit_mini/mcp_server/resources/relationships_resource.py +52 -0
  98. pyarchinit_mini/mcp_server/resources/sites_resource.py +52 -0
  99. pyarchinit_mini/mcp_server/resources/us_resource.py +52 -0
  100. pyarchinit_mini/mcp_server/server.py +564 -0
  101. pyarchinit_mini/mcp_server/tools/__init__.py +24 -0
  102. pyarchinit_mini/mcp_server/tools/base_tool.py +119 -0
  103. pyarchinit_mini/mcp_server/tools/batch_insert_tool.py +393 -0
  104. pyarchinit_mini/mcp_server/tools/build_3d_tool.py +524 -0
  105. pyarchinit_mini/mcp_server/tools/chatgpt_fetch_tool.py +183 -0
  106. pyarchinit_mini/mcp_server/tools/chatgpt_search_tool.py +130 -0
  107. pyarchinit_mini/mcp_server/tools/configure_em_nodes_tool.py +427 -0
  108. pyarchinit_mini/mcp_server/tools/create_database_tool.py +118 -0
  109. pyarchinit_mini/mcp_server/tools/create_harris_matrix_tool.py +421 -0
  110. pyarchinit_mini/mcp_server/tools/data_import_parser_tool.py +695 -0
  111. pyarchinit_mini/mcp_server/tools/data_management_tool.py +262 -0
  112. pyarchinit_mini/mcp_server/tools/database_manager_tool.py +200 -0
  113. pyarchinit_mini/mcp_server/tools/delete_data_tool.py +288 -0
  114. pyarchinit_mini/mcp_server/tools/export_harris_matrix_graphml_tool.py +184 -0
  115. pyarchinit_mini/mcp_server/tools/export_tool.py +32 -0
  116. pyarchinit_mini/mcp_server/tools/filter_tool.py +32 -0
  117. pyarchinit_mini/mcp_server/tools/generate_report_tool.py +679 -0
  118. pyarchinit_mini/mcp_server/tools/get_schema_tool.py +224 -0
  119. pyarchinit_mini/mcp_server/tools/import_excel_tool.py +270 -0
  120. pyarchinit_mini/mcp_server/tools/insert_data_tool.py +390 -0
  121. pyarchinit_mini/mcp_server/tools/manage_services_tool.py +507 -0
  122. pyarchinit_mini/mcp_server/tools/material_tool.py +32 -0
  123. pyarchinit_mini/mcp_server/tools/media_management_tool.py +494 -0
  124. pyarchinit_mini/mcp_server/tools/position_tool.py +32 -0
  125. pyarchinit_mini/mcp_server/tools/pyarchinit_sync_tool.py +172 -0
  126. pyarchinit_mini/mcp_server/tools/resolve_conflicts_tool.py +377 -0
  127. pyarchinit_mini/mcp_server/tools/service_management_tool.py +104 -0
  128. pyarchinit_mini/mcp_server/tools/thesaurus_management_tool.py +420 -0
  129. pyarchinit_mini/mcp_server/tools/update_data_tool.py +360 -0
  130. pyarchinit_mini/mcp_server/tools/upload_file_tool.py +112 -0
  131. pyarchinit_mini/mcp_server/tools/validate_relationship_format_tool.py +327 -0
  132. pyarchinit_mini/mcp_server/tools/validate_relationship_integrity_tool.py +532 -0
  133. pyarchinit_mini/mcp_server/tools/validate_stratigraphic_relationships_tool.py +261 -0
  134. pyarchinit_mini/mcp_server/tools/validate_stratigraphy_tool.py +363 -0
  135. pyarchinit_mini/media_manager/__init__.py +9 -0
  136. pyarchinit_mini/media_manager/media_handler.py +322 -0
  137. pyarchinit_mini/models/__init__.py +35 -0
  138. pyarchinit_mini/models/base.py +32 -0
  139. pyarchinit_mini/models/datazione.py +68 -0
  140. pyarchinit_mini/models/extended_matrix.py +44 -0
  141. pyarchinit_mini/models/harris_matrix.py +117 -0
  142. pyarchinit_mini/models/inventario_materiali.py +238 -0
  143. pyarchinit_mini/models/media.py +109 -0
  144. pyarchinit_mini/models/site.py +96 -0
  145. pyarchinit_mini/models/thesaurus.py +179 -0
  146. pyarchinit_mini/models/us.py +291 -0
  147. pyarchinit_mini/models/user.py +78 -0
  148. pyarchinit_mini/pdf_export/__init__.py +9 -0
  149. pyarchinit_mini/pdf_export/pdf_generator.py +1048 -0
  150. pyarchinit_mini/pdf_export/pdf_generator_backup.py +1013 -0
  151. pyarchinit_mini/pdf_export/pyarchinit_finds_template.py +404 -0
  152. pyarchinit_mini/pdf_export/pyarchinit_inventory_template.py +353 -0
  153. pyarchinit_mini/pdf_export/usm_implementation.py +465 -0
  154. pyarchinit_mini/s3d_integration/__init__.py +9 -0
  155. pyarchinit_mini/s3d_integration/model_manager.py +234 -0
  156. pyarchinit_mini/s3d_integration/s3d_converter.py +848 -0
  157. pyarchinit_mini/scripts/__init__.py +3 -0
  158. pyarchinit_mini/scripts/auto_translate.py +679 -0
  159. pyarchinit_mini/scripts/configure_claude_desktop.py +277 -0
  160. pyarchinit_mini/scripts/create_admin_user.py +152 -0
  161. pyarchinit_mini/scripts/create_correct_stratigraphy_sample.py +373 -0
  162. pyarchinit_mini/scripts/create_italian_sample_database.py +302 -0
  163. pyarchinit_mini/scripts/create_sample_for_webapp.py +463 -0
  164. pyarchinit_mini/scripts/create_sample_with_50_us.py +353 -0
  165. pyarchinit_mini/scripts/generate_test_3d_model.py +323 -0
  166. pyarchinit_mini/scripts/load_italian_as_main.py +39 -0
  167. pyarchinit_mini/scripts/load_sample_as_main.py +73 -0
  168. pyarchinit_mini/scripts/migrate_relationships.py +165 -0
  169. pyarchinit_mini/scripts/populate_sample_data.py +498 -0
  170. pyarchinit_mini/scripts/populate_simple_data.py +213 -0
  171. pyarchinit_mini/scripts/setup_auth.py +112 -0
  172. pyarchinit_mini/scripts/setup_user_env.py +316 -0
  173. pyarchinit_mini/scripts/test_cascade_delete.py +91 -0
  174. pyarchinit_mini/scripts/test_relationship_fix.py +128 -0
  175. pyarchinit_mini/scripts/update_sample_relationships.py +136 -0
  176. pyarchinit_mini/scripts/update_urls_and_versions.py +312 -0
  177. pyarchinit_mini/scripts/verify_docs.py +312 -0
  178. pyarchinit_mini/services/__init__.py +19 -0
  179. pyarchinit_mini/services/analytics_service.py +242 -0
  180. pyarchinit_mini/services/command_parser.py +159 -0
  181. pyarchinit_mini/services/datazione_service.py +244 -0
  182. pyarchinit_mini/services/export_import_service.py +430 -0
  183. pyarchinit_mini/services/extended_matrix_excel_parser.py +708 -0
  184. pyarchinit_mini/services/import_export_service.py +2381 -0
  185. pyarchinit_mini/services/inventario_service.py +347 -0
  186. pyarchinit_mini/services/mcp_executor.py +206 -0
  187. pyarchinit_mini/services/media_service.py +458 -0
  188. pyarchinit_mini/services/periodizzazione_service.py +417 -0
  189. pyarchinit_mini/services/relationship_sync_service.py +400 -0
  190. pyarchinit_mini/services/site_service.py +328 -0
  191. pyarchinit_mini/services/thesaurus_service.py +358 -0
  192. pyarchinit_mini/services/us_service.py +446 -0
  193. pyarchinit_mini/services/user_service.py +293 -0
  194. pyarchinit_mini/translations/en/LC_MESSAGES/messages.mo +0 -0
  195. pyarchinit_mini/translations/en/LC_MESSAGES/messages.po +5110 -0
  196. pyarchinit_mini/translations/it/LC_MESSAGES/messages.mo +0 -0
  197. pyarchinit_mini/translations/it/LC_MESSAGES/messages.po +4798 -0
  198. pyarchinit_mini/translations/messages.pot +0 -0
  199. pyarchinit_mini/utils/__init__.py +27 -0
  200. pyarchinit_mini/utils/auth.py +143 -0
  201. pyarchinit_mini/utils/exceptions.py +38 -0
  202. pyarchinit_mini/utils/relationship_validator.py +388 -0
  203. pyarchinit_mini/utils/stratigraphic_validator.py +613 -0
  204. pyarchinit_mini/utils/validators.py +230 -0
  205. pyarchinit_mini/web_interface/app.py +4152 -0
  206. pyarchinit_mini/web_interface/auth_routes.py +263 -0
  207. pyarchinit_mini/web_interface/em_node_config_routes.py +280 -0
  208. pyarchinit_mini/web_interface/excel_import_routes.py +336 -0
  209. pyarchinit_mini/web_interface/harris_creator_routes.py +568 -0
  210. pyarchinit_mini/web_interface/pyarchinit_import_export_routes.py +888 -0
  211. pyarchinit_mini/web_interface/s3d_routes.py +361 -0
  212. pyarchinit_mini/web_interface/socketio_events.py +537 -0
  213. pyarchinit_mini/web_interface/static/css/style.css +4 -0
  214. pyarchinit_mini/web_interface/static/images/favicon.ico +0 -0
  215. pyarchinit_mini/web_interface/static/images/logo.png +0 -0
  216. pyarchinit_mini/web_interface/static/images/pyarchinit-mini-logo.png +0 -0
  217. pyarchinit_mini/web_interface/static/js/OrbitControls.js +1045 -0
  218. pyarchinit_mini/web_interface/static/js/harris_creator_editor.js +989 -0
  219. pyarchinit_mini/web_interface/static/js/stratigraphic_layout.js +782 -0
  220. pyarchinit_mini/web_interface/static/js/three-d-viewer.js +670 -0
  221. pyarchinit_mini/web_interface/static/js/three.min.js +6 -0
  222. pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.bin +0 -0
  223. pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.gltf +92 -0
  224. pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.mtl +71 -0
  225. pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.obj +244 -0
  226. pyarchinit_mini/web_interface/templates/3d_builder/index.html +2092 -0
  227. pyarchinit_mini/web_interface/templates/3d_viewer/viewer.html +259 -0
  228. pyarchinit_mini/web_interface/templates/admin/database.html +1140 -0
  229. pyarchinit_mini/web_interface/templates/admin/database_connect.html +188 -0
  230. pyarchinit_mini/web_interface/templates/admin/database_info.html +171 -0
  231. pyarchinit_mini/web_interface/templates/admin/database_upload.html +112 -0
  232. pyarchinit_mini/web_interface/templates/analytics/dashboard.html +414 -0
  233. pyarchinit_mini/web_interface/templates/auth/login.html +52 -0
  234. pyarchinit_mini/web_interface/templates/auth/users.html +217 -0
  235. pyarchinit_mini/web_interface/templates/base.html +765 -0
  236. pyarchinit_mini/web_interface/templates/blender_viewer.html +1285 -0
  237. pyarchinit_mini/web_interface/templates/components/language_switcher.html +47 -0
  238. pyarchinit_mini/web_interface/templates/dashboard.html +226 -0
  239. pyarchinit_mini/web_interface/templates/datazioni/list.html +62 -0
  240. pyarchinit_mini/web_interface/templates/em_node_config/index.html +430 -0
  241. pyarchinit_mini/web_interface/templates/excel_import/index.html +391 -0
  242. pyarchinit_mini/web_interface/templates/export/export_import.html +240 -0
  243. pyarchinit_mini/web_interface/templates/harris_creator/editor.html +373 -0
  244. pyarchinit_mini/web_interface/templates/harris_creator/index.html +196 -0
  245. pyarchinit_mini/web_interface/templates/harris_matrix/graphml_export.html +300 -0
  246. pyarchinit_mini/web_interface/templates/harris_matrix/large_graph_message.html +108 -0
  247. pyarchinit_mini/web_interface/templates/harris_matrix/stratigraph_viewer.html +474 -0
  248. pyarchinit_mini/web_interface/templates/harris_matrix/view.html +97 -0
  249. pyarchinit_mini/web_interface/templates/harris_matrix/view_graphviz.html +202 -0
  250. pyarchinit_mini/web_interface/templates/harris_matrix/viewer_3d_integrated.html +1288 -0
  251. pyarchinit_mini/web_interface/templates/inventario/form.html +605 -0
  252. pyarchinit_mini/web_interface/templates/inventario/list.html +109 -0
  253. pyarchinit_mini/web_interface/templates/media/list.html +293 -0
  254. pyarchinit_mini/web_interface/templates/media/upload.html +125 -0
  255. pyarchinit_mini/web_interface/templates/periodizzazione/form.html +81 -0
  256. pyarchinit_mini/web_interface/templates/periodizzazione/list.html +102 -0
  257. pyarchinit_mini/web_interface/templates/periodizzazione/periods.html +107 -0
  258. pyarchinit_mini/web_interface/templates/pyarchinit_import_export/index.html +928 -0
  259. pyarchinit_mini/web_interface/templates/sites/detail.html +128 -0
  260. pyarchinit_mini/web_interface/templates/sites/form.html +315 -0
  261. pyarchinit_mini/web_interface/templates/sites/list.html +273 -0
  262. pyarchinit_mini/web_interface/templates/thesaurus/list.html +290 -0
  263. pyarchinit_mini/web_interface/templates/us/form.html +788 -0
  264. pyarchinit_mini/web_interface/templates/us/list.html +165 -0
  265. pyarchinit_mini/web_interface/templates/validation/report.html +253 -0
  266. pyarchinit_mini/web_interface/test_prova_extended_matrix.graphml +177 -0
  267. pyarchinit_mini/web_interface/three_d_builder_routes.py +1554 -0
  268. pyarchinit_mini/web_interface/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.graphml +1792 -0
  269. pyarchinit_mini/web_interface/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.json +2115 -0
  270. pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_heriverse.json +2132 -0
  271. pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_stratigraphy.graphml +2001 -0
  272. pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_stratigraphy.json +1782 -0
  273. pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.json +1457 -0
  274. pyarchinit_mini-1.9.28.dist-info/METADATA +2141 -0
  275. pyarchinit_mini-1.9.28.dist-info/RECORD +281 -0
  276. pyarchinit_mini-1.9.28.dist-info/WHEEL +5 -0
  277. pyarchinit_mini-1.9.28.dist-info/entry_points.txt +18 -0
  278. pyarchinit_mini-1.9.28.dist-info/top_level.txt +2 -0
  279. tests/__init__.py +1 -0
  280. tests/conftest.py +88 -0
  281. tests/unit/test_site_service.py +106 -0
@@ -0,0 +1,989 @@
1
+ /**
2
+ * Harris Matrix Interactive Editor
3
+ *
4
+ * Visual editor using Cytoscape.js for creating and editing Harris Matrix diagrams
5
+ */
6
+
7
+ // Global state
8
+ let cy; // Cytoscape instance
9
+ let nodeTypes = [];
10
+ let relationshipTypes = [];
11
+ let periods = []; // Period data from period_table
12
+ let selectedNodeType = 'US';
13
+ let selectedRelationshipType = 'Covers';
14
+ let editorMode = 'node'; // 'node' or 'edge'
15
+ let edgeSourceNode = null; // For edge creation
16
+ let nodeIdCounter = 1;
17
+ let selectedElement = null;
18
+
19
+ // Initialize on page load
20
+ document.addEventListener('DOMContentLoaded', function() {
21
+ initializeEditor();
22
+ });
23
+
24
+ async function initializeEditor() {
25
+ console.log('Initializing Harris Matrix Editor...');
26
+
27
+ // Load node and relationship types
28
+ await loadNodeTypes();
29
+ await loadRelationshipTypes();
30
+ await loadPeriods();
31
+
32
+ // Initialize Cytoscape
33
+ initCytoscape();
34
+
35
+ // Load existing data if in edit mode
36
+ loadExistingData();
37
+
38
+ // Setup event listeners
39
+ setupEventListeners();
40
+
41
+ console.log('Editor initialized successfully');
42
+ }
43
+
44
+ /**
45
+ * Load node types from API
46
+ */
47
+ async function loadNodeTypes() {
48
+ try {
49
+ const response = await fetch('/harris-creator/api/node-types');
50
+ nodeTypes = await response.json();
51
+
52
+ // Populate node type buttons
53
+ const container = document.getElementById('node-type-buttons');
54
+ container.innerHTML = '';
55
+
56
+ nodeTypes.forEach(type => {
57
+ const btn = document.createElement('button');
58
+ btn.className = 'btn btn-sm node-type-btn ' +
59
+ (type.value === 'US' ? 'btn-primary' : 'btn-outline-secondary');
60
+ btn.setAttribute('data-type', type.value);
61
+ btn.innerHTML = `<span class="legend-color" style="background: ${type.color}; display: inline-block; width: 12px; height: 12px; margin-right: 5px; vertical-align: middle;"></span>${type.value}`;
62
+ btn.title = type.label;
63
+
64
+ btn.addEventListener('click', function() {
65
+ // Update selected type
66
+ selectedNodeType = type.value;
67
+
68
+ // Update button styles
69
+ document.querySelectorAll('.node-type-btn').forEach(b => {
70
+ b.classList.remove('btn-primary');
71
+ b.classList.add('btn-outline-secondary');
72
+ });
73
+ btn.classList.remove('btn-outline-secondary');
74
+ btn.classList.add('btn-primary');
75
+ });
76
+
77
+ container.appendChild(btn);
78
+ });
79
+
80
+ // Populate property panel select
81
+ const propSelect = document.getElementById('prop-unit-type');
82
+ propSelect.innerHTML = '';
83
+ nodeTypes.forEach(type => {
84
+ const option = document.createElement('option');
85
+ option.value = type.value;
86
+ option.textContent = type.label;
87
+ propSelect.appendChild(option);
88
+ });
89
+
90
+ // Populate legend
91
+ const legend = document.getElementById('node-legend');
92
+ legend.innerHTML = '';
93
+ nodeTypes.slice(0, 6).forEach(type => {
94
+ const item = document.createElement('div');
95
+ item.className = 'legend-item';
96
+ item.innerHTML = `
97
+ <div class="legend-color" style="background: ${type.color};"></div>
98
+ <span>${type.value}</span>
99
+ `;
100
+ legend.appendChild(item);
101
+ });
102
+
103
+ console.log('Loaded', nodeTypes.length, 'node types');
104
+ } catch (error) {
105
+ console.error('Error loading node types:', error);
106
+ alert('Failed to load node types');
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Load relationship types from API
112
+ */
113
+ async function loadRelationshipTypes() {
114
+ try {
115
+ const response = await fetch('/harris-creator/api/relationship-types');
116
+ relationshipTypes = await response.json();
117
+
118
+ // Populate relationship type select
119
+ const select = document.getElementById('relationshipType');
120
+ const propSelect = document.getElementById('prop-relationship-type');
121
+
122
+ [select, propSelect].forEach(sel => {
123
+ sel.innerHTML = '';
124
+ relationshipTypes.forEach(type => {
125
+ const option = document.createElement('option');
126
+ option.value = type.value;
127
+ option.textContent = type.label;
128
+ sel.appendChild(option);
129
+ });
130
+ });
131
+
132
+ // Set default
133
+ selectedRelationshipType = relationshipTypes[0].value;
134
+
135
+ console.log('Loaded', relationshipTypes.length, 'relationship types');
136
+ } catch (error) {
137
+ console.error('Error loading relationship types:', error);
138
+ alert('Failed to load relationship types');
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Load periods and phases from API
144
+ */
145
+ async function loadPeriods() {
146
+ try {
147
+ const response = await fetch('/harris-creator/api/periods');
148
+ periods = await response.json();
149
+
150
+ // Populate period dropdown
151
+ const periodSelect = document.getElementById('prop-period');
152
+ periodSelect.innerHTML = '<option value="">-- Select Period --</option>';
153
+
154
+ periods.forEach(periodData => {
155
+ const option = document.createElement('option');
156
+ option.value = periodData.period;
157
+ option.textContent = periodData.period;
158
+ periodSelect.appendChild(option);
159
+ });
160
+
161
+ // Add event listener to populate phases when period changes
162
+ periodSelect.addEventListener('change', function() {
163
+ const selectedPeriod = this.value;
164
+ const phaseSelect = document.getElementById('prop-phase');
165
+ phaseSelect.innerHTML = '<option value="">-- Select Phase --</option>';
166
+
167
+ if (selectedPeriod) {
168
+ const periodData = periods.find(p => p.period === selectedPeriod);
169
+ if (periodData && periodData.phases) {
170
+ periodData.phases.forEach(phase => {
171
+ const option = document.createElement('option');
172
+ option.value = phase;
173
+ option.textContent = phase;
174
+ phaseSelect.appendChild(option);
175
+ });
176
+ }
177
+ }
178
+ });
179
+
180
+ console.log('Loaded', periods.length, 'periods');
181
+ } catch (error) {
182
+ console.error('Error loading periods:', error);
183
+ alert('Failed to load periods');
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Initialize Cytoscape instance
189
+ */
190
+ function initCytoscape() {
191
+ cy = cytoscape({
192
+ container: document.getElementById('cy'),
193
+
194
+ style: [
195
+ // Node styles - base style with default shape
196
+ {
197
+ selector: 'node',
198
+ style: {
199
+ 'label': 'data(label)',
200
+ 'text-valign': 'center',
201
+ 'text-halign': 'center',
202
+ 'background-color': 'data(color)',
203
+ 'border-width': 2,
204
+ 'border-color': '#333',
205
+ 'font-size': '12px',
206
+ 'width': 'label',
207
+ 'height': 'label',
208
+ 'padding': '10px',
209
+ 'shape': 'rectangle' // Default shape for all nodes
210
+ }
211
+ },
212
+ // Override shape for nodes that have shape data defined
213
+ {
214
+ selector: 'node[shape]',
215
+ style: {
216
+ 'shape': 'data(shape)'
217
+ }
218
+ },
219
+ {
220
+ selector: 'node:selected',
221
+ style: {
222
+ 'border-width': 4,
223
+ 'border-color': '#007bff'
224
+ }
225
+ },
226
+ // Edge styles
227
+ {
228
+ selector: 'edge',
229
+ style: {
230
+ 'width': 2,
231
+ 'line-color': '#666',
232
+ 'target-arrow-color': '#666',
233
+ 'target-arrow-shape': 'triangle',
234
+ 'curve-style': 'bezier',
235
+ 'label': 'data(label)',
236
+ 'font-size': '10px',
237
+ 'text-rotation': 'autorotate',
238
+ 'text-margin-y': -10
239
+ }
240
+ },
241
+ {
242
+ selector: 'edge:selected',
243
+ style: {
244
+ 'line-color': '#007bff',
245
+ 'target-arrow-color': '#007bff',
246
+ 'width': 3
247
+ }
248
+ },
249
+ // Edge styles by type
250
+ {
251
+ selector: 'edge[style="dashed"]',
252
+ style: {
253
+ 'line-style': 'dashed'
254
+ }
255
+ },
256
+ {
257
+ selector: 'edge[style="dotted"]',
258
+ style: {
259
+ 'line-style': 'dotted'
260
+ }
261
+ },
262
+ {
263
+ selector: 'edge[arrow="none"]',
264
+ style: {
265
+ 'target-arrow-shape': 'none'
266
+ }
267
+ }
268
+ ],
269
+
270
+ layout: {
271
+ name: 'preset'
272
+ },
273
+
274
+ // Interaction options
275
+ minZoom: 0.1,
276
+ maxZoom: 3,
277
+ wheelSensitivity: 0.2
278
+ });
279
+
280
+ // Canvas click handler (for adding nodes)
281
+ cy.on('click', function(event) {
282
+ // Only add nodes on background click in node mode
283
+ if (event.target === cy && editorMode === 'node') {
284
+ addNodeAtPosition(event.position);
285
+ }
286
+ });
287
+
288
+ // Node click handler (for selection and edge creation)
289
+ cy.on('tap', 'node', function(event) {
290
+ const node = event.target;
291
+
292
+ if (editorMode === 'edge') {
293
+ handleEdgeCreation(node);
294
+ } else {
295
+ selectElement(node);
296
+ }
297
+ });
298
+
299
+ // Edge click handler (for selection)
300
+ cy.on('tap', 'edge', function(event) {
301
+ const edge = event.target;
302
+ selectElement(edge);
303
+ });
304
+
305
+ // Background click deselects
306
+ cy.on('tap', function(event) {
307
+ if (event.target === cy) {
308
+ deselectAll();
309
+ }
310
+ });
311
+
312
+ console.log('Cytoscape initialized');
313
+ }
314
+
315
+ /**
316
+ * Add node at position
317
+ */
318
+ function addNodeAtPosition(position) {
319
+ const nodeType = nodeTypes.find(t => t.value === selectedNodeType);
320
+ if (!nodeType) return;
321
+
322
+ const usNumber = generateUSNumber();
323
+ const nodeId = `node_${nodeIdCounter++}`;
324
+
325
+ cy.add({
326
+ group: 'nodes',
327
+ data: {
328
+ id: nodeId,
329
+ label: `${selectedNodeType} ${usNumber}`,
330
+ us_number: usNumber,
331
+ unit_type: selectedNodeType,
332
+ description: '',
333
+ area: '',
334
+ period: '',
335
+ phase: '',
336
+ file_path: '',
337
+ color: nodeType.color,
338
+ shape: nodeType.shape || 'rectangle'
339
+ },
340
+ position: position
341
+ });
342
+
343
+ console.log('Added node:', nodeId, 'at position', position);
344
+ }
345
+
346
+ /**
347
+ * Generate unique US number
348
+ */
349
+ function generateUSNumber() {
350
+ const existingNumbers = cy.nodes().map(n => parseInt(n.data('us_number')) || 0);
351
+ const maxNumber = Math.max(1000, ...existingNumbers);
352
+ return (maxNumber + 1).toString();
353
+ }
354
+
355
+ /**
356
+ * Handle edge creation in edge mode
357
+ */
358
+ function handleEdgeCreation(node) {
359
+ if (!edgeSourceNode) {
360
+ // First click: select source
361
+ edgeSourceNode = node;
362
+ node.style('border-color', '#28a745');
363
+ node.style('border-width', 4);
364
+
365
+ showNotification('Source selected. Click target node.', 'info');
366
+ } else {
367
+ // Second click: create edge
368
+ const targetNode = node;
369
+
370
+ if (edgeSourceNode.id() === targetNode.id()) {
371
+ showNotification('Cannot connect node to itself', 'warning');
372
+ resetEdgeCreation();
373
+ return;
374
+ }
375
+
376
+ // Check if edge already exists
377
+ const existingEdge = cy.edges().filter(e =>
378
+ e.data('source') === edgeSourceNode.id() &&
379
+ e.data('target') === targetNode.id()
380
+ );
381
+
382
+ if (existingEdge.length > 0) {
383
+ showNotification('Edge already exists between these nodes', 'warning');
384
+ resetEdgeCreation();
385
+ return;
386
+ }
387
+
388
+ // Get relationship type details
389
+ const relType = relationshipTypes.find(r => r.value === selectedRelationshipType);
390
+
391
+ // Create edge
392
+ const edgeId = `edge_${Date.now()}`;
393
+ cy.add({
394
+ group: 'edges',
395
+ data: {
396
+ id: edgeId,
397
+ source: edgeSourceNode.id(),
398
+ target: targetNode.id(),
399
+ label: relType.symbol,
400
+ relationship: selectedRelationshipType,
401
+ style: relType.style,
402
+ arrow: relType.arrow
403
+ }
404
+ });
405
+
406
+ showNotification('Edge created successfully', 'success');
407
+ resetEdgeCreation();
408
+
409
+ console.log('Created edge:', edgeId);
410
+ }
411
+ }
412
+
413
+ /**
414
+ * Reset edge creation state
415
+ */
416
+ function resetEdgeCreation() {
417
+ if (edgeSourceNode) {
418
+ edgeSourceNode.style('border-color', '#333');
419
+ edgeSourceNode.style('border-width', 2);
420
+ edgeSourceNode = null;
421
+ }
422
+ }
423
+
424
+ /**
425
+ * Select element and show properties
426
+ */
427
+ function selectElement(element) {
428
+ // Deselect previous
429
+ if (selectedElement) {
430
+ selectedElement.unselect();
431
+ }
432
+
433
+ // Select new
434
+ element.select();
435
+ selectedElement = element;
436
+
437
+ // Show properties panel
438
+ if (element.isNode()) {
439
+ showNodeProperties(element);
440
+ } else if (element.isEdge()) {
441
+ showEdgeProperties(element);
442
+ }
443
+ }
444
+
445
+ /**
446
+ * Deselect all elements
447
+ */
448
+ function deselectAll() {
449
+ if (selectedElement) {
450
+ selectedElement.unselect();
451
+ selectedElement = null;
452
+ }
453
+
454
+ // Hide properties
455
+ document.getElementById('no-selection').style.display = 'block';
456
+ document.getElementById('node-properties').style.display = 'none';
457
+ document.getElementById('edge-properties').style.display = 'none';
458
+ }
459
+
460
+ /**
461
+ * Show node properties in panel
462
+ */
463
+ function showNodeProperties(node) {
464
+ document.getElementById('no-selection').style.display = 'none';
465
+ document.getElementById('node-properties').style.display = 'block';
466
+ document.getElementById('edge-properties').style.display = 'none';
467
+
468
+ // Populate fields
469
+ document.getElementById('prop-us-number').value = node.data('us_number') || '';
470
+ document.getElementById('prop-unit-type').value = node.data('unit_type') || 'US';
471
+ document.getElementById('prop-description').value = node.data('description') || '';
472
+ document.getElementById('prop-area').value = node.data('area') || '';
473
+ document.getElementById('prop-period').value = node.data('period') || '';
474
+ document.getElementById('prop-phase').value = node.data('phase') || '';
475
+ document.getElementById('prop-datazione').value = node.data('datazione') || '';
476
+ document.getElementById('prop-file-path').value = node.data('file_path') || '';
477
+
478
+ // Show/hide file path field for DOC type
479
+ const isDoc = node.data('unit_type') === 'DOC';
480
+ document.getElementById('file-path-field').style.display = isDoc ? 'block' : 'none';
481
+
482
+ // Update visibility based on unit type change
483
+ document.getElementById('prop-unit-type').addEventListener('change', function(e) {
484
+ const isDocType = e.target.value === 'DOC';
485
+ document.getElementById('file-path-field').style.display = isDocType ? 'block' : 'none';
486
+ });
487
+ }
488
+
489
+ /**
490
+ * Show edge properties in panel
491
+ */
492
+ function showEdgeProperties(edge) {
493
+ document.getElementById('no-selection').style.display = 'none';
494
+ document.getElementById('node-properties').style.display = 'none';
495
+ document.getElementById('edge-properties').style.display = 'block';
496
+
497
+ // Populate fields
498
+ const sourceNode = cy.getElementById(edge.data('source'));
499
+ const targetNode = cy.getElementById(edge.data('target'));
500
+
501
+ document.getElementById('prop-from-us').value = sourceNode.data('us_number') || '';
502
+ document.getElementById('prop-to-us').value = targetNode.data('us_number') || '';
503
+
504
+ // Use relationshipType (English value) for the select field
505
+ // Fall back to relationship (Italian name) for backward compatibility
506
+ const relValue = edge.data('relationshipType') || edge.data('relationship') || '';
507
+ document.getElementById('prop-relationship-type').value = relValue;
508
+ }
509
+
510
+ /**
511
+ * Load existing data (edit mode)
512
+ */
513
+ function loadExistingData() {
514
+ const nodesData = document.getElementById('existing-nodes').value;
515
+ const relsData = document.getElementById('existing-relationships').value;
516
+
517
+ if (!nodesData || !relsData) return;
518
+
519
+ try {
520
+ const nodes = JSON.parse(nodesData);
521
+ const relationships = JSON.parse(relsData);
522
+
523
+ console.log('Loading existing data:', nodes.length, 'nodes,', relationships.length, 'relationships');
524
+
525
+ // Add nodes
526
+ nodes.forEach((nodeData, index) => {
527
+ const nodeType = nodeTypes.find(t => t.value === nodeData.unit_type) || nodeTypes[0];
528
+
529
+ cy.add({
530
+ group: 'nodes',
531
+ data: {
532
+ id: nodeData.id,
533
+ label: `${nodeData.unit_type} ${nodeData.us_number}`,
534
+ us_number: nodeData.us_number,
535
+ unit_type: nodeData.unit_type,
536
+ description: nodeData.description,
537
+ area: nodeData.area,
538
+ period: nodeData.period,
539
+ phase: nodeData.phase,
540
+ datazione: nodeData.datazione || '', // datazione_estesa
541
+ file_path: nodeData.file_path,
542
+ color: nodeType.color,
543
+ shape: nodeType.shape || 'rectangle'
544
+ },
545
+ position: {
546
+ x: 100 + (index % 5) * 150,
547
+ y: 100 + Math.floor(index / 5) * 100
548
+ }
549
+ });
550
+ });
551
+
552
+ // Add edges
553
+ relationships.forEach(relData => {
554
+ // Convert to strings for comparison (database may store as integers)
555
+ const fromUsStr = String(relData.from_us);
556
+ const toUsStr = String(relData.to_us);
557
+
558
+ const sourceNode = cy.nodes().filter(n => String(n.data('us_number')) === fromUsStr).first();
559
+ const targetNode = cy.nodes().filter(n => String(n.data('us_number')) === toUsStr).first();
560
+
561
+ if (sourceNode.length && targetNode.length) {
562
+ // Bilingual matching: support both Italian (symbol) and English (value) names
563
+ const relType = relationshipTypes.find(r =>
564
+ r.symbol === relData.relationship || r.value === relData.relationship
565
+ ) || relationshipTypes[0];
566
+
567
+ cy.add({
568
+ group: 'edges',
569
+ data: {
570
+ id: `edge_${relData.from_us}_${relData.to_us}`,
571
+ source: sourceNode.id(),
572
+ target: targetNode.id(),
573
+ label: relType.symbol,
574
+ relationship: relData.relationship,
575
+ relationshipType: relType.value, // Store English value for saving
576
+ style: relType.style,
577
+ arrow: relType.arrow
578
+ }
579
+ });
580
+ } else {
581
+ console.warn('Could not find nodes for relationship:', relData);
582
+ }
583
+ });
584
+
585
+ // Apply automatic layout
586
+ applyLayout();
587
+
588
+ console.log('Loaded existing data successfully');
589
+ } catch (error) {
590
+ console.error('Error loading existing data:', error);
591
+ }
592
+ }
593
+
594
+ /**
595
+ * Setup event listeners
596
+ */
597
+ function setupEventListeners() {
598
+ // Mode switching
599
+ document.querySelectorAll('input[name="editorMode"]').forEach(radio => {
600
+ radio.addEventListener('change', function(e) {
601
+ editorMode = e.target.value;
602
+ updateEditorMode();
603
+ });
604
+ });
605
+
606
+ // Relationship type selection
607
+ document.getElementById('relationshipType').addEventListener('change', function(e) {
608
+ selectedRelationshipType = e.target.value;
609
+ });
610
+
611
+ // Apply node properties
612
+ document.getElementById('apply-node-props-btn').addEventListener('click', applyNodeProperties);
613
+
614
+ // Apply edge properties
615
+ document.getElementById('apply-edge-props-btn').addEventListener('click', applyEdgeProperties);
616
+
617
+ // Delete selected
618
+ document.getElementById('delete-selected-btn').addEventListener('click', deleteSelected);
619
+
620
+ // Clear all
621
+ document.getElementById('clear-all-btn').addEventListener('click', clearAll);
622
+
623
+ // Layout
624
+ document.getElementById('layout-btn').addEventListener('click', applyLayout);
625
+
626
+ // Canvas controls
627
+ document.getElementById('zoom-in-btn').addEventListener('click', () => cy.zoom(cy.zoom() * 1.2));
628
+ document.getElementById('zoom-out-btn').addEventListener('click', () => cy.zoom(cy.zoom() * 0.8));
629
+ document.getElementById('fit-btn').addEventListener('click', () => cy.fit(50));
630
+
631
+ // Save button
632
+ document.getElementById('save-btn').addEventListener('click', saveMatrix);
633
+
634
+ // Export buttons
635
+ document.getElementById('export-graphml-btn').addEventListener('click', () => exportMatrix('graphml'));
636
+ document.getElementById('export-dot-btn').addEventListener('click', () => exportMatrix('dot'));
637
+ }
638
+
639
+ /**
640
+ * Update editor mode UI
641
+ */
642
+ function updateEditorMode() {
643
+ const indicator = document.getElementById('mode-indicator');
644
+ const nodeTools = document.getElementById('node-tools');
645
+ const edgeTools = document.getElementById('edge-tools');
646
+
647
+ if (editorMode === 'node') {
648
+ indicator.innerHTML = '<i class="fas fa-square"></i> Node Mode';
649
+ indicator.className = '';
650
+ nodeTools.style.display = 'block';
651
+ edgeTools.style.display = 'none';
652
+ resetEdgeCreation();
653
+ } else {
654
+ indicator.innerHTML = '<i class="fas fa-arrow-right"></i> Edge Mode';
655
+ indicator.className = 'edge-mode';
656
+ nodeTools.style.display = 'none';
657
+ edgeTools.style.display = 'block';
658
+ }
659
+ }
660
+
661
+ /**
662
+ * Apply node properties from panel
663
+ */
664
+ function applyNodeProperties() {
665
+ if (!selectedElement || !selectedElement.isNode()) return;
666
+
667
+ const node = selectedElement;
668
+ const usNumber = document.getElementById('prop-us-number').value;
669
+ const unitType = document.getElementById('prop-unit-type').value;
670
+ const description = document.getElementById('prop-description').value;
671
+ const area = document.getElementById('prop-area').value;
672
+ const period = document.getElementById('prop-period').value;
673
+ const phase = document.getElementById('prop-phase').value;
674
+ const filePath = document.getElementById('prop-file-path').value;
675
+
676
+ // Update node data
677
+ node.data('us_number', usNumber);
678
+ node.data('unit_type', unitType);
679
+ node.data('description', description);
680
+ node.data('area', area);
681
+ node.data('period', period);
682
+ node.data('phase', phase);
683
+ node.data('file_path', filePath);
684
+ node.data('label', `${unitType} ${usNumber}`);
685
+
686
+ // Update color
687
+ const nodeType = nodeTypes.find(t => t.value === unitType);
688
+ if (nodeType) {
689
+ node.data('color', nodeType.color);
690
+ node.data('shape', nodeType.shape || 'rectangle');
691
+ }
692
+
693
+ showNotification('Node properties updated', 'success');
694
+ }
695
+
696
+ /**
697
+ * Apply edge properties from panel
698
+ */
699
+ function applyEdgeProperties() {
700
+ if (!selectedElement || !selectedElement.isEdge()) return;
701
+
702
+ const edge = selectedElement;
703
+ const relationship = document.getElementById('prop-relationship-type').value;
704
+
705
+ // Update edge data
706
+ edge.data('relationship', relationship);
707
+
708
+ // Update label and style
709
+ const relType = relationshipTypes.find(r => r.value === relationship);
710
+ if (relType) {
711
+ edge.data('label', relType.symbol);
712
+ edge.data('style', relType.style);
713
+ edge.data('arrow', relType.arrow);
714
+ }
715
+
716
+ showNotification('Edge properties updated', 'success');
717
+ }
718
+
719
+ /**
720
+ * Delete selected element
721
+ */
722
+ function deleteSelected() {
723
+ if (!selectedElement) {
724
+ showNotification('No element selected', 'warning');
725
+ return;
726
+ }
727
+
728
+ if (confirm('Delete selected element?')) {
729
+ cy.remove(selectedElement);
730
+ selectedElement = null;
731
+ deselectAll();
732
+ showNotification('Element deleted', 'success');
733
+ }
734
+ }
735
+
736
+ /**
737
+ * Clear all nodes and edges
738
+ */
739
+ function clearAll() {
740
+ if (cy.elements().length === 0) {
741
+ showNotification('Canvas is already empty', 'info');
742
+ return;
743
+ }
744
+
745
+ if (confirm('Clear all nodes and edges? This cannot be undone.')) {
746
+ cy.elements().remove();
747
+ deselectAll();
748
+ showNotification('Canvas cleared', 'success');
749
+ }
750
+ }
751
+
752
+ /**
753
+ * Remove redundant (transitive) edges from the graph
754
+ * Implements transitive reduction: if A->B->C exists, A->C is redundant
755
+ * ONLY removes edges with a direct 2-hop path (A->B->C), not longer paths
756
+ */
757
+ function removeTransitiveEdges() {
758
+ console.log('Starting transitive reduction...');
759
+ const edges = cy.edges();
760
+ const edgesToRemove = [];
761
+
762
+ // Build adjacency list for quick lookups
763
+ const outgoing = new Map(); // Map of node -> Set of target nodes
764
+
765
+ edges.forEach(edge => {
766
+ const src = edge.source().id();
767
+ const tgt = edge.target().id();
768
+
769
+ if (!outgoing.has(src)) {
770
+ outgoing.set(src, new Set());
771
+ }
772
+ outgoing.get(src).add(tgt);
773
+ });
774
+
775
+ // For each edge A->C, check if there exists a node B such that A->B and B->C
776
+ edges.forEach(edge => {
777
+ const source = edge.source().id();
778
+ const target = edge.target().id();
779
+
780
+ // Check if there's a 2-hop path: source -> intermediate -> target
781
+ const sourceTargets = outgoing.get(source);
782
+ if (!sourceTargets) return;
783
+
784
+ let isRedundant = false;
785
+
786
+ // For each intermediate node that source points to
787
+ for (const intermediate of sourceTargets) {
788
+ // Skip if intermediate is the target itself (that's the edge we're checking)
789
+ if (intermediate === target) continue;
790
+
791
+ // Check if intermediate also points to target
792
+ const intermediateTargets = outgoing.get(intermediate);
793
+ if (intermediateTargets && intermediateTargets.has(target)) {
794
+ // Found a 2-hop path: source -> intermediate -> target
795
+ // This makes the direct edge source -> target redundant
796
+ isRedundant = true;
797
+ console.log(`Redundant edge: ${source} -> ${target} (via ${intermediate})`);
798
+ break;
799
+ }
800
+ }
801
+
802
+ if (isRedundant) {
803
+ edgesToRemove.push(edge);
804
+ }
805
+ });
806
+
807
+ // Remove redundant edges
808
+ if (edgesToRemove.length > 0) {
809
+ const edgeCollection = cy.collection(edgesToRemove);
810
+ edgeCollection.remove();
811
+ console.log(`Removed ${edgesToRemove.length} redundant edges`);
812
+ showNotification(`Rimossi ${edgesToRemove.length} archi ridondanti`, 'info');
813
+ } else {
814
+ console.log('No redundant edges found');
815
+ showNotification('Nessun arco ridondante trovato', 'info');
816
+ }
817
+ }
818
+
819
+ /**
820
+ * Apply hierarchical layout for Harris Matrix
821
+ * Uses Dagre for reliable hierarchical visualization
822
+ */
823
+ function applyLayout() {
824
+ if (cy.nodes().length === 0) {
825
+ showNotification('No nodes to layout', 'warning');
826
+ return;
827
+ }
828
+
829
+ // Remove redundant transitive edges before layout
830
+ removeTransitiveEdges();
831
+
832
+ // Use Dagre layout (more reliable than ELK)
833
+ cy.layout({
834
+ name: 'dagre',
835
+ // Direction: top to bottom (most recent to oldest)
836
+ rankDir: 'TB',
837
+ // Alignment
838
+ align: 'UL',
839
+ // Spacing
840
+ nodeSep: 100,
841
+ edgeSep: 50,
842
+ rankSep: 120,
843
+ // Padding
844
+ padding: 40,
845
+ // Animation
846
+ animate: true,
847
+ animationDuration: 600,
848
+ animationEasing: 'ease-out',
849
+ // Fit to viewport
850
+ fit: true,
851
+ // Ranking algorithm
852
+ ranker: 'network-simplex'
853
+ }).run();
854
+
855
+ showNotification('Layout applied', 'success');
856
+ }
857
+
858
+ /**
859
+ * Save matrix to database
860
+ */
861
+ async function saveMatrix() {
862
+ const siteName = document.getElementById('site-name').value;
863
+
864
+ if (cy.nodes().length === 0) {
865
+ showNotification('No nodes to save', 'warning');
866
+ return;
867
+ }
868
+
869
+ // Collect data
870
+ const nodes = [];
871
+ cy.nodes().forEach(node => {
872
+ nodes.push({
873
+ us_number: node.data('us_number'),
874
+ unit_type: node.data('unit_type'),
875
+ description: node.data('description'),
876
+ area: node.data('area'),
877
+ period: node.data('period'),
878
+ phase: node.data('phase'),
879
+ file_path: node.data('file_path')
880
+ });
881
+ });
882
+
883
+ const relationships = [];
884
+ cy.edges().forEach(edge => {
885
+ const sourceNode = cy.getElementById(edge.data('source'));
886
+ const targetNode = cy.getElementById(edge.data('target'));
887
+
888
+ relationships.push({
889
+ from_us: sourceNode.data('us_number'),
890
+ to_us: targetNode.data('us_number'),
891
+ relationship: edge.data('relationship')
892
+ });
893
+ });
894
+
895
+ // Send to server
896
+ try {
897
+ console.log('[DEBUG] Sending data:', {
898
+ site_name: siteName,
899
+ nodes: nodes.length,
900
+ relationships: relationships.length
901
+ });
902
+
903
+ const response = await fetch('/harris-creator/api/save', {
904
+ method: 'POST',
905
+ headers: {
906
+ 'Content-Type': 'application/json'
907
+ },
908
+ body: JSON.stringify({
909
+ site_name: siteName,
910
+ nodes: nodes,
911
+ relationships: relationships
912
+ })
913
+ });
914
+
915
+ console.log('[DEBUG] Response status:', response.status);
916
+ console.log('[DEBUG] Response headers:', response.headers.get('content-type'));
917
+
918
+ // Check if response is JSON
919
+ const contentType = response.headers.get('content-type');
920
+ if (!contentType || !contentType.includes('application/json')) {
921
+ const text = await response.text();
922
+ console.error('[DEBUG] Non-JSON response:', text);
923
+ showNotification('Server error: Response is not JSON. Check server logs.', 'danger');
924
+ return;
925
+ }
926
+
927
+ const result = await response.json();
928
+ console.log('[DEBUG] Result:', result);
929
+
930
+ if (result.success) {
931
+ showNotification(
932
+ `Saved: ${result.nodes_created + result.nodes_updated} nodes, ` +
933
+ `${result.relationships_created + result.relationships_updated} relationships`,
934
+ 'success'
935
+ );
936
+ } else {
937
+ showNotification('Error: ' + result.message, 'danger');
938
+ }
939
+ } catch (error) {
940
+ console.error('Save error:', error);
941
+ showNotification('Failed to save: ' + error.message, 'danger');
942
+ }
943
+ }
944
+
945
+ /**
946
+ * Export matrix to GraphML or DOT
947
+ */
948
+ async function exportMatrix(format) {
949
+ const siteName = document.getElementById('site-name').value;
950
+
951
+ if (cy.nodes().length === 0) {
952
+ showNotification('No nodes to export. Save first.', 'warning');
953
+ return;
954
+ }
955
+
956
+ // First save
957
+ showNotification('Saving before export...', 'info');
958
+ await saveMatrix();
959
+
960
+ // Then export
961
+ const url = `/harris-creator/api/export/${format}?site=${encodeURIComponent(siteName)}`;
962
+ window.location.href = url;
963
+
964
+ showNotification(`Exporting to ${format.toUpperCase()}...`, 'success');
965
+ }
966
+
967
+ /**
968
+ * Show notification
969
+ */
970
+ function showNotification(message, type = 'info') {
971
+ // Simple implementation - you can enhance with toast notifications
972
+ console.log(`[${type.toUpperCase()}] ${message}`);
973
+
974
+ // Create Bootstrap alert
975
+ const alertDiv = document.createElement('div');
976
+ alertDiv.className = `alert alert-${type} alert-dismissible fade show position-fixed`;
977
+ alertDiv.style.cssText = 'top: 20px; right: 20px; z-index: 9999; min-width: 300px;';
978
+ alertDiv.innerHTML = `
979
+ ${message}
980
+ <button type="button" class="btn-close" data-bs-dismiss="alert"></button>
981
+ `;
982
+
983
+ document.body.appendChild(alertDiv);
984
+
985
+ // Auto-dismiss after 3 seconds
986
+ setTimeout(() => {
987
+ alertDiv.remove();
988
+ }, 3000);
989
+ }