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.
- pyarchinit_mini/__init__.py +40 -0
- pyarchinit_mini/api/__init__.py +92 -0
- pyarchinit_mini/api/auth.py +312 -0
- pyarchinit_mini/api/dependencies.py +63 -0
- pyarchinit_mini/api/graphml.py +160 -0
- pyarchinit_mini/api/inventario.py +107 -0
- pyarchinit_mini/api/schemas.py +186 -0
- pyarchinit_mini/api/site.py +220 -0
- pyarchinit_mini/api/us.py +107 -0
- pyarchinit_mini/cli/__init__.py +7 -0
- pyarchinit_mini/cli/export_import_cli.py +289 -0
- pyarchinit_mini/cli/extended_matrix_import.py +101 -0
- pyarchinit_mini/cli/graphml_cli.py +219 -0
- pyarchinit_mini/cli/harris_import.py +574 -0
- pyarchinit_mini/cli/harris_template.py +323 -0
- pyarchinit_mini/cli/migrate.py +202 -0
- pyarchinit_mini/cli/pyarchinit_import_export_cli.py +428 -0
- pyarchinit_mini/cli_interface/__init__.py +1 -0
- pyarchinit_mini/cli_interface/cli_app.py +529 -0
- pyarchinit_mini/config/__init__.py +0 -0
- pyarchinit_mini/config/connection_manager.py +286 -0
- pyarchinit_mini/config/em_node_config_manager.py +452 -0
- pyarchinit_mini/config/em_node_types.yaml +349 -0
- pyarchinit_mini/database/__init__.py +13 -0
- pyarchinit_mini/database/connection.py +168 -0
- pyarchinit_mini/database/database_creator.py +290 -0
- pyarchinit_mini/database/manager.py +231 -0
- pyarchinit_mini/database/migration_scripts/__init__.py +7 -0
- pyarchinit_mini/database/migration_scripts/add_file_path.py +152 -0
- pyarchinit_mini/database/migration_scripts/add_i18n_columns.py +312 -0
- pyarchinit_mini/database/migration_scripts/add_tipo_documento.py +154 -0
- pyarchinit_mini/database/migration_scripts/change_us_area_to_text.py +321 -0
- pyarchinit_mini/database/migration_scripts/change_us_area_to_text_simple.py +230 -0
- pyarchinit_mini/database/migrations.py +232 -0
- pyarchinit_mini/database/postgres_installer.py +375 -0
- pyarchinit_mini/database/schemas.py +177 -0
- pyarchinit_mini/desktop_gui/__init__.py +51 -0
- pyarchinit_mini/desktop_gui/analytics_dialog.py +228 -0
- pyarchinit_mini/desktop_gui/assets/logo.png +0 -0
- pyarchinit_mini/desktop_gui/dialogs.py +1984 -0
- pyarchinit_mini/desktop_gui/excel_import_dialog.py +380 -0
- pyarchinit_mini/desktop_gui/export_import_dialog.py +313 -0
- pyarchinit_mini/desktop_gui/graphml_export_dialog.py +221 -0
- pyarchinit_mini/desktop_gui/gui_app.py +106 -0
- pyarchinit_mini/desktop_gui/harris_matrix_editor.py +970 -0
- pyarchinit_mini/desktop_gui/i18n.py +191 -0
- pyarchinit_mini/desktop_gui/icons.py +70 -0
- pyarchinit_mini/desktop_gui/inventario_dialog_extended.py +966 -0
- pyarchinit_mini/desktop_gui/main_window.py +1826 -0
- pyarchinit_mini/desktop_gui/media_manager_advanced.py +141 -0
- pyarchinit_mini/desktop_gui/postgres_installer_dialog.py +357 -0
- pyarchinit_mini/desktop_gui/pyarchinit_import_export_dialog.py +585 -0
- pyarchinit_mini/desktop_gui/thesaurus_dialog.py +463 -0
- pyarchinit_mini/desktop_gui/us_dialog_extended.py +2122 -0
- pyarchinit_mini/dto/__init__.py +16 -0
- pyarchinit_mini/dto/inventario_dto.py +57 -0
- pyarchinit_mini/dto/site_dto.py +62 -0
- pyarchinit_mini/dto/us_dto.py +237 -0
- pyarchinit_mini/exceptions.py +23 -0
- pyarchinit_mini/graphml_converter/__init__.py +47 -0
- pyarchinit_mini/graphml_converter/converter.py +343 -0
- pyarchinit_mini/graphml_converter/dot_parser.py +1331 -0
- pyarchinit_mini/graphml_converter/em_palette.py +355 -0
- pyarchinit_mini/graphml_converter/graphml_builder.py +657 -0
- pyarchinit_mini/graphml_converter/graphml_exporter.py +1048 -0
- pyarchinit_mini/graphml_converter/pure_networkx_exporter.py +719 -0
- pyarchinit_mini/graphml_converter/svg_resources.py +244 -0
- pyarchinit_mini/graphml_converter/templates/EM_palette.graphml +658 -0
- pyarchinit_mini/graphml_converter/x11_colors.py +678 -0
- pyarchinit_mini/graphml_converter/yed_template.py +365 -0
- pyarchinit_mini/harris_matrix/__init__.py +11 -0
- pyarchinit_mini/harris_matrix/enhanced_visualizer.py +465 -0
- pyarchinit_mini/harris_matrix/matrix_generator.py +1244 -0
- pyarchinit_mini/harris_matrix/matrix_visualizer.py +368 -0
- pyarchinit_mini/harris_matrix/pyarchinit_visualizer.py +581 -0
- pyarchinit_mini/i18n/__init__.py +15 -0
- pyarchinit_mini/i18n/flask_babel_config.py +94 -0
- pyarchinit_mini/i18n/locale_manager.py +142 -0
- pyarchinit_mini/mcp_server/__init__.py +22 -0
- pyarchinit_mini/mcp_server/__main__.py +45 -0
- pyarchinit_mini/mcp_server/blender_client.py +525 -0
- pyarchinit_mini/mcp_server/config.py +181 -0
- pyarchinit_mini/mcp_server/event_stream.py +282 -0
- pyarchinit_mini/mcp_server/graphml_parser.py +612 -0
- pyarchinit_mini/mcp_server/http_server.py +501 -0
- pyarchinit_mini/mcp_server/period_filter.py +362 -0
- pyarchinit_mini/mcp_server/prompts/__init__.py +18 -0
- pyarchinit_mini/mcp_server/prompts/base_prompt.py +23 -0
- pyarchinit_mini/mcp_server/prompts/period_visualization_prompt.py +25 -0
- pyarchinit_mini/mcp_server/prompts/stratigraphic_model_prompt.py +25 -0
- pyarchinit_mini/mcp_server/prompts/us_description_prompt.py +25 -0
- pyarchinit_mini/mcp_server/proxy_generator.py +470 -0
- pyarchinit_mini/mcp_server/resources/__init__.py +24 -0
- pyarchinit_mini/mcp_server/resources/base_resource.py +79 -0
- pyarchinit_mini/mcp_server/resources/graphml_resource.py +382 -0
- pyarchinit_mini/mcp_server/resources/periods_resource.py +52 -0
- pyarchinit_mini/mcp_server/resources/relationships_resource.py +52 -0
- pyarchinit_mini/mcp_server/resources/sites_resource.py +52 -0
- pyarchinit_mini/mcp_server/resources/us_resource.py +52 -0
- pyarchinit_mini/mcp_server/server.py +564 -0
- pyarchinit_mini/mcp_server/tools/__init__.py +24 -0
- pyarchinit_mini/mcp_server/tools/base_tool.py +119 -0
- pyarchinit_mini/mcp_server/tools/batch_insert_tool.py +393 -0
- pyarchinit_mini/mcp_server/tools/build_3d_tool.py +524 -0
- pyarchinit_mini/mcp_server/tools/chatgpt_fetch_tool.py +183 -0
- pyarchinit_mini/mcp_server/tools/chatgpt_search_tool.py +130 -0
- pyarchinit_mini/mcp_server/tools/configure_em_nodes_tool.py +427 -0
- pyarchinit_mini/mcp_server/tools/create_database_tool.py +118 -0
- pyarchinit_mini/mcp_server/tools/create_harris_matrix_tool.py +421 -0
- pyarchinit_mini/mcp_server/tools/data_import_parser_tool.py +695 -0
- pyarchinit_mini/mcp_server/tools/data_management_tool.py +262 -0
- pyarchinit_mini/mcp_server/tools/database_manager_tool.py +200 -0
- pyarchinit_mini/mcp_server/tools/delete_data_tool.py +288 -0
- pyarchinit_mini/mcp_server/tools/export_harris_matrix_graphml_tool.py +184 -0
- pyarchinit_mini/mcp_server/tools/export_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/filter_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/generate_report_tool.py +679 -0
- pyarchinit_mini/mcp_server/tools/get_schema_tool.py +224 -0
- pyarchinit_mini/mcp_server/tools/import_excel_tool.py +270 -0
- pyarchinit_mini/mcp_server/tools/insert_data_tool.py +390 -0
- pyarchinit_mini/mcp_server/tools/manage_services_tool.py +507 -0
- pyarchinit_mini/mcp_server/tools/material_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/media_management_tool.py +494 -0
- pyarchinit_mini/mcp_server/tools/position_tool.py +32 -0
- pyarchinit_mini/mcp_server/tools/pyarchinit_sync_tool.py +172 -0
- pyarchinit_mini/mcp_server/tools/resolve_conflicts_tool.py +377 -0
- pyarchinit_mini/mcp_server/tools/service_management_tool.py +104 -0
- pyarchinit_mini/mcp_server/tools/thesaurus_management_tool.py +420 -0
- pyarchinit_mini/mcp_server/tools/update_data_tool.py +360 -0
- pyarchinit_mini/mcp_server/tools/upload_file_tool.py +112 -0
- pyarchinit_mini/mcp_server/tools/validate_relationship_format_tool.py +327 -0
- pyarchinit_mini/mcp_server/tools/validate_relationship_integrity_tool.py +532 -0
- pyarchinit_mini/mcp_server/tools/validate_stratigraphic_relationships_tool.py +261 -0
- pyarchinit_mini/mcp_server/tools/validate_stratigraphy_tool.py +363 -0
- pyarchinit_mini/media_manager/__init__.py +9 -0
- pyarchinit_mini/media_manager/media_handler.py +322 -0
- pyarchinit_mini/models/__init__.py +35 -0
- pyarchinit_mini/models/base.py +32 -0
- pyarchinit_mini/models/datazione.py +68 -0
- pyarchinit_mini/models/extended_matrix.py +44 -0
- pyarchinit_mini/models/harris_matrix.py +117 -0
- pyarchinit_mini/models/inventario_materiali.py +238 -0
- pyarchinit_mini/models/media.py +109 -0
- pyarchinit_mini/models/site.py +96 -0
- pyarchinit_mini/models/thesaurus.py +179 -0
- pyarchinit_mini/models/us.py +291 -0
- pyarchinit_mini/models/user.py +78 -0
- pyarchinit_mini/pdf_export/__init__.py +9 -0
- pyarchinit_mini/pdf_export/pdf_generator.py +1048 -0
- pyarchinit_mini/pdf_export/pdf_generator_backup.py +1013 -0
- pyarchinit_mini/pdf_export/pyarchinit_finds_template.py +404 -0
- pyarchinit_mini/pdf_export/pyarchinit_inventory_template.py +353 -0
- pyarchinit_mini/pdf_export/usm_implementation.py +465 -0
- pyarchinit_mini/s3d_integration/__init__.py +9 -0
- pyarchinit_mini/s3d_integration/model_manager.py +234 -0
- pyarchinit_mini/s3d_integration/s3d_converter.py +848 -0
- pyarchinit_mini/scripts/__init__.py +3 -0
- pyarchinit_mini/scripts/auto_translate.py +679 -0
- pyarchinit_mini/scripts/configure_claude_desktop.py +277 -0
- pyarchinit_mini/scripts/create_admin_user.py +152 -0
- pyarchinit_mini/scripts/create_correct_stratigraphy_sample.py +373 -0
- pyarchinit_mini/scripts/create_italian_sample_database.py +302 -0
- pyarchinit_mini/scripts/create_sample_for_webapp.py +463 -0
- pyarchinit_mini/scripts/create_sample_with_50_us.py +353 -0
- pyarchinit_mini/scripts/generate_test_3d_model.py +323 -0
- pyarchinit_mini/scripts/load_italian_as_main.py +39 -0
- pyarchinit_mini/scripts/load_sample_as_main.py +73 -0
- pyarchinit_mini/scripts/migrate_relationships.py +165 -0
- pyarchinit_mini/scripts/populate_sample_data.py +498 -0
- pyarchinit_mini/scripts/populate_simple_data.py +213 -0
- pyarchinit_mini/scripts/setup_auth.py +112 -0
- pyarchinit_mini/scripts/setup_user_env.py +316 -0
- pyarchinit_mini/scripts/test_cascade_delete.py +91 -0
- pyarchinit_mini/scripts/test_relationship_fix.py +128 -0
- pyarchinit_mini/scripts/update_sample_relationships.py +136 -0
- pyarchinit_mini/scripts/update_urls_and_versions.py +312 -0
- pyarchinit_mini/scripts/verify_docs.py +312 -0
- pyarchinit_mini/services/__init__.py +19 -0
- pyarchinit_mini/services/analytics_service.py +242 -0
- pyarchinit_mini/services/command_parser.py +159 -0
- pyarchinit_mini/services/datazione_service.py +244 -0
- pyarchinit_mini/services/export_import_service.py +430 -0
- pyarchinit_mini/services/extended_matrix_excel_parser.py +708 -0
- pyarchinit_mini/services/import_export_service.py +2381 -0
- pyarchinit_mini/services/inventario_service.py +347 -0
- pyarchinit_mini/services/mcp_executor.py +206 -0
- pyarchinit_mini/services/media_service.py +458 -0
- pyarchinit_mini/services/periodizzazione_service.py +417 -0
- pyarchinit_mini/services/relationship_sync_service.py +400 -0
- pyarchinit_mini/services/site_service.py +328 -0
- pyarchinit_mini/services/thesaurus_service.py +358 -0
- pyarchinit_mini/services/us_service.py +446 -0
- pyarchinit_mini/services/user_service.py +293 -0
- pyarchinit_mini/translations/en/LC_MESSAGES/messages.mo +0 -0
- pyarchinit_mini/translations/en/LC_MESSAGES/messages.po +5110 -0
- pyarchinit_mini/translations/it/LC_MESSAGES/messages.mo +0 -0
- pyarchinit_mini/translations/it/LC_MESSAGES/messages.po +4798 -0
- pyarchinit_mini/translations/messages.pot +0 -0
- pyarchinit_mini/utils/__init__.py +27 -0
- pyarchinit_mini/utils/auth.py +143 -0
- pyarchinit_mini/utils/exceptions.py +38 -0
- pyarchinit_mini/utils/relationship_validator.py +388 -0
- pyarchinit_mini/utils/stratigraphic_validator.py +613 -0
- pyarchinit_mini/utils/validators.py +230 -0
- pyarchinit_mini/web_interface/app.py +4152 -0
- pyarchinit_mini/web_interface/auth_routes.py +263 -0
- pyarchinit_mini/web_interface/em_node_config_routes.py +280 -0
- pyarchinit_mini/web_interface/excel_import_routes.py +336 -0
- pyarchinit_mini/web_interface/harris_creator_routes.py +568 -0
- pyarchinit_mini/web_interface/pyarchinit_import_export_routes.py +888 -0
- pyarchinit_mini/web_interface/s3d_routes.py +361 -0
- pyarchinit_mini/web_interface/socketio_events.py +537 -0
- pyarchinit_mini/web_interface/static/css/style.css +4 -0
- pyarchinit_mini/web_interface/static/images/favicon.ico +0 -0
- pyarchinit_mini/web_interface/static/images/logo.png +0 -0
- pyarchinit_mini/web_interface/static/images/pyarchinit-mini-logo.png +0 -0
- pyarchinit_mini/web_interface/static/js/OrbitControls.js +1045 -0
- pyarchinit_mini/web_interface/static/js/harris_creator_editor.js +989 -0
- pyarchinit_mini/web_interface/static/js/stratigraphic_layout.js +782 -0
- pyarchinit_mini/web_interface/static/js/three-d-viewer.js +670 -0
- pyarchinit_mini/web_interface/static/js/three.min.js +6 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.bin +0 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.gltf +92 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.mtl +71 -0
- pyarchinit_mini/web_interface/static/uploads/3d_models/Sito Archeologico di Esempio/site/stratigraphy_test.obj +244 -0
- pyarchinit_mini/web_interface/templates/3d_builder/index.html +2092 -0
- pyarchinit_mini/web_interface/templates/3d_viewer/viewer.html +259 -0
- pyarchinit_mini/web_interface/templates/admin/database.html +1140 -0
- pyarchinit_mini/web_interface/templates/admin/database_connect.html +188 -0
- pyarchinit_mini/web_interface/templates/admin/database_info.html +171 -0
- pyarchinit_mini/web_interface/templates/admin/database_upload.html +112 -0
- pyarchinit_mini/web_interface/templates/analytics/dashboard.html +414 -0
- pyarchinit_mini/web_interface/templates/auth/login.html +52 -0
- pyarchinit_mini/web_interface/templates/auth/users.html +217 -0
- pyarchinit_mini/web_interface/templates/base.html +765 -0
- pyarchinit_mini/web_interface/templates/blender_viewer.html +1285 -0
- pyarchinit_mini/web_interface/templates/components/language_switcher.html +47 -0
- pyarchinit_mini/web_interface/templates/dashboard.html +226 -0
- pyarchinit_mini/web_interface/templates/datazioni/list.html +62 -0
- pyarchinit_mini/web_interface/templates/em_node_config/index.html +430 -0
- pyarchinit_mini/web_interface/templates/excel_import/index.html +391 -0
- pyarchinit_mini/web_interface/templates/export/export_import.html +240 -0
- pyarchinit_mini/web_interface/templates/harris_creator/editor.html +373 -0
- pyarchinit_mini/web_interface/templates/harris_creator/index.html +196 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/graphml_export.html +300 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/large_graph_message.html +108 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/stratigraph_viewer.html +474 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/view.html +97 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/view_graphviz.html +202 -0
- pyarchinit_mini/web_interface/templates/harris_matrix/viewer_3d_integrated.html +1288 -0
- pyarchinit_mini/web_interface/templates/inventario/form.html +605 -0
- pyarchinit_mini/web_interface/templates/inventario/list.html +109 -0
- pyarchinit_mini/web_interface/templates/media/list.html +293 -0
- pyarchinit_mini/web_interface/templates/media/upload.html +125 -0
- pyarchinit_mini/web_interface/templates/periodizzazione/form.html +81 -0
- pyarchinit_mini/web_interface/templates/periodizzazione/list.html +102 -0
- pyarchinit_mini/web_interface/templates/periodizzazione/periods.html +107 -0
- pyarchinit_mini/web_interface/templates/pyarchinit_import_export/index.html +928 -0
- pyarchinit_mini/web_interface/templates/sites/detail.html +128 -0
- pyarchinit_mini/web_interface/templates/sites/form.html +315 -0
- pyarchinit_mini/web_interface/templates/sites/list.html +273 -0
- pyarchinit_mini/web_interface/templates/thesaurus/list.html +290 -0
- pyarchinit_mini/web_interface/templates/us/form.html +788 -0
- pyarchinit_mini/web_interface/templates/us/list.html +165 -0
- pyarchinit_mini/web_interface/templates/validation/report.html +253 -0
- pyarchinit_mini/web_interface/test_prova_extended_matrix.graphml +177 -0
- pyarchinit_mini/web_interface/three_d_builder_routes.py +1554 -0
- pyarchinit_mini/web_interface/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.graphml +1792 -0
- pyarchinit_mini/web_interface/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.json +2115 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_heriverse.json +2132 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_stratigraphy.graphml +2001 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Scavo archeologico_stratigraphy.json +1782 -0
- pyarchinit_mini/web_interface/web_interface/static/uploads/graphml/Sito Archeologico di Esempio_stratigraphy.json +1457 -0
- pyarchinit_mini-1.9.28.dist-info/METADATA +2141 -0
- pyarchinit_mini-1.9.28.dist-info/RECORD +281 -0
- pyarchinit_mini-1.9.28.dist-info/WHEEL +5 -0
- pyarchinit_mini-1.9.28.dist-info/entry_points.txt +18 -0
- pyarchinit_mini-1.9.28.dist-info/top_level.txt +2 -0
- tests/__init__.py +1 -0
- tests/conftest.py +88 -0
- 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
|
+
}
|