graph-explorer
Version:
Graph Explorer can be used to explore and RDF graphs in SPARQL endpoints or on the web.
13,463 lines • 577 kB
JavaScript
(function() { try { if (typeof document != "undefined") { var elementStyle = document.createElement("style"); elementStyle.appendChild(document.createTextNode(".graph-explorer {\n --ge-font-sans: \"Inter\", system-ui, -apple-system, BlinkMacSystemFont,\n \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n --ge-font-mono: \"SFMono-Regular\", \"JetBrains Mono\", \"Fira Code\", ui-monospace,\n Menlo, Consolas, monospace;\n --ge-bg: #f1f5f9;\n --ge-surface: #ffffff;\n --ge-surface-2: #f8fafc;\n --ge-surface-3: #f1f5f9;\n --ge-border: #e5e9f0;\n --ge-border-strong: #cbd5e1;\n --ge-text: #0f172a;\n --ge-text-muted: #475569;\n --ge-text-subtle: #94a3b8;\n --ge-accent: #4f46e5;\n --ge-accent-hover: #4338ca;\n --ge-accent-fg: #ffffff;\n --ge-accent-soft: #eef2ff;\n --ge-accent-soft-border: #c7d2fe;\n --ge-success: #16a34a;\n --ge-success-hover: #15803d;\n --ge-danger: #dc2626;\n --ge-danger-hover: #b91c1c;\n --ge-warning: #f59e0b;\n --ge-canvas: #fbfcfe;\n --ge-canvas-dot: #dbe2ee;\n --ge-radius-sm: 6px;\n --ge-radius: 8px;\n --ge-radius-lg: 12px;\n --ge-radius-pill: 999px;\n --ge-shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.06);\n --ge-shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.08),\n 0 1px 2px rgba(15, 23, 42, 0.06);\n --ge-shadow-md: 0 6px 16px rgba(15, 23, 42, 0.1),\n 0 2px 6px rgba(15, 23, 42, 0.06);\n --ge-shadow-lg: 0 16px 40px rgba(15, 23, 42, 0.16),\n 0 6px 12px rgba(15, 23, 42, 0.08);\n --ge-ring: 0 0 0 3px rgba(79, 70, 229, 0.28);\n --ge-transition: 0.18s cubic-bezier(0.4, 0, 0.2, 1);\n}\n\n.graph-explorer-spinner__arrow {\n animation-name: graph-explorer-spinner-rotation;\n animation-duration: 1.5s;\n animation-timing-function: cubic-bezier(0.645, 0.045, 0.355, 1);\n animation-iteration-count: infinite;\n}\n.graph-explorer-spinner[data-error=true] .graph-explorer-spinner__arrow {\n animation-iteration-count: 1;\n}\n\n@keyframes graph-explorer-spinner-rotation {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n.clearfix:before, .clearfix:after {\n display: table;\n content: \"\";\n}\n.clearfix:after {\n clear: both;\n}\n\n.graph-explorer-label {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: 700;\n}\n\n.graph-explorer-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n padding: 7px 13px;\n margin-bottom: 0;\n font-size: 13px;\n font-weight: 500;\n line-height: 1.4;\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n user-select: none;\n background-image: none;\n border: 1px solid transparent;\n border-radius: var(--ge-radius-sm);\n text-transform: none;\n transition: background-color var(--ge-transition), border-color var(--ge-transition), box-shadow var(--ge-transition), color var(--ge-transition);\n}\n.graph-explorer-btn:focus-visible {\n outline: none;\n box-shadow: var(--ge-ring);\n}\n.graph-explorer-btn[disabled] {\n cursor: not-allowed;\n opacity: 0.55;\n box-shadow: none;\n}\n\n.graph-explorer-btn-default {\n background-color: var(--ge-surface);\n border-color: var(--ge-border-strong);\n color: var(--ge-text-muted);\n box-shadow: var(--ge-shadow-xs);\n}\n.graph-explorer-btn-default:hover {\n background-color: var(--ge-surface-3);\n border-color: var(--ge-border-strong);\n color: var(--ge-text);\n}\n.graph-explorer-btn-default.active {\n background-color: var(--ge-accent-soft);\n border-color: var(--ge-accent-soft-border);\n color: var(--ge-accent-hover);\n}\n.graph-explorer-btn-default.active:hover {\n background-color: var(--ge-accent-soft);\n border-color: var(--ge-accent);\n}\n.graph-explorer-btn-default[disabled], .graph-explorer-btn-default[disabled]:hover, .graph-explorer-btn-default[disabled].active, .graph-explorer-btn-default[disabled].active:hover {\n background-color: var(--ge-surface-3);\n color: var(--ge-text-subtle);\n}\n\n.graph-explorer-btn-primary {\n color: var(--ge-accent-fg);\n background-color: var(--ge-accent);\n border-color: var(--ge-accent);\n box-shadow: var(--ge-shadow-xs);\n}\n.graph-explorer-btn-primary:hover, .graph-explorer-btn-primary:focus, .graph-explorer-btn-primary:active {\n background-color: var(--ge-accent-hover);\n border-color: var(--ge-accent-hover);\n}\n.graph-explorer-btn-primary.active {\n border-color: var(--ge-accent-hover);\n}\n.graph-explorer-btn-primary[disabled], .graph-explorer-btn-primary[disabled]:hover, .graph-explorer-btn-primary[disabled]:focus, .graph-explorer-btn-primary[disabled]:active, .graph-explorer-btn-primary[disabled].active {\n background-color: var(--ge-accent);\n}\n\n.graph-explorer-btn-success {\n background-color: var(--ge-success);\n border-color: var(--ge-success);\n color: #fff;\n box-shadow: var(--ge-shadow-xs);\n}\n.graph-explorer-btn-success:hover {\n background-color: var(--ge-success-hover);\n border-color: var(--ge-success-hover);\n}\n\n.graph-explorer-btn-danger {\n background-color: var(--ge-danger);\n border-color: var(--ge-danger);\n color: #fff;\n box-shadow: var(--ge-shadow-xs);\n}\n.graph-explorer-btn-danger:hover {\n background-color: var(--ge-danger-hover);\n border-color: var(--ge-danger-hover);\n}\n\n.graph-explorer-btn-group {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.graph-explorer-btn-group > .graph-explorer-btn, .graph-explorer-btn-group > .graph-explorer-btn-group {\n position: relative;\n float: left;\n}\n.graph-explorer-btn-group > .graph-explorer-btn:hover,\n.graph-explorer-btn-group > .graph-explorer-btn:focus,\n.graph-explorer-btn-group > .graph-explorer-btn.active,\n.graph-explorer-btn-group > .graph-explorer-btn.active:hover {\n z-index: 2;\n}\n.graph-explorer-btn-group .graph-explorer-btn + .graph-explorer-btn,\n.graph-explorer-btn-group .graph-explorer-btn + .graph-explorer-btn-group,\n.graph-explorer-btn-group .graph-explorer-btn-group + .graph-explorer-btn,\n.graph-explorer-btn-group .graph-explorer-btn-group + .graph-explorer-btn-group {\n margin-left: -1px;\n}\n.graph-explorer-btn-group > .graph-explorer-btn:first-child {\n margin-left: 0;\n}\n.graph-explorer-btn-group-sm > .graph-explorer-btn {\n font-size: 12px;\n line-height: 1.5;\n padding: 5px 10px;\n}\n.graph-explorer-btn-group-xs > .graph-explorer-btn {\n font-size: 12px;\n line-height: 1.5;\n padding: 1px 5px;\n}\n\n.graph-explorer-form-control {\n display: block;\n width: 100%;\n height: 36px;\n margin: 0;\n padding: 7px 12px;\n font-size: 14px;\n line-height: 1.5;\n color: var(--ge-text);\n background: var(--ge-surface);\n border: 1px solid var(--ge-border-strong);\n border-radius: var(--ge-radius-sm);\n transition: border-color var(--ge-transition), box-shadow var(--ge-transition);\n}\n.graph-explorer-form-control::placeholder {\n color: var(--ge-text-subtle);\n}\n.graph-explorer-form-control:focus {\n border-color: var(--ge-accent);\n outline: 0;\n box-shadow: var(--ge-ring);\n}\n.graph-explorer-form-control:disabled {\n background-color: var(--ge-surface-3);\n cursor: not-allowed;\n opacity: 0.6;\n}\n\n.graph-explorer-input-group {\n position: relative;\n display: flex;\n align-items: stretch;\n}\n.graph-explorer-input-group .graph-explorer-form-control {\n position: relative;\n z-index: 2;\n flex: 1 1 auto;\n width: 1%;\n margin-bottom: 0;\n}\n.graph-explorer-input-group .graph-explorer-form-control:not(:last-child) {\n border-top-right-radius: 0;\n border-bottom-right-radius: 0;\n}\n\n.graph-explorer-input-group-btn {\n display: flex;\n align-items: stretch;\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.graph-explorer-input-group-btn > .graph-explorer-btn {\n height: 100%;\n margin-left: -1px;\n z-index: 2;\n}\n.graph-explorer-input-group-btn:last-child > .graph-explorer-btn {\n border-top-left-radius: 0;\n border-bottom-left-radius: 0;\n}\n\n.graph-explorer-list-group {\n margin: 0;\n padding: 0;\n}\n\n.graph-explorer-list-group-item:last-child {\n margin-bottom: 0;\n}\n\n.graph-explorer-list-group-item {\n position: relative;\n display: block;\n padding: 10px 14px;\n margin-bottom: -1px;\n background-color: var(--ge-surface);\n border: 1px solid var(--ge-border);\n}\n.graph-explorer-list-group-item:first-child {\n border-top-left-radius: var(--ge-radius-sm);\n border-top-right-radius: var(--ge-radius-sm);\n}\n.graph-explorer-list-group-item:last-child {\n border-bottom-left-radius: var(--ge-radius-sm);\n border-bottom-right-radius: var(--ge-radius-sm);\n}\n\n.graph-explorer-badge {\n display: inline-block;\n min-width: 10px;\n font-size: 11px;\n font-weight: 600;\n line-height: 1;\n color: var(--ge-text-muted);\n text-align: center;\n white-space: nowrap;\n vertical-align: middle;\n background-color: var(--ge-surface-3);\n border: 1px solid var(--ge-border);\n padding: 3px 8px;\n border-radius: var(--ge-radius-pill);\n}\n\n.graph-explorer-overlayed-element {\n cursor: move;\n outline: none;\n}\n\n.graph-explorer-overlayed-element,\n.graph-explorer-exported-element {\n box-sizing: border-box;\n color: var(--ge-text, #0f172a);\n font-family: var(--ge-font-sans, \"Inter\", system-ui, -apple-system, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif);\n font-size: 14px;\n line-height: 1.5;\n}\n.graph-explorer-overlayed-element img,\n.graph-explorer-exported-element img {\n vertical-align: middle;\n}\n\n.graph-explorer-exported-element *, .graph-explorer-exported-element *:before, .graph-explorer-exported-element *:after {\n box-sizing: inherit;\n}\n\n.graph-explorer-overlayed-element--blurred {\n filter: grayscale(100%);\n opacity: 0.5;\n}\n\n.graph-explorer-link__wrap {\n stroke-width: 12px;\n stroke-linejoin: round;\n stroke-linecap: round;\n stroke: transparent;\n fill: none;\n}\n.graph-explorer-link:hover .graph-explorer-link__wrap {\n stroke: rgba(140, 140, 140, 0.44);\n}\n.graph-explorer-link__vertex {\n cursor: all-scroll;\n}\n.graph-explorer-link:not(:hover) .graph-explorer-link__vertex {\n fill: transparent;\n}\n.graph-explorer-link__vertex-tools {\n opacity: 0;\n cursor: pointer;\n}\n.graph-explorer-link__vertex-tools > circle {\n fill: gray;\n}\n.graph-explorer-link__vertex-tools > path {\n stroke: white;\n}\n.graph-explorer-link__vertex-tools:hover > circle {\n fill: black;\n}\n.graph-explorer-link:hover .graph-explorer-link__vertex-tools {\n opacity: 0.8;\n}\n.graph-explorer-link--blurred {\n opacity: 0.5;\n}\n\n.graph-explorer-paper-area {\n flex: auto;\n width: 0;\n display: flex;\n position: relative;\n cursor: default;\n background-color: var(--ge-canvas);\n}\n.graph-explorer-paper-area__area {\n flex: auto;\n overflow: hidden;\n background-image: radial-gradient(var(--ge-canvas-dot) 1px, transparent 1px);\n background-attachment: local;\n}\n.graph-explorer-paper-area__widgets {\n position: absolute;\n left: 0;\n top: 0;\n}\n.graph-explorer-paper-area__watermark {\n background-size: cover;\n width: 8%;\n max-width: 130px;\n min-width: 50px;\n position: absolute;\n top: 15px;\n right: 25px;\n cursor: pointer;\n opacity: 0.3;\n transition: opacity 0.3s;\n}\n.graph-explorer-paper-area__watermark:hover {\n opacity: 0.5;\n}\n.graph-explorer-paper-area--hide-scrollbars {\n overflow: hidden;\n}\n\n.graph-explorer-paper {\n position: relative;\n}\n\n.graph-explorer-exported-watermark {\n opacity: 0.3;\n transition: opacity 0.3s;\n}\n\n.graph-explorer-paper-area--animated .graph-explorer-overlayed-element {\n transition: transform 0.5s ease-in-out;\n}\n.graph-explorer-paper-area--animated .graph-explorer-link-layer, .graph-explorer-paper-area--animated .graph-explorer-paper-area__widgets {\n transition: none;\n opacity: 0;\n}\n\n.graph-explorer-link-layer, .graph-explorer-paper-area__widgets {\n transition: opacity 0.5s ease-in-out;\n}\n\n.graph-explorer-loading-widget {\n left: 0;\n right: 0;\n top: 0;\n bottom: 0;\n margin: auto;\n position: absolute;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n z-index: 10;\n}\n\n.graph-explorer-authoring-state__item-error {\n align-self: flex-end;\n margin: 0px 5px;\n display: flex;\n align-items: center;\n cursor: help;\n}\n.graph-explorer-authoring-state__item-error-icon {\n background: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20style='fill:%20white'%20viewBox='0%200%20576%20512'%3e%3crect%20style='fill:%20white'%20width='160'%20height='340'%20x='207.9998'%20y='136'%20ry='0'%20/%3e%3cpath%20style='fill:%20red'%20d='M569.517%20440.013C587.975%20472.007%20564.806%20512%20527.94%20512H48.054c-36.937%200-59.999-40.055-41.577-71.987L246.423%2023.985c18.467-32.009%2064.72-31.951%2083.154%200l239.94%20416.028zM288%20354c-25.405%200-46%2020.595-46%2046s20.595%2046%2046%2046%2046-20.595%2046-46-20.595-46-46-46zm-43.673-165.346l7.418%20136c.347%206.364%205.609%2011.346%2011.982%2011.346h48.546c6.373%200%2011.635-4.982%2011.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884%200-12.356%205.78-11.981%2012.654z'/%3e%3c/svg%3e\");\n height: 15px;\n width: 17px;\n}\n.graph-explorer-authoring-state__state-label {\n font-weight: bold;\n margin-right: 5px;\n}\n.graph-explorer-authoring-state__state-cancel {\n color: #3f87a6;\n cursor: pointer;\n}\n.graph-explorer-authoring-state__state-cancel:hover {\n text-decoration: underline;\n}\n.graph-explorer-authoring-state__state-indicator {\n position: absolute;\n}\n.graph-explorer-authoring-state__state-indicator-container {\n position: relative;\n}\n.graph-explorer-authoring-state__state-indicator-body {\n position: absolute;\n white-space: nowrap;\n display: flex;\n align-items: center;\n bottom: 0;\n background: rgba(255, 255, 255, 0.7);\n border-radius: 5px;\n}\n\n.graph-explorer-authoring-tools {\n margin: 10px;\n}\n.graph-explorer-authoring-tools__create-entity {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: center;\n width: 100%;\n white-space: normal;\n}\n.graph-explorer-authoring-tools__type-label {\n color: black;\n border: black 1px dashed;\n background: rgb(255, 210, 33);\n padding: 0 0.5em 0 0.5em;\n word-wrap: break-word;\n}\n\n.graph-explorer-class-tree {\n flex: auto;\n display: flex;\n flex-direction: column;\n}\n.graph-explorer-class-tree__filter {\n flex-shrink: 0;\n margin: 10px 0 10px 0;\n}\n.graph-explorer-class-tree__filter-group {\n margin-left: 10px;\n margin-right: 10px;\n}\n.graph-explorer-class-tree__only-creatable {\n display: block;\n margin-top: 5px;\n}\n.graph-explorer-class-tree__tree {\n border-top: 1px solid var(--ge-border);\n padding: 6px 8px;\n overflow: auto;\n}\n.graph-explorer-class-tree__spinner {\n align-self: center;\n /* center vertically in flexbox */\n margin: auto;\n}\n\n.graph-explorer-class-leaf {\n margin: 1px 0;\n}\n.graph-explorer-class-leaf__row {\n display: flex;\n align-items: center;\n white-space: nowrap;\n user-select: none;\n}\n.graph-explorer-class-leaf__row > * {\n flex-shrink: 0;\n}\n.graph-explorer-class-leaf__body {\n display: flex;\n align-items: center;\n text-decoration: none;\n font-size: 14px;\n padding: 4px 6px;\n border: 1px solid transparent;\n border-radius: var(--ge-radius-sm);\n transition: background var(--ge-transition), border-color var(--ge-transition);\n}\n.graph-explorer-class-leaf__body:hover {\n background: var(--ge-surface-3);\n border-color: transparent;\n cursor: pointer;\n}\n.graph-explorer-class-leaf__body--selected {\n background-color: var(--ge-accent-soft);\n border-color: var(--ge-accent-soft-border);\n}\n.graph-explorer-class-leaf__body--selected:hover {\n background-color: var(--ge-accent-soft);\n}\n.graph-explorer-class-leaf__icon-container {\n height: 18px;\n}\n.graph-explorer-class-leaf__icon {\n display: block;\n height: 100%;\n}\n.graph-explorer-class-leaf__label {\n margin-left: 7px;\n color: var(--ge-text);\n}\n.graph-explorer-class-leaf__highlighted-term {\n font-weight: bold;\n}\n.graph-explorer-class-leaf__count {\n margin-left: 5px;\n}\n.graph-explorer-class-leaf__children {\n margin-left: 20px;\n}\n.graph-explorer-class-leaf__no-toggle {\n display: inline-block;\n width: 22px;\n height: 22px;\n}\n.graph-explorer-class-leaf__toggle {\n display: inline-block;\n width: 12px;\n height: 12px;\n margin: 5px;\n border-radius: var(--ge-radius-sm);\n}\n.graph-explorer-class-leaf__toggle:hover:not(:empty) {\n background: var(--ge-surface-3);\n cursor: pointer;\n}\n.graph-explorer-class-leaf__toggle-icon {\n display: block;\n height: 100%;\n}\n.graph-explorer-class-leaf__create {\n margin-left: 5px;\n}\n.graph-explorer-class-leaf__create > button {\n cursor: move;\n}\n\n.graph-explorer-connections-menu {\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n.graph-explorer-connections-menu__sort-switch {\n display: none;\n}\n.graph-explorer-connections-menu__sort-switch:checked + .graph-explorer-connections-menu__sort-switch-label {\n color: var(--ge-accent);\n opacity: 1;\n}\n.graph-explorer-connections-menu__sort-switch-label {\n cursor: pointer;\n display: block;\n margin: 0;\n opacity: 0.4;\n text-align: center;\n}\n.graph-explorer-connections-menu__sort-switch-label:hover {\n opacity: 1;\n}\n.graph-explorer-connections-menu__search-line-input {\n flex: 1 1 100%;\n}\n\n.graph-explorer-connections-menu_bread-crumbs {\n padding: 10px;\n padding-bottom: 0px;\n overflow: hidden;\n text-overflow: ellipsis;\n height: 27px;\n min-height: 27px;\n flex-grow: 0;\n}\n\n.graph-explorer-connections-menu__title-label {\n position: absolute;\n top: -11px;\n left: 10px;\n background: var(--ge-surface);\n color: var(--ge-text);\n font-weight: 600;\n padding-left: 6px;\n padding-right: 6px;\n border-radius: var(--ge-radius-sm);\n}\n\n.graph-explorer-connections-menu_search-line {\n padding: 10px;\n display: flex;\n align-items: center;\n flex: 0 0 auto;\n}\n\n.graph-explorer-connections-menu_search-line-sort-switches {\n flex: 0 0 20px;\n margin-left: 10px;\n}\n\n.graph-explorer-connections-menu_links-list {\n padding: 5px;\n overflow-x: hidden;\n overflow-y: auto;\n border-top: 1px solid var(--ge-border);\n flex-grow: 1;\n margin: 0;\n}\n\n.ocm_links-list-empty {\n display: flex;\n align-items: center;\n}\n\n.graph-explorer-connections-menu_links-list__empty {\n width: 100%;\n position: relative;\n font-size: x-large;\n color: #cbcbcb;\n text-align: center;\n}\n\n.graph-explorer-connections-menu__loading,\n.graph-explorer-connections-menu__error {\n width: 100%;\n height: 100%;\n position: relative;\n font-size: x-large;\n display: flex;\n color: #cbcbcb;\n align-items: center;\n justify-content: center;\n}\n\n.graph-explorer-connections-menu__loading-objects {\n border: 1px solid gray;\n flex-grow: 1;\n box-shadow: inset 0px 0px 23px -8px gray;\n background-color: whitesmoke;\n position: relative;\n font-size: x-large;\n display: flex;\n color: #cbcbcb;\n align-items: center;\n justify-content: center;\n margin-bottom: 5px;\n}\n\n.graph-explorer-connections-menu__move-to-filter {\n display: flex;\n padding: 5px 10px;\n user-select: none;\n cursor: pointer;\n}\n.graph-explorer-connections-menu__move-to-filter:hover {\n background-color: #e6e6e6;\n}\n\n.link-in-popup-menu {\n display: flex;\n overflow: hidden;\n padding: 0 5px 0 0;\n align-items: center;\n margin-bottom: 4px;\n background: #f7f7f7;\n}\n.link-in-popup-menu__link-title {\n flex-grow: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n margin: 5px 0px 5px 5px;\n}\n.link-in-popup-menu__count {\n flex-shrink: 0;\n margin-left: 10px;\n}\n.link-in-popup-menu__navigate-button {\n float: right;\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAN1wAADdcBQiibeAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGrSURBVHic7dYxSgNRGEXhGwOuRQyCi1AR3IHoUiwFW21cj5sRC3tFgrExzUBIMmPmP76cD145MxcODyYRyrTgm+dJDpO8F3xbHRdJPpK8JZkVb9l7yxiL32OUQt0YRim0KoZRCqyLYZQRbRrDKCOYZbsYy/Oa5Khgb/MmSZ6yfRBvyg4ZBcgoQEYBMgqQUYCMAmQUIKMAGQXIKEBGATIKkFGAjAJkFKChUY7Hn9w+owAZBcgoQEYBMgqQUYCMAjRJ8hijoBgFyChARgEyCpBRgIwCZBQgowANjXI6/uT2DYkyT3I5/uT2GQXIKEBGATIKkFGAsFEOdvViuEWSlyTfPZ6dJjn72zm6SvKZfjfkuWBv04wBYgwQY4AYA8QYIH1jzJPcFext2pAYtwV7m2YMEGOAGAPEGCDGADEGiDFAjAFiDBBjgBgDxBggxgAxBogxQIwBMiTGTcHephkDxBggxgAxBogxQIwBYgwQY4AYA8QYIMYAMQaIMUCMAWIMkJMkX+kX47pg7164jzcDZ9MoxhjRuijGKLAqijEKdaMYA2AZxRggD/HXVv/FDxh8HInEGLhOAAAAAElFTkSuQmCC\");\n width: 16px;\n height: 16px;\n opacity: 0.5;\n margin-left: 10px;\n min-width: 16px;\n background-size: cover;\n}\n.link-in-popup-menu__navigate-button:hover {\n opacity: 1;\n}\n.link-in-popup-menu__filter-button {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAuxJREFUeF7t3V1u4jAYhWFu+FkDIHHBGlgZILbBjtN8M/labEywYzs+js8rHYFUqGo/6twNXTHGWIVdLpduv993x+ORm3Fy53L3A8Nf8sX+gSuw4e7NCFJuBAEbQcBGELARBGxOkNPp9PZCbp4Nd2/2eDy67Xb79mIu7+TO5e775+/d7/dut9u9vYnLM7lrufP++eeIMs+8MDSi5F0QhkaUPJuEoREl7aIwNKKkWRIMjShxS4qhEWXasmBoRAlbVgyNKH6bBUMjyvhmxdCI4l4RDI0o5opiaET5PwgMrXUUKAytVRRIDK01FGgMrRWUKjC0paNUhaEtFaVKDG1pKFVjaEtBWQSGVjvKojC0WlEWiaHVhrJoDK0WlCYwNHSUpjA0VJQmMTQ0lKYxNBQUSIyu697mkXEwa16VRoHCcCHoPDMOZ827UihwvxkuCFlAxgGtBTU3ChyGFIkhGYe0FtxcKJAYUiSGZBzU2qRyo8BiSIggUi4UaAwJFURKjQKPISGDSKlQqsCQEEHsn+N2u3Xr9dr+3t6T98r36J/jVwOIJJ8r1T9M2vDeOqoFJOajP5wfXYGaHp4gIDlAfg9SYP8iyPBIEIAIAhZBwCIIWAQBiyBgKcQLSGjG4a1NiiAEwYkgYCGCuCKIf8bhrSWLIP4Zh7eWLIL4ZxzeWrKaBZmAYhzeWrKaBpEFZBzeWrKaB5F5ZhzeWrIIMswj4/DWkkWQl33JOLy1ZBHEsZIRZGQlIkjCpYgggfvU2NdCIojnvuXzGp8I8mW+hbx2LIIkXIqaAaklgoDVDIjrn5gciy0GxPl3AlFzXV6OxTYVZPTvBCLmurwci20KSDX/je011+XlWGyhIFViSK7Ly7HYQkCqxZBcl5djsfmCVI0huS4vx2LzAakeQ3JdXo7F9g1kERiS6/JyLLYxkMVg1NQnEGIUygVCjILZIMQo3CsIMQBSEGKAdDgcus1mQwyUzudzd71eiYHS8/kkBpu71eoHWjSksuGbaS0AAAAASUVORK5CYII=\");\n background-size: 20px 20px;\n width: 20px;\n height: 20px;\n opacity: 0.4;\n margin-left: 10px;\n}\n.link-in-popup-menu__filter-button:hover {\n opacity: 1;\n}\n.link-in-popup-menu:hover {\n background-color: #e2e2e2;\n}\n\n.link-in-popup-menu_direction {\n background: rgba(219, 219, 219, 0.35);\n min-height: 30px;\n padding: 5px 0px 5px 10px;\n display: flex;\n align-items: center;\n}\n\n.link-in-popup-menu_direction__in-direction {\n background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAbCAYAAAAOEM1uAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAZEQAAGREBkIelaAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAG2SURBVFiFzde9axRRFMbhJ3Hjx0YRgkWElGIRGyVgaaG9XQpBrNIETJHKjxBSJCA2NiIWdhYKgoqCIELq/AU2tiJpAiliDMHEWEyKZfdO5szHrr4wzcx77vnNnXPnnktcK9jBwYCuLcwNBeHGsIGovyltDweNbYOHg9Eo4D9Tq0bsGj40BXKoO5isEjiht4ifHuE/qVpJvO/O049PvIxN/MBMH8ZP6pzeGVxK+C4kfAsl8vTMYBnI1x1Bv3E54bmWSHCAx2KfvBbgKGbxJAfOIcS7HMjnKCqpWoBRtfE5B/IVRsoAtnAGd3GlQchd/NE7Y7dwCtPYiw72xuD216KFk/zN3Ii+SYOajhqHZUU9aK1GjS3M4xumFK+yMuPexLHEsy9YbChPJR3HW+na+yjbBvNU+zdzHbcxfoTnUw7cS8XNSS3AFx1B6zif8FzKgXsmVj6VAduyFrwzcCUHcL/L9yiaJAUYXRRjON1172zC9xX3ZS+ziTk8KAFYWWX7wRGcqJAnudVV1RTu1YhP6WL3jWjXO4HvzbLE9N8fmqKAv/SpNyvQdmorSmlHVvhX1TsJltFPPPwL/+nTC6wUIf8AAAAASUVORK5CYII=\") 0 0 no-repeat;\n flex-shrink: 0;\n width: 22px;\n height: 15px;\n margin: 0 14px 0 0;\n background-size: contain;\n opacity: 0.5;\n}\n\n.link-in-popup-menu_direction__out-direction {\n background: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAbCAYAAAAOEM1uAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAZEQAAGREBkIelaAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGCSURBVFiFzdi9ShxRFAfwn+uiaAoRVBQbbfMEFqIoFja+QCoLLQQr8R0sbdIJgTQiSgT1AcTKzkdIQhpTaOlX0GyK2WJ2c8d1Pt0/XBjOnTnnP+eerxnYxj0aJa9nfEOvlLirgFx8zaQhV8Ng2jfKiVT2amWxKAr1gOwElwXpH8NWXiXtMbKZV2EMHwP6F2P7dR1O8T2PeBW/8EMHp5TpwWGtJewF06JEuWmzu4ueqgnC55juw6ZsHE8B218F8qJsgkS1b0Grh74EbDdwioGqCYbQj6MEkhcYIlxm3opZrGEyh47eJqH22JvDOZbJ5sEJ4Rgqeh1nLTMr6Mv4bBpMZSV4hj9FMknA96wxeC3qCOvyxWAd88L17wobdHkWV0VwxP+e3ksgd6LiOrgtyvhn7DRlo3gM2K68kwziNqb7QTSC9eN3m9136cWvjVuf8FOHaSZPJ8mLfRw0r/8m3RQiuIQPBZEY67CfSCyOsttV0hG/CV3/0VQTTbxVIrW9rv6z8A+8NRQ5z9bcwQAAAABJRU5ErkJggg==\") 0 0 no-repeat;\n flex-shrink: 0;\n width: 22px;\n height: 15px;\n margin: 0 8px 0 6px;\n background-size: contain;\n opacity: 0.5;\n}\n\n.graph-explorer-connections-menu_links-list__loading {\n text-align: center;\n width: 100%;\n position: relative;\n font-size: x-large;\n color: #cbcbcb;\n}\n\n.graph-explorer-connections-menu_links-list__hr {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n\n.graph-explorer-connections-menu_objects-panel {\n flex-grow: 1;\n padding: 10px;\n position: relative;\n display: flex;\n flex-direction: column;\n padding-top: 0px;\n}\n\n.graph-explorer-connections-menu_objects-panel__select-all {\n white-space: nowrap;\n margin-bottom: 5px;\n display: flex;\n}\n.graph-explorer-connections-menu_objects-panel__select-all input {\n margin-right: 5px;\n}\n.graph-explorer-connections-menu_objects-panel__select-all input:disabled {\n opacity: 0.3;\n}\n\n.graph-explorer-connections-menu_objects-panel_bottom-panel {\n height: 25px;\n white-space: nowrap;\n display: flex;\n justify-content: space-between;\n}\n\n.graph-explorer-connections-menu_objects-panel_bottom-panel__count-label {\n height: 25px;\n float: left;\n padding: 0px 5px;\n font-size: larger;\n display: flex;\n align-items: center;\n max-width: 60%;\n}\n\n.graph-explorer-connections-menu_objects-panel_bottom-panel__extra-elements {\n color: gray;\n cursor: help;\n}\n\n.graph-explorer-btn.graph-explorer-connections-menu_objects-panel_bottom-panel__add-button {\n height: 25px;\n padding-top: 1px;\n padding-bottom: 2px;\n}\n\n.graph-explorer-connections-menu_objects-panel_objects-list {\n border: 1px solid gray;\n height: 0;\n flex-grow: 1;\n overflow-x: hidden;\n overflow-y: scroll;\n padding: 5px;\n margin-bottom: 5px;\n}\n\n.graph-explorer-connections-menu__link {\n color: var(--ge-accent);\n cursor: pointer;\n text-decoration: none;\n}\n.graph-explorer-connections-menu__link:hover {\n text-decoration: underline;\n}\n\n.graph-explorer-dialog {\n background: var(--ge-surface);\n border: 1px solid var(--ge-border);\n border-radius: var(--ge-radius-lg);\n box-shadow: var(--ge-shadow-lg);\n position: absolute;\n}\n.graph-explorer-dialog__caption {\n font-weight: 600;\n position: absolute;\n top: -11px;\n left: 10px;\n background: var(--ge-surface);\n color: var(--ge-text);\n padding-left: 6px;\n padding-right: 6px;\n border-radius: var(--ge-radius-sm);\n}\n.graph-explorer-dialog__close-button {\n background: transparent url(\"data:image/svg+xml,%3csvg%20aria-hidden='true'%20focusable='false'%20data-prefix='far'%20data-icon='times-circle'%20class='svg-inline--fa%20fa-times-circle%20fa-w-16'%20role='img'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3e%3cpath%20fill='currentColor'%20d='M256%208C119%208%208%20119%208%20256s111%20248%20248%20248%20248-111%20248-248S393%208%20256%208zm0%20448c-110.5%200-200-89.5-200-200S145.5%2056%20256%2056s200%2089.5%20200%20200-89.5%20200-200%20200zm101.8-262.2L295.6%20256l62.2%2062.2c4.7%204.7%204.7%2012.3%200%2017l-22.6%2022.6c-4.7%204.7-12.3%204.7-17%200L256%20295.6l-62.2%2062.2c-4.7%204.7-12.3%204.7-17%200l-22.6-22.6c-4.7-4.7-4.7-12.3%200-17l62.2-62.2-62.2-62.2c-4.7-4.7-4.7-12.3%200-17l22.6-22.6c4.7-4.7%2012.3-4.7%2017%200l62.2%2062.2%2062.2-62.2c4.7-4.7%2012.3-4.7%2017%200l22.6%2022.6c4.7%204.7%204.7%2012.3%200%2017z'%3e%3c/path%3e%3c/svg%3e\");\n background-size: contain;\n border: 0 none;\n cursor: pointer;\n display: block;\n outline: none;\n padding: 0;\n position: absolute;\n top: -22px;\n right: -22px;\n height: 20px;\n width: 20px;\n opacity: 0.5;\n transition: 0.3s;\n}\n.graph-explorer-dialog__close-button:hover {\n opacity: 1;\n}\n\n.graph-explorer-dialog__bottom-right-handle {\n position: absolute;\n bottom: 0;\n right: 0;\n width: 0;\n height: 0;\n border-style: solid;\n border-width: 0 0 10px 10px;\n border-color: transparent transparent rgba(0, 0, 0, 0.38) transparent;\n cursor: nwse-resize;\n}\n.graph-explorer-dialog__bottom-right-handle::before {\n content: \"\";\n position: absolute;\n bottom: -10px;\n right: 0;\n width: 0;\n height: 0;\n border-style: solid;\n border-width: 0 0 5px 5px;\n border-color: transparent transparent rgba(0, 0, 0, 0.38) transparent;\n}\n.graph-explorer-dialog__bottom-right-handle:hover {\n border-color: transparent transparent rgba(0, 0, 0, 0.5) transparent;\n}\n\n.graph-explorer-dialog__bottom-handle, .graph-explorer-dialog__right-handle {\n position: absolute;\n opacity: 0;\n background-color: black;\n}\n.graph-explorer-dialog__bottom-handle:hover, .graph-explorer-dialog__right-handle:hover {\n opacity: 0.1;\n}\n\n.graph-explorer-dialog__bottom-handle {\n bottom: 0;\n width: 100%;\n height: 5px;\n cursor: ns-resize;\n}\n\n.graph-explorer-dialog__right-handle {\n top: 0;\n right: 0;\n width: 5px;\n height: 100%;\n cursor: ew-resize;\n}\n\n.graph-explorer-edit-form {\n height: 100%;\n display: flex;\n flex-direction: column;\n}\n.graph-explorer-edit-form__body {\n overflow: auto;\n padding: 10px;\n flex: 1 1 100%;\n display: flex;\n flex-direction: column;\n position: relative;\n}\n.graph-explorer-edit-form__controls {\n border-top: 1px solid #ccc;\n padding: 10px;\n text-align: right;\n flex: 0 0 auto;\n}\n.graph-explorer-edit-form__apply-button {\n margin-right: 5px;\n}\n.graph-explorer-edit-form__form-row {\n display: block;\n margin-bottom: 10px;\n}\n.graph-explorer-edit-form__element-selector {\n flex-grow: 1;\n display: flex;\n flex-direction: column;\n}\n.graph-explorer-edit-form__search {\n flex-shrink: 0;\n position: relative;\n}\n.graph-explorer-edit-form__search-icon {\n opacity: 0.6;\n position: absolute;\n top: 50%;\n left: 10px;\n margin-top: -7px;\n}\n.graph-explorer-edit-form__search-input {\n padding-left: 33px;\n}\n.graph-explorer-edit-form__existing-elements-list {\n flex: 1 1 0;\n margin-top: 7px;\n padding-right: 10px;\n overflow-y: scroll;\n}\n.graph-explorer-edit-form__separator {\n margin: 7px 0;\n overflow: hidden;\n text-align: center;\n}\n.graph-explorer-edit-form__separator-text {\n color: #555;\n display: inline-block;\n font-size: 13px;\n position: relative;\n}\n.graph-explorer-edit-form__separator-text:before, .graph-explorer-edit-form__separator-text:after {\n content: \"\";\n border-top: 1px solid;\n position: absolute;\n top: 50%;\n margin: 0 10px;\n width: 500px;\n}\n.graph-explorer-edit-form__separator-text:before {\n left: 100%;\n}\n.graph-explorer-edit-form__separator-text:after {\n right: 100%;\n}\n.graph-explorer-edit-form__progress {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n}\n.graph-explorer-edit-form__control-row {\n position: relative;\n padding-bottom: 18px;\n}\n.graph-explorer-edit-form__control-error {\n color: red;\n position: absolute;\n bottom: 0;\n left: 0;\n}\n\n.graph-explorer-halo {\n position: absolute;\n pointer-events: none;\n border: 1.5px dashed var(--ge-accent);\n border-radius: var(--ge-radius);\n box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.12);\n}\n.graph-explorer-halo__navigate {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__navigate:hover {\n opacity: 1;\n}\n.graph-explorer-halo__navigate[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__navigate {\n top: 50%;\n margin-top: -10px;\n right: -22px;\n}\n.graph-explorer-halo__navigate--open {\n background-image: url(\"data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3csvg%20xmlns:svg='http://www.w3.org/2000/svg'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20110%20110'%20version='1.1'%20width='500'%20height='500'%3e%3cpath%20d='M%2055.105965,54.648825%20M%2082.182422,8.0521107%20A%2053.892377,53.892377%200%201%200%2028.029508,101.24554%2053.892377,53.892377%200%201%200%2082.182422,8.0521107%20Z%20M%2055.105965,54.648825%20M%2076.692536,17.499824%20A%2042.965432,42.965432%200%200%201%2033.519393,91.797827%2042.965432,42.965432%200%200%201%2076.692536,17.499824%20Z'%20style='stroke-width:1.06812763;fill:%23000000'%20/%3e%3cpath%20style='fill:%23000000;stroke:none;stroke-width:0.25893259px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1'%20d='M%2025.29481,56.552597%20C%2078.882863,29.834905%2079.035536,29.834905%2079.035536,29.834905%20L%2052.165174,84.18632%2049.56974,59.3007%20Z'%20/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo__navigate--closed {\n background-image: url(\"data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3csvg%20xmlns:svg='http://www.w3.org/2000/svg'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20110%20110'%20version='1.1'%20width='500'%20height='500'%3e%3cpath%20d='M%2055.105965,54.648825%20M%2082.182422,8.0521107%20A%2053.892377,53.892377%200%201%200%2028.029508,101.24554%2053.892377,53.892377%200%201%200%2082.182422,8.0521107%20Z%20M%2055.105965,54.648825%20M%2076.692536,17.499824%20A%2042.965432,42.965432%200%200%201%2033.519393,91.797827%2042.965432,42.965432%200%200%201%2076.692536,17.499824%20Z'%20style='stroke-width:1.06812763'%20/%3e%3cpath%20d='M%2070.749364,31.224635%2078.426933,38.910146%2063.054265,54.266973%2078.41107,69.639662%2070.725571,77.317242%2055.368766,61.944553%2039.996098,77.301379%2032.318529,69.615869%2047.691197,54.259042%2032.334392,38.886353%20l%207.685499,-7.67758%2015.356805,15.372689%20z'%20style='stroke-width:1.17913187'%20/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo__folow {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__folow:hover {\n opacity: 1;\n}\n.graph-explorer-halo__folow[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__folow {\n top: 50%;\n margin-top: -10px;\n left: -22px;\n background-image: url(\"data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='utf-8'?%3e%3csvg%20version='1.1'%20id='Layer_1'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:xlink='http://www.w3.org/1999/xlink'%20x='0px'%20y='0px'%20width='512px'%20height='512px'%20viewBox='0%200%20512%20512'%20enable-background='new%200%200%20512%20512'%20xml:space='preserve'%3e%3cpath%20fill='%23010101'%20d='M459.654,233.373l-90.531,90.5c-49.969,50-131.031,50-181,0c-7.875-7.844-14.031-16.688-19.438-25.813%20l42.063-42.063c2-2.016,4.469-3.172,6.828-4.531c2.906,9.938,7.984,19.344,15.797,27.156c24.953,24.969,65.563,24.938,90.5,0%20l90.5-90.5c24.969-24.969,24.969-65.563,0-90.516c-24.938-24.953-65.531-24.953-90.5,0l-32.188,32.219%20c-26.109-10.172-54.25-12.906-81.641-8.891l68.578-68.578c50-49.984,131.031-49.984,181.031,0%20C509.623,102.342,509.623,183.389,459.654,233.373z%20M220.326,382.186l-32.203,32.219c-24.953,24.938-65.563,24.938-90.516,0%20c-24.953-24.969-24.953-65.563,0-90.531l90.516-90.5c24.969-24.969,65.547-24.969,90.5,0c7.797,7.797,12.875,17.203,15.813,27.125%20c2.375-1.375,4.813-2.5,6.813-4.5l42.063-42.047c-5.375-9.156-11.563-17.969-19.438-25.828c-49.969-49.984-131.031-49.984-181.016,0%20l-90.5,90.5c-49.984,50-49.984,131.031,0,181.031c49.984,49.969,131.031,49.969,181.016,0l68.594-68.594%20C274.561,395.092,246.42,392.342,220.326,382.186z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo__remove {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__remove:hover {\n opacity: 1;\n}\n.graph-explorer-halo__remove[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__remove {\n top: -22px;\n right: -22px;\n background-image: url(\"data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3csvg%20xmlns:svg='http://www.w3.org/2000/svg'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20110%20110'%20version='1.1'%20width='500'%20height='500'%3e%3cpath%20d='M%2082.182422,8.0521107%20C%2056.44775,-6.9018137%2023.463185,1.8377258%208.5092706,27.57238%20-6.4446434,53.307034%202.2948357,86.291616%2028.029508,101.24554%2053.76418,116.19946%2086.748746,107.45993%20101.70266,81.725271%20116.65657,55.990616%20107.91709,23.006035%2082.182422,8.0521107%20Z%20M%2071.75602,28.578553%2082.28685,38.846984%2065.453479,56.373296%2083.064738,73.314301%2072.796937,83.844485%2055.169736,66.657039%2039.010998,83.228711%2028.480167,72.96028%2044.639378,56.389069%2028.415211,40.568631%2038.683013,30.038446%2054.938893,45.85871%20Z'%20style='fill:%23000000;stroke-width:1.06812763'%20/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo__expand {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__expand:hover {\n opacity: 1;\n}\n.graph-explorer-halo__expand[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__expand {\n bottom: -22px;\n left: 50%;\n margin-left: -10px;\n}\n.graph-explorer-halo__expand--open {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIMDxQ3d8lonQAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAEOSURBVEjH7dXNK0RhFAbwXxRyZyIlKwtlMbvZkUIW9rOThezEniIf/4E/UYgkGwskaTI2V51uF/NxZ6LuU+/ivec5z/Pe9+McSpQoUeK/YTgz38AEbgv2WcUkHvKCS2ilY7tA0/VU8w2NPMICHoP5TgGmy0HvBfXviNN4D+TdHkzn8BG05n9LmMJdSNjvwrSOZtjiWruJFdwE86MOTGshr4m1TlddxWsQOW5zwTFnsdtzquAyCJ39wJ3Fc+Cu9HozR3ERBE9zODOZF7FV1FtM8BSET0JsKC0MX7FG0VVuHFeZbR/Bffi22a8SO5Y58/inh/2u70lahVph7A2quVRxnZoeDLqzJTgvG3yJP4dPo59Rw97b1k8AAAAASUVORK5CYII=\");\n}\n.graph-explorer-halo__expand--closed {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AIMDx0nu7zDsAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAD+SURBVEjH7ZQxa8JQFIU/HFSqXQri1KHQwc2txaLi4N5NHMRNdFeoqPkH/YlFRRBxcVAREakuEQ7RiDWJWHgHAuHl3Pu9e/PuAyOjO1QM+L419BEYAjugdctKlzb08DSChkaBvgCn8v4VFPQBGAjIAsLARNbKQbR3LoCufAs5Kv/0CxoBfiRx74QnCczEU/EKjTv+qXXG+wwsxJvzMjIrSdS5cKMa835NpSNJ0P5DbEritkDh0sAnYCzBzSu6lbahO2Btb+asEsBGoHUP5+MF+JVcr27GN8fJrPkwEVnJt7Q7caSMmKo+3gFFabnrjJeAjwBuvbxbtUZGRkb/R3tcflA+OTpkkAAAAABJRU5ErkJggg==\");\n}\n.graph-explorer-halo__add-to-filter {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__add-to-filter:hover {\n opacity: 1;\n}\n.graph-explorer-halo__add-to-filter[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__add-to-filter {\n bottom: -22px;\n right: -22px;\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAuxJREFUeF7t3V1u4jAYhWFu+FkDIHHBGlgZILbBjtN8M/labEywYzs+js8rHYFUqGo/6twNXTHGWIVdLpduv993x+ORm3Fy53L3A8Nf8sX+gSuw4e7NCFJuBAEbQcBGELARBGxOkNPp9PZCbp4Nd2/2eDy67Xb79mIu7+TO5e775+/d7/dut9u9vYnLM7lrufP++eeIMs+8MDSi5F0QhkaUPJuEoREl7aIwNKKkWRIMjShxS4qhEWXasmBoRAlbVgyNKH6bBUMjyvhmxdCI4l4RDI0o5opiaET5PwgMrXUUKAytVRRIDK01FGgMrRWUKjC0paNUhaEtFaVKDG1pKFVjaEtBWQSGVjvKojC0WlEWiaHVhrJoDK0WlCYwNHSUpjA0VJQmMTQ0lKYxNBQUSIyu697mkXEwa16VRoHCcCHoPDMOZ827UihwvxkuCFlAxgGtBTU3ChyGFIkhGYe0FtxcKJAYUiSGZBzU2qRyo8BiSIggUi4UaAwJFURKjQKPISGDSKlQqsCQEEHsn+N2u3Xr9dr+3t6T98r36J/jVwOIJJ8r1T9M2vDeOqoFJOajP5wfXYGaHp4gIDlAfg9SYP8iyPBIEIAIAhZBwCIIWAQBiyBgKcQLSGjG4a1NiiAEwYkgYCGCuCKIf8bhrSWLIP4Zh7eWLIL4ZxzeWrKaBZmAYhzeWrKaBpEFZBzeWrKaB5F5ZhzeWrIIMswj4/DWkkWQl33JOLy1ZBHEsZIRZGQlIkjCpYgggfvU2NdCIojnvuXzGp8I8mW+hbx2LIIkXIqaAaklgoDVDIjrn5gciy0GxPl3AlFzXV6OxTYVZPTvBCLmurwci20KSDX/je011+XlWGyhIFViSK7Ly7HYQkCqxZBcl5djsfmCVI0huS4vx2LzAakeQ3JdXo7F9g1kERiS6/JyLLYxkMVg1NQnEGIUygVCjILZIMQo3CsIMQBSEGKAdDgcus1mQwyUzudzd71eiYHS8/kkBpu71eoHWjSksuGbaS0AAAAASUVORK5CYII=\");\n}\n.graph-explorer-halo__revert {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__revert:hover {\n opacity: 1;\n}\n.graph-explorer-halo__revert[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__revert {\n top: -22px;\n left: 50%;\n margin-left: -10px;\n background-image: url(\"data:image/svg+xml,%3csvg%20aria-hidden='true'%20data-prefix='fas'%20data-icon='undo'%20class='svg-inline--fa%20fa-undo%20fa-w-16'%20role='img'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20512%20512'%3e%3cpath%20fill='currentColor'%20d='M212.333%20224.333H12c-6.627%200-12-5.373-12-12V12C0%205.373%205.373%200%2012%200h48c6.627%200%2012%205.373%2012%2012v78.112C117.773%2039.279%20184.26%207.47%20258.175%208.007c136.906.994%20246.448%20111.623%20246.157%20248.532C504.041%20393.258%20393.12%20504%20256.333%20504c-64.089%200-122.496-24.313-166.51-64.215-5.099-4.622-5.334-12.554-.467-17.42l33.967-33.967c4.474-4.474%2011.662-4.717%2016.401-.525C170.76%20415.336%20211.58%20432%20256.333%20432c97.268%200%20176-78.716%20176-176%200-97.267-78.716-176-176-176-58.496%200-110.28%2028.476-142.274%2072.333h98.274c6.627%200%2012%205.373%2012%2012v48c0%206.627-5.373%2012-12%2012z'%3e%3c/path%3e%3c/svg%3e\");\n}\n.graph-explorer-halo__establish-connection {\n position: absolute;\n background-color: transparent;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center;\n border: 0 none;\n cursor: pointer;\n outline: none;\n padding: 0;\n pointer-events: auto;\n width: 20px;\n height: 20px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo__establish-connection:hover {\n opacity: 1;\n}\n.graph-explorer-halo__establish-connection[disabled] {\n cursor: not-allowed;\n opacity: 0.2;\n}\n.graph-explorer-halo__establish-connection {\n bottom: -22px;\n left: -22px;\n background-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20384%20512'%3e%3cpath%20d='M256%20144V32c0-17.673%2014.327-32%2032-32s32%2014.327%2032%2032v112h-64zm112%2016H16c-8.837%200-16%207.163-16%2016v32c0%208.837%207.163%2016%2016%2016h16v32c0%2077.406%2054.969%20141.971%20128%20156.796V512h64v-99.204c73.031-14.825%20128-79.39%20128-156.796v-32h16c8.837%200%2016-7.163%2016-16v-32c0-8.837-7.163-16-16-16zm-240-16V32c0-17.673-14.327-32-32-32S64%2014.327%2064%2032v112h64z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo__establish-connection-spinner {\n position: absolute;\n width: 20px;\n height: 20px;\n bottom: -22px;\n left: -22px;\n}\n\n.graph-explorer-halo-link__button {\n background: transparent;\n border: 0 none;\n cursor: pointer;\n padding: 0;\n position: absolute;\n opacity: 0.8;\n outline: none;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo-link__button:hover {\n opacity: 1;\n}\n.graph-explorer-halo-link__button[disabled] {\n cursor: not-allowed;\n opacity: 0.5;\n}\n.graph-explorer-halo-link__edit {\n background-color: #ccc;\n background-position: 50%;\n background-repeat: no-repeat;\n background-size: 60%;\n border-radius: 10px;\n height: 20px;\n width: 20px;\n background-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20576%20512'%3e%3cpath%20d='M402.6%2083.2l90.2%2090.2c3.8%203.8%203.8%2010%200%2013.8L274.4%20405.6l-92.8%2010.3c-12.4%201.4-22.9-9.1-21.5-21.5l10.3-92.8L388.8%2083.2c3.8-3.8%2010-3.8%2013.8%200zm162-22.9l-48.8-48.8c-15.2-15.2-39.9-15.2-55.2%200l-35.4%2035.4c-3.8%203.8-3.8%2010%200%2013.8l90.2%2090.2c3.8%203.8%2010%203.8%2013.8%200l35.4-35.4c15.2-15.3%2015.2-40%200-55.2zM384%20346.2V448H64V128h229.8c3.2%200%206.2-1.3%208.5-3.5l40-40c7.6-7.6%202.2-20.5-8.5-20.5H48C21.5%2064%200%2085.5%200%20112v352c0%2026.5%2021.5%2048%2048%2048h352c26.5%200%2048-21.5%2048-48V306.2c0-10.7-12.9-16-20.5-8.5l-40%2040c-2.2%202.3-3.5%205.3-3.5%208.5z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo-link__delete {\n background-color: #ccc;\n background-position: 50%;\n background-repeat: no-repeat;\n background-size: 60%;\n border-radius: 10px;\n height: 20px;\n width: 20px;\n background-image: url(\"data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20448%20512'%3e%3cpath%20d='M0%2084V56c0-13.3%2010.7-24%2024-24h112l9.4-18.7c4-8.2%2012.3-13.3%2021.4-13.3h114.3c9.1%200%2017.4%205.1%2021.5%2013.3L312%2032h112c13.3%200%2024%2010.7%2024%2024v28c0%206.6-5.4%2012-12%2012H12C5.4%2096%200%2090.6%200%2084zm416%2056v324c0%2026.5-21.5%2048-48%2048H80c-26.5%200-48-21.5-48-48V140c0-6.6%205.4-12%2012-12h360c6.6%200%2012%205.4%2012%2012zm-272%2068c0-8.8-7.2-16-16-16s-16%207.2-16%2016v224c0%208.8%207.2%2016%2016%2016s16-7.2%2016-16V208zm96%200c0-8.8-7.2-16-16-16s-16%207.2-16%2016v224c0%208.8%207.2%2016%2016%2016s16-7.2%2016-16V208zm96%200c0-8.8-7.2-16-16-16s-16%207.2-16%2016v224c0%208.8%207.2%2016%2016%2016s16-7.2%2016-16V208z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-halo-link__spinner {\n position: absolute;\n}\n.graph-explorer-halo-link__edit-label-button {\n background: transparent url(\"data:image/svg+xml,%3csvg%20aria-hidden='true'%20data-prefix='fas'%20data-icon='pen-square'%20class='svg-inline--fa%20fa-pen-square%20fa-w-14'%20role='img'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20448%20512'%3e%3cpath%20fill='currentColor'%20d='M400%20480H48c-26.5%200-48-21.5-48-48V80c0-26.5%2021.5-48%2048-48h352c26.5%200%2048%2021.5%2048%2048v352c0%2026.5-21.5%2048-48%2048zM238.1%20177.9L102.4%20313.6l-6.3%2057.1c-.8%207.6%205.6%2014.1%2013.3%2013.3l57.1-6.3L302.2%20242c2.3-2.3%202.3-6.1%200-8.5L246.7%20178c-2.5-2.4-6.3-2.4-8.6-.1zM345%20165.1L314.9%20135c-9.4-9.4-24.6-9.4-33.9%200l-23.1%2023.1c-2.3%202.3-2.3%206.1%200%208.5l55.5%2055.5c2.3%202.3%206.1%202.3%208.5%200L345%20199c9.3-9.3%209.3-24.5%200-33.9z'%3e%3c/path%3e%3c/svg%3e\") no-repeat;\n background-size: cover;\n border: 0 none;\n cursor: pointer;\n padding: 0;\n position: absolute;\n margin-left: 5px;\n outline: none;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-halo-link__edit-label-button:hover {\n opacity: 1;\n}\n\n.graph-explorer-instances-search {\n flex: auto;\n display: flex;\n flex-direction: column;\n}\n.graph-explorer-instances-search__criteria {\n flex-shrink: 0;\n margin: 10px 0 10px 0;\n}\n.graph-explorer-instances-search__criterions {\n padding-left: 15px;\n margin-bottom: 10px;\n}\n.graph-explorer-instances-search__criterions:empty {\n margin-bottom: 0;\n}\n.graph-explorer-instances-search__criterion {\n display: block;\n position: relative;\n width: 100%;\n}\n.graph-explorer-instances-search__criterion-class, .graph-explorer-instances-search__criterion-element, .graph-explorer-instances-search__criterion-link-type {\n display: inline-block;\n padding: 2px 10px;\n border-radius: var(--ge-radius-pill);\n border: 1px solid transparent;\n font-size: 12px;\n font-weight: 500;\n word-wrap: break-word;\n}\n.graph-explorer-instances-search__criterion-class {\n background: #fef3c7;\n border-color: #fcd34d;\n color: #92400e;\n}\n.graph-explorer-instances-search__criterion-element {\n background: #ffedd5;\n border-color: #fdba74;\n color: #9a3412;\n}\n.graph-explorer-instances-search__criterion-link-type {\n background: #dcfce7;\n border-color: #86efac;\n color: #166534;\n}\n.graph-explorer-instances-search__link-direction {\n height: 1em;\n opacity: 0.5;\n}\n.graph-explorer-instances-search__criterion-remove {\n float: right;\n margin: 0 10px 4px 4px;\n}\n.graph-explorer-instances-search__text-criteria {\n margin: 0 10px;\n}\n.graph-explorer-instances-search__rest {\n padding: 10px 10px 0 10px;\n border-top: 1px solid var(--ge-border);\n}\n.graph-explorer-instances-search__results {\n padding-left: 0;\n padding-top: 10px;\n margin: 0 10px 0 10px;\n outline: none;\n}\n.graph-explorer-instances-search[data-state=finished] .graph-explorer-instances-search__results:empty:before {\n content: \"No items correspond to the specified criteria.\";\n font-style: italic;\n}\n.graph-explorer-instances-search__rest-end {\n margin: 5px 0 10px 0;\n}\n.graph-explorer-instances-search__load-more {\n width: 100%;\n}\n\n.link-types-toolbox {\n flex: auto;\n display: flex;\n flex-direction: column;\n margin-bottom: 0;\n}\n.link-types-toolbox__heading {\n padding: 10px;\n}\n.link-types-toolbox__searching-box {\n display: flex;\n align-items: center;\n}\n.link-types-toolbox__clearSearch {\n margin-left: -25px;\n -webkit-appearance: none;\n padding: 0;\n cursor: pointer;\n background: 0 0;\n border: 0;\n}\n.link-types-toolbox__switch-all {\n margin-top: 5px;\n}\n.link-types-toolbox__rest {\n border-top: 1px solid #dddddd;\n flex: auto;\n display: flex;\n flex-direction: column;\n}\n\n.link-types-toolbox .panel-heading {\n flex-shrink: 0;\n -webkit-flex-shrink: 0; /* safari 8 */\n}\n\n.link-types-toolbox ul {\n margin-bottom: 0;\n}\n\n.link-types-toolbox .links-heading {\n margin-left: 0.4em;\n}\n\n.link-types-toolbox .links-heading span {\n border: black 1px dashed;\n background: rgb(255, 131, 92);\n padding: 0 0.5em 0 0.5em;\n word-wrap: break-word;\n line-height: 1.3;\n}\n\n.link-types-toolbox__heading .link-types-toolbox-controls {\n padding: 5px 15px;\n font-size: 14px;\n font-style: italic;\n}\n\n.link-types-toolbox__heading .link-types-toolbox-controls > span {\n padding-left: 0.3em;\n}\n\n.graph-explorer-list-group .linkInToolBox {\n padding: 0 0 0 5px;\n word-break: break-word;\n}\n\n.linkInToolBox > div {\n display: inline;\n}\n.linkInToolBox__new-tag {\n margin-left: 0.5em;\n white-space: normal;\n word-wrap: normal;\n border-radius: 2px;\n padding: 0 5px;\n background: orange;\n}\n.linkInToolBox .graph-explorer-badge {\n display: none;\n margin-left: 0.5em;\n white-space: normal;\n word-wrap: normal;\n}\n.linkInToolBox .link-title {\n font-size: 16px;\n color: black;\n display: inline;\n}\n.linkInToolBox .graph-explorer-btn-group {\n float: left;\n margin-top: 2px;\n margin-right: 6px;\n margin-bottom: 2px;\n}\n.linkInToolBox .graph-explorer-btn.graph-explorer-btn-default {\n border-color: rgb(195, 195, 195);\n}\n.linkInToolBox__filter-button {\n display: none;\n float: right;\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjExR/NCNwAAAuxJREFUeF7t3V1u4jAYhWFu+FkDIHHBGlgZILbBjtN8M/labEywYzs+js8rHYFUqGo/6twNXTHGWIVdLpduv993x+ORm3Fy53L3A8Nf8sX+gSuw4e7NCFJuBAEbQcBGELARBGxOkNPp9PZCbp4Nd2/2eDy67Xb79mIu7+TO5e775+/d7/dut9u9vYnLM7lrufP++eeIMs+8MDSi5F0QhkaUPJuEoREl7aIwNKKkWRIMjShxS4qhEWXasmBoRAlbVgyNKH6bBUMjyvhmxdCI4l4RDI0o5opiaET5PwgMrXUUKAytVRRIDK01FGgMrRWUKjC0paNUhaEtFaVKDG1pKFVjaEtBWQSGVjvKojC0WlEWiaHVhrJoDK0WlCYwNHSUpjA0VJQmMTQ0lKYxNBQUSIyu697mkXEwa16VRoHCcCHoPDMOZ827UihwvxkuCFlAxgGtBTU3ChyGFIkhGYe0FtxcKJAYUiSGZBzU2qRyo8BiSIggUi4UaAwJFURKjQKPISGDSKlQqsCQEEHsn+N2u3Xr9dr+3t6T98r36J/jVwOIJJ8r1T9M2vDeOqoFJOajP5wfXYGaHp4gIDlAfg9SYP8iyPBIEIAIAhZBwCIIWAQBiyBgKcQLSGjG4a1NiiAEwYkgYCGCuCKIf8bhrSWLIP4Zh7eWLIL4ZxzeWrKaBZmAYhzeWrKaBpEFZBzeWrKaB5F5ZhzeWrIIMswj4/DWkkWQl33JOLy1ZBHEsZIRZGQlIkjCpYgggfvU2NdCIojnvuXzGp8I8mW+hbx2LIIkXIqaAaklgoDVDIjrn5gciy0GxPl3AlFzXV6OxTYVZPTvBCLmurwci20KSDX/je011+XlWGyhIFViSK7Ly7HYQkCqxZBcl5djsfmCVI0huS4vx2LzAakeQ3JdXo7F9g1kERiS6/JyLLYxkMVg1NQnEGIUygVCjILZIMQo3CsIMQBSEGKAdDgcus1mQwyUzudzd71eiYHS8/kkBpu71eoHWjSksuGbaS0AAAAASUVORK5CYII=\");\n background-size: 20px 20px;\n width: 20px;\n height: 20px;\n margin: 3px;\n cursor: pointer;\n opacity: 0.4;\n transition: opacity 200ms 0ms;\n}\n.linkInToolBox:hover .linkInToolBox__filter-button {\n opacity: 1;\n transition: opacity 200ms 0ms;\n}\n\n.connected-links .linkInToolBox .graph-explorer-badge {\n /* show connection count badge only in \"Connected to Element\" list */\n display: inline;\n}\n\n.connected-links .linkInToolBox__filter-button {\n /* show filter button only in \"Connected to Element\" list */\n display: inline;\n}\n\n.graph-explorer-list-element-view {\n position: relative;\n display: block;\n background: var(--ge-text-subtle);\n padding: 2px 2px 2px 6px;\n border-radius: var(--ge-radius-sm);\n margin: 0 0 4px 0;\n opacity: 1;\n cursor: move;\n user-select: none;\n box-shadow: var(--ge-shadow-xs);\n transition: box-shadow var(--ge-transition), transform var(--ge-transition);\n}\n.graph-explorer-list-element-view:hover {\n box-shadow: var(--ge-shadow-sm);\n}\n.graph-explorer-list-element-view--disabled {\n opacity: 0.4;\n cursor: default;\n box-shadow: none;\n}\n.graph-explorer-list-element-view__label {\n background: var(--ge-surface);\n border-radius: 0 var(--ge-radius-sm) var(--ge-radius-sm) 0;\n font-family: var(--ge-font-mono);\n font-size: 13px;\n color: var(--ge-text);\n min-height: 1.4em;\n padding: 3px 8px;\n overflow-wrap: break-word;\n}\n\n.graph-explorer-text-highlight {\n font-weight: 700;\n color: var(--ge-accent-hover);\n}\n\n.graph-explorer-navigator {\n display: block;\n position: absolute;\n bottom: 25px;\n right: 25px;\n overflow: hidden;\n}\n.graph-explorer .graph-explorer-navigator {\n box-sizing: content-box;\n}\n.graph-explorer-navigator {\n background: var(--ge-surface);\n border: 1px solid var(--ge-border);\n border-radius: var(--ge-radius);\n box-shadow: var(--ge-shadow-md);\n transition: width 0.3s, height 0.3s;\n}\n.graph-explorer-navigator--collapsed {\n width: 26px;\n height: 26px;\n}\n.graph-explorer-navigator > canvas {\n transition: opacity 0.3s;\n}\n.graph-explorer-navigator--expanded > canvas {\n opacity: 1;\n}\n.graph-explorer-navigator--collapsed > canvas {\n opacity: 0;\n}\n.graph-explorer-navigator__toggle {\n position: absolute;\n background: transparent;\n border: none;\n outline: none;\n padding: 4px;\n opacity: 0.5;\n transition: opacity 0.3s;\n}\n.graph-explorer-navigator__toggle:hover {\n opacity: 1;\n}\n.graph-explorer-navigator--expanded .graph-explorer-navigator__toggle {\n top: 5px;\n left: 5px;\n}\n.graph-explorer-navigator--collapsed .graph-explorer-navigator__toggle {\n top: 0px;\n left: 0px;\n}\n.graph-explorer-navigator__toggle-icon {\n width: 18px;\n height: 18px;\n background-size: 18px 18px;\n background-repeat: no-repeat;\n}\n.graph-explorer-navigator--expanded .graph-explorer-navigator__toggle-icon {\n background-image: url(\"data:image/svg+xml,%3csvg%20aria-hidden='true'%20focusable='false'%20data-prefix='far'%20data-icon='minus-square'%20class='svg-inline--fa%20fa-minus-square%20fa-w-14'%20role='img'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20448%20512'%3e%3cpath%20fill='currentColor'%20d='M108%20284c-6.6%200-12-5.4-12-12v-32c0-6.6%205.4-12%2012-12h232c6.6%200%2012%205.4%2012%2012v32c0%206.6-5.4%2012-12%2012H108zM448%2080v352c0%2026.5-21.5%2048-48%2048H48c-26.5%200-48-21.5-48-48V80c0-26.5%2021.5-48%2048-48h352c26.5%200%2048%2021.5%2048%2048zm-48%20346V86c0-3.3-2.7-6-6-6H54c-3.3%200-6%202.7-6%206v340c0%203.3%202.7%206%206%206h340c3.3%200%206-2.7%206-6z'%3e%3c/path%3e%3c/svg%3e\");\n}\n.graph-explorer-navigator--collapsed .graph-explorer-navigator__toggle-icon {\n background-image: url(\"data:image/svg+xml,%3csvg%20aria-hidden='true'%20focusable='false'%20data-prefix='far'%20data-icon='plus-square'%20class='svg-inline--fa%20fa-plus-square%20fa-w-14'%20role='img'%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%20448%20512'%3e%3cpath%20fill='currentColor'%20d='M352%20240v32c0%206.6-5.4%2012-12%2012h-88v88c0%206.6-5.4%2012-12%2012h-32c-6.6%200-12-5.4-12-12v-88h-88c-6.6%200-12-5.4-12-12v-32c0-6.6%205.4-12%2012-12h88v-88c0-6.6%205.4-12%2012-12h32c6.6%200%2012%205.4%2012%2012v88h88c6.6%200%2012%205.4%2012%2012zm96-160v352c0%2026.5-21.5%2048-48%2048H48c-26.5%200-48-21.5-48-48V80c0-26.5%2021.5-48%2048-48h352c26.5%200%2048%2021.5%2048%2048zm-48%20346V86c0-3.3-2.7-6-6-6H54c-3.3%200-6%202.7-6%206v340c0%203.3%202.7%206%206%206h340c3.3%200%206-2.7%206-6z'%3e%3c/path%3e%3c/svg%3e\");\n}\n\n@keyframes graph-explorer-progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.graph-explorer-progress-bar {\n flex-shrink: 0;\n -webkit-flex-shrink: 0; /* safari 8 */\n width: 100%;\n background-color: var(--ge-surface-3);\n background-repeat: repeat-x;\n overflow: hidden;\n}\n.graph-explorer-progress-bar__bar {\n float: left;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #fff;\n text-align: center;\n transition: width 0.6s ease;\n background-color: var(--ge-accent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-repeat: repeat-x;\n background-size: 40px 40px;\n animation: graph-explorer-progress-bar-stripes 2s linear infinite;\n}\n.graph-explorer-progress-bar--error .graph-explorer-progress-bar__bar {\n background-color: var(--ge-danger);\n}\n.graph-explorer-progress-bar--loading, .graph-explorer-progress-bar--error {\n /* property name | duration | delay */\n transition: height 300ms 300ms;\n}\n.graph-explorer-progress-bar--completed {\n /* property name | duration | delay */\n transition: height 200ms 0ms;\n}\n\n.graph-explorer-search-results {\n margin: 0;\n padding: 0;\n outline: none;\n}\n\n.graph-explorer-accordion {\n flex: auto;\n display: flex;\n height: 100%;\n width: 100%;\n}\n.graph-explorer-accordion--scrollable {\n overflow: auto;\n}\n.graph-explorer-accordion--vertical {\n flex-direction: column;\n}\n.graph-explorer-accordion--vertical:not(.graph-explorer-accordion--resizing) > .graph-explorer-accordion-item {\n transition: height 0.3s ease-in-out;\n}\n\n.graph-explorer-accordion-item {\n display: flex;\n position: relative;\n flex: auto;\n}\n.graph-explorer-accordion-item--resizing > .graph-explorer-accordion-item__handle, .graph-explorer-accordion-item__handle:hover {\n background: rgba(0, 0, 0, 0.1);\n}\n.graph-explorer-accordion-item--vertical {\n border-top: 1px solid var(--ge-border);\n}\n.graph-explorer-accordion-item--vertical:first-child {\n border-top-width: 0;\n}\n.graph-explorer-accordion-item--horizontal {\n border-right: 1px solid var(--ge-border);\n}\n.graph-explorer-accordion-item--horizontal:last-child {\n border-right-width: 0;\n}\n.graph-explorer-accordion-item__handle {\n position: absolute;\n z-index: 2;\n}\n.graph-explorer-accordion-item__handle-vertical {\n height: 5px;\n bottom: -2.5px;\n left: 0;\n width: 100%;\n cursor: ns-resize;\n}\n.graph-explorer-accordion-item__handle-horizontal {\n height: 100%;\n top: 0;\n right: -2.5px;\n width: 5px;\n cursor: ew-resize;\n}\n.graph-explorer-accordion-item__handle-btn {\n background: var(--ge-surface);\n border: 2px solid var(--ge-accent);\n box-shadow: var(--ge-shadow-sm);\n border-radius: 10px;\n cursor: pointer;\n height: 20px;\n position: absolute;\n top: 50%;\n margin-top: -10px;\n width: 20px;\n z-index: 20;\n transition: 0.3s;\n}\n.graph-explorer-accordion-item__handle-btn::before {\n background-position: 0 0;\n background-repeat: no-repeat;\n background-size: cover;\n content: \"\";\n height: 8px;\n position: absolute;\n top: 50%;\n left: 50%;\n margin-top: -4px;\n margin-left: -4px;\n width: 8px;\n transition: background 0.3s;\n}\n.graph-explorer-accordion-item__handle-btn:hover {\n background: var(--ge-accent);\n}\n.graph-explorer-accordion-item__handle-btn-left {\n left: 100%;\n margin-left: -10px;\n}\n.graph-explorer-accordion-item__handle-btn-left:before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACXSURBVHjaYvz//z8DIbBlyxYmIDUTiI/6+PgsQJZjJGQAVPMcIE4E4n9AnAA0ZDFRBqBphoELQGwKNOQPiMNEoubLQOwG04zTADyanYGaXyOrZaJEM4YBpGpGMYAczXgDkVgANwBoCyiOU4B4PpK8LhDvBbpOlCgXkGMIhhdINQRrGJBiCO2SMg6XgPh9yEmZkdLsDBBgAMUxZ3KEE1BeAAAAAElFTkSuQmCC\");\n}\n.graph-explorer-accordion-item__handle-btn-left:hover:before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACXSURBVHjaYvz//z8DIfD+/XsmIDUTiI8KCgouQJZjJGQAVPMcIE4E4n9AnAA0ZDFRBqBphoELQGwKNOQPiMNEoubLQOwG04zTADyanYGaXyOrZaJEM4YBpGpGMYAczXgDkVgANwBoCyiOU4B4PpK8LhDvBbpOlCgXkGMIhhdINQRrGJBiCO2SMg6XgPh9yEmZkdLsDBBgAKQBZhCkUF/8AAAAAElFTkSuQmCC\");\n}\n.graph-explorer-accordion-item__handle-btn-right {\n right: 100%;\n margin-right: -10px;\n}\n.graph-explorer-accordion-item__handle-btn-right:before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACdSURBVHjaYvz//z8DMtiyZUsCkLIG4nQfH59/DAQAI7IBQM2xQGoBEDMB8XwgTiFkCNwAoGYWIHUaiA2Q5AkawgRjABX9AVJuQHwZST4RiOcADWciaADUkNdAypkUQzAESTUEq6mkGILTb8QawsRAIcBpANAWUSC1F4h18UUrEyWasRpAimYMA0jVTJOk3AfE/4jVjJEbycnOAAEGABIyZ3LVo9KqAAAAAElFTkSuQmCC\");\n}\n.graph-explorer-accordion-item__handle-btn-right:hover:before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACeSURBVHjaYvz//z8DMnj//n0CkLIG4nRBQcF/DAQAC5rmWCA1F4iZgJgZyE8hZAgjzAVAxSDDTgOxAZL8fCDGawgTjAFU9AdIuQHxZST5RCCeAzSciaABUENeAylnUgzBECTVEKymkmIITr8RawgTA4UApwFAW0SB1F4g1sUXrUyUaMZqACmaMQwgVTNNknIfEP8jVjOKC8jNzgABBgBosmVE6sxD1QAAAABJRU5ErkJggg==\");\n}\n.graph-explorer-accordion-item--collapsed .graph-explorer-accordion-item__handle-btn:before {\n transform: rotate(180deg);\n}\n.graph-explorer-accordion-item__inner {\n flex: auto;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n.graph-explorer-accordion-item__body {\n flex: 1 1 0px;\n display: flex;\n flex-direction: column;\n background: var(--ge-surface);\n}\n.graph-explorer-accordion-item__header {\n padding: 11px 12px 11px 24px;\n position: relative;\n flex-shrink: 0;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.06em;\n text-transform: uppercase;\n color: var(--ge-text-muted);\n background: var(--ge-surface-2);\n border-bottom: 1px solid var(--ge-border);\n cursor: pointer;\n transition: background var(--ge-transition), color var(--ge-transition);\n /* unselectable */\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.graph-explorer-accordion-item__header:hover {\n background: var(--ge-surface-3);\n color: var(--ge-text);\n}\n.graph-explorer-accordion-item__header:before {\n border-top: 5px solid var(--ge-text-subtle);\n border-left: 4px solid transparent;\n border-right: 4px solid transparent;\n content: \"\";\n position: absolute;\n top: 50%;\n left: 9px;\n margin-top: -2px;\n transition: transform 0.15s ease;\n}\n.graph-explorer-accordion-item--collapsed .graph-explorer-accordion-item__header:before {\n -webkit-transform: rotate(-90deg);\n -moz-transform: rotate(-90deg);\n -ms-transform: rotate(-90deg);\n transform: rotate(-90deg);\n}\n.graph-explorer-accordion-item--collapsed .graph-explorer-accordion-item__body {\n display: none;\n}\n\n.graph-explorer-drag-resizable-column.graph-explorer-drag-resizable-column--docked-bottom .graph-explorer-drag-resizable-column__handle,\n.graph-explorer-drag-resizable-column.graph-explorer-drag-resizable-column--docked-top .graph-explorer-drag-resizable-column__handle {\n height: 8px;\n width: 100%;\n top: initial;\n left: 0;\n cursor: ns-resize;\n}\n.graph-explorer-drag-resizable-column.graph-explorer-drag-resizable-column--docked-bottom .graph-explorer-drag-resizable-column__handle-btn,\n.graph-explorer-drag-resizable-column.graph-explorer-drag-resizable-column--docked-top .graph-explorer-drag-resizable-column__handle-btn {\n height: 100%;\n width: 40px;\n top: 0;\n left: 50%;\n margin-left: -20px;\n margin-top: 0px;\n}\n\n.graph-explorer-drag-resizable-column {\n display: flex;\n flex-direction: column;\n position: relative;\n}\n.graph-explorer-drag-resizable-column__handle {\n background: #fff url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAECAYAAACp8Z5+AAAAHElEQVQIW2PcvHnzf19fX0YGKIAzMARgKjFUAABdvQgFy9518AAAAABJRU5ErkJggg==\") repeat;\n width: 8px;\n height: 100%;\n position: absolute;\n top: 0;\n left: initial;\n z-index: 2;\n -webkit-transition: 0.3s;\n -moz-transition: 0.3s;\n transition: 0.3s;\n cursor: ew-resize;\n}\n.graph-explorer-drag-resizable-column__handle-btn {\n background: #eee;\n cursor: pointer;\n height: 40px;\n position: absolute;\n top: 50%;\n left: 0;\n margin-top: -20px;\n width: 100%;\n z-index: 1;\n -webkit-transition: 0.3s;\n -moz-transition: 0.3s;\n transition: 0.3s;\n}\n.graph-explorer-drag-resizable-column__handle-btn::before {\n background-position: 0 0;\n background-repeat: no-repeat;\n background-size: cover;\n content: \"\";\n height: 6px;\n position: absolute;\n top: 50%;\n left: 50%;\n margin-top: -3px;\n margin-left: -3px;\n width: 6px;\n -webkit-transition: 0.3s;\n -moz-transition: 0.3s;\n transition: 0.3s;\n}\n.graph-explorer-drag-resizable-column__handle-btn:hover {\n background: #b3b3b3;\n transform: scale(1.2);\n}\n.graph-explorer-drag-resizable-column--closed .graph-explorer-drag-resizable-column__handle-btn::before {\n transform: rotate(180deg);\n}\n.graph-explorer-drag-resizable-column--docked-left {\n margin-right: 8px;\n}\n.graph-explorer-drag-resizable-column--docked-left .graph-explorer-drag-resizable-column__handle {\n right: -8px;\n}\n.graph-explorer-drag-resizable-column--docked-left .graph-explorer-drag-resizable-column__handle-btn::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACXSURBVHjaYvz//z8DIbBlyxYmIDUTiI/6+PgsQJZjJGQAVPMcIE4E4n9AnAA0ZDFRBqBphoELQGwKNOQPiMNEoubLQOwG04zTADyanYGaXyOrZaJEM4YBpGpGMYAczXgDkVgANwBoCyiOU4B4PpK8LhDvBbpOlCgXkGMIhhdINQRrGJBiCO2SMg6XgPh9yEmZkdLsDBBgAMUxZ3KEE1BeAAAAAElFTkSuQmCC\");\n}\n.graph-explorer-drag-resizable-column--docked-left .graph-explorer-drag-resizable-column__handle-btn:hover::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACXSURBVHjaYvz//z8DIfD+/XsmIDUTiI8KCgouQJZjJGQAVPMcIE4E4n9AnAA0ZDFRBqBphoELQGwKNOQPiMNEoubLQOwG04zTADyanYGaXyOrZaJEM4YBpGpGMYAczXgDkVgANwBoCyiOU4B4PpK8LhDvBbpOlCgXkGMIhhdINQRrGJBiCO2SMg6XgPh9yEmZkdLsDBBgAKQBZhCkUF/8AAAAAElFTkSuQmCC\");\n}\n.graph-explorer-drag-resizable-column--docked-right {\n margin-left: 8px;\n}\n.graph-explorer-drag-resizable-column--docked-right .graph-explorer-drag-resizable-column__handle {\n left: -8px;\n}\n.graph-explorer-drag-resizable-column--docked-right .graph-explorer-drag-resizable-column__handle-btn::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACdSURBVHjaYvz//z8DMtiyZUsCkLIG4nQfH59/DAQAI7IBQM2xQGoBEDMB8XwgTiFkCNwAoGYWIHUaiA2Q5AkawgRjABX9AVJuQHwZST4RiOcADWciaADUkNdAypkUQzAESTUEq6mkGILTb8QawsRAIcBpANAWUSC1F4h18UUrEyWasRpAimYMA0jVTJOk3AfE/4jVjJEbycnOAAEGABIyZ3LVo9KqAAAAAElFTkSuQmCC\");\n}\n.graph-explorer-drag-resizable-column--docked-right .graph-explorer-drag-resizable-column__handle-btn:hover::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAACeSURBVHjaYvz//z8DMnj//n0CkLIG4nRBQcF/DAQAC5rmWCA1F4iZgJgZyE8hZAgjzAVAxSDDTgOxAZL8fCDGawgTjAFU9AdIuQHxZST5RCCeAzSciaABUENeAylnUgzBECTVEKymkmIITr8RawgTA4UApwFAW0SB1F4g1sUXrUyUaMZqACmaMQwgVTNNknIfEP8jVjOKC8jNzgABBgBosmVE6sxD1QAAAABJRU5ErkJggg==\");\n}\n.graph-explorer-drag-resizable-column--docked-top {\n margin-bottom: 8px;\n}\n.graph-explorer-drag-resizable-column--docked-top .graph-explorer-drag-resizable-column__handle {\n bottom: -8px;\n}\n.graph-explorer-drag-resizable-column--docked-top .graph-explorer-drag-resizable-column__handle-btn::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA30lEQVQ4T7WTzw2CMBSH37Ou4BiemICYeODABiYSmcAJOoELiPHgBiXhYGKYgBNjuIJQ80ghBfsaEmKPbb/v90v/ICwcuJAHr0BKuaIAKWXLBbECgoMgyAisqirlJE6BBScm+c5JfgQOuG/vlIwEHpiVDAIGrg25tQ5x1KQTcLAQYkfrTdO8AMApQR8cRdGbBEVRbDgJ5nl+BYDUqlhTcg/384wkQ6XUERFvAECPxgkzklZrferOQCl1QMSzEGI/TZ6+QNPkqbW+xHH8GG6hLMt1GIafOX/D3vvfzzSnzRcwRIO9lTALYQAAAABJRU5ErkJggg==\");\n}\n.graph-explorer-drag-resizable-column--docked-top .graph-explorer-drag-resizable-column__handle-btn:hover::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA1ElEQVQ4T7WTQQrCMBBF88Usu2jBY3gKwVsIij2BB/ECVlx4C8FT9BhCZ9FlpV9SmlJrphSKWSZ57w+ZCczMhZm8GRWQXLgAALUWpAocXBRF5sAkSVJNEhR4GMDBCUjeNMmPYAj70jXJl0CDxySdQIHzFl5rkkagwdbajTuvquppjAlKMAZHUfRygrIsV6pERC4k016fc5fsYb8fkgDIICJ7kldjjBuaIKxIagDH5g1EZEfyZK3dDpOHE9hW8gBwjuP43u/CEsB7yt8g2d3972eaUs0Hvw2bpYUKd8YAAAAASUVORK5CYII=\");\n}\n.graph-explorer-drag-resizable-column--docked-bottom {\n margin-top: 8px;\n}\n.graph-explorer-drag-resizable-column--docked-bottom .graph-explorer-drag-resizable-column__handle {\n top: -8px;\n}\n.graph-explorer-drag-resizable-column--docked-bottom .graph-explorer-drag-resizable-column__handle-btn::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA60lEQVQ4T62TzQ2CQBBGv3FtwTI8UQEx8cCBDkw0UoEVbAU2IMaDHSwJBxNDBZwswxZc1ixhCbI/IUGOy7w3s7MzhJkfzeTRC6qqWsZx/JkiHMa2AiHEjohOjLFtkiTvkKQsy5WU8qGUOqdpeichxJ6IrgAWAF6MsY1P0sFPAGsAjVLqSEVRXABkg6xOyQg24TlxzhdRFOUADj6JB77VdZ21PQhJ9H8ppSnb5GhhznnTv4JP0hH6zhasD37mwCMZPkqf2RxagxSQWLBVgbE6JE7YKxg1FqZhrgEL7oKupJM1vun83zJNWSJXzBeWo4O9On9s3QAAAABJRU5ErkJggg==\");\n}\n.graph-explorer-drag-resizable-column--docked-bottom .graph-explorer-drag-resizable-column__handle-btn:hover::before {\n background-image: url(\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA5klEQVQ4T62TQQrCMBBF5xez7KIFj+EpBG8hKPYEHsQLWHHhLQRP4TGEZtFlpF8S2hJtUgo1q5DMf/NnJoHMXJiplx5AcgHgPQXoxzqA1npL8qiU2qRp+hqD1HW9NMbcAZyyLLtBa70jeRGRRESeSql1DNKKHyKyEpEGwMECziQLL2sQ8iN24QBKkEyqqioB7GOQkJjkNc/zwvVgDGLvjTGdbZejFwONP4Wgk9aVrXkgdmX4HY846UP8zN3h4CHFICHxwIFn86ucmDgK8Btr967bQBN6YKN/wZbTzjsoHnUw5U/8BfABHFmbpQyQ0q0AAAAASUVORK5CYII=\");\n}\n\n.introjs-tooltiptext {\n color: #333;\n font-family: \"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif;\n font-size: 14px;\n line-height: 1.42857;\n}\n\n.graph-explorer-toolbar {\n display: inline-flex;\n align-items: center;\n padding: 6px;\n background: var(--ge-surface);\n border: 1px solid var(--ge-border);\n border-radius: var(--ge-radius-lg);\n box-shadow: var(--ge-shadow-md);\n}\n.graph-explorer-toolbar .graph-explorer-btn-group {\n display: inline-flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 2px;\n}\n.graph-explorer-toolbar .graph-explorer-btn-group > .graph-explorer-btn,\n.graph-explorer-toolbar .graph-explorer-btn-group > .graph-explorer-btn-group {\n float: none;\n margin-left: 0;\n}\n.graph-explorer-toolbar .graph-explorer-btn-default {\n background: transparent;\n border-color: transparent;\n box-shadow: none;\n color: var(--ge-text-muted);\n}\n.graph-explorer-toolbar .graph-explorer-btn-default:hover {\n background: var(--ge-surface-3);\n border-color: transparent;\n color: var(--ge-text);\n}\n.graph-explorer-toolbar .graph-explorer-btn-default.active {\n background: var(--ge-accent-soft);\n border-color: transparent;\n color: var(--ge-accent-hover);\n}\n.graph-explorer-toolbar__language-selector {\n display: inline-flex;\n align-items: center;\n margin-left: 8px;\n padding-left: 10px;\n border-left: 1px solid var(--ge-border);\n color: var(--ge-text-muted);\n font-size: 13px;\n}\n.graph-explorer-toolbar__language-selector > label {\n margin-right: 8px;\n margin-bottom: 0;\n font-weight: 500;\n}\n.graph-explorer-toolbar__language-selector > label > span {\n vertical-align: middle;\n}\n.graph-explorer-toolbar__language-selector > select {\n height: 32px;\n padding: 0 10px;\n background: var(--ge-surface);\n border: 1px solid var(--ge-border-strong);\n border-radius: var(--ge-radius-sm);\n color: var(--ge-text);\n cursor: pointer;\n transition: border-color var(--ge-transition), box-shadow var(--ge-transition);\n}\n.graph-explorer-toolbar__language-selector > select:focus {\n outline: none;\n border-color: var(--ge-accent);\n box-shadow: var(--ge-ring);\n}\n.graph-explorer-toolbar__toggle {\n line-height: 1;\n}\n.graph-explorer-toolbar__toggle:after {\n background-position: center;\n background-repeat: no-repeat;\n content: \"\";\n display: block;\n height: 14px;\n margin: 2px 0;\n width: 14px;\n}\n.graph-explorer-toolbar__toggle-left:after {\n background-image: url(\"data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%20469.333%20469.333'%3e%3cpath%20fill='%23333'%20d='M448,0H21.333C9.552,0,0,9.552,0,21.333V448c0,11.781,9.552,21.333,21.333,21.333H448c11.781,0,21.333-9.552,21.333-21.333V21.333C469.333,9.552,459.781,0,448,0z%20M138.667,426.667h-96v-384h96V426.667zM426.667,426.667H181.333v-384h245.333V426.667z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-toolbar__toggle-right:after {\n background-image: url(\"data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%20469.333%20469.333'%3e%3cpath%20fill='%23333'%20d='M448,0H21.333C9.552,0,0,9.552,0,21.333V448c0,11.781,9.552,21.333,21.333,21.333H448c11.781,0,21.333-9.552,21.333-21.333V21.333C469.333,9.552,459.781,0,448,0z%20M288,426.667H42.667v-384H288V426.667zM426.667,426.667h-96v-384h96V426.667z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-toolbar__toggle-left.active:after {\n background-image: url(\"data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%20469.333%20469.333'%3e%3cpath%20fill='%23333'%20d='m%2030.5,235.66084%200,-205.208259%2059.6835,0%2059.68352,0%200,205.208259%200,205.20828%20-59.68352,0%20-59.6835,0%200,-205.20828%20z'/%3e%3cpath%20fill='%23333'%20d='M448,0H21.333C9.552,0,0,9.552,0,21.333V448c0,11.781,9.552,21.333,21.333,21.333H448c11.781,0,21.333-9.552,21.333-21.333V21.333C469.333,9.552,459.781,0,448,0z%20M138.667,426.667h-96v-384h96V426.667zM426.667,426.667H181.333v-384h245.333V426.667z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-toolbar__toggle-right.active:after {\n background-image: url(\"data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='12'%20viewBox='0%200%20469.333%20469.333'%3e%3cpath%20fill='%23333'%20d='m%20318.9149,233.50816%200,-205.208262%2059.6835,0%2059.68351,0%200,205.208262%200,205.20828%20-59.68351,0%20-59.6835,0%200,-205.20828%20z'/%3e%3cpath%20fill='%23333'%20d='M448,0H21.333C9.552,0,0,9.552,0,21.333V448c0,11.781,9.552,21.333,21.333,21.333H448c11.781,0,21.333-9.552,21.333-21.333V21.333C469.333,9.552,459.781,0,448,0z%20M288,426.667H42.667v-384H288V426.667zM426.667,426.667h-96v-384h96V426.667z'/%3e%3c/svg%3e\");\n}\n.graph-explorer-toolbar__layout-group {\n display: inline-flex;\n align-items: center;\n margin-left: 5px;\n height: 32px;\n}\n.graph-explorer-toolbar__layout-group > label {\n margin-right: 5px;\n margin-bottom: 0;\n}\n.graph-explorer-toolbar__layout-group .btn-group, .graph-explorer-toolbar__layout-group button {\n height: 100%;\n}\n\n.graph-explorer-toolbar .graph-explorer-toolbar__undo,\n.graph-explorer-toolbar .graph-explorer-toolbar__redo {\n display: none;\n}\n\n.graph-explorer {\n display: flex;\n flex-direction: column;\n height: 100%;\n box-sizing: border-box;\n white-space: initial;\n color: var(--ge-text);\n background: var(--ge-bg);\n font-family: var(--ge-font-sans);\n font-size: 14px;\n line-height: 1.5;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n text-rendering: optimizeLegibility;\n}\n.graph-explorer *, .graph-explorer *:before, .graph-explorer *:after {\n box-sizing: inherit;\n}\n.graph-explorer__toolbar-widget {\n position: absolute;\n left: 10px;\n top: 10px;\n}\n.graph-explorer__workspace {\n display: flex;\n flex: auto;\n overflow: hidden;\n flex-direction: column;\n}\n.graph-explorer__main-panel {\n display: flex;\n flex: auto;\n overflow: hidden;\n width: 0;\n}\n.graph-explorer--unselectable {\n -webkit-touch-callout: none;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.graph-explorer--horizontal-resizing * {\n cursor: ew-resize !important;\n}\n.graph-explorer--vertical-resizing * {\n cursor: ns-resize !important;\n}\n.graph-explorer h4 {\n font-size: 18px;\n font-weight: 600;\n line-height: 1.2;\n letter-spacing: -0.01em;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.graph-explorer select {\n color: inherit;\n margin: 0;\n font: inherit;\n line-height: inherit;\n text-transform: none;\n}\n.graph-explorer hr {\n border: 0;\n border-top: 1px solid var(--ge-border);\n height: 0;\n}\n.graph-explorer ::-webkit-scrollbar {\n width: 10px;\n height: 10px;\n}\n.graph-explorer ::-webkit-scrollbar-thumb {\n background: var(--ge-border-strong);\n border: 3px solid transparent;\n background-clip: content-box;\n border-radius: var(--ge-radius-pill);\n}\n.graph-explorer ::-webkit-scrollbar-thumb:hover {\n background: var(--ge-text-subtle);\n border: 3px solid transparent;\n background-clip: content-box;\n}\n.graph-explorer ::-webkit-scrollbar-track {\n background: transparent;\n}\n\n.graph-explorer-scrollable {\n flex: 1 1 0px;\n overflow-y: scroll;\n}\n\n.graph-explorer-scrollable > * {\n position: relative;\n}\n\n.jstree-icon.jstree-themeicon.jstree-themeicon-custom {\n background-size: contain !important;\n}\n\n.graph-explorer-default-template, .graph-explorer-group-template__wrap {\n max-width: 450px;\n float: left;\n border-radius: var(--ge-radius-lg);\n border-style: solid;\n border-width: 1px;\n overflow: hidden;\n box-shadow: var(--ge-shadow-md);\n}\n.graph-explorer-default-template__thumbnail {\n text-align: center;\n}\n.graph-explorer-default-template__thumbnail > img {\n max-width: 200px;\n}\n.graph-explorer-default-template[data-expanded=true] .graph-explorer-default-template__thumbnail > img, [data-expanded=true].graph-explorer-group-template__wrap .graph-explorer-default-template__thumbnail > img {\n max-width: 350px;\n}\n\n.graph-explorer-default-template_type-line, .graph-explorer-group-template__type-line {\n padding: 0px 7px;\n color: white;\n height: 18px;\n margin-bottom: 2px;\n overflow: hidden;\n display: flex;\n align-items: baseline;\n margin-top: -3px;\n}\n\ndiv.graph-explorer-default-template_type-line__icon, div.graph-explorer-group-template__type-line-icon {\n position: inherit !important;\n margin-right: 4px;\n}\n\n.graph-explorer-default-template_type-line_text-container, .graph-explorer-group-template__type-line-text-container {\n flex-grow: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n width: 0;\n}\n\n.graph-explorer-default-template_type-line_text-container__text, .graph-explorer-group-template__type-line-text {\n display: inline;\n white-space: nowrap;\n font-size: 12px;\n}\n\n.graph-explorer-default-template_body, .graph-explorer-group-template__body {\n border-bottom-left-radius: 12px;\n border-bottom-right-radius: 12px;\n border-top-style: solid;\n border-top-width: 1px;\n background: white;\n padding: 7px 10px 8px 10px;\n overflow: hidden;\n display: flex;\n flex-direction: column;\n}\n\n.graph-explorer-default-template_body__label, .graph-explorer-group-template__label {\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n color: var(--ge-text);\n overflow: hidden;\n text-overflow: ellipsis;\n margin-bottom: 0;\n white-space: nowrap;\n word-wrap: break-word;\n}\n\n.graph-explorer-default-template_body_expander {\n width: 100%;\n overflow: hidden;\n display: flex;\n}\n\n.graph-explorer-default-template_body_expander__iri_label {\n font-size: 12px;\n font-weight: 100;\n margin-right: 15px;\n color: #b6b6b6;\n}\n\n.graph-explorer-default-template_body_expander_iri {\n font-size: 12px;\n width: 100%;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.graph-explorer-default-template_body_expander_iri__link {\n color: #b6b6b6;\n}\n\n.graph-explorer-default-template_body_expander__hr {\n margin: 5px 0px 5px 0px;\n}\n\n.graph-explorer-default-template_body_expander_property-table {\n font-size: 15px;\n font-weight: 100;\n margin-bottom: 5px;\n max-height: 200px;\n overflow-y: scroll;\n overflow-x: hidden;\n}\n\n.graph-explorer-default-template_body_expander_property-table_row {\n white-space: nowrap;\n}\n\n.graph-explorer-default-template_body_expander_property-table_row__key {\n display: inline-block;\n width: 50%;\n text-overflow: ellipsis;\n overflow: hidden;\n vertical-align: top;\n}\n\n.graph-explorer-default-template_body_expander_property-table_row_key {\n display: inline-block;\n width: 50%;\n text-overflow: ellipsis;\n overflow: hidden;\n vertical-align: top;\n}\n\n.graph-explorer-default-template_body_expander_property-table_row_key_values {\n display: inline-block;\n width: 50%;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n\n.graph-explorer-default-template_body_expander_property-table_row_key_values__value {\n width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: initial;\n padding-right: 10px;\n}\n\n.graph-explorer-group-template {\n overflow: hidden;\n}\n.graph-explorer-group-template__wrap {\n max-width: none;\n}\n.graph-explorer-group-template__body {\n overflow: visible;\n}\n.graph-explorer-group-template__embedded-layer {\n margin-top: 7px;\n}\n.graph-explorer-group-template .graph-explorer-paper__canvas {\n border-color: #fff;\n}\n\n.graph-explorer-standard-template {\n min-width: 180px;\n max-width: 400px;\n float: left;\n}\n.graph-explorer-standard-template__main {\n border-radius: var(--ge-radius);\n border: 1px solid;\n overflow: hidden;\n box-shadow: var(--ge-shadow-md);\n}\n.graph-explorer-standard-template__body {\n margin-left: 8px;\n border-radius: 0 var(--ge-radius) var(--ge-radius) 0;\n border-left: 1px solid;\n padding: 4px 0;\n background: var(--ge-surface);\n}\n.graph-explorer-standard-template__body-horizontal {\n display: flex;\n align-items: center;\n justify-content: space-between;\n overflow: hidden;\n}\n.graph-explorer-standard-template__body-content {\n flex-grow: 1;\n min-width: 0;\n margin-right: 12px;\n}\n.graph-explorer-standard-template__label {\n font-size: 18px;\n font-weight: 600;\n letter-spacing: -0.01em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.graph-explorer-standard-template__thumbnail {\n font-size: 26px;\n margin: 5px 10px;\n flex-shrink: 0;\n width: 50px;\n height: 50px;\n display: flex;\n justify-content: center;\n align-items: center;\n}\n.graph-explorer-standard-template__thumbnail-image {\n max-height: 100%;\n max-width: 100%;\n}\n.graph-explorer-standard-template__thumbnail-icon {\n max-height: 26px;\n max-width: 26px;\n}\n.graph-explorer-standard-template__photo {\n border-bottom: 1px solid;\n}\n.graph-explorer-standard-template__photo-image {\n width: 100%;\n border-radius: 2px 2px 0 0;\n}\n.graph-explorer-standard-template__type {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 11px;\n font-style: italic;\n color: #999;\n display: flex;\n}\n.graph-explorer-standard-template__type-value {\n width: 0;\n flex-grow: 1;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.graph-explorer-standard-template__iri {\n width: 100%;\n overflow: hidden;\n display: flex;\n}\n.graph-explorer-standard-template__iri-key {\n font-size: 12px;\n margin-right: 4px;\n color: #b6b6b6;\n}\n.graph-explorer-standard-template__iri-value {\n font-size: 12px;\n width: 100%;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n.graph-explorer-standard-template__iri-value a {\n color: #b6b6b6;\n}\n.graph-explorer-standard-template__hr {\n margin: 5px 0;\n}\n.graph-explorer-standard-template__properties {\n font-size: 15px;\n max-height: 200px;\n overflow-y: scroll;\n overflow-x: hidden;\n}\n.graph-explorer-standard-template__propertites-row {\n white-space: nowrap;\n}\n.graph-explorer-standard-template__properties-key {\n display: inline-block;\n width: 50%;\n text-overflow: ellipsis;\n overflow: hidden;\n vertical-align: top;\n}\n.graph-explorer-standard-template__properties-values {\n display: inline-block;\n width: 50%;\n text-overflow: ellipsis;\n overflow: hidden;\n}\n.graph-explorer-standard-template__properties-value {\n width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: initial;\n padding-right: 10px;\n}\n.graph-explorer-standard-template__pinned-props {\n border-top: 1px solid;\n margin: 0 5px;\n}\n.graph-explorer-standard-template__dropdown {\n border-radius: var(--ge-radius);\n background-color: var(--ge-surface);\n margin-top: 6px;\n border: 1px solid;\n box-shadow: var(--ge-shadow-md);\n}\n.graph-explorer-standard-template__dropdown-content {\n width: 100%;\n padding: 9px;\n}\n.graph-explorer-standard-template__actions {\n display: flex;\n justify-content: space-between;\n}\n.graph-explorer-standard-template__actions button {\n padding: 5px;\n min-width: 60px;\n}/*$vite$:1*/")); document.head.appendChild(elementStyle); } } catch (e) { console.error("vite-plugin-css-injected-by-js", e); }})();
import * as React from "react";
import { Component, cloneElement, createContext, createElement } from "react";
import { hcl } from "d3-color";
import { cloneDeep, defaultsDeep, each, keyBy, map, pick, uniqueId } from "lodash";
import * as N3 from "n3";
import * as cola from "webcola";
import { createRoot } from "react-dom/client";
import saveAs from "file-saverjs";
//#region \0rolldown/runtime.js
var __defProp = Object.defineProperty;
var __exportAll = (all, no_symbols) => {
let target = {};
for (var name in all) __defProp(target, name, {
get: all[name],
enumerable: true
});
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
return target;
};
//#endregion
//#region src/graph-explorer/data/utils.ts
/** Generates random 32-digit hexadecimal string. */
function generate128BitID() {
function random32BitDigits() {
return Math.floor((1 + Math.random()) * 4294967296).toString(16).substring(1);
}
return random32BitDigits() + random32BitDigits() + random32BitDigits() + random32BitDigits();
}
/**
* Calculate a 32 bit FNV-1a hash
* Found here: https://gist.github.com/vaiorabbit/5657561
* Ref.: http://isthe.com/chongo/tech/comp/fnv/
*
* @param {string} str the input value
* @param {integer} [seed] optionally pass the hash of the previous chunk
* @returns {integer}
*/
function hashFnv32a(str, seed = 2166136261) {
let i, l, hval = seed & 2147483647;
for (i = 0, l = str.length; i < l; i++) {
hval ^= str.charCodeAt(i);
hval += (hval << 1) + (hval << 4) + (hval << 7) + (hval << 8) + (hval << 24);
}
return hval >>> 0;
}
/**
* Extracts local name for URI the same way as it's done in RDF4J.
*/
function getUriLocalName(uri) {
let index = uri.indexOf("#");
if (index < 0) index = uri.lastIndexOf("/");
if (index < 0) index = uri.lastIndexOf(":");
if (index < 0) return;
return uri.substring(index + 1);
}
//#endregion
//#region src/graph-explorer/data/model.ts
function isIriProperty(e) {
return e && e.type === "uri";
}
function isLiteralProperty(e) {
return e && e.type === "string";
}
function sameLink(left, right) {
return left.linkTypeId === right.linkTypeId && left.sourceId === right.sourceId && left.targetId === right.targetId;
}
function hashLink(link) {
const { linkTypeId, sourceId, targetId } = link;
let hash = hashFnv32a(linkTypeId);
hash = hash * 31 + hashFnv32a(sourceId);
hash = hash * 31 + hashFnv32a(targetId);
return hash;
}
function sameElement(left, right) {
return left.id === right.id && isArraysEqual(left.types, right.types) && isLiteralsEqual(left.label.values, right.label.values) && left.image === right.image && isPropertiesEqual(left.properties, right.properties) && (!left.sources && !right.sources || left.sources && right.sources && isArraysEqual(left.sources, right.sources));
}
function isArraysEqual(left, right) {
if (left.length !== right.length) return false;
for (let i = 0; i < left.length; i++) if (left[i] !== right[i]) return false;
return true;
}
function isLiteralsEqual(left, right) {
if (left.length !== right.length) return false;
for (let i = 0; i < left.length; i++) {
const leftValue = left[i];
const rightValue = right[i];
if (leftValue.value !== rightValue.value || leftValue.language !== rightValue.language) return false;
}
return true;
}
function isIriPropertiesEqual(left, right) {
if (!isIriProperty(left) || !isIriProperty(right)) return false;
if (left.values.length !== right.values.length) return false;
for (let i = 0; i < left.values.length; i++) {
const leftValue = left.values[i];
const rightValue = right.values[i];
if (leftValue.value !== rightValue.value) return false;
}
return true;
}
function isLiteralPropertiesEqual(left, right) {
if (!isLiteralProperty(left) || !isLiteralProperty(right)) return false;
return isLiteralsEqual(left.values, right.values);
}
function isPropertiesEqual(left, right) {
if (Object.keys(left).length !== Object.keys(right).length) return false;
for (const key in left.properties) if (key in left.properties) {
const leftProperty = left[key];
const rightProperty = right[key];
if (!rightProperty) return false;
if (!isIriPropertiesEqual(leftProperty, rightProperty) && !isLiteralPropertiesEqual(leftProperty, rightProperty)) return false;
}
return true;
}
//#endregion
//#region src/graph-explorer/customization/templates/utils.ts
function getProperty(props, id) {
if (props && props[id]) return getPropertyValues(props[id]).join(", ");
else return;
}
function getPropertyValues(property) {
if (isIriProperty(property)) return property.values.map(({ value }) => value);
else if (isLiteralProperty(property)) return property.values.map(({ value }) => value);
return [];
}
//#endregion
//#region src/graph-explorer/customization/templates/default.tsx
var CLASS_NAME$25 = "graph-explorer-default-template";
var DefaultElementTemplate = class extends React.Component {
render() {
const props = this.props;
const image = props.imgUrl ? /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$25}__thumbnail` }, /* @__PURE__ */ React.createElement("img", { src: props.imgUrl })) : void 0;
const expander = props.isExpanded ? /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-default-template_body_expander" }, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-default-template_body_expander__iri_label" }, "IRI:"), /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-default-template_body_expander_iri" }, /* @__PURE__ */ React.createElement("a", {
className: "graph-explorer-default-template_body_expander_iri__link",
href: props.iri,
title: props.iri
}, props.iri))), /* @__PURE__ */ React.createElement("hr", { className: "graph-explorer-default-template_body_expander__hr" }), this.renderPropertyTable()) : null;
return /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-default-template",
style: {
backgroundColor: props.color,
borderColor: props.color
},
"data-expanded": this.props.isExpanded
}, /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-default-template_type-line",
title: props.label
}, /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-default-template_type-line__icon",
"aria-hidden": "true"
}, /* @__PURE__ */ React.createElement("img", { src: props.iconUrl })), /* @__PURE__ */ React.createElement("div", {
title: props.types,
className: "graph-explorer-default-template_type-line_text-container"
}, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-default-template_type-line_text-container__text" }, props.types))), image, /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-default-template_body",
style: { borderColor: props.color }
}, /* @__PURE__ */ React.createElement("span", {
className: "graph-explorer-default-template_body__label",
title: props.label
}, props.label), expander));
}
renderPropertyTable() {
const { propsAsList } = this.props;
if (propsAsList && propsAsList.length > 0) return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-default-template_body_expander_property-table" }, propsAsList.map((prop) => {
const values = getPropertyValues(prop.property).map((text, index) => /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-default-template_body_expander_property-table_row_key_values__value",
key: index,
title: text
}, text));
return /* @__PURE__ */ React.createElement("div", {
key: prop.id,
className: "graph-explorer-default-template_body_expander_property-table_row"
}, /* @__PURE__ */ React.createElement("div", {
title: prop.name + " (" + prop.id + ")",
className: "graph-explorer-default-template_body_expander_property-table_row__key"
}, prop.name), /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-default-template_body_expander_property-table_row_key_values" }, values));
}));
else return /* @__PURE__ */ React.createElement("div", null, "no properties");
}
};
//#endregion
//#region src/graph-explorer/data/schema.ts
var DIAGRAM_CONTEXT_URL_V1 = "https://graph-explorer.org/context/v1.json";
var PLACEHOLDER_ELEMENT_TYPE = "http://graph-explorer.org/NewEntity";
var PLACEHOLDER_LINK_TYPE = "http://graph-explorer.org/NewLink";
var GRAPH_EXPLORER_ID_URL_PREFIX = "http://graph-explorer.org/data/";
var GenerateID = {
forElement() {
return `${GRAPH_EXPLORER_ID_URL_PREFIX}e_${generate128BitID()}`;
},
forLink() {
return `${GRAPH_EXPLORER_ID_URL_PREFIX}l_${generate128BitID()}`;
}
};
var TemplateProperties = {
PinnedProperties: "graph-explorer:pinnedProperties",
CustomLabel: "graph-explorer:customLabel"
};
//#endregion
//#region src/graph-explorer/viewUtils/events.ts
var EventSource = class {
constructor() {
this.listeners = /* @__PURE__ */ new Map();
}
on(eventKey, listener) {
let listeners = this.listeners.get(eventKey);
if (!listeners) {
listeners = [];
this.listeners.set(eventKey, listeners);
}
listeners.push(listener);
}
onAny(listener) {
let listeners = this.anyListeners;
if (!listeners) {
listeners = [];
this.anyListeners = listeners;
}
listeners.push(listener);
}
off(eventKey, listener) {
const listeners = this.listeners.get(eventKey);
if (!listeners) return;
const index = listeners.indexOf(listener);
if (index >= 0) listeners.splice(index, 1);
}
offAny(listener) {
const listeners = this.anyListeners;
if (!listeners) return;
const index = listeners.indexOf(listener);
if (index >= 0) listeners.splice(index, 1);
}
trigger(eventKey, data) {
const listeners = this.listeners.get(eventKey);
if (listeners) for (const listener of listeners) listener(data, eventKey);
if (this.anyListeners) for (const anyListener of this.anyListeners) anyListener({ [eventKey]: data }, eventKey);
}
};
var EventObserver = class {
constructor() {
this.unsubscribeByKey = /* @__PURE__ */ new Map();
this.onDispose = [];
}
listen(events, eventKey, listener) {
events.on(eventKey, listener);
this.onDispose.push(() => events.off(eventKey, listener));
}
listenAny(events, listener) {
events.onAny(listener);
this.onDispose.push(() => events.offAny(listener));
}
listenOnce(events, eventKey, listener) {
let handled = false;
const onceListener = (data, key) => {
handled = true;
events.off(eventKey, onceListener);
listener(data, key);
};
events.on(eventKey, onceListener);
this.onDispose.push(() => {
if (handled) return;
events.off(eventKey, onceListener);
});
}
stopListening() {
for (const unsubscribe of this.onDispose) unsubscribe();
this.onDispose.length = 0;
this.unsubscribeByKey.forEach((unsubscribers) => {
for (const unsubscribe of unsubscribers) unsubscribe();
});
this.unsubscribeByKey.clear();
}
};
//#endregion
//#region src/graph-explorer/diagram/geometry.ts
var Vector = {
equals(a, b) {
return a.x === b.x && a.y === b.y;
},
length({ x, y }) {
return Math.sqrt(x * x + y * y);
},
normalize({ x, y }) {
if (x === 0 && y === 0) return {
x,
y
};
const inverseLength = 1 / Math.sqrt(x * x + y * y);
return {
x: x * inverseLength,
y: y * inverseLength
};
},
dot({ x: x1, y: y1 }, { x: x2, y: y2 }) {
return x1 * x2 + y1 * y2;
},
cross2D({ x: x1, y: y1 }, { x: x2, y: y2 }) {
return x1 * y2 - y1 * x2;
}
};
var Rect = { center({ x, y, width, height }) {
return {
x: x + width / 2,
y: y + height / 2
};
} };
function boundsOf(element) {
const { x, y } = element.position;
const { width, height } = element.size;
return {
x,
y,
width,
height
};
}
function intersectRayFromRectangleCenter(sourceRect, rayTarget) {
const isTargetInsideRect = sourceRect.width === 0 || sourceRect.height === 0 || rayTarget.x > sourceRect.x && rayTarget.x < sourceRect.x + sourceRect.width && rayTarget.y > sourceRect.y && rayTarget.y < sourceRect.y + sourceRect.height;
const halfWidth = sourceRect.width / 2;
const halfHeight = sourceRect.height / 2;
const center = {
x: sourceRect.x + halfWidth,
y: sourceRect.y + halfHeight
};
if (isTargetInsideRect) return center;
const direction = Vector.normalize({
x: rayTarget.x - center.x,
y: rayTarget.y - center.y
});
const rightDirection = {
x: Math.abs(direction.x),
y: direction.y
};
if (Vector.cross2D({
x: halfWidth,
y: -halfHeight
}, rightDirection) > 0 && Vector.cross2D({
x: halfWidth,
y: halfHeight
}, rightDirection) < 0) return {
x: center.x + halfWidth * Math.sign(direction.x),
y: center.y + halfWidth * direction.y / Math.abs(direction.x)
};
else return {
x: center.x + halfHeight * direction.x / Math.abs(direction.y),
y: center.y + halfHeight * Math.sign(direction.y)
};
}
function isPolylineEqual(left, right) {
if (left === right) return true;
if (left.length !== right.length) return false;
for (let i = 0; i < left.length; i++) {
const a = left[i];
const b = right[i];
if (!(a.x === b.x && a.y === b.y)) return false;
}
return true;
}
function computePolyline(source, target, vertices) {
const sourceRect = boundsOf(source);
const targetRect = boundsOf(target);
const startPoint = intersectRayFromRectangleCenter(sourceRect, vertices.length > 0 ? vertices[0] : Rect.center(targetRect));
const endPoint = intersectRayFromRectangleCenter(targetRect, vertices.length > 0 ? vertices[vertices.length - 1] : Rect.center(sourceRect));
return [
startPoint,
...vertices,
endPoint
];
}
function computePolylineLength(polyline) {
let previous;
return polyline.reduce((acc, point) => {
const segmentLength = previous ? Vector.length({
x: point.x - previous.x,
y: point.y - previous.y
}) : 0;
previous = point;
return acc + segmentLength;
}, 0);
}
function getPointAlongPolyline(polyline, offset) {
if (polyline.length === 0) throw new Error("Cannot compute a point for empty polyline");
if (offset < 0) return polyline[0];
let currentOffset = 0;
for (let i = 1; i < polyline.length; i++) {
const previous = polyline[i - 1];
const point = polyline[i];
const segment = {
x: point.x - previous.x,
y: point.y - previous.y
};
const segmentLength = Vector.length(segment);
const newOffset = currentOffset + segmentLength;
if (offset < newOffset) {
const leftover = (offset - currentOffset) / segmentLength;
return {
x: previous.x + leftover * segment.x,
y: previous.y + leftover * segment.y
};
} else currentOffset = newOffset;
}
return polyline[polyline.length - 1];
}
function findNearestSegmentIndex(polyline, location) {
let minDistance = Infinity;
let foundIndex = 0;
for (let i = 0; i < polyline.length - 1; i++) {
const pivot = polyline[i];
const next = polyline[i + 1];
const target = {
x: location.x - pivot.x,
y: location.y - pivot.y
};
const segment = {
x: next.x - pivot.x,
y: next.y - pivot.y
};
const segmentLength = Vector.length(segment);
const projectionToSegment = Vector.dot(target, segment) / segmentLength;
if (projectionToSegment < 0 || projectionToSegment > segmentLength) continue;
const distanceToSegment = Math.abs(Vector.cross2D(target, segment)) / segmentLength;
if (distanceToSegment < minDistance) {
minDistance = distanceToSegment;
foundIndex = i;
}
}
return foundIndex;
}
function findElementAtPoint(elements, point) {
for (let i = elements.length - 1; i >= 0; i--) {
const element = elements[i];
const { x, y, width, height } = boundsOf(element);
if (element.temporary) continue;
if (point.x >= x && point.x <= x + width && point.y >= y && point.y <= y + height) return element;
}
}
function computeGrouping(elements) {
const grouping = /* @__PURE__ */ new Map();
for (const element of elements) {
const group = element.group;
if (typeof group === "string") {
let children = grouping.get(group);
if (!children) {
children = [];
grouping.set(group, children);
}
children.push(element);
}
}
return grouping;
}
//#endregion
//#region src/graph-explorer/diagram/elements.ts
var LinkDirection = /* @__PURE__ */ function(LinkDirection) {
LinkDirection["in"] = "in";
LinkDirection["out"] = "out";
return LinkDirection;
}({});
var Element$1 = class {
constructor(props) {
this.source = new EventSource();
this.events = this.source;
this.links = [];
const { id, data, position = {
x: 0,
y: 0
}, size = {
width: 0,
height: 0
}, expanded = false, group, elementState, temporary = false } = props;
this.id = id;
this._data = data;
this._position = position;
this._size = size;
this._expanded = expanded;
this._group = group;
this._elementState = elementState;
this._temporary = temporary;
}
get iri() {
return this._data.id;
}
get data() {
return this._data;
}
setData(value) {
const previous = this._data;
if (previous === value) return;
this._data = value;
this.source.trigger("changeData", {
source: this,
previous
});
updateLinksToReferByNewIri(this, previous.id, value.id);
}
get position() {
return this._position;
}
setPosition(value) {
const previous = this._position;
if (previous.x === value.x && previous.y === value.y) return;
this._position = value;
this.source.trigger("changePosition", {
source: this,
previous
});
}
get size() {
return this._size;
}
setSize(value) {
const previous = this._size;
if (previous.width === value.width && previous.height === value.height) return;
this._size = value;
this.source.trigger("changeSize", {
source: this,
previous
});
}
get isExpanded() {
return this._expanded;
}
setExpanded(value) {
const previous = this._expanded;
if (previous === value) return;
this._expanded = value;
this.source.trigger("changeExpanded", {
source: this,
previous
});
}
get group() {
return this._group;
}
setGroup(value) {
const previous = this._group;
if (previous === value) return;
this._group = value;
this.source.trigger("changeGroup", {
source: this,
previous
});
}
get elementState() {
return this._elementState;
}
setElementState(value) {
const previous = this._elementState;
if (previous === value) return;
this._elementState = value;
this.source.trigger("changeElementState", {
source: this,
previous
});
}
get temporary() {
return this._temporary;
}
focus() {
this.source.trigger("requestedFocus", { source: this });
}
requestGroupContent() {
this.source.trigger("requestedGroupContent", { source: this });
}
addToFilter(linkType, direction) {
this.source.trigger("requestedAddToFilter", {
source: this,
linkType,
direction
});
}
redraw() {
this.source.trigger("requestedRedraw", { source: this });
}
};
function updateLinksToReferByNewIri(element, oldIri, newIri) {
if (oldIri === newIri) return;
for (const link of element.links) {
let data = link.data;
if (data.sourceId === oldIri) data = {
...data,
sourceId: newIri
};
if (data.targetId === oldIri) data = {
...data,
targetId: newIri
};
link.setData(data);
}
}
var FatClassModel = class {
constructor(props) {
this.source = new EventSource();
this.events = this.source;
const { id, label = [], count } = props;
this.id = id;
this._label = label;
this._count = count;
}
get label() {
return this._label;
}
setLabel(value) {
const previous = this._label;
if (previous === value) return;
this._label = value;
this.source.trigger("changeLabel", {
source: this,
previous
});
}
get count() {
return this._count;
}
setCount(value) {
const previous = this._count;
if (previous === value) return;
this._count = value;
this.source.trigger("changeCount", {
source: this,
previous
});
}
};
var RichProperty = class {
constructor(props) {
this.source = new EventSource();
this.events = this.source;
const { id, label = [] } = props;
this.id = id;
this._label = label;
}
get label() {
return this._label;
}
setLabel(value) {
const previous = this._label;
if (previous === value) return;
this._label = value;
this.source.trigger("changeLabel", {
source: this,
previous
});
}
};
var Link = class {
constructor(props) {
this.source = new EventSource();
this.events = this.source;
const { id = GenerateID.forLink(), typeId, sourceId, targetId, data, vertices = [], linkState } = props;
this.id = id;
this._typeId = typeId;
this._sourceId = sourceId;
this._targetId = targetId;
this._data = data;
this._vertices = vertices;
this._linkState = linkState;
}
get typeId() {
return this._typeId;
}
get sourceId() {
return this._sourceId;
}
get targetId() {
return this._targetId;
}
get data() {
return this._data;
}
setData(value) {
const previous = this._data;
if (previous === value) return;
this._data = value;
this._typeId = value.linkTypeId;
this.source.trigger("changeData", {
source: this,
previous
});
}
get labelBounds() {
return this._labelBounds;
}
setLabelBounds(value) {
const previous = this._labelBounds;
if (previous === value) return;
this._labelBounds = value;
this.source.trigger("changeLabelBounds", {
source: this,
previous
});
}
get layoutOnly() {
return this._layoutOnly;
}
setLayoutOnly(value) {
const previous = this._layoutOnly;
if (previous === value) return;
this._layoutOnly = value;
this.source.trigger("changeLayoutOnly", {
source: this,
previous
});
}
get vertices() {
return this._vertices;
}
setVertices(value) {
const previous = this._vertices;
if (isPolylineEqual(this._vertices, value)) return;
this._vertices = value;
this.source.trigger("changeVertices", {
source: this,
previous
});
}
get linkState() {
return this._linkState;
}
setLinkState(value) {
const previous = this._linkState;
if (previous === value) return;
this._linkState = value;
this.source.trigger("changeLinkState", {
source: this,
previous
});
}
};
function linkMarkerKey(linkTypeIndex, startMarker) {
return `graph-explorer-${startMarker ? "mstart" : "mend"}-${linkTypeIndex}`;
}
/**
* Properties:
* visible: boolean
* showLabel: boolean
* isNew?: boolean
* label?: { values: LocalizedString[] }
*/
var FatLinkType = class {
constructor(props) {
this.source = new EventSource();
this.events = this.source;
this._isNew = false;
this._visible = true;
this._showLabel = true;
const { id, index, label = [] } = props;
this.id = id;
this._index = index;
this._label = label;
}
get index() {
return this._index;
}
setIndex(value) {
if (typeof this._index === "number") throw new Error("Cannot set index for link type more than once.");
this._index = value;
}
get label() {
return this._label;
}
setLabel(value) {
const previous = this._label;
if (previous === value) return;
this._label = value;
this.source.trigger("changeLabel", {
source: this,
previous
});
}
get visible() {
return this._visible;
}
get showLabel() {
return this._showLabel;
}
setVisibility(params) {
if (this._visible === params.visible && this._showLabel === params.showLabel) return;
const preventLoading = Boolean(params.preventLoading) || this._visible === params.visible;
this._visible = params.visible;
this._showLabel = params.showLabel;
this.source.trigger("changeVisibility", {
source: this,
preventLoading
});
}
get isNew() {
return this._isNew;
}
setIsNew(value) {
const previous = this._isNew;
if (previous === value) return;
this._isNew = value;
this.source.trigger("changeIsNew", {
source: this,
previous
});
}
};
var LinkVertex = class {
constructor(link, vertexIndex) {
this.link = link;
this.vertexIndex = vertexIndex;
}
createAt(location) {
const vertices = [...this.link.vertices];
vertices.splice(this.vertexIndex, 0, location);
this.link.setVertices(vertices);
}
moveTo(location) {
const vertices = [...this.link.vertices];
vertices.splice(this.vertexIndex, 1, location);
this.link.setVertices(vertices);
}
remove() {
const vertices = [...this.link.vertices];
vertices.splice(this.vertexIndex, 1);
this.link.setVertices(vertices);
}
};
//#endregion
//#region src/graph-explorer/viewUtils/async.ts
var BatchingScheduler = class {
constructor(waitingTime = 0) {
this.waitingTime = waitingTime;
this.useAnimationFrame = waitingTime === 0;
this.runSynchronously = this.runSynchronously.bind(this);
}
schedule() {
if (typeof this.scheduled === "undefined") if (this.useAnimationFrame) this.scheduled = requestAnimationFrame(this.runSynchronously);
else this.scheduled = Number(setTimeout(this.runSynchronously, this.waitingTime));
}
runSynchronously() {
if (this.cancelScheduledTimeout()) this.run();
}
dispose() {
this.cancelScheduledTimeout();
}
cancelScheduledTimeout() {
if (typeof this.scheduled !== "undefined") {
if (this.useAnimationFrame) cancelAnimationFrame(this.scheduled);
else clearTimeout(this.scheduled);
this.scheduled = void 0;
return true;
}
return false;
}
};
var BufferingQueue = class extends BatchingScheduler {
constructor(onFetch, waitingTime = 200) {
super(waitingTime);
this.onFetch = onFetch;
this.fetchingQueue = Object.create(null);
}
push(key) {
this.fetchingQueue[key] = true;
this.schedule();
}
clear() {
this.fetchingQueue = Object.create(null);
}
run() {
const { fetchingQueue, onFetch } = this;
this.fetchingQueue = Object.create(null);
onFetch(Object.keys(fetchingQueue));
}
};
var Debouncer = class extends BatchingScheduler {
call(callback) {
this.callback = callback;
this.schedule();
}
run() {
const callback = this.callback;
callback();
}
};
var Cancellation = class {
static {
this.NEVER_SIGNAL = {
aborted: false,
addEventListener: () => {},
removeEventListener: () => {}
};
}
constructor() {
this.source = new EventSource();
this.aborted = false;
this.signal = new class {
constructor(parent) {
this.parent = parent;
}
get aborted() {
return this.parent.aborted;
}
addEventListener(event, handler) {
if (event !== "abort") return;
if (this.parent.source) this.parent.source.on("abort", handler);
else handler();
}
removeEventListener(event, handler) {
if (event !== "abort") return;
if (this.parent.source) this.parent.source.off("abort", handler);
}
}(this);
}
abort() {
if (this.aborted) return;
this.aborted = true;
this.source.trigger("abort", void 0);
this.source = void 0;
}
};
var CancellationToken = {
throwIfAborted(ct) {
if (ct.aborted) throw new CancelledError();
},
mapCancelledToNull(ct, promise) {
const onResolve = (value) => {
if (ct.aborted) return null;
return value;
};
const onReject = (err) => {
if (ct.aborted) return null;
return Promise.reject(err);
};
return promise.then(onResolve, onReject);
}
};
var CancelledError = class CancelledError extends Error {
constructor(message = "Operation was cancelled") {
super(message);
this.name = CancelledError.name;
Object.setPrototypeOf(this, CancelledError.prototype);
}
};
function delay(timeout) {
return new Promise((resolve) => setTimeout(resolve, timeout));
}
function animateInterval(duration, onProgress, cancellation) {
return new Promise((resolve) => {
let animationFrameId;
let start;
const animate = (time) => {
if (cancellation && cancellation.signal.aborted) return;
start = start || time;
let timePassed = time - start;
if (timePassed > duration) timePassed = duration;
onProgress(timePassed / duration);
if (timePassed < duration) animationFrameId = requestAnimationFrame(animate);
else resolve();
};
cancellation.signal.addEventListener("abort", () => {
cancelAnimationFrame(animationFrameId);
resolve();
});
animationFrameId = requestAnimationFrame(animate);
});
}
function easeInOutBezier(t) {
if (t < 0) return 0;
if (t > 1) return 1;
return t * t * (3 - 2 * t);
}
//#endregion
//#region src/graph-explorer/viewUtils/keyedObserver.ts
var KeyedObserver = class {
constructor(subscribe) {
this.subscribe = subscribe;
this.observedKeys = /* @__PURE__ */ new Map();
}
observe(keys) {
if (keys.length === 0 && this.observedKeys.size === 0) return;
const newObservedKeys = /* @__PURE__ */ new Map();
for (const key of keys) {
if (newObservedKeys.has(key)) continue;
let unsubscribe = this.observedKeys.get(key);
if (!unsubscribe) unsubscribe = this.subscribe(key);
newObservedKeys.set(key, unsubscribe);
}
this.observedKeys.forEach((unsubscribe, key) => {
if (!newObservedKeys.has(key)) unsubscribe();
});
this.observedKeys = newObservedKeys;
}
stopListening() {
this.observe([]);
}
};
function observeElementTypes(model, event, listener) {
return new KeyedObserver((key) => {
const type = model.getClass(key);
if (type) {
type.events.on(event, listener);
return () => type.events.off(event, listener);
}
});
}
function observeProperties(model, event, listener) {
return new KeyedObserver((key) => {
const property = model.getProperty(key);
if (property) {
property.events.on(event, listener);
return () => property.events.off(event, listener);
}
});
}
function observeLinkTypes(model, event, listener) {
return new KeyedObserver((key) => {
const type = model.createLinkType(key);
if (type) {
type.events.on(event, listener);
return () => type.events.off(event, listener);
}
});
}
//#endregion
//#region src/graph-explorer/diagram/history.ts
function createAction(title, action) {
return {
title,
invoke: action
};
}
function effectAction(title, body) {
const perform = {
title,
invoke: () => {
body();
return skip;
}
};
const skip = {
title: "Skipped effect: " + title,
invoke: () => perform
};
return perform;
}
var Command = {
create: createAction,
effect: effectAction
};
var NonRememberingHistory = class {
constructor() {
this.source = new EventSource();
this.events = this.source;
this.undoStack = [];
this.redoStack = [];
this.storeBatch = () => {};
this.discardBatch = () => {};
}
reset() {
this.source.trigger("historyChanged", { hasChanges: false });
}
undo() {
throw new Error("Undo is unsupported");
}
redo() {
throw new Error("Redo is unsupported");
}
execute(command) {
command.invoke();
this.source.trigger("historyChanged", { hasChanges: true });
}
registerToUndo(_command) {
this.source.trigger("historyChanged", { hasChanges: true });
}
startBatch(_title) {
return {
history: this,
store: this.storeBatch,
discard: this.discardBatch
};
}
};
//#endregion
//#region src/graph-explorer/diagram/commands.ts
var RestoreGeometry = class RestoreGeometry {
constructor(elementState, linkState) {
this.elementState = elementState;
this.linkState = linkState;
this.title = "Move elements and links";
}
static capture(model) {
return RestoreGeometry.captureElementsAndLinks(model.elements, model.links);
}
static captureElementsAndLinks(elements, links) {
return new RestoreGeometry(elements.map((element) => ({
element,
position: element.position
})), links.map((link) => ({
link,
vertices: link.vertices
})));
}
hasChanges() {
return this.elementState.length > 0 || this.linkState.length > 0;
}
filterOutUnchanged() {
return new RestoreGeometry(this.elementState.filter(({ element, position }) => !Vector.equals(element.position, position)), this.linkState.filter(({ link, vertices }) => !isPolylineEqual(link.vertices, vertices)));
}
invoke() {
const previous = RestoreGeometry.captureElementsAndLinks(this.elementState.map((state) => state.element), this.linkState.map((state) => state.link));
for (const { element, position } of [...this.elementState].reverse()) element.setPosition(position);
for (const { link, vertices } of this.linkState) link.setVertices(vertices);
return previous;
}
};
function restoreCapturedLinkGeometry(link) {
const vertices = link.vertices;
return Command.create("Change link vertices", () => {
const capturedInverse = restoreCapturedLinkGeometry(link);
link.setVertices(vertices);
return capturedInverse;
});
}
function setElementExpanded(element, expanded) {
const title = expanded ? "Expand element" : "Collapse element";
return Command.create(title, () => {
element.setExpanded(expanded);
return setElementExpanded(element, !expanded);
});
}
function changeLinkTypeVisibility(params) {
const { linkType, visible, showLabel, preventLoading } = params;
return Command.create("Change link type visibility", () => {
const previousVisible = linkType.visible;
const previousShowLabel = linkType.showLabel;
linkType.setVisibility({
visible,
showLabel,
preventLoading
});
return changeLinkTypeVisibility({
linkType,
visible: previousVisible,
showLabel: previousShowLabel,
preventLoading
});
});
}
function setElementData(model, target, data) {
const elements = model.elements.filter((el) => el.iri === target);
const previous = elements.length > 0 ? elements[0].data : void 0;
return Command.create("Set element data", () => {
for (const element of model.elements.filter((el) => el.iri === target)) element.setData(data);
return setElementData(model, data.id, previous);
});
}
function setLinkData(model, oldData, newData) {
if (!sameLink(oldData, newData)) throw new Error("Cannot change typeId, sourceId or targetId when changing link data");
return Command.create("Set link data", () => {
for (const link of model.links) if (sameLink(link.data, oldData)) link.setData(newData);
return setLinkData(model, newData, oldData);
});
}
//#endregion
//#region images/icons/class.svg
var class_default = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCAyNiAyNiI+CiAgICA8dGV4dCB4PSIzIiB5PSIyMiIgY2xhc3M9InRleHQiIGZpbGw9IiNlYWFjNzciCiAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLCAnSGVsdmV0aWNhJywgJ0FyaWFsJywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAyNnB4OyBmb250LXdlaWdodDogYm9sZDsiPgogICAgICAgIEMKICAgIDwvdGV4dD4KPC9zdmc+Cg==";
//#endregion
//#region images/icons/objectProperty.svg
var objectProperty_default = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCAyNiAyNiI+CiAgICA8dGV4dCB4PSIwIiB5PSIyMCIgY2xhc3M9InRleHQiIGZpbGw9IiMzNGM3ZjMiCiAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLCAnSGVsdmV0aWNhJywgJ0FyaWFsJywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxOHB4OyBmb250LXdlaWdodDogYm9sZDsiPgogICAgICAgIE9QCiAgICA8L3RleHQ+Cjwvc3ZnPgo=";
//#endregion
//#region images/icons/datatypeProperty.svg
var datatypeProperty_default = "data:image/svg+xml;base64,PHN2ZyB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCAyNiAyNiI+CiAgICA8dGV4dCB4PSIwIiB5PSIyMCIgY2xhc3M9InRleHQiIGZpbGw9IiMzNGM3ZjMiCiAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5OiAnSGVsdmV0aWNhIE5ldWUnLCAnSGVsdmV0aWNhJywgJ0FyaWFsJywgc2Fucy1zZXJpZjsgZm9udC1zaXplOiAxOHB4OyBmb250LXdlaWdodDogYm9sZDsiPgogICAgICAgIERQCiAgICA8L3RleHQ+Cjwvc3ZnPgo=";
//#endregion
//#region images/icons/person.svg
var person_default = "data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20x='0px'%20y='0px'%20viewBox='0%200%20350%20350'%3e%3cpath%20fill='%23eb7777'%20d='M175,171.173c38.914,0,70.463-38.318,70.463-85.586C245.463,38.318,235.105,0,175,0s-70.465,38.318-70.465,85.587%20C104.535,132.855,136.084,171.173,175,171.173z'/%3e%3cpath%20fill='%23eb7777'%20d='M41.909,301.853C41.897,298.971,41.885,301.041,41.909,301.853L41.909,301.853z'/%3e%3cpath%20fill='%23eb7777'%20d='M308.085,304.104C308.123,303.315,308.098,298.63,308.085,304.104L308.085,304.104z'/%3e%3cpath%20fill='%23eb7777'%20d='M307.935,298.397c-1.305-82.342-12.059-105.805-94.352-120.657c0,0-11.584,14.761-38.584,14.761%20s-38.586-14.761-38.586-14.761c-81.395,14.69-92.803,37.805-94.303,117.982c-0.123,6.547-0.18,6.891-0.202,6.131%20c0.005,1.424,0.011,4.058,0.011,8.651c0,0,19.592,39.496,133.08,39.496c113.486,0,133.08-39.496,133.08-39.496%20c0-2.951,0.002-5.003,0.005-6.399C308.062,304.575,308.018,303.664,307.935,298.397z'/%3e%3c/svg%3e";
//#endregion
//#region images/icons/country.svg
var country_default = "data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20x='0px'%20y='0px'%20viewBox='0%200%20512%20512'%3e%3cpath%20fill='%2377ca98'%20d='M472.928,34.752c-4.416-3.008-10.016-3.552-14.944-1.6c-1.024,0.416-106.88,42.048-195.168,0.384%20C186.72-2.432,102.912,14.4,64,25.76V16c0-8.832-7.168-16-16-16c-8.832,0-16,7.168-16,16v32v256v192c0,8.832,7.168,16,16,16%20c8.832,0,16-7.168,16-16V315.328c28.384-9.184,112.608-31.136,185.184,3.136c34.592,16.352,70.784,21.792,103.648,21.792%20c63.2,0,114.016-20.128,117.184-21.408c6.016-2.464,9.984-8.32,9.984-14.848V48C480,42.688,477.344,37.728,472.928,34.752z'/%3e%3c/svg%3e";
//#endregion
//#region images/icons/organization.svg
var organization_default = "data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20x='0px'%20y='0px'%20viewBox='0%200%20512%20512'%3e%3cpath%20fill='%2377ca98'%20d='M155,0H15C6.716,0,0,6.716,0,15v35h170V15C170,6.716,163.284,0,155,0z'/%3e%3cpath%20fill='%2377ca98'%20d='M504.464,142.989c-4.662-2.674-10.397-2.651-15.036,0.063L341,229.852V156c0-5.374-2.875-10.337-7.536-13.011%20c-4.662-2.674-10.397-2.651-15.036,0.063L170,229.852V80H0v417c0,8.284,6.716,15,15,15h482c8.284,0,15-6.716,15-15V156%20C512,150.626,509.125,145.663,504.464,142.989z%20M230,406.5c0,8.284-6.716,15-15,15h-60c-8.284,0-15-6.716-15-15v-60%20c0-8.284,6.716-15,15-15h60c8.284,0,15,6.716,15,15V406.5z%20M401,406.5c0,8.284-6.716,15-15,15h-60c-8.284,0-15-6.716-15-15v-60%20c0-8.284,6.716-15,15-15h60c8.284,0,15,6.716,15,15V406.5z'/%3e%3c/svg%3e";
//#endregion
//#region images/icons/location.svg
var location_default = "data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20x='0px'%20y='0px'%20width='47.825px'%20height='47.825px'%20viewBox='0%200%2047.825%2047.825'%3e%3cpath%20fill='%23bebc71'%20d='M36.439,12.646c0-6.919-5.608-12.527-12.528-12.527S11.384,5.727,11.384,12.646c0,9.913,12.527,24.582,12.527,24.582%20S36.439,22.508,36.439,12.646z%20M17.733,11.898c0-3.413,2.767-6.179,6.179-6.179s6.179,2.766,6.179,6.179%20c0,3.413-2.767,6.179-6.179,6.179S17.733,15.311,17.733,11.898z'/%3e%3ccircle%20fill='%23bebc71'%20cx='23.911'%20cy='11.898'%20r='3.038'/%3e%3cpath%20fill='%23bebc71'%20d='M30.994,32.87c-1.021,1.476-1.979,2.761-2.777,3.793c7.916,0.476,13.104,2.185,15.034,3.456%20c-2.261,1.491-8.979,3.587-19.338,3.587c-10.358,0-17.077-2.097-19.338-3.587c1.93-1.271,7.114-2.979,15.022-3.455%20c-0.8-1.032-1.759-2.316-2.781-3.792C7.075,33.831,0,36.713,0,40.118c0,4.19,10.707,7.588,23.913,7.588%20c13.207,0,23.912-3.396,23.912-7.588C47.827,36.711,40.744,33.828,30.994,32.87z'/%3e%3c/svg%3e";
//#endregion
//#region images/icons/event.svg
var event_default = "data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20x='0px'%20y='0px'%20width='97.473px'%20height='97.473px'%20viewBox='0%200%2097.473%2097.473'%3e%3crect%20fill='%23b4b1fb'%20x='41.569'%20y='47.753'%20width='14.333'%20height='14.333'/%3e%3crect%20fill='%23b4b1fb'%20x='41.569'%20y='67.754'%20width='14.333'%20height='14.332'/%3e%3crect%20fill='%23b4b1fb'%20x='18.569'%20y='67.754'%20width='14.333'%20height='14.332'/%3e%3crect%20fill='%23b4b1fb'%20x='18.569'%20y='47.753'%20width='14.333'%20height='14.333'/%3e%3cpath%20fill='%23b4b1fb'%20d='M95.472,14.466H84.427V2.701c0-1.104-0.896-2-2-2H65.811c-1.104,0-2,0.896-2,2v11.765H33.662V2.701c0-1.104-0.896-2-2-2%20H15.045c-1.104,0-2,0.896-2,2v11.765H2c-1.104,0-2,0.896-2,2v78.305c0,1.104,0.896,2,2,2h93.473c1.104,0,2-0.896,2-2V16.466%20C97.472,15.362,96.576,14.466,95.472,14.466z%20M69.645,8.037c0-0.828,0.672-1.5,1.5-1.5h5.947c0.829,0,1.5,0.672,1.5,1.5v20.34%20c0,0.828-0.671,1.5-1.5,1.5h-5.947c-0.828,0-1.5-0.672-1.5-1.5V8.037z%20M18.88,8.037c0-0.828,0.671-1.5,1.5-1.5h5.947%20c0.829,0,1.5,0.672,1.5,1.5v20.34c0,0.828-0.671,1.5-1.5,1.5H20.38c-0.829,0-1.5-0.672-1.5-1.5V8.037z%20M87.068,84.902%20c0,0.828-0.67,1.5-1.5,1.5H11.903c-0.829,0-1.5-0.672-1.5-1.5v-40c0-0.828,0.671-1.5,1.5-1.5h73.666c0.83,0,1.5,0.672,1.5,1.5%20V84.902z'/%3e%3crect%20fill='%23b4b1fb'%20x='64.568'%20y='47.753'%20width='14.334'%20height='14.333'/%3e%3crect%20fill='%23b4b1fb'%20x='64.568'%20y='67.754'%20width='14.334'%20height='14.332'/%3e%3c/svg%3e";
//#endregion
//#region images/icons/object.svg
var object_default = "data:image/svg+xml,%3csvg%20version='1.1'%20xmlns='http://www.w3.org/2000/svg'%20x='0px'%20y='0px'%20width='50px'%20height='50px'%20viewBox='0%200%2050%2050'%3e%3cpath%20fill='%2353ccb2'%20d='M26.12,14.488c0.879-1.468,2.27-3.569,4.027-5.585h0.002c1.488-1.711,4.402-3.907,4.402-3.907%20c0.438-0.331,0.391-0.792-0.107-1.024l-3.522-1.646c-0.498-0.233-1.175-0.063-1.505,0.376c0,0-2.605,3.737-5.671,11.692%20c-9.48-4.288-18.491,1.718-18.491,12.162c0,10.909,8.393,27.48,19.743,22.478c11.954,5.173,19.749-11.568,19.749-22.478%20C44.747,16.022,36.647,9.846,26.12,14.488z'/%3e%3cpath%20fill='%2353ccb2'%20d='M23.997,10.955c0.548-0.046,1.032-0.531,1.076-1.078c0,0,0.352-4.266-2.621-7.242c-2.982-2.977-7.24-2.622-7.24-2.622%20c-0.548,0.046-1.032,0.531-1.077,1.078c0,0-0.358,4.268,2.619,7.245C19.729,11.31,23.997,10.955,23.997,10.955z'/%3e%3c/svg%3e";
//#endregion
//#region src/graph-explorer/customization/defaultTypeStyles.ts
var DefaultTypeStyleBundle = (types) => {
if (types.indexOf("http://www.w3.org/2002/07/owl#Class") !== -1 || types.indexOf("http://www.w3.org/2000/01/rdf-schema#Class") !== -1) return {
color: "#eaac77",
icon: class_default
};
else if (types.indexOf("http://www.w3.org/2002/07/owl#ObjectProperty") !== -1) return {
color: "#34c7f3",
icon: objectProperty_default
};
else if (types.indexOf("http://www.w3.org/2002/07/owl#DatatypeProperty") !== -1) return {
color: "#34c7f3",
icon: datatypeProperty_default
};
else if (types.indexOf("http://xmlns.com/foaf/0.1/Person") !== -1 || types.indexOf("http://www.wikidata.org/entity/Q5") !== -1) return {
color: "#eb7777",
icon: person_default
};
else if (types.indexOf("http://www.wikidata.org/entity/Q6256") !== -1) return {
color: "#77ca98",
icon: country_default
};
else if (types.indexOf("http://schema.org/Organization") !== -1 || types.indexOf("http://dbpedia.org/ontology/Organisation") !== -1 || types.indexOf("http://xmlns.com/foaf/0.1/Organization") !== -1 || types.indexOf("http://www.wikidata.org/entity/Q43229") !== -1) return {
color: "#77ca98",
icon: organization_default
};
else if (types.indexOf("http://www.wikidata.org/entity/Q618123") !== -1) return {
color: "#bebc71",
icon: location_default
};
else if (types.indexOf("http://www.wikidata.org/entity/Q1190554") !== -1) return {
color: "#b4b1fb",
icon: event_default
};
else if (types.indexOf("http://www.wikidata.org/entity/Q488383") !== -1) return {
color: "#53ccb2",
icon: object_default
};
else return;
};
//#endregion
//#region src/graph-explorer/customization/defaultLinkStyles.ts
var LINK_SHOW_IRI = { renderLink: (link) => ({ properties: [{
position: .5,
attrs: { text: {
text: [{
value: link.typeId,
language: ""
}],
fill: "gray",
"font-size": 12,
"font-weight": "lighter"
} }
}] }) };
var LINK_SUB_CLASS_OF = {
markerTarget: {
fill: "#f8a485",
stroke: "#cf8e76"
},
renderLink: () => ({ connection: {
stroke: "#f8a485",
"stroke-width": 2
} })
};
var LINK_DOMAIN = {
markerTarget: {
fill: "#34c7f3",
stroke: "#38b5db"
},
renderLink: () => ({ connection: {
stroke: "#34c7f3",
"stroke-width": 2
} })
};
var LINK_RANGE = {
markerTarget: {
fill: "#34c7f3",
stroke: "#38b5db"
},
renderLink: () => ({ connection: {
stroke: "#34c7f3",
"stroke-width": 2
} })
};
var LINK_TYPE_OF = {
markerTarget: {
fill: "#8cd965",
stroke: "#5b9a3b"
},
renderLink: () => ({ connection: {
stroke: "#8cd965",
"stroke-width": 2
} })
};
var DefaultLinkTemplateBundle = (type) => {
if (type === "http://www.w3.org/2000/01/rdf-schema#subClassOf") return LINK_SUB_CLASS_OF;
else if (type === "http://www.w3.org/2000/01/rdf-schema#domain") return LINK_DOMAIN;
else if (type === "http://www.w3.org/2000/01/rdf-schema#range") return LINK_RANGE;
else if (type === "http://www.w3.org/1999/02/22-rdf-syntax-ns#type") return LINK_TYPE_OF;
else if (type === "http://graph-explorer.org/NewLink") return { markerTarget: { fill: "none" } };
else return;
};
//#endregion
//#region src/graph-explorer/data/sparql/sparqlModels.ts
function isRdfBlank(e) {
return e && e.type === "bnode";
}
function isRdfIri(e) {
return e && e.type === "uri";
}
function isRdfLiteral(e) {
return e && e.type === "literal";
}
function isBlankBinding(binding) {
const blank = binding;
return blank.blankTrgProp !== void 0 || blank.blankTrg !== void 0 || blank.blankSrcProp !== void 0 || blank.blankSrc !== void 0;
}
var ENCODED_PREFIX = "sparql-blank:";
var BLANK_NODE_QUERY_PARAMETERS = "?blankTrgProp ?blankTrg ?blankSrc ?blankSrcProp ?listHead";
var BLANK_NODE_QUERY = `
OPTIONAL {
FILTER (ISBLANK(?inst)).
{
?inst ?blankTrgProp ?blankTrg.
?blankSrc ?blankSrcProp ?inst.
FILTER NOT EXISTS { ?inst rdf:first _:smth1 }.
BIND("blankNode" as ?blankType)
} UNION {
?inst rdf:rest*/rdf:first ?blankTrg.
?blankSrc ?blankSrcProp ?inst.
_:smth2 rdf:first ?blankTrg.
BIND(?blankSrcProp as ?blankTrgProp)
BIND("listHead" as ?blankType)
FILTER NOT EXISTS { _:smth3 rdf:rest ?inst }.
} UNION {
?listHead rdf:rest* ?inst.
FILTER NOT EXISTS { _:smth4 rdf:rest ?listHead }.
?listHead rdf:rest*/rdf:first ?blankTrg.
?blankSrc ?blankSrcProp ?listHead.
_:smth5 rdf:first ?blankTrg.
BIND(?blankSrcProp as ?blankTrgProp)
BIND("listHead" as ?blankType)
}
}
`;
function isEncodedBlank(id) {
return id.startsWith(ENCODED_PREFIX);
}
var QueryExecutor = class {
constructor(queryFunction) {
this.queryFunction = queryFunction;
this.queryDictionary = {};
}
executeQuery(query) {
const execution = this.queryDictionary[query];
if (execution) return execution;
else {
this.queryDictionary[query] = this.queryFunction(query).then((response) => {
delete this.queryDictionary[query];
return response;
});
return this.queryDictionary[query];
}
}
};
function updateFilterResults(result, queryFunction, settings) {
const completeBindings = [];
const blankBindings = [];
for (const binding of result.results.bindings) if (isBlankBinding(binding)) blankBindings.push(binding);
else completeBindings.push(binding);
return processBlankBindings(blankBindings, (callBackQuery) => {
return queryFunction(callBackQuery);
}, settings).then((processedBindings) => {
result.results.bindings = completeBindings.concat(processedBindings);
return result;
});
}
function processBlankBindings(blankBindings, queryFunction, settings) {
const bindingGroupsById = {};
for (const binding of blankBindings) {
if (binding.newInst) binding.inst = binding.newInst;
if (!bindingGroupsById[binding.inst.value]) bindingGroupsById[binding.inst.value] = [];
bindingGroupsById[binding.inst.value].push(binding);
}
const relatedBlankBindnings = [];
for (const b of blankBindings) if (isRdfBlank(b.blankTrg)) relatedBlankBindnings.push([b]);
return loadRelatedBlankNodes(relatedBlankBindnings, new QueryExecutor(queryFunction), settings).then((loadedGroupsById) => {
const idsMap = getEncodedIdDictionary(loadedGroupsById);
const groups = Object.keys(bindingGroupsById).map((key) => bindingGroupsById[key]);
for (const group of groups) {
for (const blankBinding of group) {
if (!blankBinding.label) blankBinding.label = createLabelForBlankBinding(blankBinding);
const encodedId4LoadedElement = idsMap[blankBinding.blankTrg.value];
if (encodedId4LoadedElement) blankBinding.blankTrg.value = encodedId4LoadedElement;
}
updateGroupIds(group, encodeId(group));
}
return blankBindings;
});
}
function getEncodedIdDictionary(blankBindingGroups) {
const idDictionary = {};
const keys = Object.keys(blankBindingGroups);
for (const key of keys) {
idDictionary[key] = encodeId(blankBindingGroups[key]);
updateGroupIds(blankBindingGroups[key], idDictionary[key]);
}
return idDictionary;
}
function updateGroupIds(group, newId) {
for (const loadedBlankBinding of group) loadedBlankBinding.inst.value = newId;
}
function encodeId(blankBindings) {
const bindingSet = {};
for (const binding of blankBindings) {
const { inst: _inst, ...exceptInst } = binding;
const encodedBinding = JSON.stringify(exceptInst);
bindingSet[encodedBinding] = exceptInst;
}
const normalizedBindings = Object.keys(bindingSet).sort().map((key) => bindingSet[key]);
return ENCODED_PREFIX + encodeURI(JSON.stringify(normalizedBindings));
}
function decodeId(id) {
if (!isEncodedBlank(id)) return;
try {
const clearId = id.substring(13, id.length);
return JSON.parse(decodeURI(clearId)).map((binding) => {
binding.inst = {
type: "uri",
value: id
};
return binding;
});
} catch (_error) {
return;
}
}
function createLabelForBlankBinding(bn) {
if (bn.blankType.value === "listHead") return {
type: "literal",
value: "RDFList",
"xml:lang": ""
};
else return {
type: "literal",
value: bn.class ? getUriLocalName(bn.class.value) || bn.class.value : "anonymous",
"xml:lang": ""
};
}
function loadRelatedBlankNodes(blankChains, queryExecutor, settings, recursionDeep) {
recursionDeep = recursionDeep || 1;
if (recursionDeep > 3) return Promise.resolve({});
const promises = blankChains.map((chain) => ({
query: getQueryForChain(chain, settings),
chain
})).map((pair) => queryExecutor.executeQuery(pair.query).then((response) => ({
response,
chain: pair.chain
})));
return Promise.all(promises).then((results) => {
const recursionPromises = [];
const loadedBlankBindings = {};
for (const result of results) {
const bindings = result.response.results.bindings;
if (bindings.length > 0) {
const relatedBlankBindings = [];
for (const binding of bindings) if (isRdfBlank(binding.blankTrg)) relatedBlankBindings.push(result.chain.concat([binding]));
recursionPromises.push(loadRelatedBlankNodes(relatedBlankBindings, queryExecutor, settings, recursionDeep + 1).then((loadedGroupsById) => {
const idsMap = getEncodedIdDictionary(loadedGroupsById);
const mergedResults = {};
for (const binding of bindings) {
binding.label = createLabelForBlankBinding(binding);
const encodedId = idsMap[binding.blankTrg.value];
if (encodedId) binding.blankTrg.value = encodedId;
if (!mergedResults[binding.inst.value]) mergedResults[binding.inst.value] = [];
mergedResults[binding.inst.value].push(binding);
}
Object.keys(mergedResults).forEach((key) => {
const group = mergedResults[key];
const originalId = group[0].inst.value;
loadedBlankBindings[originalId] = group;
});
return true;
}));
}
}
return Promise.all(recursionPromises).then(() => {
return loadedBlankBindings;
});
});
}
function getQueryForChain(blankNodes, sparqlDataProviderSettings) {
function getQueryBlock(blankNode, index, maxIndex) {
const trustableTrgProp = index === 0 || blankNode.blankType.value !== "listHead";
const sourceId = index > 0 ? "?inst" + (index - 1) : "<" + blankNode.blankSrc.value + ">";
const sourcePropId = trustableTrgProp ? index > 0 ? "?blankTrgProp" + (index - 1) : "<" + blankNode.blankSrcProp.value + ">" : "?anyType" + index;
const instPostfix = index === maxIndex ? "" : index.toString();
const targetPropId = trustableTrgProp ? "<" + blankNode.blankTrgProp.value + ">" : "?anyType0" + index;
return `
# ======================
${sourceId} ${sourcePropId} ?blankSrc${index}.
${index === 0 && blankNode.blankType.value === "listHead" ? `
?blankSrc${index} rdf:rest*/rdf:first ?inst${instPostfix}.
` : `?blankSrc${index} ${targetPropId} ?inst${instPostfix}.`}
BIND (<${blankNode.blankTrgProp.value}> as ?blankSrcProp${index}).
FILTER (ISBLANK(?inst${instPostfix})).
{
?inst${instPostfix} ?blankTrgProp${instPostfix} ?blankTrg${instPostfix}.
BIND("blankNode" as ?blankType${instPostfix}).
FILTER NOT EXISTS { ?inst${instPostfix} rdf:first _:smth1${index} }.
} UNION {
?inst${instPostfix} rdf:rest*/rdf:first ?blankTrg${instPostfix}.
?blankSrc${index} ?blankSrcProp${index} ?inst${instPostfix}.
_:smth2${index} rdf:first ?blankTrg${instPostfix}.
BIND(?blankSrcProp${index} as ?blankTrgProp${instPostfix})
BIND("listHead" as ?blankType${instPostfix})
FILTER NOT EXISTS { _:smth3${index} rdf:rest ?inst${instPostfix} }.
}
OPTIONAL {
?inst${instPostfix} rdf:type ?class${instPostfix}.
}
`;
}
const body = blankNodes.map((bn, index) => getQueryBlock(bn, index, blankNodes.length - 1)).join("\n");
return `${sparqlDataProviderSettings.defaultPrefix}
SELECT ?inst ?class ?label ?blankTrgProp ?blankTrg ?blankType
WHERE {
${body}
}
`;
}
function elementInfo(elementIds) {
return {
head: void 0,
results: { bindings: getElementBindings(elementIds.filter((id) => isEncodedBlank(id))) }
};
}
function linksInfo(elementIds) {
return {
head: void 0,
results: { bindings: getLinkBinding(elementIds) }
};
}
function linkTypesOf(params) {
return {
head: void 0,
results: { bindings: getLinkCountBinding(params.elementId) }
};
}
function filter(params) {
const filterResponse = {
head: void 0,
results: { bindings: [] }
};
if (params.limit === 0) params.limit = 100;
if (params.elementTypeId) filterResponse.results.bindings = [];
else if (params.refElementId && params.refElementLinkId) filterResponse.results.bindings = getAllRelatedByLinkTypeElements(params.refElementId, params.refElementLinkId, params.linkDirection);
else if (params.refElementId) filterResponse.results.bindings = getAllRelatedElements(params.refElementId);
if (params.text && filterResponse.results.bindings.length !== 0) filterResponse.results.bindings = filterResponse.results.bindings.filter((be) => be.inst.value.toLowerCase().indexOf(params.text) !== -1);
return filterResponse;
}
function getElementTypes$1(elementIds) {
const bindings = [];
for (const id of elementIds) {
const blankBindings = decodeId(id);
if (blankBindings) {
for (const be of blankBindings) if (isRdfIri(be.inst) && be.class) bindings.push({
inst: be.inst,
class: be.class
});
}
}
return {
head: void 0,
results: { bindings }
};
}
function getAllRelatedByLinkTypeElements(refElementId, refElementLinkId, linkDirection) {
const blankElements = (decodeId(refElementId) || []).concat(decodeId(refElementLinkId) || []);
let bindings = [];
if (blankElements.length > 0) {
for (const be of blankElements) if (linkDirection === "in") {
if (be.inst.value === refElementId && (isRdfIri(be.blankSrc) || isRdfBlank(be.blankSrc)) && refElementLinkId === be.blankSrcProp.value) if (isRdfIri(be.blankSrc)) bindings.push({ inst: be.blankSrc });
else bindings = bindings.concat(decodeId(be.blankSrc.value) || [{ inst: be.blankSrc }]);
else if (be.blankTrg.value === refElementId && refElementLinkId === be.blankTrgProp.value) bindings.push(be);
} else if (be.inst.value === refElementId && (isRdfIri(be.blankTrg) || isRdfBlank(be.blankTrg)) && refElementLinkId === be.blankTrgProp.value) if (isRdfIri(be.blankTrg)) bindings.push({ inst: be.blankTrg });
else bindings = bindings.concat(decodeId(be.blankTrg.value) || [{ inst: be.blankTrg }]);
else if (be.blankSrc.value === refElementId && refElementLinkId === be.blankSrcProp.value) bindings.push(be);
}
return bindings;
}
function getAllRelatedElements(id) {
const blankElements = decodeId(id);
let bindings = [];
if (blankElements) {
for (const be of blankElements) if (be.inst.value === id || id === be.blankSrc.value || id === be.blankTrg.value) {
bindings.push(be);
if (isRdfIri(be.blankSrc)) bindings.push({ inst: be.blankSrc });
else if (isRdfBlank(be.blankSrc)) bindings = bindings.concat(decodeId(be.blankSrc.value) || [{ inst: be.blankSrc }]);
if (isRdfIri(be.blankTrg)) bindings.push({ inst: be.blankTrg });
else if (isRdfBlank(be.blankTrg)) bindings = bindings.concat(decodeId(be.blankTrg.value) || [{ inst: be.blankTrg }]);
}
}
return bindings;
}
function getElementBindings(ids) {
let blankElements = [];
for (const id of ids) if (decodeId(id)) blankElements = blankElements.concat(decodeId(id));
return blankElements.filter((be) => {
return ids.indexOf(be.inst.value) !== -1;
});
}
function getLinkBinding(ids) {
let blankElements = [];
for (const id of ids) if (decodeId(id)) blankElements = blankElements.concat(decodeId(id));
const bindings = [];
for (const be of blankElements) if (ids.indexOf(be.inst.value) !== -1) {
if ((isRdfIri(be.blankSrc) || isRdfBlank(be.blankSrc)) && isRdfIri(be.blankSrcProp) && ids.indexOf(be.blankSrc.value) !== -1) bindings.push({
source: be.blankSrc,
type: be.blankSrcProp,
target: be.inst
});
if ((isRdfIri(be.blankTrg) || isRdfBlank(be.blankTrg)) && isRdfIri(be.blankTrgProp) && ids.indexOf(be.blankTrg.value) !== -1) bindings.push({
source: be.inst,
type: be.blankTrgProp,
target: be.blankTrg
});
}
return bindings;
}
function getLinkCountBinding(id) {
const blankElements = decodeId(id);
const dictionary = {};
for (const be of blankElements) if (id === be.inst.value) {
if ((isRdfIri(be.blankTrg) || isRdfBlank(be.blankTrg)) && isRdfIri(be.blankTrgProp)) {
if ((isRdfIri(be.blankSrc) || isRdfBlank(be.blankSrc)) && isRdfIri(be.blankSrcProp)) {
if (!dictionary[be.blankSrcProp.value]) dictionary[be.blankSrcProp.value] = {
link: be.blankSrcProp,
inCount: {
type: "literal",
value: "1",
"xml:lang": ""
},
outCount: {
type: "literal",
value: "0",
"xml:lang": ""
}
};
}
if (!dictionary[be.blankTrgProp.value]) dictionary[be.blankTrgProp.value] = {
link: be.blankTrgProp,
inCount: {
type: "literal",
value: "0",
"xml:lang": ""
},
outCount: {
type: "literal",
value: "1",
"xml:lang": ""
}
};
else dictionary[be.blankTrgProp.value].outCount.value = (+dictionary[be.blankTrgProp.value].outCount.value + 1).toString();
}
}
return Object.keys(dictionary).map((k) => dictionary[k]);
}
//#endregion
//#region src/graph-explorer/diagram/linkRouter.ts
var DefaultLinkRouter = class {
constructor(gap = 20) {
this.gap = gap;
}
route(model) {
const routings = /* @__PURE__ */ new Map();
for (const link of model.links) {
if (routings.has(link.id)) continue;
const { sourceId, targetId } = link;
if (!sourceId || !targetId) continue;
else if (sourceId === targetId) this.routeFeedbackSiblingLinks(model, sourceId, routings);
else this.routeNormalSiblingLinks(model, sourceId, targetId, routings);
}
return routings;
}
routeFeedbackSiblingLinks(model, elementId, routings) {
const element = model.getElement(elementId);
const { x, y } = element.position;
const { width, height } = element.size;
let index = 0;
for (const sibling of element.links) {
if (routings.has(sibling.id) || hasUserPlacedVertices(sibling)) continue;
const { sourceId, targetId } = sibling;
if (sourceId === targetId) {
const offset = this.gap * (index + 1);
const vertices = [
{
x: x - offset,
y: y + height / 2
},
{
x: x - offset,
y: y - offset
},
{
x: x + width / 2,
y: y - offset
}
];
routings.set(sibling.id, {
linkId: sibling.id,
vertices
});
index++;
}
}
}
routeNormalSiblingLinks(model, sourceId, targetId, routings) {
const source = model.getElement(sourceId);
const target = model.getElement(targetId);
const sourceCenter = centerOfElement(source);
const targetCenter = centerOfElement(target);
const midPoint = {
x: (sourceCenter.x + targetCenter.x) / 2,
y: (sourceCenter.y + targetCenter.y) / 2
};
const direction = Vector.normalize({
x: targetCenter.x - sourceCenter.x,
y: targetCenter.y - sourceCenter.y
});
const siblings = source.links.filter((link) => (link.sourceId === targetId || link.targetId === targetId) && !routings.has(link.id) && !hasUserPlacedVertices(link));
if (siblings.length <= 1) return;
const indexModifier = siblings.length % 2 ? 0 : 1;
siblings.forEach((sibling, siblingIndex) => {
const index = siblingIndex + indexModifier;
const offset = this.gap * Math.ceil(index / 2) - (indexModifier ? this.gap / 2 : 0);
const offsetDirection = index % 2 ? {
x: -direction.y,
y: direction.x
} : {
x: direction.y,
y: -direction.x
};
const vertex = {
x: midPoint.x + offsetDirection.x * offset,
y: midPoint.y + offsetDirection.y * offset
};
routings.set(sibling.id, {
linkId: sibling.id,
vertices: [vertex],
labelTextAnchor: this.getLabelAlignment(direction, siblingIndex, siblings.length)
});
});
}
getLabelAlignment(connectionDirection, siblingIndex, siblingCount) {
const angle = Math.atan2(connectionDirection.y, connectionDirection.x);
const absoluteAngle = Math.abs(angle);
const isHorizontal = absoluteAngle < Math.PI * 1 / 8 || absoluteAngle > Math.PI * 7 / 8;
const isTop = angle < 0;
const isBottom = angle > 0;
const firstOuter = siblingCount - 2;
const secondOuter = siblingCount - 1;
if (!isHorizontal) {
if (isTop && siblingIndex === secondOuter || isBottom && siblingIndex === firstOuter) return "end";
else if (isTop && siblingIndex === firstOuter || isBottom && siblingIndex === secondOuter) return "start";
}
return "middle";
}
};
function hasUserPlacedVertices(link) {
const vertices = link.vertices;
return vertices && vertices.length > 0;
}
function centerOfElement(element) {
const { x, y } = element.position;
const { width, height } = element.size;
return {
x: x + width / 2,
y: y + height / 2
};
}
//#endregion
//#region src/graph-explorer/diagram/view.ts
var IriClickIntent = /* @__PURE__ */ function(IriClickIntent) {
IriClickIntent["JumpToEntity"] = "jumpToEntity";
IriClickIntent["OpenEntityIri"] = "openEntityIri";
IriClickIntent["OpenOtherIri"] = "openOtherIri";
return IriClickIntent;
}({});
var RenderingLayer = /* @__PURE__ */ function(RenderingLayer) {
RenderingLayer[RenderingLayer["Element"] = 1] = "Element";
RenderingLayer[RenderingLayer["ElementSize"] = 2] = "ElementSize";
RenderingLayer[RenderingLayer["PaperArea"] = 3] = "PaperArea";
RenderingLayer[RenderingLayer["Link"] = 4] = "Link";
RenderingLayer[RenderingLayer["Editor"] = 5] = "Editor";
RenderingLayer[RenderingLayer["FirstToUpdate"] = 1] = "FirstToUpdate";
RenderingLayer[RenderingLayer["LastToUpdate"] = 5] = "LastToUpdate";
return RenderingLayer;
}({});
var WidgetAttachment = /* @__PURE__ */ function(WidgetAttachment) {
WidgetAttachment[WidgetAttachment["Viewport"] = 1] = "Viewport";
WidgetAttachment[WidgetAttachment["OverElements"] = 2] = "OverElements";
WidgetAttachment[WidgetAttachment["OverLinks"] = 3] = "OverLinks";
return WidgetAttachment;
}({});
var DiagramView = class {
constructor(model, options = {}) {
this.model = model;
this.options = options;
this.listener = new EventObserver();
this.source = new EventSource();
this.events = this.source;
this.disposed = false;
this.colorSeed = 195935983;
this._language = "en";
this.linkTemplates = /* @__PURE__ */ new Map();
this.routings = /* @__PURE__ */ new Map();
this.resolveTypeStyle = options.typeStyleResolver || DefaultTypeStyleBundle;
this.resolveLinkTemplate = options.linkTemplateResolver || DefaultLinkTemplateBundle;
this.resolveElementTemplate = options.elementTemplateResolver || DefaultElementTemplateBundle;
this.initRouting();
}
initRouting() {
this.router = this.options.linkRouter || new DefaultLinkRouter();
this.updateRoutings();
this.listener.listen(this.model.events, "changeCells", () => this.updateRoutings());
this.listener.listen(this.model.events, "linkEvent", ({ key: _key, data }) => {
if (data.changeVertices) this.updateRoutings();
});
this.listener.listen(this.model.events, "elementEvent", ({ key: _key, data }) => {
if (data.changePosition || data.changeSize) this.updateRoutings();
});
}
updateRoutings() {
const previousRoutes = this.routings;
const computedRoutes = this.router.route(this.model);
previousRoutes.forEach((previous, linkId) => {
const computed = computedRoutes.get(linkId);
if (computed && sameRoutedLink(previous, computed)) computedRoutes.set(linkId, previous);
});
this.routings = computedRoutes;
this.source.trigger("updateRoutings", {
source: this,
previous: previousRoutes
});
}
getRoutings() {
return this.routings;
}
getRouting(linkId) {
return this.routings.get(linkId);
}
getLanguage() {
return this._language;
}
setLanguage(value) {
if (!value) throw new Error("Cannot set empty language.");
const previous = this._language;
if (previous === value) return;
this._language = value;
this.source.trigger("changeLanguage", {
source: this,
previous
});
}
getLinkTemplates() {
return this.linkTemplates;
}
performSyncUpdate() {
for (let layer = 1; layer <= 5; layer++) this.source.trigger("syncUpdate", { layer });
}
onIriClick(iri, element, clickIntent, event) {
event.persist();
event.preventDefault();
const { onIriClick } = this.options;
if (onIriClick) onIriClick({
iri,
element,
clickIntent,
originalEvent: event
});
}
setPaperWidget(widget) {
const { key, widget: element, attachment } = widget;
const widgets = { [key]: element ? {
element,
attachment
} : void 0 };
this.source.trigger("updateWidgets", { widgets });
}
setHandlerForNextDropOnPaper(handler) {
this.dropOnPaperHandler = handler;
}
_tryHandleDropOnPaper(e) {
const { dropOnPaperHandler } = this;
if (dropOnPaperHandler) {
this.dropOnPaperHandler = void 0;
dropOnPaperHandler(e);
return true;
}
return false;
}
selectLabel(labels, language) {
const targetLanguage = typeof language === "undefined" ? this.getLanguage() : language;
const { selectLabelLanguage = defaultSelectLabel } = this.options;
return selectLabelLanguage(labels, targetLanguage);
}
formatLabel(labels, fallbackIri, language) {
return resolveLabel(this.selectLabel(labels, language), fallbackIri);
}
getElementTypeString(elementModel) {
return elementModel.types.map((typeId) => {
const type = this.model.createClass(typeId);
return this.formatLabel(type.label, type.id);
}).sort().join(", ");
}
getTypeStyle(types) {
types.sort();
const customStyle = this.resolveTypeStyle(types);
const icon = customStyle ? customStyle.icon : void 0;
let color;
if (customStyle && customStyle.color) color = hcl(customStyle.color);
else color = {
h: getHueFromClasses(types, this.colorSeed),
c: 40,
l: 75
};
return {
icon,
color
};
}
formatIri(iri) {
if (isEncodedBlank(iri)) return "(blank node)";
return `<${iri}>`;
}
getElementTemplate(types) {
return this.resolveElementTemplate(types) || StandardTemplate;
}
createLinkTemplate(linkType) {
const existingTemplate = this.linkTemplates.get(linkType.id);
if (existingTemplate) return existingTemplate;
let template = {};
const result = this.resolveLinkTemplate(linkType.id);
if (result) template = cloneDeep(result);
fillLinkTemplateDefaults(template);
this.linkTemplates.set(linkType.id, template);
this.source.trigger("changeLinkTemplates", {});
return template;
}
dispose() {
if (this.disposed) return;
this.source.trigger("dispose", {});
this.listener.stopListening();
this.disposed = true;
}
get highlighter() {
return this._highlighter;
}
setHighlighter(value) {
const previous = this._highlighter;
if (previous === value) return;
this._highlighter = value;
this.source.trigger("changeHighlight", {
source: this,
previous
});
}
_setElementDecorator(decorator) {
this._elementDecorator = decorator;
}
_decorateElement(element) {
return this._elementDecorator(element);
}
};
function sameRoutedLink(a, b) {
return a.linkId === b.linkId && a.labelTextAnchor === b.labelTextAnchor && isPolylineEqual(a.vertices, b.vertices);
}
function getHueFromClasses(classes, seed) {
let hash = seed;
for (const name of classes) hash = hashFnv32a(name, hash);
return 360 * ((hash === void 0 ? 0 : hash) / 2147483647);
}
function fillLinkTemplateDefaults(template) {
defaultsDeep(template, { markerTarget: {
d: "M0,0 L0,8 L9,4 z",
width: 9,
height: 8,
fill: "black"
} });
if (!template.renderLink) template.renderLink = () => ({});
}
function defaultSelectLabel(texts, language) {
if (texts.length === 0) return;
let defaultValue;
let englishValue;
for (const text of texts) if (text.language === language) return text;
else if (text.language === "") defaultValue = text;
else if (text.language === "en") englishValue = text;
return defaultValue !== void 0 ? defaultValue : englishValue !== void 0 ? englishValue : texts[0];
}
function resolveLabel(label, fallbackIri) {
if (label) return label.value;
return getUriLocalName(fallbackIri) || fallbackIri;
}
//#endregion
//#region src/graph-explorer/diagram/elementLayer.tsx
var ElementLayer = class extends React.Component {
constructor(props, context) {
super(props, context);
this.listener = new EventObserver();
this.redrawBatch = {
requests: /* @__PURE__ */ new Map(),
forAll: 0
};
this.delatedRedraw = new Debouncer();
this.sizeRequests = /* @__PURE__ */ new Map();
this.delayedUpdateSizes = new Debouncer();
this.onMount = (layer) => {
this.layer = layer;
};
this.requestRedraw = (element, request) => {
if ((this.redrawBatch.forAll | request) === this.redrawBatch.forAll) return;
const existing = this.redrawBatch.requests.get(element.id);
this.redrawBatch.requests.set(element.id, existing | request);
this.delatedRedraw.call(this.redrawElements);
};
this.redrawElements = () => {
const props = this.props;
this.setState((state) => ({ elementStates: applyRedrawRequests(props.view, props.group, this.redrawBatch, state.elementStates) }));
};
this.requestSizeUpdate = (element, node) => {
this.sizeRequests.set(element.id, {
element,
node
});
this.delayedUpdateSizes.call(this.recomputeQueuedSizes);
};
this.recomputeQueuedSizes = () => {
const batch = this.sizeRequests;
this.sizeRequests = /* @__PURE__ */ new Map();
batch.forEach(({ element, node }) => {
const { clientWidth, clientHeight } = node;
element.setSize({
width: clientWidth,
height: clientHeight
});
});
};
const { view, group } = this.props;
this.state = { elementStates: applyRedrawRequests(view, group, this.redrawBatch, /* @__PURE__ */ new Map()) };
}
render() {
const { view, style } = this.props;
const { elementStates } = this.state;
const elementsToRender = [];
for (const { id } of view.model.elements) {
const state = elementStates.get(id);
if (state) elementsToRender.push(state);
}
return /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-element-layer",
ref: this.onMount,
style
}, elementsToRender.map((state) => {
const overlayElement = /* @__PURE__ */ React.createElement(OverlayedElement, {
key: state.element.id,
state,
view,
onInvalidate: this.requestRedraw,
onResize: this.requestSizeUpdate
});
const elementDecorator = view._decorateElement(state.element);
if (elementDecorator) return /* @__PURE__ */ React.createElement("div", { key: state.element.id }, overlayElement, elementDecorator);
return overlayElement;
}));
}
componentDidMount() {
const { view } = this.props;
this.listener.listen(view.model.events, "changeCells", (e) => {
if (e.updateAll) this.requestRedrawAll(0);
else if (e.changedElement) this.requestRedraw(e.changedElement, 0);
});
this.listener.listen(view.events, "changeLanguage", () => {
this.requestRedrawAll(3);
});
this.listener.listen(view.events, "changeHighlight", () => {
this.requestRedrawAll(5);
});
this.listener.listen(view.model.events, "elementEvent", ({ data }) => {
const invalidatesTemplate = data.changeData || data.changeExpanded || data.changeElementState;
if (invalidatesTemplate) this.requestRedraw(invalidatesTemplate.source, 3);
const invalidatesRender = data.changePosition || data.requestedRedraw;
if (invalidatesRender) this.requestRedraw(invalidatesRender.source, 1);
});
this.listener.listen(view.events, "syncUpdate", ({ layer }) => {
if (layer === RenderingLayer.Element) this.delatedRedraw.runSynchronously();
else if (layer === RenderingLayer.ElementSize) this.delayedUpdateSizes.runSynchronously();
});
}
componentDidUpdate(nextProps) {
if (this.props.group !== nextProps.group) this.setState((state) => ({ elementStates: applyRedrawRequests(nextProps.view, nextProps.group, this.redrawBatch, state.elementStates) }));
}
componentWillUnmount() {
this.listener.stopListening();
this.delatedRedraw.dispose();
this.delayedUpdateSizes.dispose();
}
requestRedrawAll(request) {
this.redrawBatch.forAll |= request;
this.delatedRedraw.call(this.redrawElements);
}
};
function applyRedrawRequests(view, targetGroup, batch, previous) {
const computed = /* @__PURE__ */ new Map();
for (const element of view.model.elements) {
if (element.group !== targetGroup) continue;
const elementId = element.id;
if (previous.has(elementId)) {
let state = previous.get(elementId);
const request = (batch.requests.get(elementId) || 0) | batch.forAll;
if (request & 1) state = {
element,
templateProps: (request & 3) === 3 ? computeTemplateProps(state.element, view) : state.templateProps,
blurred: (request & 5) === 5 ? computeIsBlurred(state.element, view) : state.blurred
};
computed.set(elementId, state);
batch.requests.delete(elementId);
} else computed.set(element.id, {
element,
templateProps: computeTemplateProps(element, view),
blurred: computeIsBlurred(element, view)
});
}
batch.forAll = 0;
return computed;
}
var ElementContext = React.createContext(null);
var OverlayedElement = class extends React.Component {
constructor(..._args) {
super(..._args);
this.element = null;
this.listener = new EventObserver();
this.disposed = false;
this.rerenderTemplate = () => {
if (this.disposed) return;
this.props.onInvalidate(this.props.state.element, 3);
};
this.onMount = (node) => {
this.element = node;
if (!node) return;
const { state, onResize } = this.props;
onResize(state.element, node);
};
this.onLoadOrErrorEvent = () => {
const { state, onResize } = this.props;
if (this.element) onResize(state.element, this.element);
};
this.onClick = (e) => {
if (e.target instanceof HTMLElement && e.target.localName === "a") {
const anchor = e.target;
const { view, state } = this.props;
const clickIntent = e.target.getAttribute("data-iri-click-intent") === IriClickIntent.OpenEntityIri ? IriClickIntent.OpenEntityIri : IriClickIntent.OpenOtherIri;
view.onIriClick(decodeURI(anchor.href), state.element, clickIntent, e);
}
};
this.onDoubleClick = (e) => {
e.preventDefault();
e.stopPropagation();
const { view, state: { element } } = this.props;
view.model.history.execute(setElementExpanded(element, !element.isExpanded));
};
}
render() {
const { state: { element, blurred } } = this.props;
if (element.temporary) return /* @__PURE__ */ React.createElement("div", null);
const { x = 0, y = 0 } = element.position;
const transform = `translate(${x}px,${y}px)`;
const className = `graph-explorer-overlayed-element ${blurred ? "graph-explorer-overlayed-element--blurred" : ""}`;
return /* @__PURE__ */ React.createElement(ElementContext.Provider, { value: { graphExplorerElement: { element } } }, /* @__PURE__ */ React.createElement("div", {
className,
"data-element-id": element.id,
style: {
position: "absolute",
transform
},
tabIndex: 0,
ref: this.onMount,
onLoad: this.onLoadOrErrorEvent,
onError: this.onLoadOrErrorEvent,
onClick: this.onClick,
onDoubleClick: this.onDoubleClick
}, /* @__PURE__ */ React.createElement(TemplatedElement, this.props)));
}
componentDidMount() {
const { state, view } = this.props;
this.listener.listen(state.element.events, "requestedFocus", () => {
if (this.element) this.element.focus();
});
this.typesObserver = observeElementTypes(view.model, "changeLabel", this.rerenderTemplate);
this.propertiesObserver = observeProperties(view.model, "changeLabel", this.rerenderTemplate);
this.observeTypes();
}
componentWillUnmount() {
this.listener.stopListening();
this.typesObserver.stopListening();
this.propertiesObserver.stopListening();
this.disposed = true;
}
shouldComponentUpdate(nextProps) {
return this.props.state !== nextProps.state;
}
componentDidUpdate() {
this.observeTypes();
if (this.element) this.props.onResize(this.props.state.element, this.element);
}
observeTypes() {
const { state: { element } } = this.props;
this.typesObserver.observe(element.data.types);
this.propertiesObserver.observe(Object.keys(element.data.properties));
}
};
var TemplatedElement = class extends React.Component {
render() {
const { state, view } = this.props;
const { element, templateProps } = state;
const templateClass = view.getElementTemplate(element.data.types);
this.cachedTemplateClass = templateClass;
this.cachedTemplateProps = templateProps;
return React.createElement(templateClass, templateProps);
}
shouldComponentUpdate(nextProps) {
const templateClass = nextProps.view.getElementTemplate(nextProps.state.element.data.types);
return !(this.cachedTemplateClass === templateClass && this.cachedTemplateProps === nextProps.state.templateProps);
}
};
function computeTemplateProps(model, view) {
const types = model.data.types.length > 0 ? view.getElementTypeString(model.data) : "Thing";
const label = view.formatLabel(model.data.label.values, model.iri);
const { color, icon } = computeStyleFor(model, view);
const propsAsList = computePropertyTable(model, view);
return {
elementId: model.id,
data: model.data,
iri: model.iri,
types,
label,
color,
iconUrl: icon,
imgUrl: model.data.image,
isExpanded: model.isExpanded,
props: model.data.properties,
propsAsList
};
}
function computePropertyTable(model, view) {
if (!model.data.properties) return [];
const propTable = Object.keys(model.data.properties).map((key) => {
const property = view.model.createProperty(key);
return {
id: key,
name: view.formatLabel(property.label, key),
property: model.data.properties[key]
};
});
propTable.sort((a, b) => {
const aLabel = (a.name || a.id).toLowerCase();
const bLabel = (b.name || b.id).toLowerCase();
return aLabel.localeCompare(bLabel);
});
return propTable;
}
function computeStyleFor(model, view) {
const { color: { h, c, l }, icon } = view.getTypeStyle(model.data.types);
return {
icon,
color: hcl(h, c, l).toString()
};
}
function computeIsBlurred(element, view) {
return view.highlighter && !view.highlighter(element);
}
//#endregion
//#region src/graph-explorer/diagram/linkLayer.tsx
var CLASS_NAME$24 = "graph-explorer-link-layer";
var LinkLayer = class extends Component {
constructor(..._args) {
super(..._args);
this.listener = new EventObserver();
this.delayedUpdate = new Debouncer();
this.updateState = 1;
this.scheduledToUpdate = /* @__PURE__ */ new Set();
this.scheduleUpdateAll = () => {
if (this.updateState !== 2) {
this.updateState = 2;
this.scheduledToUpdate = /* @__PURE__ */ new Set();
}
this.delayedUpdate.call(this.performUpdate);
};
this.performUpdate = () => {
this.forceUpdate();
};
this.getLinks = () => {
const { view, links, group } = this.props;
if (!group) return links;
const nestedElements = computeDeepNestedElements(computeGrouping(view.model.elements), group);
return links.filter((link) => {
const { sourceId, targetId } = link;
const source = view.model.getElement(sourceId);
const target = view.model.getElement(targetId);
if (!source || !target) return false;
const sourceGroup = source.group;
const targetGroup = target.group;
return nestedElements[sourceGroup] || nestedElements[targetGroup];
});
};
}
componentDidMount() {
const { view } = this.props;
const scheduleUpdateElementLinks = (element) => {
for (const link of element.links) this.scheduleUpdateLink(link.id);
};
this.listener.listen(view.events, "changeLanguage", this.scheduleUpdateAll);
this.listener.listen(view.events, "changeHighlight", this.scheduleUpdateAll);
const updateChangedRoutes = (changed, previous) => {
changed.forEach((routing, linkId) => {
if (previous.get(linkId) !== routing) this.scheduleUpdateLink(linkId);
});
};
this.listener.listen(view.events, "updateRoutings", ({ previous }) => {
const newRoutes = view.getRoutings();
updateChangedRoutes(newRoutes, previous);
updateChangedRoutes(previous, newRoutes);
});
this.listener.listen(view.model.events, "changeCells", (e) => {
if (e.updateAll) this.scheduleUpdateAll();
else {
if (e.changedElement) scheduleUpdateElementLinks(e.changedElement);
if (e.changedLinks) for (const link of e.changedLinks) this.scheduleUpdateLink(link.id);
}
});
this.listener.listen(view.model.events, "elementEvent", ({ data }) => {
const elementEvent = data.changePosition || data.changeSize;
if (!elementEvent) return;
scheduleUpdateElementLinks(elementEvent.source);
});
this.listener.listen(view.model.events, "linkEvent", ({ data }) => {
const linkEvent = data.changeData || data.changeLayoutOnly || data.changeVertices || data.changeLinkState;
if (linkEvent) this.scheduleUpdateLink(linkEvent.source.id);
});
this.listener.listen(view.model.events, "linkTypeEvent", ({ data }) => {
const linkTypeEvent = data.changeLabel || data.changeVisibility;
if (!linkTypeEvent) return;
const linkTypeId = linkTypeEvent.source.id;
for (const link of view.model.linksOfType(linkTypeId)) this.scheduleUpdateLink(link.id);
});
this.listener.listen(view.events, "syncUpdate", ({ layer }) => {
if (layer !== RenderingLayer.Link) return;
this.delayedUpdate.runSynchronously();
});
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
this.listener.stopListening();
this.delayedUpdate.dispose();
}
scheduleUpdateLink(linkId) {
if (this.updateState === 1) this.scheduledToUpdate.add(linkId);
this.delayedUpdate.call(this.performUpdate);
}
popShouldUpdatePredicate() {
const { updateState, scheduledToUpdate } = this;
this.scheduledToUpdate = /* @__PURE__ */ new Set();
this.updateState = 1;
return updateState === 2 ? () => true : (link) => scheduledToUpdate.has(link.id);
}
render() {
const { view } = this.props;
const shouldUpdate = this.popShouldUpdatePredicate();
return /* @__PURE__ */ React.createElement("g", { className: CLASS_NAME$24 }, this.getLinks().map((model) => /* @__PURE__ */ React.createElement(LinkView, {
key: model.id,
view,
model,
shouldUpdate: shouldUpdate(model),
route: view.getRouting(model.id)
})));
}
};
function computeDeepNestedElements(grouping, groupId) {
const deepChildren = {};
function collectNestedItems(parentId) {
deepChildren[parentId] = true;
const children = grouping.get(parentId);
if (!children) return;
for (const element of children) {
if (element.group !== parentId) continue;
collectNestedItems(element.id);
}
}
collectNestedItems(groupId);
return deepChildren;
}
var LINK_CLASS = "graph-explorer-link";
var LABEL_GROUPING_PRECISION = 100;
var TEMPORARY_LABEL_LINES = /* @__PURE__ */ new Map();
var LinkView = class extends Component {
constructor(props, context) {
super(props, context);
this.onRemoveLinkVertex = (vertex) => {
this.props.view.model.history.registerToUndo(restoreCapturedLinkGeometry(vertex.link));
vertex.remove();
};
this.onBoundsUpdate = (newBounds) => {
const { model } = this.props;
model.setLabelBounds(newBounds);
};
this.grabLinkTemplate(this.props);
}
componentDidUpdate(nextProps) {
if (this.linkType.id !== nextProps.model.typeId) this.grabLinkTemplate(nextProps);
}
shouldComponentUpdate(nextProps, _nextState) {
return nextProps.shouldUpdate;
}
grabLinkTemplate(props) {
this.linkType = props.view.model.getLinkType(props.model.typeId);
this.template = props.view.createLinkTemplate(this.linkType);
}
render() {
const { view, model, route } = this.props;
const source = view.model.getElement(model.sourceId);
const target = view.model.getElement(model.targetId);
if (!(source && target)) return null;
const verticesDefinedByUser = model.vertices || [];
const polyline = computePolyline(source, target, route ? route.vertices : verticesDefinedByUser);
const path = "M" + polyline.map(({ x, y }) => `${x},${y}`).join(" L");
const { index: typeIndex, showLabel } = this.linkType;
const style = this.template.renderLink(model);
const pathAttributes = getPathAttributes(model, style);
const className = `${LINK_CLASS} ${view.highlighter && !view.highlighter(model) ? `${LINK_CLASS}--blurred` : ""}`;
return /* @__PURE__ */ React.createElement("g", {
className,
"data-link-id": model.id,
"data-source-id": source.id,
"data-target-id": target.id
}, /* @__PURE__ */ React.createElement("path", {
className: `${LINK_CLASS}__connection`,
d: path,
...pathAttributes,
markerStart: `url(#${linkMarkerKey(typeIndex, true)})`,
markerEnd: `url(#${linkMarkerKey(typeIndex, false)})`
}), /* @__PURE__ */ React.createElement("path", {
className: `${LINK_CLASS}__wrap`,
d: path
}), showLabel ? this.renderLabels(polyline, style) : void 0, this.renderVertices(verticesDefinedByUser, pathAttributes.stroke));
}
renderVertices(vertices, fill) {
const elements = [];
const vertexClass = `${LINK_CLASS}__vertex`;
const vertexRadius = 10;
let index = 0;
for (const { x, y } of vertices) {
elements.push(/* @__PURE__ */ React.createElement("circle", {
key: index * 2,
"data-vertex": index,
className: vertexClass,
cx: x,
cy: y,
r: vertexRadius,
fill
}));
elements.push(/* @__PURE__ */ React.createElement(VertexTools, {
key: index * 2 + 1,
className: `${LINK_CLASS}__vertex-tools`,
model: this.props.model,
vertexIndex: index,
vertexRadius,
x,
y,
onRemove: this.onRemoveLinkVertex
}));
index++;
}
return /* @__PURE__ */ React.createElement("g", { className: `${LINK_CLASS}__vertices` }, elements);
}
renderLabels(polyline, style) {
const { view, model, route } = this.props;
const labels = computeLinkLabels(model, style, view);
let textAnchor = "middle";
if (route && route.labelTextAnchor) textAnchor = route.labelTextAnchor;
const polylineLength = computePolylineLength(polyline);
TEMPORARY_LABEL_LINES.clear();
return /* @__PURE__ */ React.createElement("g", { className: `${LINK_CLASS}__labels` }, labels.map((label, index) => {
const { x, y } = getPointAlongPolyline(polyline, polylineLength * label.offset);
const groupKey = Math.round(label.offset * LABEL_GROUPING_PRECISION) / LABEL_GROUPING_PRECISION;
const line = TEMPORARY_LABEL_LINES.get(groupKey) || 0;
TEMPORARY_LABEL_LINES.set(groupKey, line + 1);
return /* @__PURE__ */ React.createElement(LinkLabel, {
key: index,
x,
y,
line,
label,
textAnchor,
onBoundsUpdate: index === 0 ? this.onBoundsUpdate : void 0
});
}));
}
};
function computeLinkLabels(model, style, view) {
const labels = [];
const labelStyle = style.label || {};
const labelTexts = labelStyle.attrs && labelStyle.attrs.text ? labelStyle.attrs.text.text : void 0;
let text;
let title = labelStyle.title;
if (labelTexts && labelTexts.length > 0) text = view.selectLabel(labelTexts);
else {
const type = view.model.getLinkType(model.typeId);
text = view.selectLabel(type.label) || {
value: view.formatLabel(type.label, type.id),
language: ""
};
if (title === void 0) title = `${text.value} ${view.formatIri(model.typeId)}`;
}
labels.push({
offset: labelStyle.position || .5,
text,
title,
attributes: {
text: getLabelTextAttributes(labelStyle),
rect: getLabelRectAttributes(labelStyle)
}
});
if (style.properties) for (const property of style.properties) {
if (!(property.attrs && property.attrs.text && property.attrs.text.text)) continue;
labels.push({
offset: property.position || .5,
text: view.selectLabel(property.attrs.text.text),
title: property.title,
attributes: {
text: getLabelTextAttributes(property),
rect: getLabelRectAttributes(property)
}
});
}
return labels;
}
function getPathAttributes(model, style) {
const connectionAttributes = style.connection || {};
const defaultStrokeDasharray = model.layoutOnly ? "5,5" : void 0;
const { fill = "none", stroke = "black", "stroke-width": strokeWidth, "stroke-dasharray": strokeDasharray = defaultStrokeDasharray } = connectionAttributes;
return {
fill,
stroke,
strokeWidth,
strokeDasharray
};
}
function getLabelTextAttributes(label) {
const { fill = "black", stroke = "none", "stroke-width": strokeWidth = 0, "font-family": fontFamily = "\"Helvetica Neue\", \"Helvetica\", \"Arial\", sans-serif", "font-size": fontSize = "inherit", "font-weight": fontWeight = "bold" } = label.attrs ? label.attrs.text : {};
return {
fill,
stroke,
strokeWidth,
fontFamily,
fontSize,
fontWeight
};
}
function getLabelRectAttributes(label) {
const { fill = "white", stroke = "none", "stroke-width": strokeWidth = 0 } = label.attrs && label.attrs.rect ? label.attrs.rect : {};
return {
fill,
stroke,
strokeWidth
};
}
var GROUPED_LABEL_MARGIN = 2;
var LinkLabel = class extends Component {
constructor(props) {
super(props);
this.shouldUpdateBounds = true;
this.onTextMount = (text) => {
this.text = text;
};
this.state = {
width: 0,
height: 0
};
}
render() {
const { x, y, label, line, textAnchor } = this.props;
const { width, height } = this.state;
const { x: rectX, y: rectY } = this.getLabelRectangle(width, height);
const transform = line === 0 ? void 0 : `translate(0, ${line * (height + GROUPED_LABEL_MARGIN)}px)`;
return /* @__PURE__ */ React.createElement("g", { style: transform ? { transform } : void 0 }, label.title ? /* @__PURE__ */ React.createElement("title", null, label.title) : void 0, /* @__PURE__ */ React.createElement("rect", {
x: rectX,
y: rectY,
width,
height,
style: label.attributes.rect
}), /* @__PURE__ */ React.createElement("text", {
ref: this.onTextMount,
x,
y,
dy: "0.6ex",
textAnchor,
style: label.attributes.text
}, label.text.value));
}
getLabelRectangle(width, height) {
const { x, y, textAnchor } = this.props;
let xOffset = 0;
if (textAnchor === "middle") xOffset = -width / 2;
else if (textAnchor === "end") xOffset = -width;
return {
x: x + xOffset,
y: y - height / 2,
width,
height
};
}
componentDidMount() {
this.recomputeBounds(this.props);
}
componentWillUnmount() {
const { onBoundsUpdate } = this.props;
onBoundsUpdate(void 0);
}
componentDidUpdate(_nextProps) {
this.shouldUpdateBounds = true;
}
UNSAFE_componentWillReceiveProps() {
this.recomputeBounds(this.props);
}
recomputeBounds(_props) {
if (this.shouldUpdateBounds) {
const { onBoundsUpdate } = this.props;
this.shouldUpdateBounds = false;
const bounds = this.text.getBBox();
if (onBoundsUpdate) onBoundsUpdate(this.getLabelRectangle(bounds.width, bounds.height));
this.setState({
width: bounds.width,
height: bounds.height
});
}
}
};
var VertexTools = class extends Component {
constructor(..._args2) {
super(..._args2);
this.onRemoveVertex = (e) => {
if (e.button !== 0) return;
e.preventDefault();
e.stopPropagation();
const { onRemove, model, vertexIndex } = this.props;
onRemove(new LinkVertex(model, vertexIndex));
};
}
render() {
const { className, vertexRadius, x, y } = this.props;
const transform = `translate(${x + 2 * vertexRadius},${y - 2 * vertexRadius})scale(${vertexRadius})`;
return /* @__PURE__ */ React.createElement("g", {
className,
transform,
onMouseDown: this.onRemoveVertex
}, /* @__PURE__ */ React.createElement("title", null, "Remove vertex"), /* @__PURE__ */ React.createElement("circle", { r: 1 }), /* @__PURE__ */ React.createElement("path", {
d: "M-0.5,-0.5 L0.5,0.5 M0.5,-0.5 L-0.5,0.5",
strokeWidth: 2 / vertexRadius
}));
}
};
var LinkMarkers = class extends Component {
constructor(..._args3) {
super(..._args3);
this.listener = new EventObserver();
this.delayedUpdate = new Debouncer();
}
render() {
const { view } = this.props;
const markers = [];
view.getLinkTemplates().forEach((template, linkTypeId) => {
const type = view.model.getLinkType(linkTypeId);
if (!type) return;
const typeIndex = type.index;
if (template.markerSource) markers.push(/* @__PURE__ */ React.createElement(LinkMarker, {
key: typeIndex * 2,
linkTypeIndex: typeIndex,
style: template.markerSource,
isStartMarker: true
}));
if (template.markerTarget) markers.push(/* @__PURE__ */ React.createElement(LinkMarker, {
key: typeIndex * 2 + 1,
linkTypeIndex: typeIndex,
style: template.markerTarget,
isStartMarker: false
}));
});
return /* @__PURE__ */ React.createElement("defs", null, markers);
}
componentDidMount() {
const { view } = this.props;
this.listener.listen(view.events, "syncUpdate", ({ layer }) => {
if (layer !== RenderingLayer.Link) return;
this.delayedUpdate.runSynchronously();
});
this.listener.listen(view.events, "changeLinkTemplates", () => {
this.delayedUpdate.call(() => this.forceUpdate());
});
}
shouldComponentUpdate() {
return false;
}
componentWillUnmount() {
this.listener.stopListening();
this.delayedUpdate.dispose();
}
};
var SVG_NAMESPACE$1 = "http://www.w3.org/2000/svg";
var LinkMarker = class extends Component {
constructor(..._args4) {
super(..._args4);
this.onMarkerMount = (marker) => {
if (!marker) return;
const { linkTypeIndex, isStartMarker, style } = this.props;
marker.setAttribute("id", linkMarkerKey(linkTypeIndex, isStartMarker));
marker.setAttribute("markerWidth", style.width.toString());
marker.setAttribute("markerHeight", style.height.toString());
marker.setAttribute("orient", "auto");
const xOffset = isStartMarker ? 0 : style.width - 1;
marker.setAttribute("refX", xOffset.toString());
marker.setAttribute("refY", (style.height / 2).toString());
marker.setAttribute("markerUnits", "userSpaceOnUse");
const path = document.createElementNS(SVG_NAMESPACE$1, "path");
path.setAttribute("d", style.d);
if (style.fill !== void 0) path.setAttribute("fill", style.fill);
if (style.stroke !== void 0) path.setAttribute("stroke", style.stroke);
if (style.strokeWidth !== void 0) path.setAttribute("stroke-width", style.strokeWidth);
marker.appendChild(path);
};
}
render() {
return /* @__PURE__ */ React.createElement("marker", { ref: this.onMarkerMount });
}
shouldComponentUpdate() {
return false;
}
};
//#endregion
//#region src/graph-explorer/diagram/paper.tsx
var CLASS_NAME$23 = "graph-explorer-paper";
var Paper = class extends Component {
constructor(..._args) {
super(..._args);
this.onMouseDown = (e) => {
const { view, onPointerDown } = this.props;
const cell = e.target instanceof Element ? findCell(e.target, e.currentTarget, view.model) : void 0;
if (onPointerDown) onPointerDown(e, cell);
};
}
render() {
const { view, group, paperTransform, linkLayerWidgets, elementLayerWidgets } = this.props;
const { width, height, originX, originY, scale, paddingX, paddingY } = paperTransform;
const scaledWidth = width * scale;
const scaledHeight = height * scale;
const style = {
width: scaledWidth + paddingX,
height: scaledHeight + paddingY,
marginLeft: paddingX,
marginTop: paddingY,
paddingRight: paddingX,
paddingBottom: paddingY
};
const htmlTransformStyle = {
position: "absolute",
left: 0,
top: 0,
transform: `scale(${scale},${scale})translate(${originX}px,${originY}px)`
};
return /* @__PURE__ */ React.createElement("div", {
className: CLASS_NAME$23,
style,
onMouseDown: this.onMouseDown
}, /* @__PURE__ */ React.createElement(TransformedSvgCanvas, {
className: `${CLASS_NAME$23}__canvas`,
style: { overflow: "visible" },
paperTransform
}, /* @__PURE__ */ React.createElement(LinkMarkers, { view }), /* @__PURE__ */ React.createElement(LinkLayer, {
view,
links: view.model.links,
group
})), linkLayerWidgets, /* @__PURE__ */ React.createElement(ElementLayer, {
view,
group,
style: htmlTransformStyle
}), elementLayerWidgets);
}
};
function findCell(bottom, top, model) {
let target = bottom;
let vertexIndex;
while (true) {
if (target instanceof Element) {
if (target.hasAttribute("data-element-id")) return model.getElement(target.getAttribute("data-element-id"));
else if (target.hasAttribute("data-link-id")) {
const link = model.getLinkById(target.getAttribute("data-link-id"));
return typeof vertexIndex === "number" ? new LinkVertex(link, vertexIndex) : link;
} else if (target.hasAttribute("data-vertex")) vertexIndex = Number(target.getAttribute("data-vertex"));
}
if (!target || target === top) break;
target = target.parentNode;
}
}
var TransformedSvgCanvas = class TransformedSvgCanvas extends Component {
static {
this.SVG_STYLE = {
position: "absolute",
top: 0,
left: 0
};
}
render() {
const { paperTransform, style, children, ...otherProps } = this.props;
const { width, height, originX, originY, scale } = paperTransform;
const scaledWidth = width * scale;
const scaledHeight = height * scale;
let svgStyle = TransformedSvgCanvas.SVG_STYLE;
if (style) svgStyle = {
...svgStyle,
...style
};
return /* @__PURE__ */ React.createElement("svg", {
width: scaledWidth,
height: scaledHeight,
style: svgStyle,
...otherProps
}, /* @__PURE__ */ React.createElement("g", { transform: `scale(${scale},${scale})translate(${originX},${originY})` }, children));
}
};
/**
* @returns scrollable pane size in non-scaled pane coords.
*/
function totalPaneSize(pt) {
return {
x: pt.width * pt.scale + pt.paddingX * 2,
y: pt.height * pt.scale + pt.paddingY * 2
};
}
/**
* @returns scrollable pane top-left corner position in non-scaled pane coords.
*/
function paneTopLeft(pt) {
return {
x: -pt.paddingX,
y: -pt.paddingY
};
}
function paneFromPaperCoords(paper, pt) {
return {
x: (paper.x + pt.originX) * pt.scale,
y: (paper.y + pt.originY) * pt.scale
};
}
function paperFromPaneCoords(pane, pt) {
return {
x: pane.x / pt.scale - pt.originX,
y: pane.y / pt.scale - pt.originY
};
}
//#endregion
//#region src/graph-explorer/viewUtils/toSvg.ts
var SVG_NAMESPACE = "http://www.w3.org/2000/svg";
/**
* Padding (in px) for <foreignObject> elements of exported SVG to
* mitigate issues with elements body overflow caused by missing styles
* in exported image.
*/
var FOREIGN_OBJECT_SIZE_PADDING = 2;
var BORDER_PADDING = 100;
function toSVG(options) {
return exportSVG(options).then((svg) => new XMLSerializer().serializeToString(svg));
}
function exportSVG(options) {
const { contentBox: bbox, watermarkSvg } = options;
const { svgClone, imageBounds } = clonePaperSvg(options, FOREIGN_OBJECT_SIZE_PADDING);
const paddedWidth = bbox.width + 2 * BORDER_PADDING;
const paddedHeight = bbox.height + 2 * BORDER_PADDING;
if (options.preserveDimensions) {
svgClone.setAttribute("width", paddedWidth.toString());
svgClone.setAttribute("height", paddedHeight.toString());
} else {
svgClone.setAttribute("width", "100%");
svgClone.setAttribute("height", "100%");
}
const viewBox = {
x: bbox.x - BORDER_PADDING,
y: bbox.y - BORDER_PADDING,
width: paddedWidth,
height: paddedHeight
};
svgClone.setAttribute("viewBox", `${viewBox.x} ${viewBox.y} ${viewBox.width} ${viewBox.height}`);
addWatermark(svgClone, viewBox, watermarkSvg);
const images = [];
foreachNode(svgClone.querySelectorAll("img"), (node) => images.push(node));
return Promise.all(images.map((img) => {
const exportKey = img.getAttribute("export-key");
img.removeAttribute("export-key");
if (exportKey) {
const { width, height } = imageBounds[exportKey];
img.setAttribute("width", width.toString());
img.setAttribute("height", height.toString());
if (!options.convertImagesToDataUris) return Promise.resolve();
return exportAsDataUri(img).then((dataUri) => {
if (dataUri && dataUri !== "data:image/svg+xml,") img.src = dataUri;
}).catch((err) => {
console.warn("Failed to export image: " + img.src, err);
});
} else return Promise.resolve();
})).then(() => {
const exportedCssText = extractCSSFromDocument(svgClone);
const exportedVariables = extractCSSVariables(options.paper.closest(".graph-explorer"));
const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs");
defs.innerHTML = `<style>${exportedVariables}\n${exportedCssText}</style>`;
svgClone.insertBefore(defs, svgClone.firstChild);
if (options.elementsToRemoveSelector) foreachNode(svgClone.querySelectorAll(options.elementsToRemoveSelector), (node) => node.remove());
return svgClone;
});
}
function addWatermark(svg, viewBox, watermarkSvg) {
const WATERMARK_CLASS = "graph-explorer-exported-watermark";
const WATERMARK_MAX_WIDTH = 120;
const WATERMARK_PADDING = 20;
const image = document.createElementNS(SVG_NAMESPACE, "image");
image.setAttributeNS("http://www.w3.org/1999/xlink", "href", watermarkSvg);
image.setAttribute("class", WATERMARK_CLASS);
const width = Math.min(viewBox.width * .2, WATERMARK_MAX_WIDTH);
const x = viewBox.x + viewBox.width - width - WATERMARK_PADDING;
const y = viewBox.y + WATERMARK_PADDING;
image.setAttribute("x", x.toString());
image.setAttribute("y", y.toString());
image.setAttribute("width", width.toString());
image.setAttribute("opacity", "0.3");
svg.insertBefore(image, svg.firstChild);
}
function forEachStyleRule(callback) {
for (let i = 0; i < document.styleSheets.length; i++) {
let rules;
try {
const cssSheet = document.styleSheets[i];
rules = cssSheet.cssRules || cssSheet.rules;
if (!rules) continue;
} catch (_e) {
continue;
}
for (let j = 0; j < rules.length; j++) {
const rule = rules[j];
if (rule instanceof CSSStyleRule) callback(rule);
}
}
}
/**
* The exported SVG is detached from the workspace, so CSS custom properties
* (the design tokens) declared on the workspace root are not in scope and every
* `var(--…)` reference in the exported rules would resolve to nothing, dropping
* colours, radii and shadows. Re-declare the resolved values on the exported
* root so the export matches what is on the canvas.
*/
function extractCSSVariables(source) {
if (!source) return "";
const computed = getComputedStyle(source);
const declarations = /* @__PURE__ */ new Map();
forEachStyleRule((rule) => {
for (let i = 0; i < rule.style.length; i++) {
const property = rule.style.item(i);
if (!property.startsWith("--") || declarations.has(property)) continue;
const value = computed.getPropertyValue(property).trim();
if (value) declarations.set(property, value);
}
});
if (declarations.size === 0) return "";
return `svg { ${Array.from(declarations, ([name, value]) => `${name}: ${value};`).join(" ")} }`;
}
function extractCSSFromDocument(targetSubtree) {
const exportedRules = /* @__PURE__ */ new Set();
forEachStyleRule((rule) => {
try {
if (targetSubtree.querySelector(rule.selectorText)) exportedRules.add(rule);
} catch (_e) {}
});
const exportedCssTexts = [];
exportedRules.forEach((rule) => exportedCssTexts.push(rule.cssText));
return exportedCssTexts.join("\n");
}
function clonePaperSvg(options, elementSizePadding) {
const { model, paper, getOverlayedElement } = options;
const svgClone = paper.cloneNode(true);
svgClone.removeAttribute("class");
svgClone.removeAttribute("style");
function findViewport() {
let child = svgClone.firstChild;
while (child) {
if (child instanceof SVGGElement) return child;
child = child.nextSibling;
}
}
const viewport = findViewport();
viewport.removeAttribute("transform");
const imageBounds = {};
for (const element of model.elements) {
const modelId = element.id;
const overlayedView = getOverlayedElement(modelId);
if (!overlayedView) continue;
const elementRoot = document.createElementNS(SVG_NAMESPACE, "g");
const overlayedViewContent = overlayedView.firstChild.cloneNode(true);
elementRoot.setAttribute("class", "graph-explorer-exported-element");
const newRoot = document.createElementNS(SVG_NAMESPACE, "foreignObject");
newRoot.appendChild(overlayedViewContent);
const { x, y, width, height } = boundsOf(element);
newRoot.setAttribute("transform", `translate(${x},${y})`);
newRoot.setAttribute("width", (width + elementSizePadding).toString());
newRoot.setAttribute("height", (height + elementSizePadding).toString());
elementRoot.appendChild(newRoot);
viewport.appendChild(elementRoot);
const clonedNodes = overlayedViewContent.querySelectorAll("img");
foreachNode(overlayedView.querySelectorAll("img"), (img, index) => {
const exportKey = uniqueId("export-key-");
clonedNodes[index].setAttribute("export-key", exportKey);
imageBounds[exportKey] = {
width: img.clientWidth,
height: img.clientHeight
};
});
}
return {
svgClone,
imageBounds
};
}
function exportAsDataUri(original) {
const url = original.src;
if (!url || url.startsWith("data:")) return Promise.resolve(url);
return loadCrossOriginImage(original.src).then((image) => {
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
canvas.getContext("2d").drawImage(image, 0, 0);
const extensionMatch = url.match(/\.([a-zA-Z0-9]+)[^.a-zA-Z0-9]?[^.]*$/);
const extension = extensionMatch ? extensionMatch[1].toLowerCase() : "png";
try {
const mimeType = "image/" + (extension === "jpg" ? "jpeg" : extension);
const dataUri = canvas.toDataURL(mimeType);
return Promise.resolve(dataUri);
} catch (_e) {
if (extension !== "svg") return Promise.reject("Failed to convert image to data URI");
return fetch(url).then((response) => response.text()).then((svg) => svg.length > 0 ? "data:image/svg+xml," + encodeURIComponent(svg) : "");
}
});
}
function loadCrossOriginImage(src) {
const image = new Image();
image.crossOrigin = "anonymous";
const promise = new Promise((resolve, reject) => {
image.onload = () => resolve(image);
image.onerror = (ev) => reject(ev);
});
image.src = src;
return promise;
}
function foreachNode(nodeList, callback) {
for (let i = 0; i < nodeList.length; i++) callback(nodeList[i], i);
}
var MAX_CANVAS_LENGTH = 4096;
async function toDataURL(options) {
const { paper: _paper, mimeType = "image/png" } = options;
const svg = await exportSVG({
...options,
convertImagesToDataUris: true,
mockImages: false,
preserveDimensions: true
});
const svgBox = {
width: Number(svg.getAttribute("width")),
height: Number(svg.getAttribute("height"))
};
const { innerSize, outerSize, offset } = computeAutofit(svgBox, typeof options.width === "number" || typeof options.height === "number" ? {
width: options.width,
height: options.height
} : fallbackContainerSize(svgBox));
svg.setAttribute("width", innerSize.width.toString());
svg.setAttribute("height", innerSize.height.toString());
const svgString = new XMLSerializer().serializeToString(svg);
const { canvas, context } = createCanvas(outerSize.width, outerSize.height, options.backgroundColor);
const image = await loadImage("data:image/svg+xml," + encodeURIComponent(svgString));
context.drawImage(image, offset.x, offset.y, innerSize.width, innerSize.height);
return canvas.toDataURL(mimeType, options.quality);
function createCanvas(canvasWidth, canvasHeight, backgroundColor) {
const cnv = document.createElement("canvas");
cnv.width = canvasWidth;
cnv.height = canvasHeight;
const cnt = cnv.getContext("2d");
if (backgroundColor) {
cnt.fillStyle = backgroundColor;
cnt.fillRect(0, 0, canvasWidth, canvasHeight);
}
return {
canvas: cnv,
context: cnt
};
}
}
function loadImage(source) {
return new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function(ev) {
reject(ev);
};
image.src = source;
});
}
function computeAutofit(itemSize, containerSize) {
const fit = fitRectKeepingAspectRatio(itemSize.width, itemSize.height, containerSize.width, containerSize.height);
const innerSize = {
width: Math.floor(fit.width),
height: Math.floor(fit.height)
};
const outerSize = {
width: typeof containerSize.width === "number" ? containerSize.width : innerSize.width,
height: typeof containerSize.height === "number" ? containerSize.height : innerSize.height
};
return {
innerSize,
outerSize,
offset: {
x: Math.round((outerSize.width - innerSize.width) / 2),
y: Math.round((outerSize.height - innerSize.height) / 2)
}
};
}
function fallbackContainerSize(itemSize) {
const maxResolutionScale = Math.min(MAX_CANVAS_LENGTH / itemSize.width, MAX_CANVAS_LENGTH / itemSize.height);
const resolutionScale = Math.min(2, maxResolutionScale);
return {
width: Math.floor(itemSize.width * resolutionScale),
height: Math.floor(itemSize.height * resolutionScale)
};
}
function fitRectKeepingAspectRatio(sourceWidth, sourceHeight, targetWidth, targetHeight) {
if (!(typeof targetWidth === "number" || typeof targetHeight === "number")) return {
width: sourceWidth,
height: sourceHeight
};
const sourceAspectRatio = sourceWidth / sourceHeight;
targetWidth = typeof targetWidth === "number" ? targetWidth : targetHeight * sourceAspectRatio;
targetHeight = typeof targetHeight === "number" ? targetHeight : targetWidth / sourceAspectRatio;
if (targetHeight * sourceAspectRatio <= targetWidth) return {
width: targetHeight * sourceAspectRatio,
height: targetHeight
};
else return {
width: targetWidth,
height: targetWidth / sourceAspectRatio
};
}
/**
* Creates and returns a blob from a data URL (either base64 encoded or not).
*
* @param {string} dataURL The data URL to convert.
* @return {Blob} A blob representing the array buffer data.
*/
function dataURLToBlob(dataURL) {
const BASE64_MARKER = ";base64,";
if (dataURL.indexOf(BASE64_MARKER) === -1) {
const parts = dataURL.split(",");
const contentType = parts[0].split(":")[1];
const raw = decodeURIComponent(parts[1]);
return new Blob([raw], { type: contentType });
} else {
const parts = dataURL.split(BASE64_MARKER);
const contentType = parts[0].split(":")[1];
const raw = window.atob(parts[1]);
const rawLength = raw.length;
const uInt8Array = new Uint8Array(rawLength);
for (let i = 0; i < rawLength; ++i) uInt8Array[i] = raw.charCodeAt(i);
return new Blob([uInt8Array], { type: contentType });
}
}
//#endregion
//#region src/graph-explorer/diagram/paperArea.tsx
var PaperAreaContext = React.createContext(null);
var CLASS_NAME$22 = "graph-explorer-paper-area";
var DEFAULT_ANIMATION_DURATION = 500;
var LEFT_MOUSE_BUTTON = 0;
var MIDDLE_MOUSE_BUTTON = 1;
/** Spacing of the background dot grid, in paper (unscaled) coordinates. */
var GRID_STEP = 24;
var PaperArea = class extends React.Component {
get zoomOptions() {
const { min = .2, max = 2, step = .1, maxFit = 1, fitPadding = 20, requireCtrl = true } = this.props.zoomOptions || {};
return {
min,
max,
step,
maxFit,
fitPadding,
requireCtrl
};
}
constructor(props, context) {
super(props, context);
this.listener = new EventObserver();
this.source = new EventSource();
this.events = this.source;
this.widgets = {};
this.pageSize = {
x: 1500,
y: 800
};
this.cssAnimations = 0;
this.delayedPaperAdjust = new Debouncer();
this.onOuterMount = (outer) => {
this.outer = outer;
};
this.onAreaMount = (area) => {
this.area = area;
};
this.onWidgetsMouseDown = (e) => {
e.stopPropagation();
};
this.adjustPaper = (callback) => {
const { clientWidth, clientHeight } = this.area;
const adjusted = {
...this.computeAdjustedBox(),
paddingX: Math.ceil(clientWidth),
paddingY: Math.ceil(clientHeight)
};
const previous = this.state;
if (!(adjusted.paperWidth === previous.paperWidth && adjusted.paperHeight === previous.paperHeight && adjusted.originX === previous.originX && adjusted.originY === previous.originY && adjusted.paddingX === previous.paddingX && adjusted.paddingY === previous.paddingY)) {
this.scrollBeforeUpdate = {
left: this.area.scrollLeft,
top: this.area.scrollTop
};
this.setState(adjusted, callback);
} else if (callback) callback();
};
this.onPaperPointerDown = (e, cell) => {
if (this.movingState) return;
const restore = RestoreGeometry.capture(this.props.view.model);
const batch = this.props.view.model.history.startBatch(restore.title);
if (cell && e.button === LEFT_MOUSE_BUTTON) if (cell instanceof Element$1) {
e.preventDefault();
this.startMoving(e, cell);
this.listenToPointerMove(e, cell, batch, restore);
} else {
e.preventDefault();
this.listenToPointerMove(e, cell, batch, restore);
}
else {
e.preventDefault();
this.listenToPointerMove(e, void 0, batch, restore);
}
};
this.onAreaPointerDown = (e) => {
if (e.target === this.area) this.onPaperPointerDown(e, void 0);
};
this.onPointerMove = (e) => {
if (!this.movingState || this.scrollBeforeUpdate) return;
const { origin, target, panning } = this.movingState;
const pageOffsetX = e.pageX - origin.pageX;
const pageOffsetY = e.pageY - origin.pageY;
if (Math.abs(pageOffsetX) >= 1 && Math.abs(pageOffsetY) >= 1) this.movingState.pointerMoved = true;
if (typeof target === "undefined") {
if (panning) {
this.area.scrollLeft = this.panningScrollOrigin.scrollLeft - pageOffsetX;
this.area.scrollTop = this.panningScrollOrigin.scrollTop - pageOffsetY;
}
this.source.trigger("pointerMove", {
source: this,
sourceEvent: e,
target,
panning
});
} else if (target instanceof Element$1) {
const { x, y } = this.pageToPaperCoords(e.pageX, e.pageY);
const { pointerX, pointerY, elementX, elementY } = this.movingElementOrigin;
target.setPosition({
x: elementX + x - pointerX,
y: elementY + y - pointerY
});
this.source.trigger("pointerMove", {
source: this,
sourceEvent: e,
target,
panning
});
this.props.view.performSyncUpdate();
} else if (target instanceof Link) {
const location = this.pageToPaperCoords(e.pageX, e.pageY);
const linkVertex = this.generateLinkVertex(target, location);
linkVertex.createAt(location);
this.movingState.target = linkVertex;
} else if (target instanceof LinkVertex) {
const location = this.pageToPaperCoords(e.pageX, e.pageY);
target.moveTo(location);
this.source.trigger("pointerMove", {
source: this,
sourceEvent: e,
target,
panning
});
this.props.view.performSyncUpdate();
}
};
this.stopListeningToPointerMove = (e) => {
const movingState = this.movingState;
this.movingState = void 0;
if (movingState) {
document.removeEventListener("mousemove", this.onPointerMove);
document.removeEventListener("mouseup", this.stopListeningToPointerMove);
}
if (e && movingState) {
const { pointerMoved, target, batch, restoreGeometry } = movingState;
this.source.trigger("pointerUp", {
source: this,
sourceEvent: e,
target,
panning: movingState.panning,
triggerAsClick: !pointerMoved
});
const restore = restoreGeometry.filterOutUnchanged();
if (restore.hasChanges()) batch.history.registerToUndo(restore);
batch.store();
}
};
this.onWheel = (e) => {
if (this.shouldStartZooming(e)) {
e.preventDefault();
const delta = Math.max(-1, Math.min(1, e.deltaY || e.deltaX));
const pivot = this.pageToPaperCoords(e.pageX, e.pageY);
this.zoomBy(-delta * .1, { pivot });
}
};
this.onDragOver = (e) => {
if (e.preventDefault) e.preventDefault();
e.dataTransfer.dropEffect = "move";
const { x: _x, y: _y } = clientCoordsFor(this.area, e);
return false;
};
this.onDragDrop = (e) => {
if (this.props.onDragDrop) {
const { x, y } = clientCoordsFor(this.area, e);
const paperPosition = this.clientToPaperCoords(x, y);
this.props.onDragDrop(e, paperPosition);
}
};
this.onScroll = () => {
this.source.trigger("scroll", { source: this });
};
this.applyViewportState = (targetState) => {
const scale = targetState.scale.x;
const paperCenter = targetState.center;
this.setState({ scale }, () => {
const { originX, originY, paddingX, paddingY } = this.state;
const scrollCenterX = (paperCenter.x + originX) * scale;
const scrollCenterY = (paperCenter.y + originY) * scale;
const { clientWidth, clientHeight } = this.area;
this.area.scrollLeft = scrollCenterX - clientWidth / 2 + paddingX;
this.area.scrollTop = scrollCenterY - clientHeight / 2 + paddingY;
if (this.props.onZoom) this.props.onZoom(scale, scale);
});
};
this.state = {
paperWidth: this.pageSize.x,
paperHeight: this.pageSize.y,
originX: 0,
originY: 0,
scale: 1,
paddingX: 0,
paddingY: 0,
renderedWidgets: []
};
}
render() {
const { view, watermarkSvg, watermarkUrl } = this.props;
const { paperWidth, paperHeight, originX, originY, scale, paddingX, paddingY, renderedWidgets } = this.state;
const paperTransform = {
width: paperWidth,
height: paperHeight,
originX,
originY,
scale,
paddingX,
paddingY
};
const widgetProps = {
paperArea: this,
paperTransform
};
let areaClass = `${CLASS_NAME$22}__area`;
if (this.props.hideScrollBars) areaClass += ` ${CLASS_NAME$22}--hide-scrollbars`;
let componentClass = CLASS_NAME$22;
if (this.isAnimatingGraph()) componentClass += ` ${CLASS_NAME$22}--animated`;
const gridStep = GRID_STEP * scale;
const gridStyle = {
backgroundSize: `${gridStep}px ${gridStep}px`,
backgroundPosition: `${paddingX + originX * scale}px ${paddingY + originY * scale}px`
};
return /* @__PURE__ */ React.createElement(PaperAreaContext.Provider, { value: { paperArea: {
paperArea: this,
view
} } }, /* @__PURE__ */ React.createElement("div", {
className: componentClass,
ref: this.onOuterMount
}, /* @__PURE__ */ React.createElement("div", {
className: areaClass,
ref: this.onAreaMount,
style: gridStyle,
onMouseDown: this.onAreaPointerDown
}, /* @__PURE__ */ React.createElement(Paper, {
view,
paperTransform,
onPointerDown: this.onPaperPointerDown,
linkLayerWidgets: /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$22}__widgets`,
onMouseDown: this.onWidgetsMouseDown
}, renderedWidgets.filter((w) => w.attachment === WidgetAttachment.OverLinks).map((widget) => React.cloneElement(widget.element, widgetProps))),
elementLayerWidgets: /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$22}__widgets`,
onMouseDown: this.onWidgetsMouseDown
}, renderedWidgets.filter((w) => w.attachment === WidgetAttachment.OverElements).map((widget) => React.cloneElement(widget.element, widgetProps)))
}), watermarkSvg ? /* @__PURE__ */ React.createElement("a", {
href: watermarkUrl,
target: "_blank",
rel: "noopener noreferrer"
}, /* @__PURE__ */ React.createElement("img", {
className: `${CLASS_NAME$22}__watermark`,
src: watermarkSvg,
draggable: false
})) : null), renderedWidgets.filter((w) => w.attachment === WidgetAttachment.Viewport).map((widget) => {
return React.cloneElement(widget.element, widgetProps);
})));
}
componentDidMount() {
this.adjustPaper(() => this.centerTo());
const { view } = this.props;
const delayedAdjust = () => this.delayedPaperAdjust.call(this.adjustPaper);
this.listener.listen(view.model.events, "changeCells", delayedAdjust);
this.listener.listen(view.model.events, "elementEvent", ({ data }) => {
if (data.changePosition || data.changeSize) delayedAdjust();
});
this.listener.listen(view.model.events, "linkEvent", ({ data }) => {
if (data.changeVertices) delayedAdjust();
});
this.listener.listen(view.events, "syncUpdate", ({ layer }) => {
if (layer !== RenderingLayer.PaperArea) return;
this.delayedPaperAdjust.runSynchronously();
});
this.listener.listen(view.events, "updateWidgets", ({ widgets }) => {
this.updateWidgets(widgets);
});
this.area.addEventListener("dragover", this.onDragOver);
this.area.addEventListener("drop", this.onDragDrop);
this.area.addEventListener("scroll", this.onScroll);
this.area.addEventListener("wheel", this.onWheel, { passive: false });
}
componentDidUpdate(prevProps, prevState) {
if (this.scrollBeforeUpdate) {
const { scale, originX, originY, paddingX, paddingY } = this.state;
const scrollX = (originX - prevState.originX) * scale + (paddingX - prevState.paddingX);
const scrollY = (originY - prevState.originY) * scale + (paddingY - prevState.paddingY);
const scrollLeft = this.scrollBeforeUpdate.left + scrollX;
const scrollTop = this.scrollBeforeUpdate.top + scrollY;
this.area.scrollLeft = scrollLeft;
this.area.scrollTop = scrollTop;
this.scrollBeforeUpdate = void 0;
}
}
componentWillUnmount() {
this.stopListeningToPointerMove();
this.listener.stopListening();
this.area.removeEventListener("dragover", this.onDragOver);
this.area.removeEventListener("drop", this.onDragDrop);
this.area.removeEventListener("scroll", this.onScroll);
this.area.removeEventListener("wheel", this.onWheel);
}
updateWidgets(update) {
this.widgets = {
...this.widgets,
...update
};
const renderedWidgets = Object.keys(this.widgets).filter((key) => this.widgets[key]).map((key) => {
const widget = this.widgets[key];
const element = React.cloneElement(widget.element, { key });
return {
...widget,
element
};
});
this.setState({ renderedWidgets });
}
pageToPaperCoords(pageX, pageY) {
const { left, top } = this.area.getBoundingClientRect();
return this.clientToPaperCoords(pageX - (left + window.pageXOffset), pageY - (top + window.pageYOffset));
}
clientToPaperCoords(areaClientX, areaClientY) {
const { x: paneX, y: paneY } = this.clientToScrollablePaneCoords(areaClientX, areaClientY);
return this.scrollablePaneToPaperCoords(paneX, paneY);
}
clientToScrollablePaneCoords(areaClientX, areaClientY) {
const { paddingX, paddingY } = this.state;
return {
x: areaClientX + this.area.scrollLeft - paddingX,
y: areaClientY + this.area.scrollTop - paddingY
};
}
scrollablePaneToPaperCoords(paneX, paneY) {
const { scale, originX, originY } = this.state;
return {
x: paneX / scale - originX,
y: paneY / scale - originY
};
}
paperToScrollablePaneCoords(paperX, paperY) {
const { scale, originX, originY } = this.state;
return {
x: (paperX + originX) * scale,
y: (paperY + originY) * scale
};
}
/** Returns bounding box of paper content in paper coordinates. */
getContentFittingBox() {
const { elements, links } = this.props.view.model;
return getContentFittingBox(elements, links);
}
/** Returns paper size in paper coordinates. */
getPaperSize() {
const { paperWidth: width, paperHeight: height, scale } = this.state;
return {
width: width / scale,
height: height / scale
};
}
getAreaMetrics() {
const { clientWidth, clientHeight, offsetWidth, offsetHeight } = this.area;
return {
clientWidth,
clientHeight,
offsetWidth,
offsetHeight
};
}
computeAdjustedBox() {
const bbox = this.getContentFittingBox();
const bboxLeft = bbox.x;
const bboxTop = bbox.y;
const bboxRight = bbox.x + bbox.width;
const bboxBottom = bbox.y + bbox.height;
const { x: gridWidth, y: gridHeight } = this.pageSize;
const bboxGrid = {
left: Math.floor(bboxLeft / gridWidth),
top: Math.floor(bboxTop / gridHeight),
right: Math.ceil(bboxRight / gridWidth),
bottom: Math.ceil(bboxBottom / gridHeight)
};
const originX = -bboxGrid.left * gridWidth;
const originY = -bboxGrid.top * gridHeight;
return {
paperWidth: Math.max(bboxGrid.right - bboxGrid.left, 1) * gridWidth,
paperHeight: Math.max(bboxGrid.bottom - bboxGrid.top, 1) * gridHeight,
originX,
originY
};
}
shouldStartZooming(e) {
return Boolean(e.ctrlKey) && Boolean(this.zoomOptions.requireCtrl) || !this.zoomOptions.requireCtrl;
}
shouldStartPanning(e) {
if (e.button === MIDDLE_MOUSE_BUTTON) return true;
const modifierPressed = e.ctrlKey || e.shiftKey || e.altKey;
return e.button === LEFT_MOUSE_BUTTON && !modifierPressed;
}
startMoving(e, element) {
const { x: pointerX, y: pointerY } = this.pageToPaperCoords(e.pageX, e.pageY);
const { x: elementX, y: elementY } = element.position;
this.movingElementOrigin = {
pointerX,
pointerY,
elementX,
elementY
};
}
startPanning(_event) {
const { scrollLeft, scrollTop } = this.area;
this.panningScrollOrigin = {
scrollLeft,
scrollTop
};
this.clearTextSelectionInArea();
}
/** Clears accidental text selection in the diagram area. */
clearTextSelectionInArea() {
if (document.getSelection) {
const selection = document.getSelection();
if (selection.removeAllRanges) selection.removeAllRanges();
}
}
generateLinkVertex(link, location) {
const previous = link.vertices;
const vertices = previous ? [...previous] : [];
const model = this.props.view.model;
return new LinkVertex(link, findNearestSegmentIndex(computePolyline(model.getElement(link.sourceId), model.getElement(link.targetId), vertices), location));
}
listenToPointerMove(event, cell, batch, restoreGeometry) {
if (this.movingState) return;
const panning = cell === void 0 && this.shouldStartPanning(event);
if (panning) this.startPanning(event);
const { pageX, pageY } = event;
this.movingState = {
origin: {
pageX,
pageY
},
target: cell,
panning,
pointerMoved: false,
batch,
restoreGeometry
};
document.addEventListener("mousemove", this.onPointerMove);
document.addEventListener("mouseup", this.stopListeningToPointerMove);
this.source.trigger("pointerDown", {
source: this,
sourceEvent: event,
target: cell,
panning
});
}
centerTo(paperPosition, options = {}) {
const { paperWidth, paperHeight } = this.state;
const viewportState = { center: paperPosition || {
x: paperWidth / 2,
y: paperHeight / 2
} };
return this.setViewportState(viewportState, options);
}
centerContent(options = {}) {
const bbox = this.getContentFittingBox();
return this.centerTo({
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
}, options);
}
getScale() {
return this.state.scale;
}
setScale(value, options) {
let scale = value;
const { min, max } = this.zoomOptions;
scale = Math.max(scale, min);
scale = Math.min(scale, max);
let viewportState;
if (options && options.pivot) {
const { x, y } = options.pivot;
const paperCenter = this.clientToPaperCoords(this.area.clientWidth / 2, this.area.clientHeight / 2);
const previousScale = this.state.scale;
const scaledBy = scale / previousScale;
viewportState = {
center: {
x: x - (x - paperCenter.x) / scaledBy,
y: y - (y - paperCenter.y) / scaledBy
},
scale: {
x: scale,
y: scale
}
};
} else viewportState = { scale: {
x: scale,
y: scale
} };
return this.setViewportState(viewportState, options);
}
zoomBy(value, options) {
return this.setScale(this.getScale() + value, options);
}
zoomIn(scaleOptions) {
return this.zoomBy(this.zoomOptions.step, scaleOptions);
}
zoomOut(scaleOptions) {
return this.zoomBy(-this.zoomOptions.step, scaleOptions);
}
zoomToFit(options = {}) {
if (options.elements && options.elements.size === 0) return;
if (this.props.view.model.elements.length === 0) return this.centerTo();
let elements;
if (options.elements) {
const selectionElements = [];
options.elements.forEach((el) => selectionElements.push(el));
elements = selectionElements;
} else elements = this.props.view.model.elements;
const bbox = getContentFittingBox(elements, []);
return this.zoomToFitRect(bbox, options);
}
zoomToFitRect(bbox, options = {}) {
const { clientWidth, clientHeight } = this.area;
if (bbox.width === 0) return;
const { width } = fitRectKeepingAspectRatio(bbox.width, bbox.height, clientWidth, clientHeight);
let scale = width / bbox.width;
const { min, maxFit } = this.zoomOptions;
scale = Math.max(scale, min);
scale = Math.min(scale, maxFit);
const viewPortState = {
center: {
x: bbox.x + bbox.width / 2,
y: bbox.y + bbox.height / 2
},
scale: {
x: scale,
y: scale
}
};
return this.setViewportState(viewPortState, options);
}
makeToSVGOptions() {
const svg = this.area.querySelector(".graph-explorer-paper__canvas");
if (!svg) throw new Error("Cannot find SVG canvas to export");
return {
model: this.props.view.model,
paper: svg,
contentBox: this.getContentFittingBox(),
getOverlayedElement: (id) => this.area.querySelector(`[data-element-id='${id}']`),
preserveDimensions: true,
convertImagesToDataUris: true,
elementsToRemoveSelector: ".graph-explorer-link__vertex-tools",
watermarkSvg: this.props.watermarkSvg
};
}
exportSVG() {
return toSVG(this.makeToSVGOptions());
}
exportPNG(options) {
return toDataURL({
...options,
...this.makeToSVGOptions()
});
}
isAnimatingGraph() {
return this.cssAnimations > 0;
}
/**
* Starts animation for graph elements and links.
*
* @param setupChanges immediately called function to perform animatable changes on graph
* @param duration animation duration in milliseconds (requires custom CSS to override)
* @returns promise which resolves when this animation ends
*/
animateGraph(setupChanges, duration) {
this.changeGraphAnimationCount(1);
setupChanges();
return delay(typeof duration === "number" ? duration : DEFAULT_ANIMATION_DURATION).then(() => this.onGraphAnimationEnd());
}
onGraphAnimationEnd() {
this.changeGraphAnimationCount(-1);
}
changeGraphAnimationCount(change) {
const newValue = this.cssAnimations + change;
if (newValue < 0) return;
const previous = this.isAnimatingGraph();
this.cssAnimations = newValue;
if (previous !== this.isAnimatingGraph()) {
this.forceUpdate();
this.source.trigger("changeAnimatingGraph", {
source: this,
previous
});
}
}
get viewportState() {
const { clientWidth, clientHeight } = this.area;
const { originX, originY, paddingX, paddingY, scale } = this.state;
const scrollCenterX = this.area.scrollLeft + clientWidth / 2 - paddingX;
const scrollCenterY = this.area.scrollTop + clientHeight / 2 - paddingY;
return {
center: {
x: scrollCenterX / scale - originX,
y: scrollCenterY / scale - originY
},
scale: {
x: scale,
y: scale
}
};
}
setViewportState(state, options) {
if (this.viewportAnimation) this.viewportAnimation.cancellation.abort();
const from = this.viewportState;
const to = {
...from,
...state
};
if (options && (options.animate || options.duration > 0)) {
const viewportAnimation = {
from,
to,
cancellation: new Cancellation()
};
const awaitPromise = animateInterval(typeof options.duration === "number" ? options.duration : DEFAULT_ANIMATION_DURATION, (progress) => {
const t = easeInOutBezier(progress);
const computed = {
center: {
x: from.center.x + (to.center.x - from.center.x) * t,
y: from.center.y + (to.center.y - from.center.y) * t
},
scale: {
x: from.scale.x + (to.scale.x - from.scale.x) * t,
y: from.scale.y + (to.scale.y - from.scale.y) * t
}
};
this.applyViewportState(computed);
}, viewportAnimation.cancellation);
this.viewportAnimation = viewportAnimation;
return awaitPromise.then(() => {
this.viewportAnimation = void 0;
});
} else {
this.applyViewportState(to);
return Promise.resolve();
}
}
};
function clientCoordsFor(container, e) {
const targetBox = (e.target instanceof SVGElement && e.target.ownerSVGElement !== null ? e.target.ownerSVGElement : e.target).getBoundingClientRect();
const containerBox = container.getBoundingClientRect();
return {
x: e.offsetX + (targetBox.left - containerBox.left),
y: e.offsetY + (targetBox.top - containerBox.top)
};
}
function getContentFittingBox(elements, links) {
let minX = Infinity, minY = Infinity;
let maxX = -Infinity, maxY = -Infinity;
for (const element of elements) {
const { x, y } = element.position;
const size = element.size;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x + size.width);
maxY = Math.max(maxY, y + size.height);
}
for (const link of links) {
const vertices = link.vertices || [];
for (const { x, y } of vertices) {
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x);
maxY = Math.max(maxY, y);
}
}
return {
x: Number.isFinite(minX) ? minX : 0,
y: Number.isFinite(minY) ? minY : 0,
width: Number.isFinite(minX) && Number.isFinite(maxX) ? maxX - minX : 0,
height: Number.isFinite(minY) && Number.isFinite(maxY) ? maxY - minY : 0
};
}
//#endregion
//#region src/graph-explorer/diagram/embeddedLayer.tsx
var EmbeddedLayerInner = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
this.nestedElementListener = new EventObserver();
this.layerOffsetLeft = 0;
this.layerOffsetTop = 0;
this.isApplyingParentMove = false;
this.isNestedElementMoving = false;
this.previousPositions = [];
this.moveNestedElements = (offsetX, offsetY) => {
this.isApplyingParentMove = true;
try {
for (const element of this.getNestedElements()) {
const { x, y } = element.position;
const newPosition = {
x: x + offsetX,
y: y + offsetY
};
element.setPosition(newPosition);
}
} finally {
this.isApplyingParentMove = false;
this.recomputeSelfBounds();
}
};
this.recomputeSelfBounds = () => {
if (this.isApplyingParentMove) return;
const { element } = this.props.context.graphExplorerElement;
const { x: offsetX, y: offsetY, width: paperWidth, height: paperHeight } = this.getContentFittingBox();
if (this.isNestedElementMoving) {
const position = {
x: offsetX - this.layerOffsetLeft,
y: offsetY - this.layerOffsetTop
};
element.setPosition(position);
}
this.setState({
offsetX,
offsetY,
paperWidth,
paperHeight
}, () => element.redraw());
};
this.onPaperPointerDown = (e, cell) => {
if (e.button !== 0) return;
if (cell && cell instanceof Element$1) {
e.preventDefault();
this.isNestedElementMoving = true;
}
};
this.onLayerInit = (layer) => {
if (!layer) return;
const { left, top } = this.calculateOffset(layer);
this.layerOffsetLeft = left;
this.layerOffsetTop = top;
};
this.state = {
paperWidth: 0,
paperHeight: 0,
offsetX: 0,
offsetY: 0
};
}
componentDidMount() {
const { element } = this.props.context.graphExplorerElement;
const { paperArea, view } = this.props.context.paperArea;
this.listener.listen(view.model.events, "changeGroupContent", ({ group }) => {
if (group === element.id) {
this.listenNestedElements(this.getNestedElements());
const { offsetX, offsetY } = this.getOffset();
this.moveNestedElements(offsetX, offsetY);
}
});
this.listener.listen(element.events, "changePosition", () => {
if (this.isNestedElementMoving) return;
const { offsetX, offsetY } = this.getOffset();
const { x, y } = this.getContentFittingBox();
const diffX = offsetX - x;
const diffY = offsetY - y;
this.moveNestedElements(diffX, diffY);
this.setState({
offsetX,
offsetY
});
});
this.listener.listen(paperArea.events, "pointerUp", (_e) => {
this.isNestedElementMoving = false;
});
const nestedElements = this.getNestedElements();
this.listenNestedElements(nestedElements);
if (nestedElements.length > 0) {
const { x: offsetX, y: offsetY, width: paperWidth, height: paperHeight } = this.getContentFittingBox();
this.setState({
offsetX,
offsetY,
paperWidth,
paperHeight
}, () => element.redraw());
} else element.requestGroupContent();
}
listenNestedElements(nestedElements) {
const listener = new EventObserver();
for (const nestedElement of nestedElements) {
listener.listen(nestedElement.events, "changePosition", this.recomputeSelfBounds);
listener.listen(nestedElement.events, "changeSize", this.recomputeSelfBounds);
}
this.nestedElementListener.stopListening();
this.nestedElementListener = listener;
}
componentWillUnmount() {
this.listener.stopListening();
this.nestedElementListener.stopListening();
this.removeElements();
this.setState({
paperWidth: 0,
paperHeight: 0,
offsetX: 0,
offsetY: 0
});
}
getNestedElements() {
const { element } = this.props.context.graphExplorerElement;
const { view } = this.props.context.paperArea;
return view.model.elements.filter((el) => el.group === element.id);
}
getContentFittingBox() {
return getContentFittingBox(this.getNestedElements(), []);
}
removeElements() {
const { view } = this.props.context.paperArea;
const batch = view.model.history.startBatch();
for (const element of this.getNestedElements()) view.model.removeElement(element.id);
batch.discard();
}
getOffset() {
const { element } = this.props.context.graphExplorerElement;
const { x: elementX, y: elementY } = element.position;
return {
offsetX: elementX + this.layerOffsetLeft,
offsetY: elementY + this.layerOffsetTop
};
}
calculateOffset(layer) {
const { paperArea } = this.props.context.paperArea;
const scale = paperArea.getScale();
const parent = findParentElement(layer);
const { left, top } = layer.getBoundingClientRect();
const { left: parentLeft, top: parentTop } = parent.getBoundingClientRect();
return {
left: (left - parentLeft) / scale,
top: (top - parentTop) / scale
};
}
render() {
const { element } = this.props.context.graphExplorerElement;
const { view } = this.props.context.paperArea;
const { paperWidth, paperHeight, offsetX, offsetY } = this.state;
const paperTransform = {
width: paperWidth,
height: paperHeight,
originX: -offsetX,
originY: -offsetY,
scale: 1,
paddingX: 0,
paddingY: 0
};
return /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-embedded-layer",
ref: this.onLayerInit
}, /* @__PURE__ */ React.createElement(Paper, {
view,
paperTransform,
onPointerDown: this.onPaperPointerDown,
group: element.id
}));
}
};
function EmbeddedLayer() {
const elementContext = React.useContext(ElementContext);
const paperAreaContext = React.useContext(PaperAreaContext);
return /* @__PURE__ */ React.createElement(EmbeddedLayerInner, { context: {
...elementContext,
...paperAreaContext
} });
}
function findParentElement(layer) {
const parent = layer.parentElement;
if (!parent) throw new Error("Cannot find parent diagram element for EmbeddedLayer");
else if (parent.hasAttribute("data-element-id")) return parent;
else return findParentElement(parent);
}
//#endregion
//#region src/graph-explorer/customization/templates/group.tsx
var CLASS = "graph-explorer-group-template";
var GroupTemplate = class extends React.Component {
render() {
const { label, iconUrl, types, color, isExpanded } = this.props;
return /* @__PURE__ */ React.createElement("div", { className: CLASS }, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS}__wrap`,
style: {
backgroundColor: color,
borderColor: color
}
}, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS}__type-line`,
title: label
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS}__type-line-icon` }, /* @__PURE__ */ React.createElement("img", { src: iconUrl })), /* @__PURE__ */ React.createElement("div", {
title: types,
className: `${CLASS}__type-line-text-container`
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS}__type-line-text` }, types))), /* @__PURE__ */ React.createElement("div", {
className: `${CLASS}__body`,
style: { borderColor: color }
}, /* @__PURE__ */ React.createElement("span", {
className: `${CLASS}__label`,
title: label
}, label), isExpanded ? /* @__PURE__ */ React.createElement("div", { className: `${CLASS}__embedded-layer` }, /* @__PURE__ */ React.createElement(EmbeddedLayer, null)) : null)));
}
};
//#endregion
//#region src/graph-explorer/workspace/workspaceContext.ts
var WorkspaceEventKey = /* @__PURE__ */ function(WorkspaceEventKey) {
WorkspaceEventKey["searchUpdateCriteria"] = "search:updateCriteria";
WorkspaceEventKey["searchQueryItem"] = "search:queryItems";
WorkspaceEventKey["connectionsLoadLinks"] = "connections:loadLinks";
WorkspaceEventKey["connectionsExpandLink"] = "connections:expandLink";
WorkspaceEventKey["connectionsLoadElements"] = "connections:loadElements";
WorkspaceEventKey["editorChangeSelection"] = "editor:changeSelection";
WorkspaceEventKey["editorToggleDialog"] = "editor:toggleDialog";
WorkspaceEventKey["editorAddElements"] = "editor:addElements";
return WorkspaceEventKey;
}({});
var WorkspaceContext = createContext(null);
//#endregion
//#region src/graph-explorer/viewUtils/collections.ts
function objectValues(obj) {
const items = [];
for (const key in obj) {
if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
items.push(obj[key]);
}
return items;
}
function isEmptyMap(map) {
for (const key in map) if (Object.prototype.hasOwnProperty.call(map, key)) return false;
return true;
}
/**
* Clones Map collection. Required due to IE11 not supporing `new Map(map)`.
*/
function cloneMap(map) {
const clone = /* @__PURE__ */ new Map();
map.forEach((value, key) => clone.set(key, value));
return clone;
}
/**
* Clones Set collection. Required due to IE11 not supporing `new Set(set)`.
*/
function cloneSet(set) {
const clone = /* @__PURE__ */ new Set();
set.forEach((item) => clone.add(item));
return clone;
}
function getOrCreateArrayInMap(map, key) {
let values = map.get(key);
if (!values) {
values = [];
map.set(key, values);
}
return values;
}
function getOrCreateSetInMap(map, key) {
let values = map.get(key);
if (!values) {
values = /* @__PURE__ */ new Set();
map.set(key, values);
}
return values;
}
var OrderedMap = class {
constructor() {
this.mapping = /* @__PURE__ */ new Map();
this.ordered = [];
}
reorder(compare) {
this.ordered.sort(compare);
}
get items() {
return this.ordered;
}
get(key) {
return this.mapping.get(key);
}
push(key, value) {
if (this.mapping.has(key)) {
const previous = this.mapping.get(key);
if (previous === value) return;
const index = this.ordered.indexOf(previous);
this.ordered.splice(index, 1);
}
this.mapping.set(key, value);
this.ordered.push(value);
}
delete(key) {
if (!this.mapping.has(key)) return;
const previous = this.mapping.get(key);
const index = this.ordered.indexOf(previous);
this.ordered.splice(index, 1);
this.mapping.delete(key);
return previous;
}
};
var HashMap = class HashMap {
constructor(hashCode, equals) {
this.hashCode = hashCode;
this.equals = equals;
this.map = /* @__PURE__ */ new Map();
this._size = 0;
}
get size() {
return this._size;
}
has(key) {
const items = this.map.get(this.hashCode(key));
if (!items) return false;
return Boolean(items.find((p) => this.equals(p.key, key)));
}
get(key) {
const items = this.map.get(this.hashCode(key));
if (!items) return;
const pair = items.find((p) => this.equals(p.key, key));
return pair ? pair.value : void 0;
}
set(key, value) {
const hash = this.hashCode(key);
let items = this.map.get(hash);
if (items) {
const index = items.findIndex((p) => this.equals(p.key, key));
if (index >= 0) items.splice(index, 1);
else this._size++;
items.push({
key,
value
});
} else {
items = [{
key,
value
}];
this.map.set(hash, items);
this._size++;
}
return this;
}
delete(key) {
const items = this.map.get(this.hashCode(key));
if (!items) return false;
const index = items.findIndex((p) => this.equals(p.key, key));
if (index >= 0) {
items.splice(index, 1);
this._size--;
return true;
} else return false;
}
clear() {
this.map.clear();
this._size = 0;
}
forEach(callback) {
this.map.forEach((items) => {
for (const { key, value } of items) callback(value, key, this);
});
}
clone() {
const clone = new HashMap(this.hashCode, this.equals);
clone._size = this.size;
this.map.forEach((value, key) => clone.map.set(key, [...value]));
return clone;
}
};
var MoveDirection = /* @__PURE__ */ function(MoveDirection) {
MoveDirection[MoveDirection["ToStart"] = -1] = "ToStart";
MoveDirection[MoveDirection["ToEnd"] = 1] = "ToEnd";
return MoveDirection;
}({});
function makeMoveComparator(items, selected, moveDirection) {
const orderMap = /* @__PURE__ */ new Map();
const selectionIndexOffset = moveDirection * items.length;
items.forEach((item, index) => {
orderMap.set(item, index);
});
for (const selectedItem of selected) orderMap.set(selectedItem, selectionIndexOffset + orderMap.get(selectedItem));
return (a, b) => {
const orderA = orderMap.get(a);
const orderB = orderMap.get(b);
return orderA > orderB ? 1 : orderA < orderB ? -1 : 0;
};
}
//#endregion
//#region src/graph-explorer/editor/authoringState.ts
var AuthoringKind = /* @__PURE__ */ function(AuthoringKind) {
AuthoringKind["ChangeElement"] = "changeElement";
AuthoringKind["ChangeLink"] = "changeLink";
return AuthoringKind;
}({});
var AuthoringState = {
empty: {
elements: /* @__PURE__ */ new Map(),
links: new HashMap(hashLink, sameLink)
},
isEmpty(state) {
return state.elements.size === 0 && state.links.size === 0;
},
clone(index) {
return {
elements: cloneMap(index.elements),
links: index.links.clone()
};
},
has(state, event) {
return event.type === "changeElement" ? state.elements.get(event.after.id) === event : state.links.get(event.after) === event;
},
discard(state, discarded) {
if (!this.has(state, discarded)) return state;
const newState = this.clone(state);
if (discarded.type === "changeElement") {
newState.elements.delete(discarded.after.id);
if (!discarded.before) state.links.forEach((e) => {
if (isLinkConnectedToElement(e.after, discarded.after.id)) newState.links.delete(e.after);
});
} else newState.links.delete(discarded.after);
return newState;
},
addElement(state, item) {
const event = {
type: "changeElement",
after: item,
deleted: false
};
const newState = this.clone(state);
newState.elements.set(event.after.id, event);
return newState;
},
addLink(state, item) {
const event = {
type: "changeLink",
after: item,
deleted: false
};
const newState = this.clone(state);
newState.links.set(event.after, event);
return newState;
},
changeElement(state, before, after) {
const newState = this.clone(state);
newState.elements.delete(before.id);
const previous = state.elements.get(before.id);
if (previous && !previous.before) {
newState.elements.set(after.id, {
type: "changeElement",
after,
deleted: false
});
if (before.id !== after.id) state.links.forEach((e) => {
if (!e.before && isLinkConnectedToElement(e.after, before.id)) {
const updatedLink = updateLinkToReferByNewIri(e.after, before.id, after.id);
newState.links.delete(e.after);
newState.links.set(updatedLink, {
type: "changeLink",
after: updatedLink,
deleted: false
});
}
});
} else {
const iriChanged = after.id !== before.id;
const previousBefore = previous ? previous.before : void 0;
newState.elements.set(before.id, {
type: "changeElement",
before: previousBefore || before,
after: iriChanged ? {
...after,
id: before.id
} : after,
newIri: iriChanged ? after.id : void 0,
deleted: false
});
}
return newState;
},
changeLink(state, before, after) {
if (!sameLink(before, after)) throw new Error("Cannot move link to another element or change its type");
const newState = this.clone(state);
const previous = state.links.get(before);
newState.links.set(before, {
type: "changeLink",
before: previous ? previous.before : void 0,
after,
deleted: false
});
return newState;
},
deleteElement(state, model) {
const newState = this.clone(state);
newState.elements.delete(model.id);
state.links.forEach((e) => {
if (isLinkConnectedToElement(e.after, model.id)) newState.links.delete(e.after);
});
if (!this.isNewElement(state, model.id)) newState.elements.set(model.id, {
type: "changeElement",
before: model,
after: model,
deleted: true
});
return newState;
},
deleteLink(state, target) {
const newState = this.clone(state);
newState.links.delete(target);
if (!this.isNewLink(state, target)) newState.links.set(target, {
type: "changeLink",
before: target,
after: target,
deleted: true
});
return newState;
},
deleteNewLinksConnectedToElements(state, elementIris) {
const newState = this.clone(state);
state.links.forEach((e) => {
if (!e.before) {
const target = e.after;
if (elementIris.has(target.sourceId) || elementIris.has(target.targetId)) newState.links.delete(target);
}
});
return newState;
},
isNewElement(state, target) {
const event = state.elements.get(target);
return event && event.type === "changeElement" && !event.before;
},
isDeletedElement(state, target) {
const event = state.elements.get(target);
return event && event.deleted;
},
isElementWithModifiedIri(state, target) {
const event = state.elements.get(target);
return event && event.type === "changeElement" && event.before && Boolean(event.newIri);
},
isNewLink(state, linkModel) {
const event = state.links.get(linkModel);
return event && !event.before;
},
isDeletedLink(state, linkModel) {
const event = state.links.get(linkModel);
return event && event.deleted || this.isDeletedElement(state, linkModel.sourceId) || this.isDeletedElement(state, linkModel.targetId);
},
isUncertainLink(state, linkModel) {
return !this.isDeletedLink(state, linkModel) && (this.isElementWithModifiedIri(state, linkModel.sourceId) || this.isElementWithModifiedIri(state, linkModel.targetId));
}
};
function isLinkConnectedToElement(link, elementIri) {
return link.sourceId === elementIri || link.targetId === elementIri;
}
function updateLinkToReferByNewIri(link, oldIri, newIri) {
return {
...link,
sourceId: link.sourceId === oldIri ? newIri : link.sourceId,
targetId: link.targetId === oldIri ? newIri : link.targetId
};
}
//#endregion
//#region src/graph-explorer/editor/authoredEntity.tsx
/**
* Component to simplify tracking changes in validation messages (property and link type labels).
*/
var AuthoredEntityInner = class extends React.Component {
constructor(props) {
super(props);
this.queryCancellation = new Cancellation();
this.onChangeAuthoringState = (e) => {
const { source: editor, previous } = e;
const iri = this.props.templateProps.data.id;
if (editor.authoringState.elements.get(iri) !== previous.elements.get(iri)) this.queryAllowedActions();
};
this.onEdit = () => {
const { editor } = this.props.context.workspace;
const { elementId } = this.props.templateProps;
const element = editor.model.getElement(elementId);
editor.showEditEntityForm(element);
};
this.onDelete = () => {
const { editor } = this.props.context.workspace;
const { data } = this.props.templateProps;
editor.deleteEntity(data.id);
};
this.state = {};
}
componentDidMount() {
const { editor } = this.props.context.workspace;
editor.events.on("changeAuthoringState", this.onChangeAuthoringState);
this.queryAllowedActions();
}
componentDidUpdate(prevProps) {
if (!(this.props.templateProps.data === prevProps.templateProps.data && this.props.templateProps.isExpanded === prevProps.templateProps.isExpanded)) this.queryAllowedActions();
}
componentWillUnmount() {
const { editor } = this.props.context.workspace;
editor.events.off("changeAuthoringState", this.onChangeAuthoringState);
this.queryCancellation.abort();
}
queryAllowedActions() {
const { isExpanded, data } = this.props.templateProps;
if (!isExpanded) return;
this.queryCancellation.abort();
this.queryCancellation = new Cancellation();
const { editor } = this.props.context.workspace;
if (!editor.metadataApi || AuthoringState.isDeletedElement(editor.authoringState, data.id)) this.setState({
canEdit: false,
canDelete: false
});
else {
this.queryCanEdit(data);
this.queryCanDelete(data);
}
}
queryCanEdit(data) {
const { editor } = this.props.context.workspace;
const signal = this.queryCancellation.signal;
this.setState({ canEdit: void 0 });
CancellationToken.mapCancelledToNull(signal, editor.metadataApi.canEditElement(data, signal)).then((canEdit) => {
if (canEdit === null) return;
this.setState({ canEdit });
});
}
queryCanDelete(data) {
const { editor } = this.props.context.workspace;
const signal = this.queryCancellation.signal;
this.setState({ canDelete: void 0 });
CancellationToken.mapCancelledToNull(signal, editor.metadataApi.canDeleteElement(data, signal)).then((canDelete) => {
if (canDelete === null) return;
this.setState({ canDelete });
});
}
render() {
const { children: renderTemplate } = this.props;
const { view } = this.props.context.paperArea;
const { editor } = this.props.context.workspace;
const { canEdit, canDelete } = this.state;
const iri = this.props.templateProps.iri;
const elementEvent = editor.authoringState.elements.get(iri);
return renderTemplate({
editor,
view,
canEdit,
canDelete,
editedIri: elementEvent && elementEvent.type === AuthoringKind.ChangeElement ? elementEvent.newIri : void 0,
onEdit: this.onEdit,
onDelete: this.onDelete
});
}
};
function AuthoredEntity(props) {
const paperAreaContext = React.useContext(PaperAreaContext);
const workspaceContext = React.useContext(WorkspaceContext);
return /* @__PURE__ */ React.createElement(AuthoredEntityInner, {
...props,
context: {
...paperAreaContext,
...workspaceContext
}
});
}
//#endregion
//#region src/graph-explorer/viewUtils/spinner.tsx
var CLASS_NAME$21 = "graph-explorer-spinner";
var Spinner = class extends React.Component {
render() {
const { position = {
x: 0,
y: 0
}, size = 50, statusText, errorOccured } = this.props;
const textLeftMargin = 5;
const pathGeometry = "m3.47,-19.7 a20,20 0 1,1 -6.95,0 m0,0 l-6,5 m6,-5 l-8,-0" + (errorOccured ? "M-8,-8L8,8M-8,8L8,-8" : "");
return /* @__PURE__ */ React.createElement("g", {
className: CLASS_NAME$21,
"data-error": errorOccured,
transform: `translate(${position.x},${position.y})`
}, /* @__PURE__ */ React.createElement("g", { className: `${CLASS_NAME$21}__arrow` }, /* @__PURE__ */ React.createElement("path", {
d: pathGeometry,
transform: `scale(0.02)scale(${size})`,
fill: "none",
stroke: errorOccured ? "red" : "black",
strokeWidth: "3",
strokeLinecap: "round"
})), /* @__PURE__ */ React.createElement("text", {
style: { dominantBaseline: "middle" },
x: size / 2 + textLeftMargin
}, statusText));
}
};
var HtmlSpinner = class extends React.Component {
render() {
const { width, height } = this.props;
const size = Math.min(width, height);
return /* @__PURE__ */ React.createElement("svg", {
width,
height
}, /* @__PURE__ */ React.createElement(Spinner, {
size,
position: {
x: width / 2,
y: height / 2
}
}));
}
};
//#endregion
//#region src/graph-explorer/customization/templates/standard.tsx
var FOAF_NAME = "http://xmlns.com/foaf/0.1/name";
var CLASS_NAME$20 = "graph-explorer-standard-template";
var StandardTemplate = class extends Component {
render() {
return /* @__PURE__ */ React.createElement(AuthoredEntity, { templateProps: this.props }, (context) => this.renderTemplate(context));
}
renderTemplate(context) {
const { color, types, isExpanded, iri, propsAsList } = this.props;
const label = this.getLabel();
const { editor } = context;
const leftStripeColor = AuthoringState.isNewElement(editor.authoringState, iri) ? "white" : color;
const pinnedProperties = this.findPinnedProperties(context);
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$20 }, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__main`,
style: {
backgroundColor: leftStripeColor,
borderColor: color
}
}, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__body`,
style: { borderLeftColor: color }
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__body-horizontal` }, this.renderThumbnail(), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__body-content` }, /* @__PURE__ */ React.createElement("div", {
title: types,
className: `${CLASS_NAME$20}__type`
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__type-value` }, this.getTypesLabel())), /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__label`,
title: label
}, label))), pinnedProperties ? /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__pinned-props`,
style: { borderColor: color }
}, this.renderProperties(pinnedProperties)) : null)), isExpanded ? /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__dropdown`,
style: { borderColor: color }
}, this.renderPhoto(), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__dropdown-content` }, this.renderIri(context), this.renderProperties(propsAsList), editor.inAuthoringMode ? /* @__PURE__ */ React.createElement("hr", { className: `${CLASS_NAME$20}__hr` }) : null, editor.inAuthoringMode ? this.renderActions(context) : null)) : null);
}
findPinnedProperties(context) {
const { isExpanded, propsAsList, elementId } = this.props;
if (isExpanded) return;
const templateState = context.view.model.getElement(elementId).elementState;
if (!templateState) return;
const pinned = templateState[TemplateProperties.PinnedProperties];
if (!pinned) return;
const filtered = propsAsList.filter((prop) => Boolean(pinned[prop.id]));
return filtered.length === 0 ? void 0 : filtered;
}
renderProperties(propsAsList) {
if (!propsAsList.length) return /* @__PURE__ */ React.createElement("div", null, "no properties");
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__properties` }, propsAsList.map(({ name, id, property }) => {
const propertyValues = getPropertyValues(property);
return /* @__PURE__ */ React.createElement("div", {
key: id,
className: `${CLASS_NAME$20}__properties-row`
}, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__properties-key`,
title: `${name} (${id})`
}, name), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__properties-values` }, propertyValues.map((text, index) => /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__properties-value`,
key: index,
title: text
}, text))));
}));
}
renderPhoto() {
const { color, imgUrl } = this.props;
if (!imgUrl) return null;
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__photo`,
style: { borderColor: color }
}, /* @__PURE__ */ React.createElement("img", {
src: imgUrl,
className: `${CLASS_NAME$20}__photo-image`
}));
}
renderIri(context) {
const { iri } = this.props;
const finalIri = context.editedIri === void 0 ? iri : context.editedIri;
return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__iri` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__iri-key` }, "IRI", context.editedIri ? "\xA0(edited)" : "", ":"), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__iri-value` }, isEncodedBlank(finalIri) ? /* @__PURE__ */ React.createElement("span", null, "(blank node)") : /* @__PURE__ */ React.createElement("a", {
href: finalIri,
title: finalIri,
"data-iri-click-intent": "openEntityIri"
}, finalIri))), /* @__PURE__ */ React.createElement("hr", { className: `${CLASS_NAME$20}__hr` }));
}
renderThumbnail() {
const { color, imgUrl, iconUrl } = this.props;
if (imgUrl) return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__thumbnail`,
"aria-hidden": "true"
}, /* @__PURE__ */ React.createElement("img", {
src: imgUrl,
className: `${CLASS_NAME$20}__thumbnail-image`
}));
else if (iconUrl) return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__thumbnail`,
"aria-hidden": "true"
}, /* @__PURE__ */ React.createElement("img", {
src: iconUrl,
className: `${CLASS_NAME$20}__thumbnail-icon`
}));
const typeLabel = this.getTypesLabel();
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$20}__thumbnail`,
"aria-hidden": "true",
style: { color }
}, typeLabel.length > 0 ? typeLabel.charAt(0).toUpperCase() : "✳");
}
getTypesLabel() {
return this.props.types;
}
getLabel() {
const { label, props } = this.props;
return getProperty(props, FOAF_NAME) || label;
}
renderActions(context) {
const { canEdit, canDelete, onEdit, onDelete } = context;
const SPINNER_WIDTH = 15;
const SPINNER_HEIGHT = 12;
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$20}__actions` }, /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: canDelete ? "Delete entity" : "Deletion is unavailable for the selected element",
disabled: !canDelete,
onClick: onDelete
}, /* @__PURE__ */ React.createElement("span", { className: "fa fa-trash" }), "\xA0", canEdit === void 0 ? /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: SPINNER_WIDTH,
height: SPINNER_HEIGHT
}) : "Delete"), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: canEdit ? "Edit entity" : "Editing is unavailable for the selected element",
disabled: !canEdit,
onClick: onEdit
}, /* @__PURE__ */ React.createElement("span", { className: "fa fa-edit" }), "\xA0", canEdit === void 0 ? /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: SPINNER_WIDTH,
height: SPINNER_HEIGHT
}) : "Edit"));
}
};
//#endregion
//#region src/graph-explorer/customization/templates/index.ts
var DefaultElementTemplateBundle = (_types) => void 0;
//#endregion
//#region src/graph-explorer/data/demo/provider.ts
var DemoDataProvider = class {
constructor(allClasses, allLinkTypes, allElements, allLinks) {
this.allClasses = allClasses;
this.allLinkTypes = allLinkTypes;
this.allElements = allElements;
this.allLinks = allLinks;
}
simulateNetwork(result) {
const MEAN_DELAY = 200;
const cloned = cloneDeep(result);
const delay = -Math.log(Math.random()) * MEAN_DELAY;
return new Promise((resolve) => {
setTimeout(() => resolve(cloned), delay);
});
}
classTree() {
return this.simulateNetwork(this.allClasses);
}
classInfo(params) {
const classIds = params.classIds || [];
return this.simulateNetwork(this.allClasses.filter((cl) => classIds.indexOf(cl.id)));
}
linkTypes() {
return this.simulateNetwork(this.allLinkTypes);
}
linkTypesInfo(params) {
const types = keyBy(params.linkTypeIds);
const linkTypes = this.allLinkTypes.filter((type) => types[type.id]);
return this.simulateNetwork(linkTypes);
}
elementInfo(params) {
const elements = params.elementIds.map((elementId) => this.allElements[elementId]).filter((element) => element !== void 0);
return this.simulateNetwork(keyBy(elements, (element) => element.id));
}
linksInfo(params) {
const nodes = keyBy(params.elementIds);
const types = keyBy(params.linkTypeIds);
const links = this.allLinks.filter((link) => types[link.linkTypeId] && nodes[link.sourceId] && nodes[link.targetId]);
return this.simulateNetwork(links);
}
linkTypesOf(params) {
const counts = {};
for (const link of this.allLinks) if (link.sourceId === params.elementId || link.targetId === params.elementId) {
const linkCount = counts[link.linkTypeId];
const isSource = link.sourceId === params.elementId;
if (linkCount) if (isSource) linkCount.outCount++;
else linkCount.inCount++;
else counts[link.linkTypeId] = isSource ? {
id: link.linkTypeId,
inCount: 0,
outCount: 1
} : {
id: link.linkTypeId,
inCount: 1,
outCount: 0
};
}
return this.simulateNetwork(map(counts));
}
linkElements(params) {
return this.filter({
refElementId: params.elementId,
refElementLinkId: params.linkId,
linkDirection: params.direction,
limit: params.limit,
offset: params.offset,
languageCode: ""
});
}
filter(params) {
if (params.limit === void 0) params.limit = 100;
if (params.offset > 0) return Promise.resolve({});
let filtered = {};
if (params.elementTypeId) each(this.allElements, (element) => {
if (element.types.indexOf(params.elementTypeId) >= 0) filtered[element.id] = element;
});
else if (params.refElementId) {
const filteredLinks = params.refElementLinkId ? this.allLinks.filter((link) => link.linkTypeId === params.refElementLinkId) : this.allLinks;
const nodeId = params.refElementId;
for (const link of filteredLinks) {
let linkedElementId;
if (link.sourceId === nodeId && params.linkDirection !== "in") linkedElementId = link.targetId;
else if (link.targetId === nodeId && params.linkDirection !== "out") linkedElementId = link.sourceId;
if (linkedElementId !== void 0) {
const linkedElement = this.allElements[linkedElementId];
if (linkedElement) filtered[linkedElement.id] = linkedElement;
}
}
} else if (params.text) filtered = this.allElements;
else return Promise.reject(/* @__PURE__ */ new Error("This type of filter is not implemented"));
if (params.text) {
const filteredByText = {};
const text = params.text.toLowerCase();
each(filtered, (element) => {
if (element.id.toLowerCase().indexOf(text) >= 0 || element.label.values.some((label) => label.value.toLowerCase().indexOf(text) >= 0)) filteredByText[element.id] = element;
});
return this.simulateNetwork(filteredByText);
} else return this.simulateNetwork(filtered);
}
};
//#endregion
//#region src/graph-explorer/data/sparql/responseHandler.ts
var LABEL_URI = "http://www.w3.org/2000/01/rdf-schema#label";
var RDF_TYPE_URI = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
var EMPTY_MAP = /* @__PURE__ */ new Map();
function getClassTree(response) {
const treeNodes = createClassMap(response.results.bindings);
const allNodes = [];
treeNodes.forEach((node) => {
allNodes.push(node);
node.parents.forEach((parent) => {
treeNodes.get(parent).children.push(node);
});
node.parents = void 0;
});
const withoutCycles = breakCyclesAndCalculateCounts(allNodes);
const leafs = /* @__PURE__ */ new Set();
for (const node of withoutCycles) for (const child of node.children) leafs.add(child.id);
return withoutCycles.filter((node) => !leafs.has(node.id));
}
function flattenClassTree(classTree) {
const all = [];
const visitClasses = (classes) => {
for (const model of classes) {
all.push(model);
visitClasses(model.children);
}
};
visitClasses(classTree);
return all;
}
function createClassMap(bindings) {
const treeNodes = /* @__PURE__ */ new Map();
for (const binding of bindings) {
if (!isRdfIri(binding.class)) continue;
const classIri = binding.class.value;
let node = treeNodes.get(classIri);
if (!node) {
node = createEmptyModel(classIri);
treeNodes.set(classIri, node);
}
if (binding.label) appendLabel(node.label, getLocalizedString(binding.label));
if (binding.parent) {
const parentIri = binding.parent.value;
node.parents.add(parentIri);
}
if (binding.instcount) node.count = getInstCount(binding.instcount);
}
for (const binding of bindings) if (binding.parent) {
const parentIri = binding.parent.value;
if (!treeNodes.has(parentIri)) treeNodes.set(parentIri, createEmptyModel(parentIri));
}
function createEmptyModel(iri) {
return {
id: iri,
children: [],
label: { values: [] },
count: void 0,
parents: /* @__PURE__ */ new Set()
};
}
return treeNodes;
}
function breakCyclesAndCalculateCounts(tree) {
const visiting = /* @__PURE__ */ new Set();
function reduceChildren(acc, node) {
if (visiting.has(node.id)) return acc;
if (!node.children) return;
visiting.add(node.id);
node.children = node.children.reduce(reduceChildren, []);
visiting.delete(node.id);
let childCount;
node.children.forEach(({ count }) => {
if (count === void 0) return;
childCount = childCount === void 0 ? count : childCount + count;
});
if (childCount !== void 0) node.count = node.count === void 0 ? childCount : node.count + childCount;
acc.push(node);
return acc;
}
return tree.reduce(reduceChildren, []);
}
function getClassInfo(response) {
const classes = {};
for (const binding of response.results.bindings) {
if (!binding.class) continue;
const id = binding.class.value;
const model = classes[id];
if (model) {
appendLabel(model.label, getLocalizedString(binding.label));
const instanceCount = getInstCount(binding.instcount);
if (instanceCount !== void 0) model.count = Math.max(model.count, instanceCount);
} else {
const label = getLocalizedString(binding.label);
classes[id] = {
id,
children: [],
label: { values: label ? [label] : [] },
count: getInstCount(binding.instcount)
};
}
}
const classesList = [];
for (const id in classes) {
if (!Object.prototype.hasOwnProperty.call(classes, id)) continue;
const model = classes[id];
classesList.push(model);
}
return classesList;
}
function getPropertyInfo(response) {
const models = {};
for (const sProperty of response.results.bindings) {
const sPropertyTypeId = sProperty.property.value;
if (models[sPropertyTypeId]) {
if (sProperty.label) {
const label = models[sPropertyTypeId].label;
if (label.values.length === 1 && !label.values[0].language) label.values = [];
label.values.push(getLocalizedString(sProperty.label));
}
} else models[sPropertyTypeId] = getPropertyModel(sProperty);
}
return models;
}
function getLinkTypes(response) {
const sInst = response.results.bindings;
const linkTypes = [];
const linkTypesMap = {};
for (const sLink of sInst) {
const sInstTypeId = sLink.link.value;
if (linkTypesMap[sInstTypeId]) {
if (sLink.label) {
const label = linkTypesMap[sInstTypeId].label;
if (label.values.length === 1 && !label.values[0].language) label.values = [];
label.values.push(getLocalizedString(sLink.label));
}
} else {
linkTypesMap[sInstTypeId] = getLinkTypeInfo(sLink);
linkTypes.push(linkTypesMap[sInstTypeId]);
}
}
return linkTypes;
}
function triplesToElementBinding(triples) {
const map = {};
const convertedResponse = {
head: { vars: [
"inst",
"class",
"label",
"blankType",
"propType",
"propValue"
] },
results: { bindings: [] }
};
for (const triple of triples) {
const trippleId = triple.subject.value;
if (!map[trippleId]) map[trippleId] = createAndPushBinding(triple);
if (triple.predicate.value === LABEL_URI && isRdfLiteral(triple.object)) {
if (map[trippleId].label) map[trippleId] = createAndPushBinding(triple);
map[trippleId].label = triple.object;
} else if (triple.predicate.value === RDF_TYPE_URI && isRdfIri(triple.object) && isRdfIri(triple.predicate)) {
if (map[trippleId].class) map[trippleId] = createAndPushBinding(triple);
map[trippleId].class = triple.object;
} else if (!isRdfBlank(triple.object) && isRdfIri(triple.predicate)) {
if (map[trippleId].propType) map[trippleId] = createAndPushBinding(triple);
map[trippleId].propType = triple.predicate;
map[trippleId].propValue = triple.object;
}
}
function createAndPushBinding(tripple) {
const binding = { inst: tripple.subject };
convertedResponse.results.bindings.push(binding);
return binding;
}
return convertedResponse;
}
function getElementsInfo(response, types = EMPTY_MAP, propertyByPredicate = EMPTY_MAP, openWorldProperties = true) {
const instancesMap = {};
for (const binding of response.results.bindings) {
if (!isRdfIri(binding.inst)) continue;
const iri = binding.inst.value;
let model = instancesMap[iri];
if (!model) {
model = emptyElementInfo(iri);
instancesMap[iri] = model;
}
enrichElement(model, binding);
}
if (!openWorldProperties || propertyByPredicate.size > 0) for (const iri in instancesMap) {
if (!Object.hasOwnProperty.call(instancesMap, iri)) continue;
const model = instancesMap[iri];
model.properties = mapPropertiesByConfig(model, types.get(model.id), propertyByPredicate, openWorldProperties);
}
return instancesMap;
}
function mapPropertiesByConfig(model, modelTypes, propertyByPredicate, openWorldProperties) {
const mapped = {};
for (const propertyIri in model.properties) {
if (!Object.hasOwnProperty.call(model.properties, propertyIri)) continue;
const properties = propertyByPredicate.get(propertyIri);
if (properties && properties.length > 0) {
for (const property of properties) if (typeMatchesDomain(property, modelTypes)) mapped[property.id] = model.properties[propertyIri];
} else if (openWorldProperties) mapped[propertyIri] = model.properties[propertyIri];
}
return mapped;
}
function enrichElementsWithImages(response, elementsInfo) {
const respElements = response.results.bindings;
for (const respEl of respElements) {
const elementInfo = elementsInfo[respEl.inst.value];
if (elementInfo) elementInfo.image = respEl.image.value;
}
}
function getElementTypes(response) {
const types = /* @__PURE__ */ new Map();
for (const binding of response.results.bindings) if (isRdfIri(binding.inst) && isRdfIri(binding.class)) {
const element = binding.inst.value;
const type = binding.class.value;
getOrCreateSetInMap(types, element).add(type);
}
return types;
}
function getLinksInfo(response, types = EMPTY_MAP, linkByPredicateType = EMPTY_MAP, openWorldLinks = true) {
const sparqlLinks = response.results.bindings;
const links = new HashMap(hashLink, sameLink);
for (const binding of sparqlLinks) {
const model = {
sourceId: binding.source.value,
linkTypeId: binding.type.value,
targetId: binding.target.value,
properties: {}
};
if (links.has(model)) {
if (binding.propType) mergeProperties$1(links.get(model).properties, binding.propType, binding.propValue);
} else {
if (binding.propType) mergeProperties$1(model.properties, binding.propType, binding.propValue);
const linkConfigs = linkByPredicateType.get(model.linkTypeId);
if (linkConfigs && linkConfigs.length > 0) {
for (const linkConfig of linkConfigs) if (typeMatchesDomain(linkConfig, types.get(model.sourceId))) {
const mappedModel = isDirectLink(linkConfig) ? {
...model,
linkTypeId: linkConfig.id
} : model;
links.set(mappedModel, mappedModel);
}
} else if (openWorldLinks) links.set(model, model);
}
}
const linkArray = [];
links.forEach((value) => linkArray.push(value));
return linkArray;
}
function getLinksTypesOf(response) {
return response.results.bindings.filter((b) => !isRdfBlank(b.link)).map((sLink) => getLinkCount(sLink));
}
function getLinksTypeIds(response, linkByPredicateType = EMPTY_MAP, openWorldLinks = true) {
const linkTypes = [];
for (const binding of response.results.bindings) {
if (!isRdfIri(binding.link)) continue;
const linkConfigs = linkByPredicateType.get(binding.link.value);
if (linkConfigs && linkConfigs.length > 0) for (const linkConfig of linkConfigs) {
const mappedLinkType = isDirectLink(linkConfig) ? linkConfig.id : binding.link.value;
linkTypes.push(mappedLinkType);
}
else if (openWorldLinks) linkTypes.push(binding.link.value);
}
return linkTypes;
}
function getLinkStatistics(response) {
for (const binding of response.results.bindings) if (isRdfIri(binding.link)) return getLinkCount(binding);
}
function getFilteredData(response, sourceTypes, linkByPredicateType = EMPTY_MAP, openWorldLinks = true) {
const instancesMap = {};
const resultTypes = /* @__PURE__ */ new Map();
const outPredicates = /* @__PURE__ */ new Map();
const inPredicates = /* @__PURE__ */ new Map();
for (const binding of response.results.bindings) {
if (!isRdfIri(binding.inst) && !isRdfBlank(binding.inst)) continue;
const iri = binding.inst.value;
let model = instancesMap[iri];
if (!model) {
model = emptyElementInfo(iri);
instancesMap[iri] = model;
}
enrichElement(model, binding);
if (isRdfIri(binding.classAll)) getOrCreateSetInMap(resultTypes, iri).add(binding.classAll.value);
if (!openWorldLinks && binding.link && binding.direction) getOrCreateSetInMap(binding.direction.value === "out" ? outPredicates : inPredicates, model.id).add(binding.link.value);
}
if (!openWorldLinks) for (const id of Object.keys(instancesMap)) {
const model = instancesMap[id];
const targetTypes = resultTypes.get(model.id);
if (!(matchesDomainForLink(sourceTypes, outPredicates.get(model.id), linkByPredicateType) && matchesDomainForLink(targetTypes, inPredicates.get(model.id), linkByPredicateType))) delete instancesMap[id];
}
return instancesMap;
}
function matchesDomainForLink(types, predicates, linkByPredicateType) {
if (!predicates) return true;
let hasMatch = false;
predicates.forEach((predicate) => {
const matched = linkByPredicateType.get(predicate);
if (matched) {
for (const link of matched) if (typeMatchesDomain(link, types)) hasMatch = true;
}
});
return hasMatch;
}
function isDirectLink(link) {
return !/[?$][a-zA-Z]+\b/.test(link.path);
}
function isDirectProperty(property) {
return !/[?$][a-zA-Z]+\b/.test(property.path);
}
function typeMatchesDomain(config, types) {
if (!config.domain || config.domain.length === 0) return true;
else if (!types) return false;
else {
for (const type of config.domain) if (types.has(type)) return true;
return false;
}
}
/**
* Modifies properties with merging with new values, couls be new peroperty or new value for existing properties.
* @param properties
* @param propType
* @param propValue
*/
function mergeProperties$1(properties, propType, propValue) {
let property = properties[propType.value];
if (isRdfIri(propValue)) {
if (!property) property = {
type: "uri",
values: []
};
if (isIriProperty(property) && property.values.every(({ value }) => value !== propValue.value)) property.values = [...property.values, propValue];
} else if (isRdfLiteral(propValue)) {
if (!property) property = {
type: "string",
values: []
};
const propertyValue = getLocalizedString(propValue);
if (isLiteralProperty(property) && property.values.every((value) => !isLocalizedEqual(value, propertyValue))) property.values = [...property.values, propertyValue];
}
properties[propType.value] = property;
}
function enrichElement(element, sInst) {
if (!element) return;
if (sInst.label) {
const label = getLocalizedString(sInst.label);
if (element.label.values.every((value) => !isLocalizedEqual(value, label))) element.label.values.push(label);
}
if (sInst.class && element.types.indexOf(sInst.class.value) < 0) element.types.push(sInst.class.value);
if (sInst.propType && sInst.propType.value !== LABEL_URI) mergeProperties$1(element.properties, sInst.propType, sInst.propValue);
}
function appendLabel(container, newLabel) {
if (!newLabel) return;
for (const existing of container.values) if (isLocalizedEqual(existing, newLabel)) return;
container.values.push(newLabel);
}
function isLocalizedEqual(left, right) {
return left.language === right.language && left.value === right.value;
}
function getLocalizedString(label) {
if (label) return {
value: label.value,
language: label["xml:lang"],
datatype: label.datatype ? { value: label.datatype } : void 0
};
else return;
}
function getInstCount(instcount) {
return instcount ? +instcount.value : void 0;
}
function getPropertyModel(node) {
const label = getLocalizedString(node.label);
return {
id: node.property.value,
label: { values: label ? [label] : [] }
};
}
function getLinkCount(sLinkType) {
return {
id: sLinkType.link.value,
inCount: getInstCount(sLinkType.inCount),
outCount: getInstCount(sLinkType.outCount)
};
}
function emptyElementInfo(id) {
return {
id,
label: { values: [] },
types: [],
properties: {}
};
}
function getLinkTypeInfo(sLinkInfo) {
if (!sLinkInfo) return;
const label = getLocalizedString(sLinkInfo.label);
return {
id: sLinkInfo.link.value,
label: { values: label ? [label] : [] },
count: getInstCount(sLinkInfo.instcount)
};
}
function prependAdditionalBindings(base, additional) {
if (!additional) return base;
return {
head: { vars: base.head.vars },
results: { bindings: [...additional.results.bindings, ...base.results.bindings] }
};
}
//#endregion
//#region src/graph-explorer/data/sparql/sparqlDataProviderSettings.ts
var RDFSettings = {
linkConfigurations: [],
openWorldLinks: false,
propertyConfigurations: [],
openWorldProperties: false,
linksInfoQuery: `SELECT ?source ?type ?target
WHERE {
\${linkConfigurations}
VALUES (?source) {\${ids}}
VALUES (?target) {\${ids}}
}`,
defaultPrefix: "",
schemaLabelProperty: "rdfs:label",
dataLabelProperty: "rdfs:label",
fullTextSearch: {
prefix: "",
queryPattern: ``,
elementFirst: false
},
classTreeQuery: ``,
classInfoQuery: `SELECT ?class ?label ?instcount WHERE {
VALUES(?class) {\${ids}}
OPTIONAL { ?class \${schemaLabelProperty} ?label }
BIND("" as ?instcount)
}`,
linkTypesQuery: `SELECT DISTINCT ?link ?instcount ?label WHERE {
\${linkTypesPattern}
OPTIONAL { ?link \${schemaLabelProperty} ?label }
}`,
linkTypesPattern: ``,
linkTypesInfoQuery: `SELECT ?link ?label WHERE {
VALUES(?link) {\${ids}}
OPTIONAL { ?link \${schemaLabelProperty} ?label }
}`,
propertyInfoQuery: `SELECT ?property ?label WHERE {
VALUES(?property) {\${ids}}
OPTIONAL { ?property \${schemaLabelProperty} ?label }
}`,
elementInfoQuery: ``,
imageQueryPattern: ``,
linkTypesOfQuery: ``,
linkTypesStatisticsQuery: ``,
filterRefElementLinkPattern: "",
filterTypePattern: ``,
filterAdditionalRestriction: ``,
filterElementInfoPattern: ``
};
var WikidataSettingsOverride = {
defaultPrefix: `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX wdt: <http://www.wikidata.org/prop/direct/>
PREFIX wd: <http://www.wikidata.org/entity/>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
`,
schemaLabelProperty: "rdfs:label",
dataLabelProperty: "rdfs:label",
fullTextSearch: {
prefix: "PREFIX bds: <http://www.bigdata.com/rdf/search#>\n",
queryPattern: `
?inst rdfs:label ?searchLabel.
SERVICE bds:search {
?searchLabel bds:search "\${text}*" ;
bds:minRelevance '0.5' ;
bds:matchAllTerms 'true' .
}
BIND(IF(STRLEN(?strInst) > 33,
0-<http://www.w3.org/2001/XMLSchema#integer>(SUBSTR(?strInst, 33)),
-10000) as ?score)
`
},
classTreeQuery: `
SELECT distinct ?class ?label ?parent WHERE {
?class rdfs:label ?label.
{ ?class wdt:P279 wd:Q35120. }
UNION
{ ?parent wdt:P279 wd:Q35120.
?class wdt:P279 ?parent. }
UNION
{ ?parent wdt:P279/wdt:P279 wd:Q35120.
?class wdt:P279 ?parent. }
}
`,
linkTypesPattern: `?link wdt:P279* wd:Q18616576.
BIND(0 as ?instcount)
`,
elementInfoQuery: `
CONSTRUCT {
?inst rdf:type ?class .
?inst rdfs:label ?label .
?inst ?propType ?propValue.
} WHERE {
VALUES (?inst) {\${ids}}
OPTIONAL {
?inst wdt:P31 ?class
}
OPTIONAL {?inst rdfs:label ?label}
OPTIONAL {
\${propertyConfigurations}
FILTER (isLiteral(?propValue))
}
}
`,
imageQueryPattern: ` { ?inst ?linkType ?fullImage } union { ?inst wdt:P163/wdt:P18 ?fullImage }
BIND(CONCAT("https://commons.wikimedia.org/w/thumb.php?f=",
STRAFTER(STR(?fullImage), "Special:FilePath/"), "&w=200") AS ?image)`,
linkTypesOfQuery: `
SELECT DISTINCT ?link
WHERE {
\${linkConfigurations}
?claim <http://wikiba.se/ontology#directClaim> ?link .
}
`,
linkTypesStatisticsQuery: `
SELECT (\${linkId} as ?link) (COUNT(?outObject) AS ?outCount) (COUNT(?inObject) AS ?inCount)
WHERE {
{
{
SELECT DISTINCT ?outObject WHERE {
\${linkConfigurationOut}
FILTER(ISIRI(?outObject))
?outObject ?someprop ?someobj.
}
LIMIT 101
}
} UNION {
{
SELECT DISTINCT ?inObject WHERE {
\${linkConfigurationIn}
FILTER(ISIRI(?inObject))
?inObject ?someprop ?someobj.
}
LIMIT 101
}
}
}
`,
filterRefElementLinkPattern: "?claim <http://wikiba.se/ontology#directClaim> ?link .",
filterTypePattern: `?inst wdt:P31 ?instType. ?instType wdt:P279* ?class`,
filterAdditionalRestriction: `FILTER ISIRI(?inst)
BIND(STR(?inst) as ?strInst)
FILTER exists {?inst ?someprop ?someobj}
`,
filterElementInfoPattern: `OPTIONAL {?inst wdt:P31 ?foundClass}
BIND (coalesce(?foundClass, owl:Thing) as ?class)
OPTIONAL {?inst rdfs:label ?label}
`
};
var WikidataSettings = {
...RDFSettings,
...WikidataSettingsOverride
};
var OWLRDFSSettingsOverride = {
defaultPrefix: `PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX owl: <http://www.w3.org/2002/07/owl#>
`,
schemaLabelProperty: "rdfs:label",
dataLabelProperty: "rdfs:label",
fullTextSearch: {
prefix: "",
queryPattern: ` OPTIONAL {?inst \${dataLabelProperty} ?search1}
FILTER regex(COALESCE(str(?search1), str(?extractedLabel)), "\${text}", "i")
BIND(0 as ?score)
`,
extractLabel: true
},
classTreeQuery: `
SELECT ?class ?label ?parent
WHERE {
{
?class a rdfs:Class
} UNION {
?class a owl:Class
}
FILTER ISIRI(?class)
OPTIONAL {?class rdfs:label ?label}
OPTIONAL {?class rdfs:subClassOf ?parent. FILTER ISIRI(?parent)}
}
`,
linkTypesPattern: `{ ?link a rdf:Property
} UNION {
?link a owl:ObjectProperty
}
BIND('' as ?instcount)
`,
elementInfoQuery: `
CONSTRUCT {
?inst rdf:type ?class .
?inst rdfs:label ?label .
?inst ?propType ?propValue.
} WHERE {
VALUES (?inst) {\${ids}}
OPTIONAL { ?inst a ?class }
OPTIONAL {?inst \${dataLabelProperty} ?label}
OPTIONAL {
\${propertyConfigurations}
FILTER (isLiteral(?propValue))
}
}
`,
imageQueryPattern: `{ ?inst ?linkType ?image } UNION { [] ?linkType ?inst. BIND(?inst as ?image) }`,
linkTypesOfQuery: `
SELECT DISTINCT ?link
WHERE {
\${linkConfigurations}
}
`,
linkTypesStatisticsQuery: `
SELECT ?link ?outCount ?inCount
WHERE {
{
SELECT (\${linkId} as ?link) (count(?outObject) as ?outCount) WHERE {
\${linkConfigurationOut}
\${navigateElementFilterOut}
} LIMIT 101
} {
SELECT (\${linkId} as ?link) (count(?inObject) as ?inCount) WHERE {
\${linkConfigurationIn}
\${navigateElementFilterIn}
} LIMIT 101
}
}
`,
filterRefElementLinkPattern: "",
filterTypePattern: `?inst a ?instType. ?instType rdfs:subClassOf* ?class`,
filterElementInfoPattern: `OPTIONAL {?inst rdf:type ?foundClass}
BIND (coalesce(?foundClass, owl:Thing) as ?class)
OPTIONAL {?inst \${dataLabelProperty} ?label}`,
filterAdditionalRestriction: ""
};
var OWLRDFSSettings = {
...RDFSettings,
...OWLRDFSSettingsOverride
};
var OWLStatsOverride = { classTreeQuery: `
SELECT ?class ?instcount ?label ?parent
WHERE {
{SELECT ?class (count(?inst) as ?instcount)
WHERE {
?inst rdf:type ?class.
FILTER ISIRI(?class)
} GROUP BY ?class } UNION
{
?class rdf:type rdfs:Class
} UNION {
?class rdf:type owl:Class
}
OPTIONAL {?class rdfs:label ?label}
OPTIONAL {?class rdfs:subClassOf ?parent. FILTER ISIRI(?parent)}
}
` };
var OWLStatsSettings = {
...OWLRDFSSettings,
...OWLStatsOverride
};
var DBPediaOverride = {
fullTextSearch: {
prefix: "PREFIX dbo: <http://dbpedia.org/ontology/>\n",
queryPattern: `
?inst rdfs:label ?searchLabel.
?searchLabel bif:contains "\${text}".
?inst dbo:wikiPageID ?origScore .
BIND(0-?origScore as ?score)
`
},
classTreeQuery: `
SELECT distinct ?class ?label ?parent WHERE {
?class rdfs:label ?label.
OPTIONAL {?class rdfs:subClassOf ?parent}
?root rdfs:subClassOf owl:Thing.
?class rdfs:subClassOf? | rdfs:subClassOf/rdfs:subClassOf ?root
}
`,
elementInfoQuery: `
CONSTRUCT {
?inst rdf:type ?class .
?inst rdfs:label ?label .
?inst ?propType ?propValue.
} WHERE {
VALUES (?inst) {\${ids}}
?inst a ?class .
?inst rdfs:label ?label .
FILTER (!contains(str(?class), 'http://dbpedia.org/class/yago'))
OPTIONAL {
\${propertyConfigurations}
FILTER (isLiteral(?propValue))
}
}
`,
filterTypePattern: `?inst a ?instType. ?instType rdfs:subClassOf* ?class`,
filterElementInfoPattern: `
OPTIONAL {?inst rdf:type ?foundClass. FILTER (!contains(str(?foundClass), 'http://dbpedia.org/class/yago'))}
BIND (coalesce(?foundClass, owl:Thing) as ?class)
OPTIONAL {?inst \${dataLabelProperty} ?label}`,
imageQueryPattern: ` { ?inst ?linkType ?fullImage } UNION { [] ?linkType ?inst. BIND(?inst as ?fullImage) }
BIND(CONCAT("https://commons.wikimedia.org/w/thumb.php?f=",
STRAFTER(STR(?fullImage), "Special:FilePath/"), "&w=200") AS ?image)
`
};
var DBPediaSettings = {
...OWLRDFSSettings,
...DBPediaOverride
};
var QLeverOverride = { fullTextSearch: {
prefix: "PREFIX ql: <http://qlever.cs.uni-freiburg.de/builtin-functions/>\n",
queryPatternPerWord: `
?inst \${dataLabelProperty} ?searchLabel .
?searchLabel ql:has-word "\${word}" .
`
} };
var QLeverSettings = {
...OWLRDFSSettings,
...QLeverOverride
};
//#endregion
//#region src/graph-explorer/data/sparql/turtle.ts
function parseTurtleText(turtleText) {
return new Promise((resolve, reject) => {
const triples = [];
new N3.Parser().parse(turtleText, (error, triple, _hash) => {
if (error) reject(error);
else if (triple) triples.push({
subject: n3toRdfNode(triple.subject),
predicate: n3toRdfNode(triple.predicate),
object: n3toRdfNode(triple.object)
});
else resolve(triples);
});
});
}
function n3toRdfNode(entity) {
if (N3.Util.isLiteral(entity)) {
const literal = entity;
return {
type: "literal",
value: literal.value,
datatype: literal.datatype.value,
"xml:lang": literal.language
};
} else return {
type: "uri",
value: entity.value
};
}
//#endregion
//#region src/graph-explorer/data/sparql/sparqlDataProvider.ts
var SparqlQueryMethod = /* @__PURE__ */ function(SparqlQueryMethod) {
SparqlQueryMethod[SparqlQueryMethod["GET"] = 1] = "GET";
SparqlQueryMethod[SparqlQueryMethod["POST"] = 2] = "POST";
return SparqlQueryMethod;
}({});
var SparqlDataProvider = class {
constructor(options, settings = OWLStatsSettings) {
this.linkByPredicate = /* @__PURE__ */ new Map();
this.linkById = /* @__PURE__ */ new Map();
this.propertyByPredicate = /* @__PURE__ */ new Map();
const { queryFunction = queryInternal } = options;
this.options = {
...options,
queryFunction
};
this.settings = settings;
for (const link of settings.linkConfigurations) {
this.linkById.set(link.id, link);
const predicate = isDirectLink(link) ? link.path : link.id;
getOrCreateArrayInMap(this.linkByPredicate, predicate).push(link);
}
this.openWorldLinks = settings.linkConfigurations.length === 0 || Boolean(settings.openWorldLinks);
for (const property of settings.propertyConfigurations) {
const predicate = isDirectProperty(property) ? property.path : property.id;
getOrCreateArrayInMap(this.propertyByPredicate, predicate).push(property);
}
this.openWorldProperties = settings.propertyConfigurations.length === 0 || Boolean(settings.openWorldProperties);
}
async classTree() {
const { defaultPrefix, schemaLabelProperty, classTreeQuery } = this.settings;
if (!classTreeQuery) return [];
const query = defaultPrefix + resolveTemplate(classTreeQuery, { schemaLabelProperty });
const classTree = getClassTree(await this.executeSparqlQuery(query));
if (this.options.prepareLabels) await attachLabels(flattenClassTree(classTree), this.options.prepareLabels);
return classTree;
}
async propertyInfo(params) {
const { defaultPrefix, schemaLabelProperty, propertyInfoQuery } = this.settings;
let properties;
if (propertyInfoQuery) {
const query = defaultPrefix + resolveTemplate(propertyInfoQuery, {
ids: params.propertyIds.map(escapeIri).map((id) => ` ( ${id} )`).join(" "),
schemaLabelProperty
});
properties = getPropertyInfo(await this.executeSparqlQuery(query));
} else {
properties = {};
for (const id of params.propertyIds) properties[id] = {
id,
label: { values: [] }
};
}
if (this.options.prepareLabels) await attachLabels(objectValues(properties), this.options.prepareLabels);
return properties;
}
async classInfo(params) {
const { defaultPrefix, schemaLabelProperty, classInfoQuery } = this.settings;
let classes;
if (classInfoQuery) {
const query = defaultPrefix + resolveTemplate(classInfoQuery, {
ids: params.classIds.map(escapeIri).map((id) => ` ( ${id} )`).join(" "),
schemaLabelProperty
});
classes = getClassInfo(await this.executeSparqlQuery(query));
} else classes = params.classIds.map((id) => ({
id,
label: { values: [] },
children: []
}));
if (this.options.prepareLabels) await attachLabels(classes, this.options.prepareLabels);
return classes;
}
async linkTypesInfo(params) {
const { defaultPrefix, schemaLabelProperty, linkTypesInfoQuery } = this.settings;
let linkTypes;
if (linkTypesInfoQuery) {
const query = defaultPrefix + resolveTemplate(linkTypesInfoQuery, {
ids: params.linkTypeIds.map(escapeIri).map((id) => ` ( ${id} )`).join(" "),
schemaLabelProperty
});
linkTypes = getLinkTypes(await this.executeSparqlQuery(query));
} else linkTypes = params.linkTypeIds.map((id) => ({
id,
label: { values: [] }
}));
if (this.options.prepareLabels) await attachLabels(linkTypes, this.options.prepareLabels);
return linkTypes;
}
async linkTypes() {
const { defaultPrefix, schemaLabelProperty, linkTypesQuery, linkTypesPattern } = this.settings;
if (!linkTypesQuery) return [];
const query = defaultPrefix + resolveTemplate(linkTypesQuery, {
linkTypesPattern,
schemaLabelProperty
});
const linkTypes = getLinkTypes(await this.executeSparqlQuery(query));
if (this.options.prepareLabels) await attachLabels(linkTypes, this.options.prepareLabels);
return linkTypes;
}
async elementInfo(params) {
const nonBlankResources = params.elementIds.filter((id) => !isEncodedBlank(id));
const blankNodeResponse = this.options.acceptBlankNodes ? elementInfo(params.elementIds) : void 0;
let triples;
if (nonBlankResources.length > 0) {
const ids = nonBlankResources.map(escapeIri).map((id) => ` (${id})`).join(" ");
const { defaultPrefix, dataLabelProperty, elementInfoQuery } = this.settings;
const query = defaultPrefix + resolveTemplate(elementInfoQuery, {
ids,
dataLabelProperty,
propertyConfigurations: this.formatPropertyInfo()
});
triples = await this.executeSparqlConstruct(query);
} else triples = [];
const types = this.queryManyElementTypes(this.settings.propertyConfigurations.length > 0 ? params.elementIds : []);
const elementModels = getElementsInfo(prependAdditionalBindings(triplesToElementBinding(triples), blankNodeResponse), await types, this.propertyByPredicate, this.openWorldProperties);
if (this.options.prepareLabels) await attachLabels(objectValues(elementModels), this.options.prepareLabels);
if (this.options.prepareImages) await prepareElementImages(this.options.prepareImages, elementModels);
else if (this.options.imagePropertyUris && this.options.imagePropertyUris.length) await this.attachImages(elementModels, this.options.imagePropertyUris);
return elementModels;
}
async attachImages(elementsInfo, types) {
const ids = Object.keys(elementsInfo).filter((id) => !isEncodedBlank(id)).map(escapeIri).map((id) => ` ( ${id} )`).join(" ");
const typesString = types.map(escapeIri).map((id) => ` ( ${id} )`).join(" ");
const query = this.settings.defaultPrefix + `
SELECT ?inst ?linkType ?image
WHERE {{
VALUES (?inst) {${ids}}
VALUES (?linkType) {${typesString}}
${this.settings.imageQueryPattern}
}}
`;
try {
enrichElementsWithImages(await this.executeSparqlQuery(query), elementsInfo);
} catch (err) {
console.error(err);
}
}
async linksInfo(params) {
const nonBlankResources = params.elementIds.filter((id) => !isEncodedBlank(id));
const blankNodeResponse = this.options.acceptBlankNodes ? linksInfo(params.elementIds) : void 0;
const linkConfigurations = this.formatLinkLinks();
let bindings;
let types;
if (nonBlankResources.length > 0) {
const ids = nonBlankResources.map(escapeIri).map((id) => ` ( ${id} )`).join(" ");
const linksInfoQuery = this.settings.defaultPrefix + resolveTemplate(this.settings.linksInfoQuery, {
ids,
linkConfigurations
});
bindings = this.executeSparqlQuery(linksInfoQuery);
types = this.queryManyElementTypes(params.elementIds);
} else {
bindings = Promise.resolve({
head: { vars: [] },
results: { bindings: [] }
});
types = this.queryManyElementTypes([]);
}
return getLinksInfo(prependAdditionalBindings(await bindings, blankNodeResponse), await types, this.linkByPredicate, this.openWorldLinks);
}
async linkTypesOf(params) {
if (this.options.acceptBlankNodes && isEncodedBlank(params.elementId)) return Promise.resolve(getLinksTypesOf(linkTypesOf(params)));
const { defaultPrefix, linkTypesOfQuery, linkTypesStatisticsQuery, filterTypePattern } = this.settings;
const elementIri = escapeIri(params.elementId);
const forAll = this.formatLinkUnion(params.elementId, void 0, void 0, "?outObject", "?inObject", false);
if (forAll.usePredicatePart) {
forAll.unionParts.push(`{ ${elementIri} ?link ?outObject }`);
forAll.unionParts.push(`{ ?inObject ?link ${elementIri} }`);
}
const query = defaultPrefix + resolveTemplate(linkTypesOfQuery, {
elementIri,
linkConfigurations: forAll.unionParts.join("\nUNION\n")
});
const linkTypeIds = getLinksTypeIds(await this.executeSparqlQuery(query), this.linkByPredicate, this.openWorldLinks);
const navigateElementFilterOut = this.options.acceptBlankNodes ? `FILTER (IsIri(?outObject) || IsBlank(?outObject))` : `FILTER IsIri(?outObject)`;
const navigateElementFilterIn = this.options.acceptBlankNodes ? `FILTER (IsIri(?inObject) || IsBlank(?inObject))` : `FILTER IsIri(?inObject)`;
const foundLinkStats = [];
await Promise.all(linkTypeIds.map(async (linkId) => {
const linkConfig = this.linkById.get(linkId);
let linkConfigurationOut;
let linkConfigurationIn;
if (!linkConfig || isDirectLink(linkConfig)) {
const predicate = escapeIri(linkConfig && isDirectLink(linkConfig) ? linkConfig.path : linkId);
linkConfigurationOut = `${elementIri} ${predicate} ?outObject`;
linkConfigurationIn = `?inObject ${predicate} ${elementIri}`;
} else {
linkConfigurationOut = this.formatLinkPath(linkConfig.path, elementIri, "?outObject");
linkConfigurationIn = this.formatLinkPath(linkConfig.path, "?inObject", elementIri);
}
if (linkConfig && linkConfig.domain?.length > 0) {
const commaSeparatedDomains = linkConfig.domain.map(escapeIri).join(", ");
const restrictionOut = filterTypePattern.replace(/[?$]inst\b/g, elementIri);
const restrictionIn = filterTypePattern.replace(/[?$]inst\b/g, "?inObject");
linkConfigurationOut += ` { ${restrictionOut} FILTER(?class IN (${commaSeparatedDomains})) }`;
linkConfigurationIn += ` { ${restrictionIn} FILTER(?class IN (${commaSeparatedDomains})) }`;
}
const statsQuery = defaultPrefix + resolveTemplate(linkTypesStatisticsQuery, {
linkId: escapeIri(linkId),
elementIri,
linkConfigurationOut,
linkConfigurationIn,
navigateElementFilterOut,
navigateElementFilterIn
});
const linkStats = getLinkStatistics(await this.executeSparqlQuery(statsQuery));
if (linkStats) foundLinkStats.push(linkStats);
}));
return foundLinkStats;
}
linkElements(params) {
return this.filter({
refElementId: params.elementId,
refElementLinkId: params.linkId,
linkDirection: params.direction,
limit: params.limit,
offset: params.offset,
languageCode: ""
});
}
async filter(baseParams) {
const params = { ...baseParams };
if (params.limit === void 0) params.limit = 100;
const types = this.querySingleElementTypes(params.refElementId && this.settings.linkConfigurations.length > 0 ? params.refElementId : void 0);
const blankFiltration = this.options.acceptBlankNodes ? filter(params) : void 0;
if (blankFiltration && blankFiltration.results.bindings.length > 0) return getFilteredData(blankFiltration, await types, this.linkByPredicate, this.openWorldLinks);
const filterQuery = this.createFilterQuery(params);
const bindings = await this.executeSparqlQuery(filterQuery);
let bindingsWithBlanks;
if (this.options.acceptBlankNodes) bindingsWithBlanks = await updateFilterResults(bindings, (blankQuery) => this.executeSparqlQuery(blankQuery), this.settings);
else bindingsWithBlanks = bindings;
const elementModels = getFilteredData(bindingsWithBlanks, await types, this.linkByPredicate, this.openWorldLinks);
if (this.options.prepareLabels) await attachLabels(objectValues(elementModels), this.options.prepareLabels);
return elementModels;
}
createFilterQuery(params) {
if (!params.refElementId && params.refElementLinkId) throw new Error("Cannot execute refElementLink filter without refElement");
let outerProjection = "?inst ?class ?label ?blankType";
let innerProjection = "?inst";
let refQueryPart = "";
let refQueryTypes = "";
if (params.refElementId) {
outerProjection += " ?link ?direction";
innerProjection += " ?link ?direction";
refQueryPart = this.createRefQueryPart({
elementId: params.refElementId,
linkId: params.refElementLinkId,
direction: params.linkDirection
});
if (this.settings.linkConfigurations.length > 0) {
outerProjection += " ?classAll";
refQueryTypes = this.settings.filterTypePattern.replace(/[?$]class\b/g, "?classAll");
}
}
let elementTypePart = "";
if (params.elementTypeId) {
const elementTypeIri = escapeIri(params.elementTypeId);
elementTypePart = this.settings.filterTypePattern.replace(/[?$]class\b/g, elementTypeIri);
elementTypePart += " .";
}
const { defaultPrefix, fullTextSearch, dataLabelProperty } = this.settings;
let textSearchPart = "";
if (params.text) {
innerProjection += " ?score";
if (this.settings.fullTextSearch.extractLabel) textSearchPart += sparqlExtractLabel("?inst", "?extractedLabel");
if (fullTextSearch.queryPatternPerWord) textSearchPart = params.text.trim().split(/\s+/).filter(Boolean).map((word) => resolveTemplate(fullTextSearch.queryPatternPerWord, {
word: escapeSparqlStringLiteral(word),
dataLabelProperty
})).join("\n") + "\nBIND(0 as ?score)";
else textSearchPart = resolveTemplate(fullTextSearch.queryPattern, {
text: params.text,
dataLabelProperty
});
}
const blankNodes = this.options.acceptBlankNodes;
if (blankNodes) outerProjection += ` ${BLANK_NODE_QUERY_PARAMETERS}`;
return `${defaultPrefix}
${fullTextSearch.prefix}
SELECT ${outerProjection}
WHERE {
{
SELECT DISTINCT ${innerProjection} WHERE {
${fullTextSearch.elementFirst ? "" : textSearchPart}
${elementTypePart}
${refQueryPart}
${fullTextSearch.elementFirst ? textSearchPart : ""}
${this.settings.filterAdditionalRestriction}
}
${textSearchPart ? "ORDER BY DESC(?score)" : ""}
LIMIT ${params.limit} OFFSET ${params.offset}
}
${refQueryTypes}
${resolveTemplate(this.settings.filterElementInfoPattern, { dataLabelProperty })}
${blankNodes ? BLANK_NODE_QUERY : ""}
} ${textSearchPart ? "ORDER BY DESC(?score)" : ""}
`;
}
executeSparqlQuery(query) {
const method = this.options.queryMethod ? this.options.queryMethod : 1;
return executeSparqlQuery(this.options.endpointUrl, query, method, this.options.queryFunction);
}
executeSparqlConstruct(query) {
const method = this.options.queryMethod ? this.options.queryMethod : 1;
return executeSparqlConstruct(this.options.endpointUrl, query, method, this.options.queryFunction);
}
createRefQueryPart(params) {
const { elementId, linkId, direction } = params;
const { unionParts, usePredicatePart } = this.formatLinkUnion(elementId, linkId, direction, "?inst", "?inst", true);
if (usePredicatePart) {
const refElementIRI = escapeIri(params.elementId);
let refLinkType;
if (linkId) {
const link = this.linkById.get(linkId);
refLinkType = link && isDirectLink(link) ? escapeIri(link.path) : escapeIri(linkId);
}
const linkPattern = refLinkType || "?link";
const bindType = refLinkType ? `BIND(${refLinkType} as ?link)` : "";
const blankFilter = this.options.acceptBlankNodes ? "FILTER(isIri(?inst) || isBlank(?inst))" : "FILTER(isIri(?inst))";
if (!direction || direction === "out") unionParts.push(`{ ${refElementIRI} ${linkPattern} ?inst BIND("out" as ?direction) ${bindType} ${blankFilter} }`);
if (!direction || direction === "in") unionParts.push(`{ ?inst ${linkPattern} ${refElementIRI} BIND("in" as ?direction) ${bindType} ${blankFilter} }`);
}
let resultPattern = unionParts.length === 0 ? "FILTER(false)" : unionParts.join(`\nUNION\n`);
if (!linkId && this.settings.filterRefElementLinkPattern.length > 0) resultPattern += `\n${this.settings.filterRefElementLinkPattern}`;
return resultPattern;
}
formatLinkUnion(refElementIri, linkIri, direction, outElementVar, inElementVar, bindDirection) {
const { linkConfigurations } = this.settings;
const fixedIri = escapeIri(refElementIri);
const unionParts = [];
let hasDirectLink = false;
for (const link of linkConfigurations) {
if (linkIri && link.id !== linkIri) continue;
if (isDirectLink(link)) hasDirectLink = true;
else {
const linkType = escapeIri(link.id);
if (!direction || direction === "out") {
const path = this.formatLinkPath(link.path, fixedIri, outElementVar);
const boundedDirection = bindDirection ? `BIND("out" as ?direction) ` : "";
unionParts.push(`{ ${path} BIND(${linkType} as ?link) ${boundedDirection}}`);
}
if (!direction || direction === "in") {
const path = this.formatLinkPath(link.path, inElementVar, fixedIri);
const boundedDirection = bindDirection ? `BIND("in" as ?direction) ` : "";
unionParts.push(`{ ${path} BIND(${linkType} as ?link) ${boundedDirection}}`);
}
}
}
return {
unionParts,
usePredicatePart: this.openWorldLinks || hasDirectLink
};
}
formatLinkLinks() {
const unionParts = [];
let hasDirectLink = false;
for (const link of this.settings.linkConfigurations) if (isDirectLink(link)) hasDirectLink = true;
else {
const linkType = escapeIri(link.id);
unionParts.push(`{ ${this.formatLinkPath(link.path, "?source", "?target")} BIND(${linkType} as ?type) }`);
}
if (this.openWorldLinks || hasDirectLink) unionParts.push(`{ ?source ?type ?target }`);
return unionParts.join("\nUNION\n");
}
formatLinkPath(path, source, target) {
return path.replace(/[?$]source\b/g, source).replace(/[?$]target\b/g, target);
}
formatPropertyInfo() {
const unionParts = [];
let hasDirectProperty = false;
for (const property of this.settings.propertyConfigurations) if (isDirectProperty(property)) hasDirectProperty = true;
else {
const propType = escapeIri(property.id);
const formatted = this.formatPropertyPath(property.path, "?inst", "?propValue");
unionParts.push(`{ ${formatted} BIND(${propType} as ?propType) }`);
}
if (this.openWorldProperties || hasDirectProperty) unionParts.push(`{ ?inst ?propType ?propValue }`);
return unionParts.join("\nUNION\n");
}
formatPropertyPath(path, subject, value) {
return path.replace(/[?$]inst\b/g, subject).replace(/[?$]value\b/g, value);
}
async querySingleElementTypes(element) {
return (await this.queryManyElementTypes(element ? [element] : [])).get(element);
}
async queryManyElementTypes(elements) {
if (elements.length === 0) return /* @__PURE__ */ new Map();
const { filterTypePattern } = this.settings;
const ids = elements.filter((iri) => !isEncodedBlank(iri)).map((iri) => `(${escapeIri(iri)})`).join(" ");
const query = resolveTemplate(this.settings.defaultPrefix + `SELECT ?inst ?class { VALUES(?inst) { \${ids} } \${filterTypePattern} }`, {
ids,
filterTypePattern
});
let response = await this.executeSparqlQuery(query);
if (this.options.acceptBlankNodes && elements.find(isEncodedBlank)) {
const blankResponse = getElementTypes$1(elements);
response = prependAdditionalBindings(response, blankResponse);
}
return getElementTypes(response);
}
};
async function attachLabels(items, fetchLabels) {
const resources = /* @__PURE__ */ new Set();
for (const item of items) {
if (isEncodedBlank(item.id)) continue;
resources.add(item.id);
}
const labels = await fetchLabels(resources);
for (const item of items) if (labels.has(item.id)) item.label = { values: labels.get(item.id) };
}
function prepareElementImages(fetchImages, elementsInfo) {
return fetchImages(elementsInfo).then((images) => {
for (const iri in images) if (Object.prototype.hasOwnProperty.call(images, iri) && elementsInfo[iri]) elementsInfo[iri].image = images[iri];
});
}
function resolveTemplate(template, values) {
let result = template;
for (const replaceKey in values) {
if (!Object.prototype.hasOwnProperty.call(values, replaceKey)) continue;
const replaceValue = values[replaceKey];
if (replaceValue) result = result.replace(new RegExp("\\${" + replaceKey + "}", "g"), replaceValue);
}
return result;
}
function executeSparqlQuery(endpoint, query, method, queryFunction) {
let internalQuery;
if (method === 1) internalQuery = queryFunction({
url: appendQueryParams(endpoint, { query }),
headers: { Accept: "application/sparql-results+json" },
method: "GET"
});
else internalQuery = queryFunction({
url: endpoint,
body: query,
headers: {
Accept: "application/sparql-results+json",
"Content-Type": "application/sparql-query; charset=UTF-8"
},
method: "POST"
});
return internalQuery.then((response) => {
if (response.ok) return response.json();
else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
});
}
function executeSparqlConstruct(endpoint, query, method, queryFunction) {
let internalQuery;
if (method === 1) internalQuery = queryFunction({
url: appendQueryParams(endpoint, { query }),
headers: { Accept: "text/turtle" },
method: "GET"
});
else internalQuery = queryFunction({
url: endpoint,
body: query,
headers: {
Accept: "text/turtle",
"Content-Type": "application/sparql-query; charset=UTF-8"
},
method: "POST"
});
return internalQuery.then((response) => {
if (response.ok) return response.text();
else {
const error = new Error(response.statusText);
error.response = response;
throw error;
}
}).then(parseTurtleText);
}
function appendQueryParams(endpoint, queryParams = {}) {
return endpoint + ((endpoint.indexOf("?") < 0 ? "?" : "&") + Object.keys(queryParams).map((key) => `${key}=${encodeURIComponent(queryParams[key])}`).join("&"));
}
function queryInternal(params) {
return fetch(params.url, {
method: params.method,
body: params.body,
credentials: "same-origin",
mode: "cors",
cache: "default",
headers: params.headers
});
}
function sparqlExtractLabel(subject, label) {
return `
BIND ( str( ${subject} ) as ?uriStr)
BIND ( strafter(?uriStr, "#") as ?label3)
BIND ( strafter(strafter(?uriStr, "//"), "/") as ?label6)
BIND ( strafter(?label6, "/") as ?label5)
BIND ( strafter(?label5, "/") as ?label4)
BIND (if (?label3 != "", ?label3,
if (?label4 != "", ?label4,
if (?label5 != "", ?label5, ?label6))) as ${label})
`;
}
function escapeIri(iri) {
if (typeof iri !== "string") throw new Error(`Cannot escape IRI of type "${typeof iri}"`);
return `<${iri}>`;
}
/** Escapes a value for embedding in a double-quoted SPARQL string literal. */
function escapeSparqlStringLiteral(value) {
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n").replace(/\r/g, "\\r");
}
//#endregion
//#region src/graph-explorer/data/composite/mergeUtils.ts
var DATA_PROVIDER_PROPERTY = "http://graph-explorer.org/property/DataProvider";
function mergeClassTree(composite) {
const lists = composite.filter((r) => r.response).map(({ useInStats, response }) => ({
useInStats,
classes: classTreeToArray(response)
}));
const dictionary = {};
const topLevelModels = {};
const childrenMap = {};
for (const { useInStats, classes } of lists) for (const model of classes) {
const childrenIds = childrenMap[model.id] || [];
model.children.map((ch) => ch.id).forEach((id) => {
if (childrenIds.indexOf(id) === -1) childrenIds.push(id);
});
model.children = [];
if (!useInStats) delete model.count;
if (!dictionary[model.id]) {
topLevelModels[model.id] = model;
dictionary[model.id] = model;
childrenMap[model.id] = childrenIds;
} else {
topLevelModels[model.id] = mergeClassModel(dictionary[model.id], model);
dictionary[model.id] = topLevelModels[model.id];
}
}
const models = Object.keys(dictionary).map((key) => dictionary[key]);
for (const m of models) m.children = (childrenMap[m.id] || []).map((id) => {
delete topLevelModels[id];
return dictionary[id];
});
return Object.keys(topLevelModels).map((key) => topLevelModels[key]);
}
function mergePropertyInfo(response) {
const result = {};
const props = response.filter((r) => r.response).map((r) => r.response);
for (const model of props) {
const keys = Object.keys(model);
for (const key of keys) {
const prop = model[key];
if (!result[key]) result[key] = prop;
else result[key].label = mergeLabels(result[key].label, prop.label);
}
}
return result;
}
function mergeClassInfo(response) {
const dictionaries = response.filter((r) => r.response).map((r) => r.response);
const dictionary = {};
for (const models of dictionaries) for (const model of models) if (!dictionary[model.id]) dictionary[model.id] = model;
else dictionary[model.id] = mergeClassModel(dictionary[model.id], model);
return Object.keys(dictionary).map((key) => dictionary[key]);
}
function mergeLinkTypesInfo(response) {
const lists = response.filter((r) => r.response).map((r) => r.response);
const mergeLinkType = (a, b) => {
return {
id: a.id,
label: mergeLabels(a.label, b.label),
count: a.count + b.count
};
};
const dictionary = {};
for (const linkTypes of lists) for (const linkType of linkTypes) if (!dictionary[linkType.id]) dictionary[linkType.id] = linkType;
else dictionary[linkType.id] = mergeLinkType(dictionary[linkType.id], linkType);
return Object.keys(dictionary).map((key) => dictionary[key]);
}
function mergeLinkTypes(response) {
return mergeLinkTypesInfo(response);
}
function mergeElementInfo(response) {
const mergeElementModels = (a, b) => {
const types = a.types;
for (const t of b.types) if (types.indexOf(t) === -1) types.push(t);
const sources = [];
for (const s of a.sources) if (sources.indexOf(s) === -1) sources.push(s);
for (const s of b.sources) if (sources.indexOf(s) === -1) sources.push(s);
return {
id: a.id,
label: mergeLabels(a.label, b.label),
types,
image: a.image || b.image,
properties: mergeProperties(a.properties, b.properties),
sources
};
};
const dictionary = {};
for (const resp of response) {
if (!resp.response) continue;
const list = Object.keys(resp.response).map((k) => resp.response[k]);
for (const em of list) {
em.sources = [resp.dataSourceName];
em.properties[DATA_PROVIDER_PROPERTY] = {
type: "string",
values: [{
value: resp.dataSourceName,
language: ""
}]
};
if (!dictionary[em.id]) dictionary[em.id] = em;
else dictionary[em.id] = mergeElementModels(dictionary[em.id], em);
}
}
return dictionary;
}
function mergeProperties(a, b) {
const aLists = Object.keys(a);
const bLists = Object.keys(b);
const result = {};
function createIdForProperty(baseId) {
let counter = 1;
while (result[baseId + "_" + counter]) counter++;
return baseId + "_" + counter;
}
for (const pKey of aLists) {
const prop = a[pKey];
if (!result[pKey]) result[pKey] = prop;
else result[createIdForProperty(pKey)] = prop;
}
for (const pKey of bLists) {
const prop = b[pKey];
if (!result[pKey]) result[pKey] = prop;
else result[createIdForProperty(pKey)] = prop;
}
return result;
}
function mergeLinksInfo(response) {
const lists = response.filter((r) => r.response).map((r) => r.response);
const resultInfo = [];
function compareLinksInfo(a, b) {
return a.sourceId === b.sourceId && a.targetId === b.targetId && a.linkTypeId === b.linkTypeId;
}
for (const linkInfo of lists) for (const linkModel of linkInfo) if (!resultInfo.some((l) => compareLinksInfo(l, linkModel))) resultInfo.push(linkModel);
return resultInfo;
}
function mergeLinkTypesOf(response) {
const lists = response.filter((r) => r.response).map((r) => r.response);
const dictionary = {};
const merge = (a, b) => {
return {
id: a.id,
inCount: a.inCount + b.inCount,
outCount: a.outCount + b.outCount
};
};
for (const linkCount of lists) for (const lCount of linkCount) if (!dictionary[lCount.id]) dictionary[lCount.id] = lCount;
else dictionary[lCount.id] = merge(lCount, dictionary[lCount.id]);
return Object.keys(dictionary).map((key) => dictionary[key]);
}
function mergeLinkElements(response) {
return mergeElementInfo(response);
}
function mergeFilter(response) {
return mergeElementInfo(response);
}
function classTreeToArray(models) {
let resultArray = models;
function getDescendants(model) {
let descendants = model.children || [];
for (const descendant of descendants) {
const nextGeneration = getDescendants(descendant);
descendants = descendants.concat(nextGeneration);
}
return descendants;
}
for (const model of models) {
const descendants = getDescendants(model);
resultArray = resultArray.concat(descendants);
}
return resultArray;
}
function mergeLabels(a, b) {
function compareLabels(l1, l2) {
return l1.language === l2.language && l1.value === l2.value;
}
const mergedValuesList = a.values;
for (const locStr of b.values) if (!mergedValuesList.some((l) => compareLabels(l, locStr))) mergedValuesList.push(locStr);
return { values: mergedValuesList };
}
function mergeCounts(a, b) {
if (a === void 0 && b === void 0) return;
return (a || 0) + (b || 0);
}
function mergeClassModel(a, b) {
const childrenDictionary = {};
for (const child of a.children.concat(b.children)) if (!childrenDictionary[child.id]) childrenDictionary[child.id] = child;
return {
id: a.id,
label: mergeLabels(a.label, b.label),
count: mergeCounts(a.count, b.count),
children: Object.keys(childrenDictionary).map((key) => childrenDictionary[key])
};
}
//#endregion
//#region src/graph-explorer/data/composite/composite.ts
function isDefinition(dp) {
const definition = dp;
return definition.name !== void 0 && definition.dataProvider !== void 0;
}
var CompositeDataProvider = class {
constructor(dataProviders, params) {
this.mergeMode = "fetchAll";
let dpCounter = 1;
this.dataProviders = dataProviders.map((dp) => {
if (isDefinition(dp)) return dp;
else return {
name: "dataProvider_" + dpCounter++,
dataProvider: dp
};
});
if (params && params.mergeMode) this.mergeMode = params.mergeMode;
}
classTree() {
return this.fetchSequentially("classTree", mergeClassTree, void 0);
}
propertyInfo(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("propertyInfo", mergePropertyInfo, params);
else {
let propertyIds = params.propertyIds;
return this.queueProcessResults((previousResult, dp) => {
propertyIds = propertyIds.filter((id) => !previousResult || !previousResult[id]);
return propertyIds.length > 0 ? dp.dataProvider.propertyInfo({ propertyIds }) : void 0;
}).then(mergePropertyInfo);
}
}
classInfo(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("classInfo", mergeClassInfo, params);
else {
let classIds = params.classIds;
return this.queueProcessResults((previousResult, dp) => {
classIds = classIds.filter((id) => !previousResult || previousResult.map((cm) => cm.id).indexOf(id) === -1);
return classIds.length > 0 ? dp.dataProvider.classInfo({ classIds }) : void 0;
}).then(mergeClassInfo);
}
}
linkTypesInfo(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("linkTypesInfo", mergeLinkTypesInfo, params);
else {
let linkTypeIds = params.linkTypeIds;
return this.queueProcessResults((previousResult, dp) => {
linkTypeIds = linkTypeIds.filter((id) => !previousResult || previousResult.map((lt) => lt.id).indexOf(id) === -1);
return linkTypeIds.length > 0 ? dp.dataProvider.linkTypesInfo({ linkTypeIds }) : void 0;
}).then(mergeLinkTypesInfo);
}
}
linkTypes() {
return this.fetchSequentially("linkTypes", mergeLinkTypes, void 0);
}
elementInfo(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("elementInfo", mergeElementInfo, params);
else {
let elementIds = params.elementIds;
return this.queueProcessResults((previousResult, dp) => {
elementIds = elementIds.filter((id) => !previousResult || !previousResult[id]);
return elementIds.length > 0 ? dp.dataProvider.elementInfo({ elementIds }) : void 0;
}).then(mergeElementInfo);
}
}
linksInfo(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("linksInfo", mergeLinksInfo, params);
else {
let elementIds = params.elementIds;
return this.queueProcessResults((previousResult, dp) => {
elementIds = elementIds.filter((id) => {
if (previousResult) {
for (const linkModel of previousResult) if (linkModel.sourceId === id) return false;
}
return true;
});
return elementIds.length > 0 ? dp.dataProvider.linksInfo({
elementIds,
linkTypeIds: params.linkTypeIds
}) : void 0;
}).then(mergeLinksInfo);
}
}
linkTypesOf(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("linkTypesOf", mergeLinkTypesOf, params);
else return this.queueProcessResults((previousResult, dp) => {
if (!previousResult || previousResult && previousResult.length === 0) return dp.dataProvider.linkTypesOf(params);
else return;
}).then(mergeLinkTypesOf);
}
linkElements(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("linkElements", mergeLinkElements, params);
else return this.queueProcessResults((previousResult, dp) => {
if (!previousResult || previousResult && Object.keys(previousResult).length === 0) return dp.dataProvider.linkElements(params);
else return;
}).then(mergeLinkElements);
}
filter(params) {
if (this.mergeMode === "fetchAll") return this.fetchSequentially("filter", mergeFilter, params);
else return this.queueProcessResults((previousResult, dp) => {
if (!previousResult || previousResult && Object.keys(previousResult).length === 0) return dp.dataProvider.filter(params);
else return;
}).then(mergeFilter);
}
processResults(responsePromise, dpName, useProviderInStats) {
return responsePromise.then((response) => ({
dataSourceName: dpName,
useInStats: useProviderInStats,
response
})).catch((error) => {
console.error(error);
return {
dataSourceName: dpName,
useInStats: useProviderInStats,
response: void 0
};
});
}
queueProcessResults(callBack) {
let counter = 0;
const responseList = [];
const recursiveCall = (result) => {
if (this.dataProviders.length > counter) {
const dp = this.dataProviders[counter++];
const callBackResult = callBack(result, dp);
if (!callBackResult) return Promise.resolve(responseList);
return callBackResult.then((newResult) => {
responseList.push({
dataSourceName: dp.name,
response: newResult
});
return recursiveCall(newResult);
}).catch((error) => {
console.error(error);
return recursiveCall(result);
});
} else return Promise.resolve(responseList);
};
return recursiveCall();
}
fetchSequentially(functionName, mergeFunction, params) {
const resultPromises = this.dataProviders.map((dp) => {
const providerMethod = dp.dataProvider[functionName];
return this.processResults(providerMethod.call(dp.dataProvider, params), dp.name, dp.useInStats);
});
return Promise.all(resultPromises).then(mergeFunction);
}
};
//#endregion
//#region src/graph-explorer/editor/serializedDiagram.ts
var serializedCellProperties = [
"id",
"type",
"size",
"angle",
"isExpanded",
"position",
"iri",
"group",
"typeId",
"source",
"target",
"vertices"
];
function emptyDiagram() {
return {
"@context": DIAGRAM_CONTEXT_URL_V1,
"@type": "Diagram",
layoutData: emptyLayoutData(),
linkTypeOptions: []
};
}
function emptyLayoutData() {
return {
"@type": "Layout",
elements: [],
links: []
};
}
function convertToSerializedDiagram(params) {
const elements = [];
const links = [];
for (const cell of params.layoutData.cells) {
const newCell = pick(cell, serializedCellProperties);
if (newCell.type === "GraphExplorer.Element" || newCell.type === "element") newCell.type = "Element";
if (newCell.type === "link") newCell.type = "Link";
if (!newCell.iri) newCell.iri = newCell.id;
newCell["@id"] = newCell.id;
delete newCell.id;
newCell["@type"] = newCell.type;
delete newCell.type;
switch (newCell["@type"]) {
case "Element":
elements.push(newCell);
break;
case "Link":
newCell.source["@id"] = newCell.source.id;
delete newCell.source.id;
newCell.target["@id"] = newCell.target.id;
delete newCell.target.id;
newCell.property = newCell.typeId;
delete newCell.typeId;
links.push(newCell);
break;
}
}
return {
...emptyDiagram(),
layoutData: {
"@type": "Layout",
elements,
links
},
linkTypeOptions: params.linkTypeOptions
};
}
function makeSerializedDiagram(params) {
const diagram = {
...emptyDiagram(),
linkTypeOptions: params.linkTypeOptions
};
if (params.layoutData) diagram.layoutData = params.layoutData;
return diagram;
}
function makeLayoutData(modelElements, modelLinks) {
return {
"@type": "Layout",
elements: modelElements.map((element) => ({
"@type": "Element",
"@id": element.id,
iri: element.iri,
position: element.position,
size: element.size,
isExpanded: element.isExpanded,
group: element.group,
elementState: element.elementState
})),
links: modelLinks.map((link) => ({
"@type": "Link",
"@id": link.id,
property: link.typeId,
source: { "@id": link.sourceId },
target: { "@id": link.targetId },
vertices: [...link.vertices],
linkState: link.linkState
}))
};
}
//#endregion
//#region src/graph-explorer/viewUtils/layout.ts
function groupForceLayout(params) {
new cola.Layout().nodes(params.nodes).links(params.links).avoidOverlaps(params.avoidOvelaps).convergenceThreshold(1e-9).jaccardLinkLengths(params.preferredLinkLength).handleDisconnected(true).start(30, 0, 10, void 0, false);
}
function groupRemoveOverlaps(nodes) {
const nodeRectangles = [];
for (const node of nodes) nodeRectangles.push(new cola.Rectangle(node.x, node.x + node.width, node.y, node.y + node.height));
cola.removeOverlaps(nodeRectangles);
for (let i = 0; i < nodeRectangles.length; i++) {
const node = nodes[i];
const rectangle = nodeRectangles[i];
node.x = rectangle.x;
node.y = rectangle.y;
}
}
function translateToPositiveQuadrant(positions, offset) {
let minX = Infinity, minY = Infinity;
positions.forEach((position) => {
minX = Math.min(minX, position.x);
minY = Math.min(minY, position.y);
});
const { x, y } = offset;
positions.forEach((position, key) => {
positions.set(key, {
x: position.x - minX + x,
y: position.y - minY + y
});
});
}
function uniformGrid(params) {
return (cellIndex) => {
const row = Math.floor(cellIndex / params.rows);
return {
x: (cellIndex - row * params.rows) * params.cellSize.x,
y: row * params.cellSize.y,
width: params.cellSize.x,
height: params.cellSize.y
};
};
}
function padded(nodes, padding, transform) {
if (padding) for (const node of nodes) {
node.x -= padding.x;
node.y -= padding.y;
node.width += 2 * padding.x;
node.height += 2 * padding.y;
}
transform();
if (padding) for (const node of nodes) {
node.x += padding.x;
node.y += padding.y;
node.width -= 2 * padding.x;
node.height -= 2 * padding.y;
}
}
function biasFreePadded(nodes, padding, transform) {
const nodeSizeMap = /* @__PURE__ */ new Map();
const possibleCompression = {
x: Infinity,
y: Infinity
};
for (const node of nodes) {
nodeSizeMap.set(node.id, {
width: node.width,
height: node.height
});
const maxSide = Math.max(node.width, node.height);
const compressionX = node.width ? maxSide / node.width : 1;
const compressionY = node.height ? maxSide / node.height : 1;
possibleCompression.x = Math.min(1 + (compressionX - 1), possibleCompression.x);
possibleCompression.y = Math.min(1 + (compressionY - 1), possibleCompression.y);
node.height = maxSide;
node.width = maxSide;
}
padded(nodes, padding, () => transform());
const fittingBox = getContentFittingBoxForLayout(nodes);
for (const node of nodes) {
const size = nodeSizeMap.get(node.id);
node.x = (node.x - fittingBox.x) / possibleCompression.x + fittingBox.x;
node.y = (node.y - fittingBox.y) / possibleCompression.y + fittingBox.y;
node.height = size.height;
node.width = size.width;
}
}
function calculateLayout(params) {
const grouping = computeGrouping(params.model.elements);
const { layoutFunction, model, fixedElements, selectedElements } = params;
if (selectedElements && selectedElements.size <= 1) return {
positions: /* @__PURE__ */ new Map(),
nestedLayouts: [],
keepAveragePosition: false
};
return internalRecursion(params.group);
function internalRecursion(group) {
const elementsToProcess = group ? grouping.get(group) : model.elements.filter((el) => el.group === void 0);
const elements = selectedElements ? elementsToProcess.filter((el) => selectedElements.has(el)) : elementsToProcess;
const nestedLayouts = [];
for (const element of elements) if (grouping.has(element.id)) nestedLayouts.push(internalRecursion(element.id));
const nodes = [];
const nodeById = {};
for (const element of elements) {
const { x, y, width, height } = boundsOf(element);
const node = {
id: element.id,
x,
y,
width,
height,
fixed: fixedElements && fixedElements.has(element) ? 1 : 0
};
nodeById[element.id] = node;
nodes.push(node);
}
const links = [];
for (const link of model.links) {
if (!model.isSourceAndTargetVisible(link)) continue;
const source = model.sourceOf(link);
const target = model.targetOf(link);
const sourceNode = nodeById[source.id];
const targetNode = nodeById[target.id];
if (sourceNode && targetNode) links.push({
source: sourceNode,
target: targetNode
});
}
layoutFunction(nodes, links, group);
const positions = /* @__PURE__ */ new Map();
for (const node of nodes) positions.set(node.id, {
x: node.x,
y: node.y
});
return {
positions,
group,
nestedLayouts,
keepAveragePosition: Boolean(selectedElements)
};
}
}
function applyLayout(model, layout) {
const { positions, group, nestedLayouts, keepAveragePosition } = layout;
const elements = model.elements.filter(({ id }) => positions.has(id));
for (const nestedLayout of nestedLayouts) applyLayout(model, nestedLayout);
if (group) translateToPositiveQuadrant(positions, getContentFittingBox(elements, []));
const averagePosition = keepAveragePosition ? calculateAveragePosition(elements) : void 0;
for (const element of elements) element.setPosition(positions.get(element.id));
if (keepAveragePosition) {
const newAveragePosition = calculateAveragePosition(elements);
const averageDiff = {
x: averagePosition.x - newAveragePosition.x,
y: averagePosition.y - newAveragePosition.y
};
positions.forEach((position, elementId) => {
model.getElement(elementId).setPosition({
x: position.x + averageDiff.x,
y: position.y + averageDiff.y
});
});
}
}
function calculateAveragePosition(position) {
let xSum = 0;
let ySum = 0;
for (const element of position) {
xSum += element.position.x + element.size.width / 2;
ySum += element.position.y + element.size.height / 2;
}
return {
x: xSum / position.length,
y: ySum / position.length
};
}
function placeElementsAround(params) {
const { model, elements, targetElement, prefferedLinksLength } = params;
const targetElementBounds = boundsOf(targetElement);
const targetPosition = {
x: targetElementBounds.x + targetElementBounds.width / 2,
y: targetElementBounds.y + targetElementBounds.height / 2
};
let outgoingAngle = 0;
if (targetElement.links.length > 0) {
const averageSourcePosition = calculateAveragePosition(targetElement.links.map((link) => {
const linkSource = model.sourceOf(link);
return linkSource !== targetElement ? linkSource : model.targetOf(link);
}));
const vectorDiff = {
x: targetPosition.x - averageSourcePosition.x,
y: targetPosition.y - averageSourcePosition.y
};
if (vectorDiff.x !== 0 || vectorDiff.y !== 0) outgoingAngle = Math.atan2(vectorDiff.y, vectorDiff.x);
}
const step = Math.min(Math.PI / elements.length, Math.PI / 6);
const elementsSteck = [].concat(elements);
const placeElementFromSteck = (curAngle, element) => {
if (element) {
const size = element.size;
element.setPosition({
x: targetPosition.x + prefferedLinksLength * Math.cos(curAngle) - size.width / 2,
y: targetPosition.y + prefferedLinksLength * Math.sin(curAngle) - size.height / 2
});
}
};
if (elementsSteck.length % 2 === 0) for (let angle = step / 2; elementsSteck.length > 0; angle += step) {
placeElementFromSteck(outgoingAngle - angle, elementsSteck.pop());
placeElementFromSteck(outgoingAngle + angle, elementsSteck.pop());
}
else {
placeElementFromSteck(outgoingAngle, elementsSteck.pop());
for (let angle = step; elementsSteck.length > 0; angle += step) {
placeElementFromSteck(outgoingAngle - angle, elementsSteck.pop());
placeElementFromSteck(outgoingAngle + angle, elementsSteck.pop());
}
}
return new Promise((resolve) => {
const listener = new EventObserver();
listener.listen(model.events, "changeCells", () => {
listener.stopListening();
removeOverlaps({
model,
padding: {
x: 15,
y: 15
}
});
resolve(true);
});
});
}
function removeOverlaps(params) {
const { padding, model, group, fixedElements, selectedElements } = params;
return calculateLayout({
model,
group,
fixedElements,
selectedElements,
layoutFunction: (nodes) => {
padded(nodes, padding, () => groupRemoveOverlaps(nodes));
}
});
}
function forceLayout(params) {
const { model, group, fixedElements, selectedElements } = params;
return calculateLayout({
model,
group,
fixedElements,
selectedElements,
layoutFunction: (nodes, links) => {
if (fixedElements && fixedElements.size > 0) biasFreePadded(nodes, {
x: 50,
y: 50
}, () => groupForceLayout({
nodes,
links,
preferredLinkLength: 200,
avoidOvelaps: true
}));
else {
groupForceLayout({
nodes,
links,
preferredLinkLength: 200
});
biasFreePadded(nodes, {
x: 50,
y: 50
}, () => groupRemoveOverlaps(nodes));
}
}
});
}
function getContentFittingBoxForLayout(nodes) {
let minX = Infinity, minY = Infinity;
let maxX = -Infinity, maxY = -Infinity;
for (const node of nodes) {
const { x, y, width, height } = node;
minX = Math.min(minX, x);
minY = Math.min(minY, y);
maxX = Math.max(maxX, x + width);
maxY = Math.max(maxY, y + height);
}
return {
x: Number.isFinite(minX) ? minX : 0,
y: Number.isFinite(minY) ? minY : 0,
width: Number.isFinite(minX) && Number.isFinite(maxX) ? maxX - minX : 0,
height: Number.isFinite(minY) && Number.isFinite(maxY) ? maxY - minY : 0
};
}
//#endregion
//#region src/graph-explorer/data/sparql/graphBuilder.ts
var GREED_STEP = 150;
var GraphBuilder = class {
constructor(dataProvider) {
this.dataProvider = dataProvider;
}
createGraph(graph) {
return this.dataProvider.elementInfo({ elementIds: graph.elementIds }).then((elementsInfo) => ({
preloadedElements: elementsInfo,
diagram: makeLayout(graph.elementIds, graph.links)
}));
}
getGraphFromRDFGraph(graph) {
const { elementIds, links } = makeGraphItems(graph);
return this.createGraph({
elementIds,
links
});
}
getGraphFromTurtleGraph(graph) {
return parseTurtleText(graph).then((triples) => this.getGraphFromRDFGraph(triples));
}
};
function makeGraphItems(response) {
const elements = {};
const links = [];
for (const { subject, predicate, object } of response) {
if (subject.type === "uri" && !elements[subject.value]) elements[subject.value] = true;
if (object.type === "uri" && !elements[object.value]) elements[object.value] = true;
if (subject.type === "uri" && object.type === "uri") links.push({
linkTypeId: predicate.value,
sourceId: subject.value,
targetId: object.value
});
}
return {
elementIds: Object.keys(elements),
links
};
}
function makeLayout(elementsIds, linksInfo) {
const grid = uniformGrid({
rows: Math.ceil(Math.sqrt(elementsIds.length)),
cellSize: {
x: GREED_STEP,
y: GREED_STEP
}
});
const elements = elementsIds.map((id, index) => {
const { x, y } = grid(index);
return {
"@type": "Element",
"@id": GenerateID.forElement(),
iri: id,
position: {
x,
y
}
};
});
const layoutElementsMap = keyBy(elements, "iri");
const links = [];
linksInfo.forEach((link, _index) => {
const source = layoutElementsMap[link.sourceId];
const target = layoutElementsMap[link.targetId];
if (!source || !target) return;
links.push({
"@type": "Link",
"@id": GenerateID.forLink(),
property: link.linkTypeId,
source: { "@id": source["@id"] },
target: { "@id": target["@id"] }
});
});
return makeSerializedDiagram({
layoutData: {
"@type": "Layout",
elements,
links
},
linkTypeOptions: []
});
}
//#endregion
//#region src/graph-explorer/data/sparql/sparqlGraphBuilder.ts
var DEFAULT_PREFIX = "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>\n PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>\n PREFIX owl: <http://www.w3.org/2002/07/owl#>\n\n";
var SparqlGraphBuilder = class {
constructor(dataProvider) {
this.dataProvider = dataProvider;
this.graphBuilder = new GraphBuilder(dataProvider);
}
getGraphFromConstruct(constructQuery) {
const query = DEFAULT_PREFIX + constructQuery;
return this.dataProvider.executeSparqlConstruct(query).then((graph) => this.graphBuilder.getGraphFromRDFGraph(graph));
}
};
//#endregion
//#region src/graph-explorer/diagram/graph.ts
var Graph = class Graph {
constructor() {
this.source = new EventSource();
this.events = this.source;
this.elements = new OrderedMap();
this.links = new OrderedMap();
this.classesById = /* @__PURE__ */ new Map();
this.propertiesById = /* @__PURE__ */ new Map();
this.linkTypes = /* @__PURE__ */ new Map();
this.onElementEvent = (data, key) => {
this.source.trigger("elementEvent", {
key,
data
});
};
this.onLinkEvent = (data, key) => {
this.source.trigger("linkEvent", {
key,
data
});
};
this.onLinkTypeEvent = (data, key) => {
this.source.trigger("linkTypeEvent", {
key,
data
});
};
this.onClassEvent = (data, key) => {
this.source.trigger("classEvent", {
key,
data
});
};
}
static {
this.nextLinkTypeIndex = 0;
}
getElements() {
return this.elements.items;
}
getLinks() {
return this.links.items;
}
getLink(linkId) {
return this.links.get(linkId);
}
findLink(linkTypeId, sourceId, targetId) {
const source = this.getElement(sourceId);
if (!source) return;
const index = findLinkIndex(source.links, linkTypeId, sourceId, targetId);
return index >= 0 ? source.links[index] : void 0;
}
sourceOf(link) {
return this.getElement(link.sourceId);
}
targetOf(link) {
return this.getElement(link.targetId);
}
reorderElements(compare) {
this.elements.reorder(compare);
}
getElement(elementId) {
return this.elements.get(elementId);
}
addElement(element) {
if (this.getElement(element.id)) throw new Error(`Element '${element.id}' already exists.`);
element.events.onAny(this.onElementEvent);
this.elements.push(element.id, element);
this.source.trigger("changeCells", {
updateAll: false,
changedElement: element
});
}
removeElement(elementId) {
const element = this.elements.get(elementId);
if (element) {
const options = { silent: true };
const changedLinks = [...element.links];
for (const link of changedLinks) this.removeLink(link.id, options);
this.elements.delete(elementId);
element.events.offAny(this.onElementEvent);
this.source.trigger("changeCells", {
updateAll: false,
changedElement: element,
changedLinks
});
}
}
addLink(link) {
if (this.getLink(link.id)) throw new Error(`Link '${link.id}' already exists.`);
if (!this.getLinkType(link.typeId)) throw new Error(`Link type '${link.typeId}' not found.`);
this.registerLink(link);
}
registerLink(link) {
this.sourceOf(link).links.push(link);
if (link.sourceId !== link.targetId) this.targetOf(link).links.push(link);
link.events.onAny(this.onLinkEvent);
this.links.push(link.id, link);
this.source.trigger("changeCells", {
updateAll: false,
changedLinks: [link]
});
}
removeLink(linkId, options) {
const link = this.links.delete(linkId);
if (link) {
const { typeId, sourceId, targetId } = link;
link.events.offAny(this.onLinkEvent);
this.removeLinkReferences(typeId, sourceId, targetId);
if (!(options && options.silent)) this.source.trigger("changeCells", {
updateAll: false,
changedLinks: [link]
});
}
}
removeLinkReferences(linkTypeId, sourceId, targetId) {
const source = this.getElement(sourceId);
if (source) removeLinkFrom(source.links, linkTypeId, sourceId, targetId);
const target = this.getElement(targetId);
if (target) removeLinkFrom(target.links, linkTypeId, sourceId, targetId);
}
getLinkTypes() {
const result = [];
this.linkTypes.forEach((type) => result.push(type));
return result;
}
getLinkType(linkTypeId) {
return this.linkTypes.get(linkTypeId);
}
addLinkType(linkType) {
if (this.getLinkType(linkType.id)) throw new Error(`Link type '${linkType.id}' already exists.`);
linkType.setIndex(Graph.nextLinkTypeIndex++);
linkType.events.onAny(this.onLinkTypeEvent);
this.linkTypes.set(linkType.id, linkType);
}
getProperty(propertyId) {
return this.propertiesById.get(propertyId);
}
addProperty(property) {
if (this.getProperty(property.id)) throw new Error(`Property '${property.id}' already exists.`);
this.propertiesById.set(property.id, property);
}
getClass(classId) {
return this.classesById.get(classId);
}
getClasses() {
const classes = [];
this.classesById.forEach((richClass) => classes.push(richClass));
return classes;
}
addClass(classModel) {
if (this.getClass(classModel.id)) throw new Error(`Class '${classModel.id}' already exists.`);
classModel.events.onAny(this.onClassEvent);
this.classesById.set(classModel.id, classModel);
}
};
function removeLinkFrom(links, linkTypeId, sourceId, targetId) {
if (!links) return;
while (true) {
const index = findLinkIndex(links, linkTypeId, sourceId, targetId);
if (index < 0) break;
links.splice(index, 1);
}
}
function findLinkIndex(haystack, linkTypeId, sourceId, targetId) {
for (let i = 0; i < haystack.length; i++) {
const link = haystack[i];
if (link.sourceId === sourceId && link.targetId === targetId && link.typeId === linkTypeId) return i;
}
return -1;
}
//#endregion
//#region src/graph-explorer/diagram/model.ts
/**
* Model of diagram.
*/
var DiagramModel = class {
constructor(history) {
this.history = history;
this.source = new EventSource();
this.events = this.source;
this.graph = new Graph();
this.graphListener = new EventObserver();
}
get elements() {
return this.graph.getElements();
}
get links() {
return this.graph.getLinks();
}
getElement(elementId) {
return this.graph.getElement(elementId);
}
getLinkById(linkId) {
return this.graph.getLink(linkId);
}
linksOfType(linkTypeId) {
return this.graph.getLinks().filter((link) => link.typeId === linkTypeId);
}
findLink(linkTypeId, sourceId, targetId) {
return this.graph.findLink(linkTypeId, sourceId, targetId);
}
sourceOf(link) {
return this.getElement(link.sourceId);
}
targetOf(link) {
return this.getElement(link.targetId);
}
isSourceAndTargetVisible(link) {
return Boolean(this.sourceOf(link) && this.targetOf(link));
}
resetGraph() {
if (this.graphListener) {
this.graphListener.stopListening();
this.graphListener = new EventObserver();
}
this.graph = new Graph();
}
subscribeGraph() {
this.graphListener.listen(this.graph.events, "changeCells", (e) => {
this.source.trigger("changeCells", e);
});
this.graphListener.listen(this.graph.events, "elementEvent", (e) => {
this.source.trigger("elementEvent", e);
});
this.graphListener.listen(this.graph.events, "linkEvent", (e) => {
this.source.trigger("linkEvent", e);
});
this.graphListener.listen(this.graph.events, "linkTypeEvent", (e) => {
this.source.trigger("linkTypeEvent", e);
});
this.graphListener.listen(this.graph.events, "classEvent", (e) => {
this.source.trigger("classEvent", e);
});
this.source.trigger("changeCells", { updateAll: true });
}
reorderElements(compare) {
this.graph.reorderElements(compare);
}
createElement(elementIriOrModel, group) {
const elementIri = typeof elementIriOrModel === "string" ? elementIriOrModel : elementIriOrModel.id;
const elements = this.elements.filter((el) => el.iri === elementIri && el.group === group);
if (elements.length > 0) return elements[0];
let data = typeof elementIriOrModel === "string" ? placeholderDataFromIri(elementIri) : elementIriOrModel;
data = {
...data,
id: data.id
};
const element = new Element$1({
id: GenerateID.forElement(),
data,
group
});
this.addElement(element);
return element;
}
addElement(element) {
this.history.execute(addElement(this.graph, element, []));
}
removeElement(elementId) {
const element = this.getElement(elementId);
if (element) this.history.execute(removeElement(this.graph, element));
}
addLink(link) {
const { typeId, sourceId, targetId, data } = link;
if (data && data.linkTypeId !== typeId) throw new Error("linkTypeId must match linkType.id");
const existingLink = this.findLink(typeId, sourceId, targetId);
if (existingLink) {
if (link.data) {
existingLink.setLayoutOnly(false);
existingLink.setData(data);
}
return existingLink;
}
const linkType = this.createLinkType(link.typeId);
const source = this.getElement(sourceId);
const target = this.getElement(targetId);
if (!(linkType.visible && source && target)) return;
if (!link.data) link.setData({
linkTypeId: typeId,
sourceId: source.iri,
targetId: target.iri
});
this.graph.addLink(link);
return link;
}
removeLink(linkId) {
this.graph.removeLink(linkId);
}
getClass(classIri) {
return this.graph.getClass(classIri);
}
createClass(classIri) {
const existing = this.graph.getClass(classIri);
if (existing) return existing;
const classModel = new FatClassModel({ id: classIri });
this.addClass(classModel);
return classModel;
}
addClass(model) {
this.graph.addClass(model);
}
getLinkType(linkTypeIri) {
return this.graph.getLinkType(linkTypeIri);
}
createLinkType(linkTypeIri) {
const existing = this.graph.getLinkType(linkTypeIri);
if (existing) return existing;
const linkType = new FatLinkType({ id: linkTypeIri });
this.graph.addLinkType(linkType);
return linkType;
}
getProperty(propertyTypeIri) {
return this.graph.getProperty(propertyTypeIri);
}
createProperty(propertyIri) {
const existing = this.graph.getProperty(propertyIri);
if (existing) return existing;
const property = new RichProperty({ id: propertyIri });
this.graph.addProperty(property);
return property;
}
triggerChangeGroupContent(group) {
this.source.trigger("changeGroupContent", { group });
}
createTemporaryElement() {
const target = new Element$1({
id: GenerateID.forElement(),
data: placeholderDataFromIri(""),
temporary: true
});
this.graph.addElement(target);
return target;
}
};
function placeholderDataFromIri(iri) {
return {
id: iri,
types: [],
label: { values: [] },
properties: {}
};
}
function addElement(graph, element, connectedLinks) {
return Command.create("Add element", () => {
graph.addElement(element);
for (const link of connectedLinks) if (!(graph.getLink(link.id) || graph.findLink(link.typeId, link.sourceId, link.targetId))) graph.addLink(link);
return removeElement(graph, element);
});
}
function removeElement(graph, element) {
return Command.create("Remove element", () => {
const connectedLinks = [...element.links];
graph.removeElement(element.id);
return addElement(graph, element, connectedLinks);
});
}
//#endregion
//#region src/graph-explorer/editor/dataFetcher.ts
var DataFetcher = class {
constructor(graph, dataProvider) {
this.graph = graph;
this.dataProvider = dataProvider;
this.classQueue = new BufferingQueue((classIds) => {
this.dataProvider.classInfo({ classIds }).then(this.onClassesLoaded);
});
this.linkTypeQueue = new BufferingQueue((linkTypeIds) => {
this.dataProvider.linkTypesInfo({ linkTypeIds }).then(this.onLinkTypesLoaded);
});
this.propertyTypeQueue = new BufferingQueue((propertyIds) => {
this.dataProvider.propertyInfo({ propertyIds }).then(this.onPropertyTypesLoaded);
});
this.onElementInfoLoaded = (elements) => {
for (const element of this.graph.getElements()) {
const loadedModel = elements[element.iri];
if (loadedModel) element.setData(loadedModel);
}
};
this.onClassesLoaded = (classInfos) => {
for (const { id, label, count } of classInfos) {
const model = this.graph.getClass(id);
if (!model) continue;
model.setLabel(label.values);
if (typeof count === "number") model.setCount(count);
}
};
this.onLinkTypesLoaded = (linkTypesInfo) => {
for (const { id, label } of linkTypesInfo) {
const model = this.graph.getLinkType(id);
if (!model) continue;
model.setLabel(label.values);
}
};
this.onPropertyTypesLoaded = (propertyModels) => {
for (const propId in propertyModels) {
if (!Object.prototype.hasOwnProperty.call(propertyModels, propId)) continue;
const { id, label } = propertyModels[propId];
const targetProperty = this.graph.getProperty(id);
if (targetProperty) targetProperty.setLabel(label.values);
}
};
}
fetchElementData(elementIris) {
if (elementIris.length === 0) return Promise.resolve();
return this.dataProvider.elementInfo({ elementIds: [...elementIris] }).then(this.onElementInfoLoaded);
}
fetchClass(model) {
this.classQueue.push(model.id);
}
fetchLinkType(linkType) {
this.linkTypeQueue.push(linkType.id);
}
fetchPropertyType(propertyType) {
if (!this.dataProvider.propertyInfo) return;
this.propertyTypeQueue.push(propertyType.id);
}
};
//#endregion
//#region src/graph-explorer/editor/asyncModel.ts
var AsyncModel = class extends DiagramModel {
constructor(history, groupByProperties) {
super(history);
this.groupByProperties = groupByProperties;
this.linkSettings = {};
this.onLinkTypeVisibilityChanged = (e) => {
if (e.source.visible) {
if (!e.preventLoading) this.requestLinksOfType([e.source.id]);
} else for (const link of this.linksOfType(e.source.id)) this.graph.removeLink(link.id);
};
}
get asyncSource() {
return this.source;
}
get dataProvider() {
return this._dataProvider;
}
subscribeGraph() {
super.subscribeGraph();
this.graphListener.listen(this.graph.events, "linkTypeEvent", (e) => {
if (e.key === "changeVisibility") this.onLinkTypeVisibilityChanged(e.data[e.key], e.key);
});
}
setDataProvider(dataProvider) {
this._dataProvider = dataProvider;
this.fetcher = new DataFetcher(this.graph, dataProvider);
}
createNewDiagram(dataProvider) {
this.resetGraph();
this.setDataProvider(dataProvider);
this.asyncSource.trigger("loadingStart", { source: this });
return this.dataProvider.linkTypes().then((linkTypes) => {
const allLinkTypes = this.initLinkTypes(linkTypes);
return this.loadAndRenderLayout({
allLinkTypes,
markLinksAsLayoutOnly: false
});
}).catch((error) => {
console.error(error);
this.asyncSource.trigger("loadingError", {
source: this,
error
});
return Promise.reject(error);
});
}
importLayout(params) {
this.resetGraph();
this.setDataProvider(params.dataProvider);
this.asyncSource.trigger("loadingStart", { source: this });
return this.dataProvider.linkTypes().then((linkTypes) => {
const allLinkTypes = this.initLinkTypes(linkTypes);
const diagram = params.diagram ? params.diagram : emptyDiagram();
this.setLinkSettings(diagram.linkTypeOptions);
const loadingModels = this.loadAndRenderLayout({
layoutData: diagram.layoutData,
preloadedElements: params.preloadedElements || {},
markLinksAsLayoutOnly: params.validateLinks || false,
allLinkTypes,
hideUnusedLinkTypes: params.hideUnusedLinkTypes
});
const requestingLinks = params.validateLinks ? this.requestLinksOfType() : Promise.resolve();
return Promise.all([loadingModels, requestingLinks]);
}).then(() => {
this.asyncSource.trigger("loadingSuccess", { source: this });
}).catch((error) => {
console.error(error);
this.asyncSource.trigger("loadingError", {
source: this,
error
});
return Promise.reject(error);
});
}
exportLayout() {
return makeSerializedDiagram({
layoutData: makeLayoutData(this.graph.getElements(), this.graph.getLinks()),
linkTypeOptions: this.graph.getLinkTypes().filter((linkType) => (!linkType.visible || !linkType.showLabel) && linkType.id !== "http://graph-explorer.org/NewLink").map(({ id, visible, showLabel }) => ({
"@type": "LinkTypeOptions",
property: id,
visible,
showLabel
}))
});
}
initLinkTypes(linkTypes) {
const types = [];
for (const { id, label } of linkTypes) {
const linkType = new FatLinkType({
id,
label: label.values
});
this.graph.addLinkType(linkType);
types.push(linkType);
}
return types;
}
setLinkSettings(settings) {
if (!settings) return;
for (const setting of settings) {
const { visible = true, showLabel = true } = setting;
const linkTypeId = setting.property;
this.linkSettings[linkTypeId] = {
"@type": "LinkTypeOptions",
property: linkTypeId,
visible,
showLabel
};
const linkType = this.getLinkType(linkTypeId);
if (linkType) linkType.setVisibility({
visible,
showLabel
});
}
}
loadAndRenderLayout(params) {
const { layoutData = emptyLayoutData(), preloadedElements = {}, markLinksAsLayoutOnly, hideUnusedLinkTypes } = params;
const elementIrisToRequestData = [];
const usedLinkTypes = {};
for (const layoutElement of layoutData.elements) {
const { "@id": id, iri, position, size, isExpanded, group, elementState } = layoutElement;
const template = preloadedElements[iri];
const element = new Element$1({
id,
data: template || placeholderDataFromIri(iri),
position,
size,
expanded: isExpanded,
group,
elementState
});
this.graph.addElement(element);
if (!template) elementIrisToRequestData.push(element.iri);
}
for (const layoutLink of layoutData.links) {
const { "@id": id, property, source, target, vertices, linkState } = layoutLink;
const linkType = this.createLinkType(property);
usedLinkTypes[linkType.id] = linkType;
const link = this.addLink(new Link({
id,
typeId: linkType.id,
sourceId: source["@id"],
targetId: target["@id"],
vertices,
linkState
}));
if (link) link.setLayoutOnly(markLinksAsLayoutOnly);
}
this.subscribeGraph();
const requestingModels = this.requestElementData(elementIrisToRequestData);
if (hideUnusedLinkTypes && params.allLinkTypes) this.hideUnusedLinkTypes(params.allLinkTypes, usedLinkTypes);
return requestingModels;
}
hideUnusedLinkTypes(allTypes, usedTypes) {
for (const linkType of allTypes) if (!usedTypes[linkType.id]) linkType.setVisibility({
visible: false,
showLabel: linkType.showLabel
});
}
requestElementData(elementIris) {
return this.fetcher.fetchElementData(elementIris);
}
requestLinksOfType(linkTypeIds) {
const linkTypes = linkTypeIds || this.graph.getLinkTypes().filter((type) => type.visible).map((type) => type.id);
return this.dataProvider.linksInfo({
elementIds: this.graph.getElements().map((element) => element.iri),
linkTypeIds: linkTypes
}).then((links) => this.onLinkInfoLoaded(links));
}
createClass(classId) {
if (this.graph.getClass(classId)) return super.getClass(classId);
const classModel = super.createClass(classId);
this.fetcher.fetchClass(classModel);
return classModel;
}
createLinkType(linkTypeId) {
if (this.graph.getLinkType(linkTypeId)) return super.createLinkType(linkTypeId);
const linkType = super.createLinkType(linkTypeId);
const setting = this.linkSettings[linkType.id];
if (setting) {
const { visible, showLabel } = setting;
linkType.setVisibility({
visible,
showLabel,
preventLoading: true
});
}
this.fetcher.fetchLinkType(linkType);
return linkType;
}
createProperty(propertyIri) {
if (this.graph.getProperty(propertyIri)) return super.createProperty(propertyIri);
const property = super.createProperty(propertyIri);
this.fetcher.fetchPropertyType(property);
return property;
}
onLinkInfoLoaded(links) {
let allowToCreate;
const cancel = () => {
allowToCreate = false;
};
for (const linkModel of links) {
this.createLinkType(linkModel.linkTypeId);
allowToCreate = true;
this.asyncSource.trigger("createLoadedLink", {
source: this,
model: linkModel,
cancel
});
if (allowToCreate) this.createLinks(linkModel);
}
}
createLinks(data) {
const sources = this.graph.getElements().filter((el) => el.iri === data.sourceId);
const targets = this.graph.getElements().filter((el) => el.iri === data.targetId);
const typeId = data.linkTypeId;
for (const source of sources) for (const target of targets) this.addLink(new Link({
typeId,
sourceId: source.id,
targetId: target.id,
data
}));
}
loadEmbeddedElements(elementIri) {
const elements = this.groupByProperties.map((groupBy) => this.dataProvider.linkElements({
elementId: elementIri,
linkId: groupBy.linkType,
offset: 0,
direction: groupBy.linkDirection
}));
return Promise.all(elements).then((res) => res.reduce((memo, current) => Object.assign(memo, current), {}));
}
};
function requestElementData(model, elementIris) {
return Command.effect("Fetch element data", () => {
model.requestElementData(elementIris);
});
}
function restoreLinksBetweenElements(model) {
return Command.effect("Restore links between elements", () => {
model.requestLinksOfType();
});
}
//#endregion
//#region src/graph-explorer/workspace/draggableHandle.tsx
var DraggableHandle = class extends React.Component {
constructor(..._args) {
super(..._args);
this.isHoldingMouse = false;
this.onHandleMouseDown = (e) => {
if (e.target !== e.currentTarget) return;
if (this.isHoldingMouse) return;
if (e.button !== 0) return;
this.isHoldingMouse = true;
this.originPageX = e.pageX;
this.originPageY = e.pageY;
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
this.props.onBeginDragHandle(e);
};
this.onMouseMove = (e) => {
if (!this.isHoldingMouse) return;
e.preventDefault();
this.props.onDragHandle(e, e.pageX - this.originPageX, e.pageY - this.originPageY);
};
this.onMouseUp = (e) => {
this.removeListeners();
if (this.props.onEndDragHandle) this.props.onEndDragHandle(e);
};
}
render() {
const { onBeginDragHandle: _onBeginDragHandle, onDragHandle: _onDragHandle, onEndDragHandle: _onEndDragHandle, ...props } = this.props;
return /* @__PURE__ */ React.createElement("div", {
...props,
onMouseDown: this.onHandleMouseDown
}, this.props.children);
}
componentWillUnmount() {
this.removeListeners();
}
removeListeners() {
if (this.isHoldingMouse) {
this.isHoldingMouse = false;
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
}
}
};
//#endregion
//#region src/graph-explorer/widgets/dialog.tsx
var DEFAULT_WIDTH = 300;
var DEFAULT_HEIGHT = 300;
var MIN_WIDTH = 250;
var MIN_HEIGHT = 250;
var MAX_WIDTH = 800;
var MAX_HEIGHT = 800;
var ELEMENT_OFFSET = 40;
var LINK_OFFSET = 20;
var FOCUS_OFFSET = 20;
var CLASS_NAME$19 = "graph-explorer-dialog";
var Dialog = class extends React.Component {
static {
this.defaultProps = { size: {
width: DEFAULT_WIDTH,
height: DEFAULT_HEIGHT
} };
}
constructor(props) {
super(props);
this.unsubscribeFromTarget = void 0;
this.handler = new EventObserver();
this.updateAll = () => this.forceUpdate();
this.onStartDragging = (_e) => {
this.preventSelection();
const { size } = this.props;
this.startSize = {
x: this.state.width || size.width,
y: this.state.height || size.height
};
};
this.onDragHandleBottom = (e, dx, dy) => {
const height = this.calculateHeight(this.startSize.y + dy);
this.setState({ height });
};
this.onDragHandleRight = (e, dx) => {
const width = this.calculateWidth(this.startSize.x + dx);
this.setState({ width });
};
this.onDragHandleBottomRight = (e, dx, dy) => {
const width = this.calculateWidth(this.startSize.x + dx);
const height = this.calculateHeight(this.startSize.y + dy);
this.setState({
width,
height
});
};
this.preventSelection = () => {
const onMouseUp = () => {
document.body.classList.remove("graph-explorer--unselectable");
document.removeEventListener("mouseup", onMouseUp);
};
document.addEventListener("mouseup", onMouseUp);
document.body.classList.add("graph-explorer--unselectable");
};
this.state = {};
}
componentDidMount() {
this.listenToTarget(this.props.target);
this.focusOn();
}
componentDidUpdate(nextProps) {
if (nextProps.target !== this.props.target) this.listenToTarget(nextProps.target);
}
componentWillUnmount() {
this.listenToTarget(void 0);
}
listenToTarget(target) {
if (this.unsubscribeFromTarget) {
this.unsubscribeFromTarget();
this.unsubscribeFromTarget = void 0;
}
if (target) {
const { view } = this.props;
if (target instanceof Element$1) this.listenToElement(target);
else if (target instanceof Link) this.listenToLink(target);
this.handler.listen(view.events, "changeLanguage", this.updateAll);
this.unsubscribeFromTarget = () => {
this.handler.stopListening();
};
}
}
listenToElement(element) {
this.handler.listen(element.events, "changePosition", this.updateAll);
this.handler.listen(element.events, "changeSize", this.updateAll);
}
listenToLink(link) {
const { view } = this.props;
const source = view.model.getElement(link.sourceId);
const target = view.model.getElement(link.targetId);
this.listenToElement(source);
this.listenToElement(target);
this.handler.listen(link.events, "changeVertices", this.updateAll);
this.handler.listen(link.events, "changeLabelBounds", this.updateAll);
}
calculatePositionForElement(element) {
const { paperArea, size } = this.props;
const bbox = boundsOf(element);
const { y: y0 } = paperArea.paperToScrollablePaneCoords(bbox.x, bbox.y);
const { x: x1, y: y1 } = paperArea.paperToScrollablePaneCoords(bbox.x + bbox.width, bbox.y + bbox.height);
return {
x: x1 + ELEMENT_OFFSET,
y: (y0 + y1) / 2 - size.height / 2
};
}
calculatePositionForLink(link) {
const { view, paperArea } = this.props;
const source = view.model.getElement(link.sourceId);
const target = view.model.getElement(link.targetId);
if (!source || !target) throw new Error("Source and target are not specified");
const route = view.getRouting(link.id);
const verticesDefinedByUser = link.vertices || [];
const polyline = computePolyline(source, target, route ? route.vertices : verticesDefinedByUser);
const targetPoint = getPointAlongPolyline(polyline, computePolylineLength(polyline) / 2);
const { x, y } = paperArea.paperToScrollablePaneCoords(targetPoint.x, targetPoint.y);
return {
y: y + LINK_OFFSET,
x: x + LINK_OFFSET
};
}
calculatePosition() {
const { target, paperArea, offset = {
x: 0,
y: 0
}, calculatePosition } = this.props;
if (calculatePosition) {
const pos = calculatePosition();
const { x, y } = paperArea.paperToScrollablePaneCoords(pos.x, pos.y);
return {
x: x + offset.x,
y: y + offset.y
};
}
if (target instanceof Element$1) return this.calculatePositionForElement(target);
else if (target instanceof Link) return this.calculatePositionForLink(target);
throw new Error("Unknown target type");
}
getViewPortScrollablePoints() {
const { paperArea } = this.props;
const paperAreaMetrix = paperArea.getAreaMetrics();
return {
min: paperArea.clientToScrollablePaneCoords(0, 0),
max: paperArea.clientToScrollablePaneCoords(paperAreaMetrix.clientWidth, paperAreaMetrix.clientHeight)
};
}
getDialogScrollablePoints() {
const { size } = this.props;
const { x, y } = this.calculatePosition();
const min = {
x: x - FOCUS_OFFSET,
y: y - FOCUS_OFFSET
};
return {
min,
max: {
x: min.x + size.width + FOCUS_OFFSET * 2,
y: min.y + size.height + FOCUS_OFFSET * 2
}
};
}
focusOn() {
const { paperArea } = this.props;
const { min: viewPortMin, max: viewPortMax } = this.getViewPortScrollablePoints();
const { min, max } = this.getDialogScrollablePoints();
let xOffset = 0;
if (min.x < viewPortMin.x) xOffset = min.x - viewPortMin.x;
else if (max.x > viewPortMax.x) xOffset = max.x - viewPortMax.x;
let yOffset = 0;
if (min.y < viewPortMin.y) yOffset = min.y - viewPortMin.y;
else if (max.y > viewPortMax.y) yOffset = max.y - viewPortMax.y;
const curScrollableCenter = {
x: viewPortMin.x + (viewPortMax.x - viewPortMin.x) / 2,
y: viewPortMin.y + (viewPortMax.y - viewPortMin.y) / 2
};
const newScrollabalCenter = {
x: curScrollableCenter.x + xOffset,
y: curScrollableCenter.y + yOffset
};
const paperCenter = paperArea.scrollablePaneToPaperCoords(newScrollabalCenter.x, newScrollabalCenter.y);
paperArea.centerTo(paperCenter);
}
calculateHeight(height) {
const { size } = this.props;
const minHeight = Math.min(size.height, MIN_HEIGHT);
const maxHeight = Math.max(size.height, MAX_HEIGHT);
return Math.max(minHeight, Math.min(maxHeight, height));
}
calculateWidth(width) {
const { size } = this.props;
const minWidth = Math.min(size.width, MIN_WIDTH);
const maxWidth = Math.max(size.width, MAX_WIDTH);
return Math.max(minWidth, Math.min(maxWidth, width));
}
render() {
const { size, caption } = this.props;
const { x, y } = this.calculatePosition();
const style = {
top: y,
left: x,
width: this.state.width || size.width,
height: this.state.height || size.height
};
return /* @__PURE__ */ React.createElement("div", {
className: CLASS_NAME$19,
style
}, /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$19}__close-button`,
onClick: () => this.props.onClose()
}), caption ? /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-dialog__caption" }, caption) : null, this.props.children, /* @__PURE__ */ React.createElement(DraggableHandle, {
className: `${CLASS_NAME$19}__bottom-handle`,
onBeginDragHandle: this.onStartDragging,
onDragHandle: this.onDragHandleBottom
}), /* @__PURE__ */ React.createElement(DraggableHandle, {
className: `${CLASS_NAME$19}__right-handle`,
onBeginDragHandle: this.onStartDragging,
onDragHandle: this.onDragHandleRight
}), /* @__PURE__ */ React.createElement(DraggableHandle, {
className: `${CLASS_NAME$19}__bottom-right-handle`,
onBeginDragHandle: this.onStartDragging,
onDragHandle: this.onDragHandleBottomRight
}));
}
};
//#endregion
//#region src/graph-explorer/widgets/progressBar.tsx
var ProgressState = /* @__PURE__ */ function(ProgressState) {
ProgressState["none"] = "none";
ProgressState["loading"] = "loading";
ProgressState["error"] = "error";
ProgressState["completed"] = "completed";
return ProgressState;
}({});
var CLASS_NAME$18 = "graph-explorer-progress-bar";
var ProgressBar = class extends React.Component {
render() {
const { state, percent = 100, height = 20 } = this.props;
const className = `${CLASS_NAME$18} ${CLASS_NAME$18}--${state}`;
const showBar = state === "loading" || state === "error";
return /* @__PURE__ */ React.createElement("div", {
className,
style: { height: showBar ? height : 0 }
}, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$18}__bar`,
role: "progressbar",
style: { width: `${percent}%` },
"aria-valuemin": 0,
"aria-valuemax": 100,
"aria-valuenow": percent
}));
}
};
//#endregion
//#region src/graph-explorer/widgets/listElementView.tsx
var CLASS_NAME$17 = "graph-explorer-list-element-view";
var ListElementView = class extends React.Component {
constructor(..._args) {
super(..._args);
this.onClick = (event) => {
const { disabled, model, onClick } = this.props;
if (!disabled && onClick) {
event.persist();
onClick(event, model);
}
};
}
render() {
const { className, view, model, highlightText, disabled, selected, onDragStart } = this.props;
const { h, c, l } = view.getTypeStyle(model.types).color;
const frontColor = selected && !disabled ? hcl(h, c, l * 1.2) : hcl("white");
let classNames = `${CLASS_NAME$17}`;
classNames += disabled ? ` ${CLASS_NAME$17}--disabled` : "";
classNames += className ? ` ${className}` : "";
const localizedText = view.formatLabel(model.label.values, model.id);
const classesString = model.types.length > 0 ? `\nClasses: ${view.getElementTypeString(model)}` : "";
return /* @__PURE__ */ React.createElement("li", {
className: classNames,
draggable: !disabled && Boolean(onDragStart),
title: `${localizedText} ${view.formatIri(model.id)}${classesString}`,
style: { background: hcl(h, c, l).toString() },
onClick: this.onClick,
onDragStart
}, /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$17}__label`,
style: { background: frontColor.toString() }
}, highlightSubstring(localizedText, highlightText)));
}
};
function startDragElements(e, iris) {
try {
e.dataTransfer.setData("application/x-graph-explorer-elements", JSON.stringify(iris));
} catch (_ex) {
e.dataTransfer.setData("text", JSON.stringify(iris));
}
return false;
}
var DEFAULT_HIGHLIGHT_PROPS = { className: `graph-explorer-text-highlight` };
function highlightSubstring(text, substring, highlightProps = DEFAULT_HIGHLIGHT_PROPS) {
if (!substring) return /* @__PURE__ */ React.createElement("span", null, text);
const start = text.toLowerCase().indexOf(substring.toLowerCase());
if (start < 0) return /* @__PURE__ */ React.createElement("span", null, text);
const end = start + substring.length;
const before = text.substring(0, start);
const highlighted = text.substring(start, end);
const after = text.substring(end);
return /* @__PURE__ */ React.createElement("span", null, before, /* @__PURE__ */ React.createElement("span", highlightProps, highlighted), after);
}
//#endregion
//#region src/graph-explorer/widgets/searchResults.tsx
var CLASS_NAME$16 = "graph-explorer-search-results";
var SearchResults = class extends React.Component {
static {
this.defaultProps = { useDragAndDrop: true };
}
constructor(props) {
super(props);
this.listener = new EventObserver();
this.delayedChangeCells = new Debouncer();
this.startSelection = 0;
this.endSelection = 0;
this.onRootMount = (root) => {
this.root = root;
};
this.renderResultItem = (model) => {
const { useDragAndDrop } = this.props;
const canBeSelected = this.canBeSelected(model);
return /* @__PURE__ */ React.createElement(ListElementView, {
key: model.id,
model,
view: this.props.view,
highlightText: this.props.highlightText,
disabled: !canBeSelected,
selected: this.props.selection.has(model.id),
onClick: canBeSelected ? this.onItemClick : void 0,
onDragStart: useDragAndDrop ? (e) => {
const { selection } = this.props;
const iris = [];
selection.forEach((iri) => iris.push(iri));
if (!selection.has(model.id)) iris.push(model.id);
return startDragElements(e, iris);
} : void 0
});
};
this.onChangeCells = () => {
const { items, selection } = this.props;
if (selection.size === 0) {
if (items && items.length > 0) this.forceUpdate();
} else {
const newSelection = cloneSet(selection);
for (const element of this.props.view.model.elements) if (element.group === void 0 && selection.has(element.iri)) newSelection.delete(element.iri);
this.updateSelection(newSelection);
}
};
this.addKeyListener = () => {
document.addEventListener("keydown", this.onKeyUp);
};
this.removeKeyListener = () => {
document.removeEventListener("keydown", this.onKeyUp);
};
this.onKeyUp = (event) => {
const { items } = this.props;
const isPressedUp = event.keyCode === 38 || event.which === 38;
const isPressDown = event.keyCode === 40 || event.which === 40;
if (isPressedUp || isPressDown) if (event.shiftKey) {
if (isPressedUp) this.endSelection = this.getNextIndex(this.endSelection, 0);
else if (isPressDown) this.endSelection = this.getNextIndex(this.endSelection, 1);
const startIndex = Math.min(this.startSelection, this.endSelection);
const finishIndex = Math.max(this.startSelection, this.endSelection);
const selection = this.selectRange(startIndex, finishIndex);
this.updateSelection(selection);
this.focusOn(this.endSelection);
} else {
const startIndex = Math.min(this.startSelection, this.endSelection);
const finishIndex = Math.max(this.startSelection, this.endSelection);
if (isPressedUp) this.startSelection = this.getNextIndex(startIndex, 0);
else if (isPressDown) this.startSelection = this.getNextIndex(finishIndex, 1);
this.endSelection = this.startSelection;
const focusElement = items[this.startSelection];
const newSelection = /* @__PURE__ */ new Set();
newSelection.add(focusElement.id);
this.updateSelection(newSelection);
this.focusOn(this.startSelection);
}
event.preventDefault();
};
this.onItemClick = (event, model) => {
event.preventDefault();
const { items, selection, onSelectionChanged } = this.props;
const modelIndex = items.indexOf(model);
let newSelection;
if (event.shiftKey && this.startSelection !== -1) {
const start = Math.min(this.startSelection, modelIndex);
const end = Math.max(this.startSelection, modelIndex);
newSelection = this.selectRange(start, end);
} else {
this.endSelection = this.startSelection = modelIndex;
if (event.ctrlKey || event.metaKey) {
newSelection = cloneSet(selection);
if (selection.has(model.id)) newSelection.delete(model.id);
else newSelection.add(model.id);
} else {
newSelection = /* @__PURE__ */ new Set();
newSelection.add(model.id);
}
}
onSelectionChanged(newSelection);
};
this.state = { selection: props.selection || {} };
}
render() {
const items = this.props.items || [];
return /* @__PURE__ */ React.createElement("ul", {
className: CLASS_NAME$16,
ref: this.onRootMount,
tabIndex: -1,
onFocus: this.addKeyListener,
onBlur: this.removeKeyListener
}, items.map(this.renderResultItem));
}
componentDidMount() {
this.listener.listen(this.props.view.model.events, "changeCells", () => {
this.delayedChangeCells.call(this.onChangeCells);
});
}
UNSAFE_componentWillReceiveProps(props) {
this.setState({ selection: props.selection || {} });
}
componentWillUnmount() {
this.removeKeyListener();
this.listener.stopListening();
this.delayedChangeCells.dispose();
}
updateSelection(selection) {
const { onSelectionChanged } = this.props;
onSelectionChanged(selection);
}
selectRange(start, end) {
const { items } = this.props;
const selection = /* @__PURE__ */ new Set();
for (let i = start; i <= end; i++) {
const selectedModel = items[i];
if (this.canBeSelected(selectedModel)) selection.add(selectedModel.id);
}
return selection;
}
getNextIndex(startIndex, direction) {
const { items } = this.props;
if (items.length === 0) return startIndex;
const indexDelta = direction === 0 ? -1 : 1;
for (let step = 1; step < items.length; step++) {
let nextIndex = startIndex + step * indexDelta;
if (nextIndex < 0) nextIndex += items.length;
if (nextIndex >= items.length) nextIndex -= items.length;
if (this.canBeSelected(items[nextIndex])) return nextIndex;
}
return startIndex;
}
canBeSelected(model) {
const alreadyOnDiagram = this.props.view.model.elements.findIndex((element) => element.iri === model.id && element.group === void 0) >= 0;
return !this.props.useDragAndDrop || !alreadyOnDiagram;
}
focusOn(index) {
const scrollableContainer = this.root.parentElement;
const containerBounds = scrollableContainer.getBoundingClientRect();
const item = this.root.children.item(index);
const itemBounds = item.getBoundingClientRect();
const itemTop = itemBounds.top - containerBounds.top;
const itemBottom = itemBounds.bottom - containerBounds.top;
if (itemTop < 0) scrollableContainer.scrollTop += itemTop;
else if (itemBottom > containerBounds.height) scrollableContainer.scrollTop += itemBottom - containerBounds.height;
item.focus();
}
};
//#endregion
//#region src/graph-explorer/widgets/connectionsMenu.tsx
var MAX_LINK_COUNT = 100;
var ALL_RELATED_ELEMENTS_LINK = new FatLinkType({
id: "allRelatedElements",
label: [{
value: "All",
language: ""
}]
});
var ConnectionsMenu = class extends React.Component {
constructor(..._args) {
super(..._args);
this.handler = new EventObserver();
this.linkTypesListener = new EventObserver();
this.loadingState = ProgressState.none;
this.updateAll = () => this.forceUpdate();
this.addSelectedElements = (selectedObjects) => {
const { onClose, onAddElements } = this.props;
const addedElementsIris = selectedObjects.map((item) => item.model.id);
const linkType = this.linkDataChunk ? this.linkDataChunk.link : void 0;
onAddElements(addedElementsIris, this.linkDataChunk && linkType !== ALL_RELATED_ELEMENTS_LINK ? linkType : void 0);
onClose();
};
this.onExpandLink = (linkDataChunk) => {
if (!(this.objects && this.linkDataChunk && this.linkDataChunk.link === linkDataChunk.link && this.linkDataChunk.direction === linkDataChunk.direction)) this.loadObjects(linkDataChunk);
this.updateAll();
this.context.workspace.triggerWorkspaceEvent(WorkspaceEventKey.connectionsExpandLink);
};
this.onMoveToFilter = (linkDataChunk) => {
const { view, target } = this.props;
const { link, direction } = linkDataChunk;
if (link === ALL_RELATED_ELEMENTS_LINK) target.addToFilter();
else view.model.getElement(target.id).addToFilter(link, direction);
};
}
static {
this.contextType = WorkspaceContext;
}
componentDidMount() {
const { view } = this.props;
this.handler.listen(view.events, "changeLanguage", this.updateAll);
this.loadLinks();
}
componentWillUnmount() {
this.handler.stopListening();
this.linkTypesListener.stopListening();
}
resubscribeOnLinkTypeEvents(linkTypesOfElement) {
this.linkTypesListener.stopListening();
for (const linkType of linkTypesOfElement) {
this.linkTypesListener.listen(linkType.events, "changeLabel", this.updateAll);
this.linkTypesListener.listen(linkType.events, "changeVisibility", this.updateAll);
}
}
loadLinks() {
const { view, editor, target } = this.props;
this.loadingState = ProgressState.loading;
this.links = [];
this.countMap = {};
editor.model.dataProvider.linkTypesOf({ elementId: target.iri }).then((linkTypes) => {
this.loadingState = ProgressState.completed;
const countMap = {};
const links = [];
for (const { id: linkTypeId, inCount, outCount } of linkTypes) {
countMap[linkTypeId] = {
inCount,
outCount
};
links.push(view.model.createLinkType(linkTypeId));
}
countMap[ALL_RELATED_ELEMENTS_LINK.id] = Object.keys(countMap).map((key) => countMap[key]).reduce((a, b) => {
return {
inCount: a.inCount + b.inCount,
outCount: a.outCount + b.outCount
};
}, {
inCount: 0,
outCount: 0
});
this.countMap = countMap;
this.links = links;
this.resubscribeOnLinkTypeEvents(this.links);
this.updateAll();
this.context.workspace.triggerWorkspaceEvent(WorkspaceEventKey.connectionsLoadLinks);
}).catch((err) => {
console.error(err);
this.loadingState = ProgressState.error;
this.updateAll();
});
this.updateAll();
}
loadObjects(linkDataChunk) {
const { view, editor, target } = this.props;
const { link, direction } = linkDataChunk;
const offset = linkDataChunk.offset || 0;
this.loadingState = ProgressState.loading;
this.linkDataChunk = linkDataChunk;
this.objects = [];
editor.model.dataProvider.linkElements({
elementId: target.iri,
linkId: link === ALL_RELATED_ELEMENTS_LINK ? void 0 : link.id,
limit: offset + MAX_LINK_COUNT,
offset,
direction
}).then((elements) => {
this.loadingState = ProgressState.completed;
this.objects = Object.keys(elements).map((iri) => ({
model: elements[iri],
presentOnDiagram: view.model.elements.findIndex((element) => element.iri === iri && element.group === void 0) >= 0
}));
this.updateAll();
this.context.workspace.triggerWorkspaceEvent(WorkspaceEventKey.connectionsLoadElements);
}).catch((err) => {
console.error(err);
this.loadingState = ProgressState.error;
this.updateAll();
});
}
render() {
const connectionsData = {
links: this.links || [],
countMap: this.countMap || {}
};
let objectsData = null;
if (this.linkDataChunk && this.objects) objectsData = {
linkDataChunk: this.linkDataChunk,
objects: this.objects
};
const { view, target, suggestProperties } = this.props;
return /* @__PURE__ */ React.createElement(ConnectionsMenuMarkup, {
target,
connectionsData,
objectsData,
state: this.loadingState,
view,
onExpandLink: this.onExpandLink,
onPressAddSelected: this.addSelectedElements,
onMoveToFilter: this.onMoveToFilter,
propertySuggestionCall: suggestProperties
});
}
};
var ConnectionsMenuMarkup = class extends React.Component {
constructor(props) {
super(props);
this.onChangeFilter = (e) => {
const filterKey = e.currentTarget.value;
this.setState({ filterKey });
};
this.getTitle = () => {
if (this.props.objectsData && this.state.panel === "objects") return "Objects";
else if (this.props.connectionsData && this.state.panel === "connections") return "Connections";
return "Error";
};
this.onExpandLink = (linkDataChunk) => {
this.setState({
filterKey: "",
panel: "objects"
});
this.props.onExpandLink(linkDataChunk);
};
this.onCollapseLink = () => {
this.setState({
filterKey: "",
panel: "connections"
});
};
this.getBreadCrumbs = () => {
if (this.props.objectsData && this.state.panel === "objects") {
const { link, direction } = this.props.objectsData.linkDataChunk;
const localizedText = this.props.view.formatLabel(link.label, link.id);
return /* @__PURE__ */ React.createElement("span", { className: "graph-explorer-connections-menu_bread-crumbs" }, /* @__PURE__ */ React.createElement("a", {
className: "graph-explorer-connections-menu__link",
onClick: this.onCollapseLink
}, "Connections"), "\xA0/\xA0", localizedText, " ", direction ? `(${direction})` : null);
} else return null;
};
this.getBody = () => {
if (this.props.state === "error") return /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label graph-explorer-connections-menu__error" }, "Error");
else if (this.props.objectsData && this.state.panel === "objects") return /* @__PURE__ */ React.createElement(ObjectsPanel, {
data: this.props.objectsData,
onMoveToFilter: this.props.onMoveToFilter,
view: this.props.view,
filterKey: this.state.filterKey,
loading: this.props.state === ProgressState.loading,
onPressAddSelected: this.props.onPressAddSelected
});
else if (this.props.connectionsData && this.state.panel === "connections") {
if (this.props.state === ProgressState.loading) return /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label graph-explorer-connections-menu__loading" }, "Loading...");
return /* @__PURE__ */ React.createElement(ConnectionsList, {
id: this.props.target.id,
data: this.props.connectionsData,
view: this.props.view,
filterKey: this.state.filterKey,
onExpandLink: this.onExpandLink,
onMoveToFilter: this.props.onMoveToFilter,
propertySuggestionCall: this.props.propertySuggestionCall,
sortMode: this.state.sortMode
});
} else return /* @__PURE__ */ React.createElement("div", null);
};
this.onSortChange = (e) => {
const value = e.target.value;
if (this.state.sortMode === value) return;
this.setState({ sortMode: value });
};
this.renderSortSwitch = (id, icon, title) => {
return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("input", {
type: "radio",
name: "sort",
id,
value: id,
className: "graph-explorer-connections-menu__sort-switch",
onChange: this.onSortChange,
checked: this.state.sortMode === id
}), /* @__PURE__ */ React.createElement("label", {
htmlFor: id,
className: "graph-explorer-connections-menu__sort-switch-label",
title
}, /* @__PURE__ */ React.createElement("i", { className: `fa ${icon}` })));
};
this.renderSortSwitches = () => {
if (this.state.panel !== "connections" || !this.props.propertySuggestionCall) return null;
return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu_search-line-sort-switches" }, this.renderSortSwitch("alphabet", "fa-sort-alpha-asc", "Sort alphabetically"), this.renderSortSwitch("smart", "fa-lightbulb-o", "Smart sort"));
};
this.state = {
filterKey: "",
panel: "connections",
sortMode: "alphabet"
};
}
render() {
return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu" }, /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label graph-explorer-connections-menu__title-label" }, this.getTitle()), this.getBreadCrumbs(), /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu_search-line" }, /* @__PURE__ */ React.createElement("input", {
type: "text",
className: "search-input graph-explorer-form-control graph-explorer-connections-menu__search-line-input",
value: this.state.filterKey,
onChange: this.onChangeFilter,
placeholder: "Search for..."
}), this.renderSortSwitches()), /* @__PURE__ */ React.createElement(ProgressBar, {
state: this.props.state,
height: 10
}), this.getBody());
}
};
var ConnectionsList = class extends React.Component {
constructor(props) {
super(props);
this.updateScores = (props) => {
if (props.propertySuggestionCall && (props.filterKey || props.sortMode === "smart")) {
const { id, data, view, filterKey } = props;
const lang = view.getLanguage();
const token = filterKey.trim();
const properties = data.links.map((l) => l.id);
props.propertySuggestionCall({
elementId: id,
token,
properties,
lang
}).then((scores) => this.setState({ scores }));
}
};
this.compareLinks = (a, b) => {
const { view } = this.props;
const aText = view.formatLabel(a.label, a.id);
const bText = view.formatLabel(b.label, b.id);
return aText.localeCompare(bText);
};
this.compareLinksByWeight = (a, b) => {
const { view } = this.props;
const aText = view.formatLabel(a.label, a.id);
const bText = view.formatLabel(b.label, b.id);
const aWeight = this.state.scores[a.id] ? this.state.scores[a.id].score : 0;
const bWeight = this.state.scores[b.id] ? this.state.scores[b.id].score : 0;
return aWeight > bWeight ? -1 : aWeight < bWeight ? 1 : aText.localeCompare(bText);
};
this.getLinks = () => {
const { view, data, filterKey } = this.props;
return (data.links || []).filter((link) => {
const text = view.formatLabel(link.label, link.id).toLowerCase();
return !filterKey || text.indexOf(filterKey.toLowerCase()) >= 0;
}).sort(this.compareLinks);
};
this.getProbableLinks = () => {
const isSmartMode = this.isSmartMode();
return (this.props.data.links || []).filter((link) => {
return this.state.scores[link.id] && (this.state.scores[link.id].score > 0 || isSmartMode);
}).sort(this.compareLinksByWeight);
};
this.getViews = (links, notSure) => {
const { view } = this.props;
const countMap = this.props.data.countMap || {};
const views = [];
const addView = (link, direction) => {
const count = direction === "in" ? countMap[link.id].inCount : countMap[link.id].outCount;
if (count === 0) return;
const postfix = notSure ? "-probable" : "";
views.push(/* @__PURE__ */ React.createElement(LinkInPopupMenu, {
key: `${direction}-${link.id}-${postfix}`,
link,
onExpandLink: this.props.onExpandLink,
view,
count,
direction,
filterKey: notSure ? "" : this.props.filterKey,
onMoveToFilter: this.props.onMoveToFilter,
probability: this.state.scores[link.id] && notSure ? this.state.scores[link.id].score : 0
}));
};
for (const link of links) {
addView(link, "in");
addView(link, "out");
}
return views;
};
this.state = { scores: {} };
this.updateScores(props);
}
componentDidUpdate(newProps) {
this.updateScores(newProps);
}
isSmartMode() {
return this.props.sortMode === "smart" && !this.props.filterKey;
}
render() {
const { view } = this.props;
const isSmartMode = this.isSmartMode();
const links = isSmartMode ? [] : this.getLinks();
const probableLinks = this.getProbableLinks().filter((link) => links.indexOf(link) === -1);
const views = this.getViews(links);
const probableViews = this.getViews(probableLinks, true);
let viewList;
if (views.length === 0 && probableViews.length === 0) viewList = /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label graph-explorer-connections-menu_links-list__empty" }, "List empty");
else {
viewList = views;
if (views.length > 1 || isSmartMode && probableViews.length > 1) {
const allRelatedElements = (this.props.data.countMap || {})[ALL_RELATED_ELEMENTS_LINK.id];
viewList = [/* @__PURE__ */ React.createElement(LinkInPopupMenu, {
key: ALL_RELATED_ELEMENTS_LINK.id,
link: ALL_RELATED_ELEMENTS_LINK,
onExpandLink: this.props.onExpandLink,
view,
count: allRelatedElements.inCount + allRelatedElements.outCount,
onMoveToFilter: this.props.onMoveToFilter
}), /* @__PURE__ */ React.createElement("hr", {
key: "graph-explorer-hr-line",
className: "graph-explorer-connections-menu_links-list__hr"
})].concat(viewList);
}
}
let probablePart = null;
if (probableViews.length !== 0) probablePart = [isSmartMode ? null : /* @__PURE__ */ React.createElement("li", { key: "probable-links" }, /* @__PURE__ */ React.createElement("span", { className: "graph-explorer-label" }, "Probably, you‘re looking for..")), probableViews];
return /* @__PURE__ */ React.createElement("ul", { className: "graph-explorer-connections-menu_links-list " + (views.length === 0 && probableViews.length === 0 ? "ocm_links-list-empty" : "") }, viewList, probablePart);
}
};
var LinkInPopupMenu = class extends React.Component {
constructor(..._args2) {
super(..._args2);
this.onExpandLink = (expectedCount, direction) => {
this.props.onExpandLink({
link: this.props.link,
direction,
expectedCount
});
};
this.onMoveToFilter = (evt) => {
evt.stopPropagation();
this.props.onMoveToFilter({
link: this.props.link,
direction: this.props.direction,
expectedCount: this.props.count
});
};
}
render() {
const { view, link } = this.props;
const fullText = view.formatLabel(link.label, link.id);
const probability = Math.round(this.props.probability * 100);
const textLine = highlightSubstring(fullText + (probability > 0 ? " (" + probability + "%)" : ""), this.props.filterKey);
const directionName = this.props.direction === "in" ? "source" : this.props.direction === "out" ? "target" : "all connected";
return /* @__PURE__ */ React.createElement("li", {
"data-linktypeid": this.props.link.id,
className: "link-in-popup-menu",
title: `${directionName} of "${fullText}" ${view.formatIri(link.id)}`,
onClick: () => this.onExpandLink(this.props.count, this.props.direction)
}, this.props.direction === "in" || this.props.direction === "out" ? /* @__PURE__ */ React.createElement("div", { className: "link-in-popup-menu_direction" }, this.props.direction === "in" && /* @__PURE__ */ React.createElement("div", { className: "link-in-popup-menu_direction__in-direction" }), this.props.direction === "out" && /* @__PURE__ */ React.createElement("div", { className: "link-in-popup-menu_direction__out-direction" })) : null, /* @__PURE__ */ React.createElement("div", { className: "link-in-popup-menu__link-title" }, textLine), /* @__PURE__ */ React.createElement("span", { className: "graph-explorer-badge link-in-popup-menu__count" }, this.props.count <= MAX_LINK_COUNT ? this.props.count : "100+"), /* @__PURE__ */ React.createElement("div", {
className: "link-in-popup-menu__filter-button",
onClick: this.onMoveToFilter,
title: "Set as filter in the Instances panel"
}), /* @__PURE__ */ React.createElement("div", {
className: "link-in-popup-menu__navigate-button",
title: `Navigate to ${directionName} "${fullText}" elements`
}));
}
};
var ObjectsPanel = class extends React.Component {
constructor(props) {
super(props);
this.onSelectAll = () => {
const objects = this.props.data.objects;
if (objects.length === 0) return;
const newSelection = allNonPresentedAreSelected(objects, this.state.selection) ? /* @__PURE__ */ new Set() : selectNonPresented(this.props.data.objects);
this.updateSelection(newSelection);
};
this.updateSelection = (newSelection) => {
this.setState({ selection: newSelection });
};
this.counter = (activeObjCount) => {
const countString = `${activeObjCount}\u00A0of\u00A0${this.props.data.objects.length}`;
const wrongNodes = Math.min(MAX_LINK_COUNT, this.props.data.linkDataChunk.expectedCount) - this.props.data.objects.length;
const wrongNodesString = Math.abs(wrongNodes) > MAX_LINK_COUNT ? `${MAX_LINK_COUNT}+` : Math.abs(wrongNodes).toString();
const wrongNodesCount = wrongNodes === 0 ? "" : wrongNodes < 0 ? `\u00A0(${wrongNodesString})` : `\u00A0(${wrongNodesString})`;
const wrongNodesTitle = wrongNodes === 0 ? "" : wrongNodes > 0 ? "Unavailable nodes" : "Extra nodes";
return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-label graph-explorer-connections-menu_objects-panel_bottom-panel__count-label" }, /* @__PURE__ */ React.createElement("span", null, countString), /* @__PURE__ */ React.createElement("span", {
className: "graph-explorer-connections-menu_objects-panel_bottom-panel__extra-elements",
title: wrongNodesTitle
}, wrongNodesCount));
};
this.state = { selection: /* @__PURE__ */ new Set() };
}
componentDidUpdate(nextProps) {
if (this.props.data.objects.length < nextProps.data.objects.length) this.setState({ selection: /* @__PURE__ */ new Set() });
}
getFilteredObjects() {
if (!this.props.filterKey) return this.props.data.objects;
const filterKey = this.props.filterKey.toLowerCase();
return this.props.data.objects.filter((element) => {
const text = this.props.view.formatLabel(element.model.label.values, element.model.id).toLowerCase();
return text && text.indexOf(filterKey) >= 0;
});
}
getItems(list) {
const added = {};
const result = [];
for (const obj of list) {
if (added[obj.model.id]) continue;
added[obj.model.id] = true;
result.push(obj.model);
}
return result;
}
render() {
const { onPressAddSelected, filterKey } = this.props;
const { selection } = this.state;
const objects = this.getFilteredObjects();
const isAllSelected = allNonPresentedAreSelected(objects, selection);
const nonPresented = objects.filter((el) => !el.presentOnDiagram);
const active = nonPresented.filter((el) => selection.has(el.model.id));
return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu_objects-panel" }, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu_objects-panel__select-all" }, /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement("input", {
type: "checkbox",
checked: isAllSelected && nonPresented.length > 0,
onChange: this.onSelectAll,
disabled: nonPresented.length === 0
}), "Select All")), this.props.loading ? /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label graph-explorer-connections-menu__loading-objects" }, "Loading...") : objects.length === 0 ? /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label graph-explorer-connections-menu__loading-objects" }, "No available nodes") : /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu_objects-panel_objects-list" }, /* @__PURE__ */ React.createElement(SearchResults, {
view: this.props.view,
items: this.getItems(objects),
selection: this.state.selection,
onSelectionChanged: this.updateSelection,
highlightText: filterKey
}), this.props.data.linkDataChunk.expectedCount > MAX_LINK_COUNT ? /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer-connections-menu__move-to-filter",
onClick: () => this.props.onMoveToFilter(this.props.data.linkDataChunk)
}, "The list was truncated, for more data click here to use the filter panel") : null), /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-connections-menu_objects-panel_bottom-panel" }, this.counter(active.length), /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-primary pull-right graph-explorer-connections-menu_objects-panel_bottom-panel__add-button",
disabled: this.props.loading || nonPresented.length === 0,
onClick: () => onPressAddSelected(active.length > 0 ? active : nonPresented)
}, active.length > 0 ? "Add selected" : "Add all")));
}
};
function selectNonPresented(objects) {
const selection = /* @__PURE__ */ new Set();
for (const object of objects) {
if (object.presentOnDiagram) continue;
selection.add(object.model.id);
}
return selection;
}
function allNonPresentedAreSelected(objects, selection) {
let allSelected = true;
for (const object of objects) {
if (object.presentOnDiagram) continue;
allSelected = allSelected && selection.has(object.model.id);
}
return allSelected;
}
//#endregion
//#region src/graph-explorer/forms/editEntityForm.tsx
var CLASS_NAME$15 = "graph-explorer-edit-form";
var EditEntityForm = class extends React.Component {
constructor(props) {
super(props);
this.renderProperty = (key, property) => {
const { view } = this.props;
const richProperty = view.model.getProperty(key);
const label = view.formatLabel(richProperty.label, key);
let values = [];
if (isLiteralProperty(property)) values = property.values.map(({ value, language }) => {
return {
value,
language
};
});
return /* @__PURE__ */ React.createElement("div", {
key,
className: `${CLASS_NAME$15}__form-row`
}, /* @__PURE__ */ React.createElement("label", null, label, values.map((value, index) => /* @__PURE__ */ React.createElement("input", {
key: index,
className: "graph-explorer-form-control",
defaultValue: value.value,
lang: value.language,
onChange: this.onChangeProp,
"data-iri": key
}))));
};
this.onChangeIri = (e) => {
const iri = e.target.value;
this.setState((prevState) => {
return { elementModel: {
...prevState.elementModel,
id: iri
} };
});
};
this.onChangeLabel = (e) => {
const target = e.target;
const labels = target.value.length > 0 ? [{
value: target.value,
language: ""
}] : [];
this.setState({ elementModel: {
...this.state.elementModel,
label: { values: labels }
} });
};
this.onChangeProp = (e) => {
const target = e.target;
const values = [];
target.parentElement.querySelectorAll("[data-iri=\"" + target.dataset["iri"] + "\"]").forEach((Element) => {
const typedElement = Element;
values.push({
value: typedElement.value,
language: typedElement.lang
});
});
this.setState({ elementModel: {
...this.state.elementModel,
properties: {
...this.state.elementModel.properties,
[target.dataset["iri"]]: {
type: "string",
values
}
}
} });
};
this.state = { elementModel: props.entity };
}
componentDidUpdate(nextProps) {
if (this.props.entity !== nextProps.entity) this.setState({ elementModel: nextProps.entity });
}
renderProperties() {
const { properties } = this.props.entity;
const propertyIris = Object.keys(properties);
return /* @__PURE__ */ React.createElement("div", null, propertyIris.map((iri) => {
return this.renderProperty(iri, properties[iri]);
}));
}
renderType() {
const { view } = this.props;
const { elementModel } = this.state;
const label = view.getElementTypeString(elementModel);
return /* @__PURE__ */ React.createElement("label", null, "Type", /* @__PURE__ */ React.createElement("input", {
className: "graph-explorer-form-control",
value: label,
disabled: true
}));
}
renderIri() {
const { elementModel } = this.state;
return /* @__PURE__ */ React.createElement("label", null, "IRI", /* @__PURE__ */ React.createElement("input", {
className: "graph-explorer-form-control",
defaultValue: elementModel.id,
onChange: this.onChangeIri
}));
}
renderLabel() {
const { view } = this.props;
const label = view.selectLabel(this.state.elementModel.label.values);
const text = label ? label.value : "";
return /* @__PURE__ */ React.createElement("label", null, "Label", /* @__PURE__ */ React.createElement("input", {
className: "graph-explorer-form-control",
value: text,
onChange: this.onChangeLabel
}));
}
render() {
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$15 }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$15}__body` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$15}__form-row` }, this.renderIri()), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$15}__form-row` }, this.renderType()), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$15}__form-row` }, this.renderLabel()), this.renderProperties()), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$15}__controls` }, /* @__PURE__ */ React.createElement("button", {
className: `graph-explorer-btn graph-explorer-btn-success ${CLASS_NAME$15}__apply-button`,
onClick: () => this.props.onApply(this.state.elementModel)
}, "Apply"), /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-danger",
onClick: this.props.onCancel
}, "Cancel")));
}
};
//#endregion
//#region src/graph-explorer/widgets/instancesSearch.tsx
var CLASS_NAME$14 = "graph-explorer-instances-search";
var InstancesSearch = class extends React.Component {
static {
this.contextType = WorkspaceContext;
}
constructor(props) {
super(props);
this.listener = new EventObserver();
this.onSelectionChanged = (newSelection) => {
this.setState({ selection: newSelection });
};
this.state = {
resultId: 0,
selection: /* @__PURE__ */ new Set()
};
}
render() {
const ENTER_KEY_CODE = 13;
const className = `${CLASS_NAME$14} ${this.props.className || ""}`;
const progressState = this.state.quering ? ProgressState.loading : this.state.error ? ProgressState.error : this.state.items ? ProgressState.completed : ProgressState.none;
const searchTerm = this.state.inputText === void 0 ? this.props.criteria.text : this.state.inputText;
return /* @__PURE__ */ React.createElement("div", { className }, /* @__PURE__ */ React.createElement(ProgressBar, { state: progressState }), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$14}__criteria` }, this.renderCriteria(), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$14}__text-criteria graph-explorer-input-group` }, /* @__PURE__ */ React.createElement("input", {
type: "text",
className: "graph-explorer-form-control",
placeholder: "Search for...",
value: searchTerm || "",
onChange: (e) => this.setState({ inputText: e.currentTarget.value }),
onKeyUp: (e) => {
if (e.keyCode === ENTER_KEY_CODE) this.submitCriteriaUpdate();
}
}), /* @__PURE__ */ React.createElement("span", { className: "graph-explorer-input-group-btn" }, /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-default",
type: "button",
title: "Search",
onClick: () => this.submitCriteriaUpdate()
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-search",
"aria-hidden": "true"
}))))), /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$14}__rest graph-explorer-scrollable`,
key: this.state.resultId
}, /* @__PURE__ */ React.createElement(SearchResults, {
view: this.props.view,
items: this.state.items,
highlightText: this.props.criteria.text,
selection: this.state.selection,
onSelectionChanged: this.onSelectionChanged
}), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$14}__rest-end` }, /* @__PURE__ */ React.createElement("button", {
type: "button",
className: `${CLASS_NAME$14}__load-more graph-explorer-btn graph-explorer-btn-primary`,
disabled: this.state.quering,
style: { display: this.state.moreItemsAvailable ? void 0 : "none" },
onClick: () => this.queryItems(true)
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-chevron-down",
"aria-hidden": "true"
}), "\xA0Show more"))));
}
renderCriteria() {
const { criteria = {}, view } = this.props;
const criterions = [];
if (criteria.elementType) {
const classInfo = criteria.elementType;
const classLabel = view.formatLabel(classInfo.label, classInfo.id);
criterions.push(/* @__PURE__ */ React.createElement("div", {
key: "hasType",
className: `${CLASS_NAME$14}__criterion`
}, this.renderRemoveCriterionButtons(() => this.props.onCriteriaChanged({
...this.props.criteria,
elementType: void 0
})), "Has type", " ", /* @__PURE__ */ React.createElement("span", {
className: `${CLASS_NAME$14}__criterion-class`,
title: classInfo.id
}, classLabel)));
} else if (criteria.refElement) {
const element = criteria.refElement;
const elementLabel = view.formatLabel(element.data.label.values, element.iri);
const linkType = criteria.refElementLink;
const linkTypeLabel = linkType ? view.formatLabel(linkType.label, linkType.id) : void 0;
criterions.push(/* @__PURE__ */ React.createElement("div", {
key: "hasLinkedElement",
className: `${CLASS_NAME$14}__criterion`
}, this.renderRemoveCriterionButtons(() => this.props.onCriteriaChanged({
...this.props.criteria,
refElement: void 0,
refElementLink: void 0
})), "Connected to", " ", /* @__PURE__ */ React.createElement("span", {
className: `${CLASS_NAME$14}__criterion-element`,
title: element ? element.iri : void 0
}, elementLabel), linkType && /* @__PURE__ */ React.createElement("span", null, " through ", /* @__PURE__ */ React.createElement("span", {
className: `${CLASS_NAME$14}__criterion-link-type`,
title: linkType ? linkType.id : void 0
}, linkTypeLabel), criteria.linkDirection === "in" && /* @__PURE__ */ React.createElement("span", null, " as ", /* @__PURE__ */ React.createElement("img", {
className: `${CLASS_NAME$14}__link-direction`,
src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAbCAYAAAAOEM1uAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAZEQAAGREBkIelaAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAG2SURBVFiFzde9axRRFMbhJ3Hjx0YRgkWElGIRGyVgaaG9XQpBrNIETJHKjxBSJCA2NiIWdhYKgoqCIELq/AU2tiJpAiliDMHEWEyKZfdO5szHrr4wzcx77vnNnXPnnktcK9jBwYCuLcwNBeHGsIGovyltDweNbYOHg9Eo4D9Tq0bsGj40BXKoO5isEjiht4ifHuE/qVpJvO/O049PvIxN/MBMH8ZP6pzeGVxK+C4kfAsl8vTMYBnI1x1Bv3E54bmWSHCAx2KfvBbgKGbxJAfOIcS7HMjnKCqpWoBRtfE5B/IVRsoAtnAGd3GlQchd/NE7Y7dwCtPYiw72xuD216KFk/zN3Ii+SYOajhqHZUU9aK1GjS3M4xumFK+yMuPexLHEsy9YbChPJR3HW+na+yjbBvNU+zdzHbcxfoTnUw7cS8XNSS3AFx1B6zif8FzKgXsmVj6VAduyFrwzcCUHcL/L9yiaJAUYXRRjON1172zC9xX3ZS+ziTk8KAFYWWX7wRGcqJAnudVV1RTu1YhP6WL3jWjXO4HvzbLE9N8fmqKAv/SpNyvQdmorSmlHVvhX1TsJltFPPPwL/+nTC6wUIf8AAAAASUVORK5CYII="
}), "\xA0source"), criteria.linkDirection === "out" && /* @__PURE__ */ React.createElement("span", null, " as ", /* @__PURE__ */ React.createElement("img", {
className: `${CLASS_NAME$14}__link-direction`,
src: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAbCAYAAAAOEM1uAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAAZEQAAGREBkIelaAAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAAAGCSURBVFiFzdi9ShxRFAfwn+uiaAoRVBQbbfMEFqIoFja+QCoLLQQr8R0sbdIJgTQiSgT1AcTKzkdIQhpTaOlX0GyK2WJ2c8d1Pt0/XBjOnTnnP+eerxnYxj0aJa9nfEOvlLirgFx8zaQhV8Ng2jfKiVT2amWxKAr1gOwElwXpH8NWXiXtMbKZV2EMHwP6F2P7dR1O8T2PeBW/8EMHp5TpwWGtJewF06JEuWmzu4ueqgnC55juw6ZsHE8B218F8qJsgkS1b0Grh74EbDdwioGqCYbQj6MEkhcYIlxm3opZrGEyh47eJqH22JvDOZbJ5sEJ4Rgqeh1nLTMr6Mv4bBpMZSV4hj9FMknA96wxeC3qCOvyxWAd88L17wobdHkWV0VwxP+e3ksgd6LiOrgtyvhn7DRlo3gM2K68kwziNqb7QTSC9eN3m9136cWvjVuf8FOHaSZPJ8mLfRw0r/8m3RQiuIQPBZEY67CfSCyOsttV0hG/CV3/0VQTTbxVIrW9rv6z8A+8NRQ5z9bcwQAAAABJRU5ErkJggg=="
}), "\xA0target"))));
}
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$14}__criterions` }, criterions);
}
renderRemoveCriterionButtons(onClick) {
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$14}__criterion-remove ontoidia-btn-group graph-explorer-btn-group-xs` }, /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Remove criteria",
onClick
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-times",
"aria-hidden": "true"
})));
}
submitCriteriaUpdate() {
let text = this.state.inputText === void 0 ? this.props.criteria.text : this.state.inputText;
text = text === "" ? void 0 : text;
this.props.onCriteriaChanged({
...this.props.criteria,
text
});
}
componentDidMount() {
this.listener.listen(this.props.view.events, "changeLanguage", () => this.forceUpdate());
this.queryItems(false);
}
componentDidUpdate(nextProps) {
const languageChanged = this.currentRequest ? this.currentRequest.languageCode !== nextProps.view.getLanguage() : false;
if (this.props.criteria !== nextProps.criteria || languageChanged) this.setState({ inputText: void 0 }, () => this.queryItems(false));
}
componentWillUnmount() {
this.listener.stopListening();
this.currentRequest = void 0;
}
queryItems(loadMoreItems) {
let request;
if (loadMoreItems) {
if (!this.currentRequest) throw new Error("Cannot request more items without initial request.");
const { offset, limit } = this.currentRequest;
request = {
...this.currentRequest,
offset: offset + limit
};
} else request = createRequest(this.props.criteria, this.props.view.getLanguage());
if (!(request.text || request.elementTypeId || request.refElementId || request.refElementLinkId)) {
this.setState({
quering: false,
error: void 0,
items: void 0,
selection: /* @__PURE__ */ new Set(),
moreItemsAvailable: false
});
return;
}
this.currentRequest = request;
this.setState({
quering: true,
error: void 0,
moreItemsAvailable: false
});
this.props.model.dataProvider.filter(request).then((elements) => {
if (this.currentRequest !== request) return;
this.processFilterData(elements);
this.context.workspace.triggerWorkspaceEvent(WorkspaceEventKey.searchQueryItem);
}).catch((error) => {
if (this.currentRequest !== request) return;
console.error(error);
this.setState({
quering: false,
error
});
});
}
processFilterData(elements) {
const requestedAdditionalItems = this.currentRequest.offset > 0;
const existingIris = {};
if (requestedAdditionalItems) this.state.items.forEach((item) => existingIris[item.id] = true);
const items = requestedAdditionalItems ? [...this.state.items] : [];
for (const iri in elements) {
if (!Object.prototype.hasOwnProperty.call(elements, iri)) continue;
if (existingIris[iri]) continue;
items.push(elements[iri]);
}
const moreItemsAvailable = Object.keys(elements).length >= this.currentRequest.limit;
if (requestedAdditionalItems) this.setState({
quering: false,
items,
error: void 0,
moreItemsAvailable
});
else this.setState({
quering: false,
resultId: this.state.resultId + 1,
items,
selection: /* @__PURE__ */ new Set(),
error: void 0,
moreItemsAvailable
});
}
};
function createRequest(criteria, language) {
const { text, elementType, refElement, refElementLink, linkDirection } = criteria;
return {
text,
elementTypeId: elementType ? elementType.id : void 0,
refElementId: refElement ? refElement.iri : void 0,
refElementLinkId: refElementLink ? refElementLink.id : void 0,
linkDirection,
offset: 0,
limit: 100,
languageCode: language || "en"
};
}
//#endregion
//#region src/graph-explorer/forms/elementTypeSelector.tsx
var CLASS_NAME$13 = "graph-explorer-edit-form";
var ElementTypeSelector = class extends React.Component {
constructor(props) {
super(props);
this.cancellation = new Cancellation();
this.filterCancellation = new Cancellation();
this.loadingItemCancellation = new Cancellation();
this.onElementTypeChange = async (e) => {
this.setState({ isLoading: true });
this.loadingItemCancellation.abort();
this.loadingItemCancellation = new Cancellation();
const signal = this.loadingItemCancellation.signal;
const { onChange, metadataApi } = this.props;
const classId = e.target.value;
const elementModel = await CancellationToken.mapCancelledToNull(signal, metadataApi.generateNewElement([classId], signal));
if (elementModel === null) return;
this.setState({ isLoading: false });
onChange({
value: elementModel,
isNew: true,
loading: false
});
};
this.renderPossibleElementType = (elementType) => {
const { view } = this.props;
const type = view.model.createClass(elementType);
const label = view.formatLabel(type.label, type.id);
return /* @__PURE__ */ React.createElement("option", {
key: elementType,
value: elementType
}, label);
};
this.state = {
searchString: "",
existingElements: []
};
}
componentDidMount() {
this.fetchPossibleElementTypes();
}
componentDidUpdate(prevProps, prevState) {
const { searchString } = this.state;
if (searchString !== prevState.searchString) this.searchExistingElements();
}
componentWillUnmount() {
this.cancellation.abort();
this.filterCancellation.abort();
this.loadingItemCancellation.abort();
}
async fetchPossibleElementTypes() {
const { view, metadataApi, source } = this.props;
if (!metadataApi) return;
const elementTypes = await CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.typesOfElementsDraggedFrom(source, this.cancellation.signal));
if (elementTypes === null) return;
elementTypes.sort(makeElementTypeComparatorByLabel(view));
this.setState({ elementTypes });
}
searchExistingElements() {
const { editor, view } = this.props;
const { searchString } = this.state;
this.setState({ existingElements: [] });
if (searchString.length > 0) {
this.setState({ isLoading: true });
this.filterCancellation.abort();
this.filterCancellation = new Cancellation();
const signal = this.filterCancellation.signal;
const request = createRequest({ text: searchString }, view.getLanguage());
editor.model.dataProvider.filter(request).then((elements) => {
if (signal.aborted) return;
const existingElements = Object.keys(elements).map((key) => elements[key]);
this.setState({
existingElements,
isLoading: false
});
});
}
}
renderElementTypeSelector() {
const { elementValue } = this.props;
const { elementTypes, isLoading } = this.state;
const value = elementValue.value.types.length ? elementValue.value.types[0] : "";
if (isLoading) return /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
});
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$13}__control-row` }, /* @__PURE__ */ React.createElement("label", null, "Entity Type"), elementTypes ? /* @__PURE__ */ React.createElement("select", {
className: "graph-explorer-form-control",
value,
onChange: this.onElementTypeChange
}, /* @__PURE__ */ React.createElement("option", {
value: PLACEHOLDER_ELEMENT_TYPE,
disabled: true
}, "Select entity type"), elementTypes.map(this.renderPossibleElementType)) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
})), elementValue.error ? /* @__PURE__ */ React.createElement("span", { className: `${CLASS_NAME$13}__control-error` }, elementValue.error) : "");
}
renderExistingElementsList() {
const { view, editor, elementValue } = this.props;
const { elementTypes, isLoading, existingElements } = this.state;
if (isLoading) return /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
});
if (existingElements.length > 0) return existingElements.map((element) => {
const isAlreadyOnDiagram = !editor.temporaryState.elements.has(element.id) && Boolean(editor.model.elements.find(({ iri, group }) => iri === element.id && group === void 0));
const hasAppropriateType = Boolean(elementTypes.find((type) => element.types.indexOf(type) >= 0));
return /* @__PURE__ */ React.createElement(ListElementView, {
key: element.id,
view,
model: element,
disabled: isAlreadyOnDiagram || !hasAppropriateType,
selected: element.id === elementValue.value.id,
onClick: (e, model) => this.onSelectExistingItem(model)
});
});
return /* @__PURE__ */ React.createElement("span", null, "No results");
}
async onSelectExistingItem(model) {
const { editor, onChange } = this.props;
this.loadingItemCancellation.abort();
this.loadingItemCancellation = new Cancellation();
const signal = this.loadingItemCancellation.signal;
onChange({
value: model,
isNew: false,
loading: true
});
const result = await editor.model.dataProvider.elementInfo({ elementIds: [model.id] });
if (signal.aborted) return;
const loadedModel = result[model.id];
onChange({
value: loadedModel,
isNew: false,
loading: false
});
}
render() {
const { searchString } = this.state;
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$13}__form-row ${CLASS_NAME$13}__element-selector` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$13}__search` }, /* @__PURE__ */ React.createElement("i", { className: `fa fa-search ${CLASS_NAME$13}__search-icon` }), /* @__PURE__ */ React.createElement("input", {
value: searchString,
onChange: (e) => this.setState({ searchString: e.target.value }),
className: `graph-explorer-form-control ${CLASS_NAME$13}__search-input`,
placeholder: "Search for...",
autoFocus: true
})), searchString.length > 0 ? /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$13}__existing-elements-list` }, this.renderExistingElementsList()) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$13}__separator` }, /* @__PURE__ */ React.createElement("i", { className: `${CLASS_NAME$13}__separator-text` }, "or create new entity")), this.renderElementTypeSelector()));
}
};
function makeElementTypeComparatorByLabel(view) {
return (a, b) => {
const typeA = view.model.createClass(a);
const typeB = view.model.createClass(b);
const labelA = view.formatLabel(typeA.label, typeA.id);
const labelB = view.formatLabel(typeB.label, typeB.id);
return labelA.localeCompare(labelB);
};
}
function validateElementType(element) {
const error = !(element.types.indexOf("http://graph-explorer.org/NewEntity") < 0) ? "Required." : void 0;
return Promise.resolve({
error,
allowChange: true
});
}
//#endregion
//#region src/graph-explorer/forms/linkTypeSelector.tsx
var CLASS_NAME$12 = "graph-explorer-edit-form";
var LinkTypeSelector = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
this.cancellation = new Cancellation();
this.updateAll = () => this.forceUpdate();
this.onChangeType = (e) => {
const { link: originalLink, direction: originalDirection } = this.props.linkValue.value;
const index = parseInt(e.currentTarget.value, 10);
const { fatLinkType, direction } = this.state.fatLinkTypes[index];
const link = {
...originalLink,
linkTypeId: fatLinkType.id
};
if (originalDirection !== direction) {
link.sourceId = originalLink.targetId;
link.targetId = originalLink.sourceId;
}
this.props.onChange({
link,
direction
});
};
this.renderPossibleLinkType = ({ fatLinkType, direction }, index) => {
const { view, source, target } = this.props;
const label = view.formatLabel(fatLinkType.label, fatLinkType.id);
let [sourceLabel, targetLabel] = [source, target].map((element) => view.formatLabel(element.label.values, element.id));
if (direction === LinkDirection.in) [sourceLabel, targetLabel] = [targetLabel, sourceLabel];
return /* @__PURE__ */ React.createElement("option", {
key: index,
value: index
}, label, " [", sourceLabel, " → ", targetLabel, "]");
};
this.state = { fatLinkTypes: [] };
}
componentDidMount() {
this.fetchPossibleLinkTypes();
}
componentDidUpdate(prevProps) {
const { source, target } = this.props;
if (prevProps.source !== source || prevProps.target !== target) {
this.setState({ fatLinkTypes: void 0 });
this.fetchPossibleLinkTypes();
}
}
componentWillUnmount() {
this.listener.stopListening();
this.cancellation.abort();
}
async fetchPossibleLinkTypes() {
const { view, metadataApi, source, target } = this.props;
if (!metadataApi) return;
const linkTypes = await CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.possibleLinkTypes(source, target, this.cancellation.signal));
if (linkTypes === null) return;
const fatLinkTypes = [];
linkTypes.forEach(({ linkTypeIri, direction }) => {
const fatLinkType = view.model.createLinkType(linkTypeIri);
fatLinkTypes.push({
fatLinkType,
direction
});
});
fatLinkTypes.sort(makeLinkTypeComparatorByLabelAndDirection(view));
this.setState({ fatLinkTypes });
this.listenToLinkLabels(fatLinkTypes);
}
listenToLinkLabels(fatLinkTypes) {
fatLinkTypes.forEach(({ fatLinkType }) => this.listener.listen(fatLinkType.events, "changeLabel", this.updateAll));
}
render() {
const { linkValue, disabled } = this.props;
const { fatLinkTypes } = this.state;
const value = (fatLinkTypes || []).findIndex(({ fatLinkType, direction }) => fatLinkType.id === linkValue.value.link.linkTypeId && direction === linkValue.value.direction);
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$12}__control-row` }, /* @__PURE__ */ React.createElement("label", null, "Link Type"), fatLinkTypes ? /* @__PURE__ */ React.createElement("select", {
className: "graph-explorer-form-control",
value,
onChange: this.onChangeType,
disabled
}, /* @__PURE__ */ React.createElement("option", {
value: -1,
disabled: true
}, "Select link type"), fatLinkTypes.map(this.renderPossibleLinkType)) : /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
})), linkValue.error ? /* @__PURE__ */ React.createElement("span", { className: `${CLASS_NAME$12}__control-error` }, linkValue.error) : "");
}
};
function makeLinkTypeComparatorByLabelAndDirection(view) {
return (a, b) => {
const labelA = view.formatLabel(a.fatLinkType.label, a.fatLinkType.id);
const labelB = view.formatLabel(b.fatLinkType.label, b.fatLinkType.id);
const labelCompareResult = labelA.localeCompare(labelB);
if (labelCompareResult !== 0) return labelCompareResult;
if (a.direction === LinkDirection.out && b.direction === LinkDirection.in) return -1;
if (a.direction === LinkDirection.in && b.direction === LinkDirection.out) return 1;
return 0;
};
}
function validateLinkType(editor, currentLink, originalLink) {
if (currentLink.linkTypeId === "http://graph-explorer.org/NewLink") return Promise.resolve({
error: "Required.",
allowChange: true
});
if (sameLink(currentLink, originalLink)) return Promise.resolve({
error: void 0,
allowChange: true
});
if (editor.model.links.find(({ data: { linkTypeId, sourceId, targetId } }) => linkTypeId === currentLink.linkTypeId && sourceId === currentLink.sourceId && targetId === currentLink.targetId && !editor.temporaryState.links.has(currentLink))) return Promise.resolve({
error: "The link already exists.",
allowChange: false
});
return editor.model.dataProvider.linksInfo({
elementIds: [currentLink.sourceId, currentLink.targetId],
linkTypeIds: [currentLink.linkTypeId]
}).then((links) => {
return links.some((link) => sameLink(link, currentLink)) ? {
error: "The link already exists.",
allowChange: false
} : {
error: void 0,
allowChange: true
};
});
}
//#endregion
//#region src/graph-explorer/forms/editElementTypeForm.tsx
var CLASS_NAME$11 = "graph-explorer-edit-form";
var EditElementTypeForm = class extends React.Component {
constructor(props) {
super(props);
this.validationCancellation = new Cancellation();
const { target, link } = this.props;
this.state = {
elementValue: {
value: target.value,
isNew: target.isNew,
loading: false,
validated: true,
allowChange: true
},
linkValue: {
value: {
link,
direction: LinkDirection.out
},
validated: true,
allowChange: true
}
};
}
componentDidMount() {
this.validate();
}
componentWillUnmount() {
this.validationCancellation.abort();
}
setElementOrLink({ elementValue, linkValue }) {
this.setState((state) => ({
elementValue: elementValue || state.elementValue,
linkValue: linkValue || state.linkValue
}), () => {
if (elementValue && !elementValue.validated || linkValue && !linkValue.validated) this.validate();
if (elementValue && elementValue.validated && elementValue.allowChange) this.props.onChangeElement(elementValue.value);
if (linkValue && linkValue.validated && linkValue.allowChange) this.props.onChangeLink(linkValue.value.link);
});
}
validate() {
const { editor, link: originalLink } = this.props;
const { elementValue, linkValue } = this.state;
this.setState({ isValidating: true });
this.validationCancellation.abort();
this.validationCancellation = new Cancellation();
const signal = this.validationCancellation.signal;
const validateElement = validateElementType(elementValue.value);
const validateLink = validateLinkType(editor, linkValue.value.link, originalLink);
Promise.all([validateElement, validateLink]).then(([elementError, linkError]) => {
if (signal.aborted) return;
this.setState({ isValidating: false });
this.setElementOrLink({
elementValue: {
...elementValue,
...elementError,
validated: true
},
linkValue: {
...linkValue,
...linkError,
validated: true
}
});
});
}
render() {
const { editor, view, metadataApi, source, link: originalLink } = this.props;
const { elementValue, linkValue, isValidating } = this.state;
const isValid = !elementValue.error && !linkValue.error;
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$11 }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$11}__body` }, /* @__PURE__ */ React.createElement(ElementTypeSelector, {
editor,
view,
metadataApi,
source,
elementValue,
onChange: (newState) => {
this.setElementOrLink({
elementValue: {
value: newState.value,
isNew: newState.isNew,
loading: newState.loading,
error: void 0,
validated: false,
allowChange: false
},
linkValue: {
value: {
link: {
...originalLink,
targetId: newState.value.id
},
direction: LinkDirection.out
},
validated: false,
allowChange: false
}
});
}
}), elementValue.loading ? /* @__PURE__ */ React.createElement("div", { style: { display: "flex" } }, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
}), "\xA0Loading entity...") : /* @__PURE__ */ React.createElement(LinkTypeSelector, {
editor,
view,
metadataApi,
linkValue,
source,
target: elementValue.value,
onChange: (value) => this.setElementOrLink({ linkValue: {
value,
error: void 0,
validated: false,
allowChange: false
} }),
disabled: elementValue.error !== void 0
}), isValidating ? /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$11}__progress` }, /* @__PURE__ */ React.createElement(ProgressBar, {
state: ProgressState.loading,
height: 10
})) : null), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$11}__controls` }, /* @__PURE__ */ React.createElement("button", {
className: `graph-explorer-btn graph-explorer-btn-success ${CLASS_NAME$11}__apply-button`,
onClick: () => this.props.onApply(elementValue.value, elementValue.isNew, linkValue.value.link),
disabled: elementValue.loading || !isValid || isValidating
}, "Apply"), /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-danger",
onClick: this.props.onCancel
}, "Cancel")));
}
};
//#endregion
//#region src/graph-explorer/forms/editLinkForm.tsx
var CLASS_NAME$10 = "graph-explorer-edit-form";
var EditLinkForm = class extends React.Component {
constructor(props) {
super(props);
this.validationCancellation = new Cancellation();
this.state = { linkValue: {
value: {
link: props.link,
direction: LinkDirection.out
},
validated: true,
allowChange: true
} };
}
componentDidMount() {
this.validate();
}
componentDidUpdate(prevProps, prevState) {
const { linkValue } = this.state;
if (!sameLink(linkValue.value.link, prevState.linkValue.value.link)) this.validate();
if (linkValue !== prevState.linkValue && linkValue.validated && linkValue.allowChange) this.props.onChange(linkValue.value.link);
}
componentWillUnmount() {
this.validationCancellation.abort();
}
validate() {
const { editor, link: originalLink } = this.props;
const { linkValue: { value } } = this.state;
this.setState({ isValidating: true });
this.validationCancellation.abort();
this.validationCancellation = new Cancellation();
const signal = this.validationCancellation.signal;
validateLinkType(editor, value.link, originalLink).then((error) => {
if (signal.aborted) return;
this.setState(({ linkValue }) => ({
linkValue: {
...linkValue,
...error,
validated: true
},
isValidating: false
}));
});
}
render() {
const { editor, view, metadataApi, source, target } = this.props;
const { linkValue, isValidating } = this.state;
const isValid = !linkValue.error;
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$10 }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$10}__body` }, /* @__PURE__ */ React.createElement(LinkTypeSelector, {
editor,
view,
metadataApi,
linkValue,
source,
target,
onChange: (value) => this.setState({ linkValue: {
value,
error: void 0,
validated: false,
allowChange: false
} })
}), isValidating ? /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$10}__progress` }, /* @__PURE__ */ React.createElement(ProgressBar, {
state: ProgressState.loading,
height: 10
})) : null), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$10}__controls` }, /* @__PURE__ */ React.createElement("button", {
className: `graph-explorer-btn graph-explorer-btn-success ${CLASS_NAME$10}__apply-button`,
onClick: () => this.props.onApply(linkValue.value.link),
disabled: !isValid || isValidating
}, "Apply"), /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-danger",
onClick: this.props.onCancel
}, "Cancel")));
}
};
//#endregion
//#region src/graph-explorer/forms/editLinkLabelForm.tsx
var CLASS_NAME$9 = "graph-explorer-edit-form";
var EditLinkLabelForm = class extends React.Component {
constructor(props) {
super(props);
const label = this.computeLabel();
this.state = { label };
}
componentDidUpdate(prevProps) {
if (this.props.link.typeId !== prevProps.link.typeId) {
const label = this.computeLabel();
this.setState({ label });
}
}
computeLabel() {
const { view, link } = this.props;
const linkType = view.model.getLinkType(link.typeId);
const { label = {} } = view.createLinkTemplate(linkType).renderLink(link);
const labelTexts = label.attrs && label.attrs.text ? label.attrs.text.text : void 0;
return labelTexts && labelTexts.length > 0 ? view.selectLabel(labelTexts).value : view.formatLabel(linkType.label, linkType.id);
}
render() {
const { onApply, onCancel } = this.props;
const { label } = this.state;
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$9 }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$9}__body` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$9}__form-row` }, /* @__PURE__ */ React.createElement("label", null, "Link Label"), /* @__PURE__ */ React.createElement("input", {
className: "graph-explorer-form-control",
value: label,
onChange: (e) => this.setState({ label: e.target.value })
}))), /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$9}__controls` }, /* @__PURE__ */ React.createElement("button", {
className: `graph-explorer-btn graph-explorer-btn-success ${CLASS_NAME$9}__apply-button`,
onClick: () => onApply(label)
}, "Apply"), /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-danger",
onClick: () => onCancel()
}, "Cancel")));
}
};
//#endregion
//#region src/graph-explorer/widgets/halo.tsx
var CLASS_NAME$8 = "graph-explorer-halo";
var Halo = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
this.targetListener = new EventObserver();
this.queryDebouncer = new Debouncer();
this.queryCancellation = new Cancellation();
this.onElementEvent = (data) => {
if (data.changePosition || data.changeSize || data.changeExpanded) this.forceUpdate();
if (data.changeData) this.queryAllowedActions();
};
this.onEstablishNewLink = (e) => {
const point = this.props.paperArea.pageToPaperCoords(e.pageX, e.pageY);
this.props.onEstablishNewLink(point);
};
this.state = {};
}
componentDidMount() {
const { editor, target } = this.props;
this.listener.listen(editor.events, "changeAuthoringState", () => {
this.queryAllowedActions();
});
this.listenToElement(target);
this.queryAllowedActions();
}
componentDidUpdate(prevProps) {
if (prevProps.target !== this.props.target) {
this.listenToElement(this.props.target);
this.queryAllowedActions();
}
}
componentWillUnmount() {
this.listener.stopListening();
this.listenToElement(void 0);
this.queryDebouncer.dispose();
this.queryCancellation.abort();
}
listenToElement(element) {
this.targetListener.stopListening();
if (element) this.targetListener.listenAny(element.events, this.onElementEvent);
}
queryAllowedActions() {
this.queryDebouncer.call(() => {
this.queryCancellation.abort();
this.queryCancellation = new Cancellation();
this.canLink(this.props.target);
});
}
canLink(target) {
const { metadataApi, editor } = this.props;
if (!metadataApi) {
this.setState({ canLink: false });
return;
}
const event = editor.authoringState.elements.get(target.iri);
if (event && event.deleted) this.setState({ canLink: false });
else {
this.setState({ canLink: void 0 });
const signal = this.queryCancellation.signal;
CancellationToken.mapCancelledToNull(signal, metadataApi.canLinkElement(target.data, signal)).then((canLink) => {
if (canLink === null) return;
if (this.props.target.iri === target.iri) this.setState({ canLink });
});
}
}
render() {
const { paperArea, editor, target, navigationMenuOpened, onToggleNavigationMenu, onAddToFilter, onExpand, onFollowLink } = this.props;
if (!target) return /* @__PURE__ */ React.createElement("div", {
className: CLASS_NAME$8,
style: { display: "none" }
});
const bbox = boundsOf(target);
const { x: x0, y: y0 } = paperArea.paperToScrollablePaneCoords(bbox.x, bbox.y);
const { x: x1, y: y1 } = paperArea.paperToScrollablePaneCoords(bbox.x + bbox.width, bbox.y + bbox.height);
const MARGIN = 5;
const style = {
left: x0 - MARGIN,
top: y0 - MARGIN,
width: x1 - x0 + MARGIN * 2,
height: y1 - y0 + MARGIN * 2
};
return /* @__PURE__ */ React.createElement("div", {
className: CLASS_NAME$8,
style
}, this.renderRemoveOrDeleteButton(), onToggleNavigationMenu && /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$8}__navigate ${CLASS_NAME$8}__navigate--${navigationMenuOpened ? "closed" : "open"}`,
role: "button",
title: "Open a dialog to navigate to connected elements",
onClick: onToggleNavigationMenu
}), onFollowLink && /* @__PURE__ */ React.createElement("a", {
className: `${CLASS_NAME$8}__folow`,
href: target.iri,
role: "button",
title: "Jump to resource",
onClick: (e) => onFollowLink(target, e)
}), onAddToFilter && /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$8}__add-to-filter`,
role: "button",
title: "Search for connected elements",
onClick: onAddToFilter
}), onExpand && /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$8}__expand ${CLASS_NAME$8}__expand--${target.isExpanded ? "closed" : "open"}`,
role: "button",
title: `Expand an element to reveal additional properties`,
onClick: onExpand
}), editor.inAuthoringMode ? this.renderEstablishNewLinkButton() : null);
}
renderRemoveOrDeleteButton() {
const { editor, target, onRemove } = this.props;
if (!onRemove) return null;
const isNewElement = AuthoringState.isNewElement(editor.authoringState, target.iri);
return /* @__PURE__ */ React.createElement("div", {
className: isNewElement ? `${CLASS_NAME$8}__delete` : `${CLASS_NAME$8}__remove`,
role: "button",
title: isNewElement ? "Delete new element" : "Remove an element from the diagram",
onClick: onRemove
});
}
renderEstablishNewLinkButton() {
const { onEstablishNewLink } = this.props;
const { canLink } = this.state;
if (!onEstablishNewLink) return null;
if (canLink === void 0) return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$8}__establish-connection-spinner` }, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
}));
const title = canLink ? "Establish connection" : "Establishing connection is unavailable for the selected element";
return /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$8}__establish-connection`,
title,
onMouseDown: this.onEstablishNewLink,
disabled: !canLink
});
}
};
//#endregion
//#region src/graph-explorer/widgets/haloLink.tsx
var CLASS_NAME$7 = "graph-explorer-halo-link";
var BUTTON_SIZE = 20;
var HaloLink = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
this.targetListener = new EventObserver();
this.queryDebouncer = new Debouncer();
this.queryCancellation = new Cancellation();
this.updateAll = () => this.forceUpdate();
this.onSourceMove = (e) => {
const point = this.props.paperArea.pageToPaperCoords(e.pageX, e.pageY);
this.props.onSourceMove(point);
};
this.onTargetMove = (e) => {
const point = this.props.paperArea.pageToPaperCoords(e.pageX, e.pageY);
this.props.onTargetMove(point);
};
this.state = {};
}
componentDidMount() {
const { target, editor } = this.props;
this.listener.listen(editor.events, "changeAuthoringState", () => {
this.queryAllowedActions();
});
this.listenToTarget(target);
this.queryAllowedActions();
}
componentDidUpdate(prevProps) {
if (prevProps.target !== this.props.target) {
this.listenToTarget(this.props.target);
this.queryAllowedActions();
}
}
componentWillUnmount() {
this.listener.stopListening();
this.listenToTarget(void 0);
this.queryDebouncer.dispose();
this.queryCancellation.abort();
}
queryAllowedActions() {
this.queryDebouncer.call(() => {
this.queryCancellation.abort();
this.queryCancellation = new Cancellation();
this.queryCanDelete(this.props.target);
this.queryCanEdit(this.props.target);
});
}
queryCanDelete(link) {
const { metadataApi, editor, view } = this.props;
if (!metadataApi) {
this.setState({ canDelete: false });
return;
}
if (isSourceOrTargetDeleted(editor.authoringState, link)) this.setState({ canDelete: false });
else {
this.setState({ canDelete: void 0 });
const source = view.model.getElement(link.sourceId);
const target = view.model.getElement(link.targetId);
const signal = this.queryCancellation.signal;
CancellationToken.mapCancelledToNull(signal, metadataApi.canDeleteLink(link.data, source.data, target.data, signal)).then((canDelete) => {
if (canDelete === null) return;
if (this.props.target.id === link.id) this.setState({ canDelete });
});
}
}
queryCanEdit(link) {
const { metadataApi, editor, view } = this.props;
if (!metadataApi) {
this.setState({ canEdit: false });
return;
}
if (isDeletedLink(editor.authoringState, link)) this.setState({ canEdit: false });
else {
this.setState({ canEdit: void 0 });
const source = view.model.getElement(link.sourceId);
const target = view.model.getElement(link.targetId);
const signal = this.queryCancellation.signal;
CancellationToken.mapCancelledToNull(signal, metadataApi.canEditLink(link.data, source.data, target.data, signal)).then((canEdit) => {
if (canEdit === null) return;
if (this.props.target.id === link.id) this.setState({ canEdit });
});
}
}
listenToTarget(link) {
const { view } = this.props;
this.targetListener.stopListening();
if (link) {
const source = view.model.getElement(link.sourceId);
const target = view.model.getElement(link.targetId);
const listenToElement = (element) => {
this.targetListener.listen(element.events, "changePosition", this.updateAll);
this.targetListener.listen(element.events, "changeSize", this.updateAll);
};
listenToElement(source);
listenToElement(target);
this.targetListener.listen(link.events, "changeVertices", this.updateAll);
this.targetListener.listen(link.events, "changeLabelBounds", this.updateAll);
}
}
paperToScrollablePaneCoords(point) {
return this.props.paperArea.paperToScrollablePaneCoords(point.x, point.y);
}
computePolyline() {
const { view, target } = this.props;
const sourceElement = view.model.getElement(target.sourceId);
const targetElement = view.model.getElement(target.targetId);
if (!(sourceElement && targetElement)) return;
const route = view.getRouting(target.id);
const verticesDefinedByUser = target.vertices || [];
return computePolyline(sourceElement, targetElement, route ? route.vertices : verticesDefinedByUser);
}
calculateDegree(source, target) {
const x = target.x - source.x;
const y = target.y - source.y;
const r = Math.sqrt(x * x + y * y);
const unit = {
x: x / r,
y: y / r
};
return Math.atan2(unit.y, unit.x) * (180 / Math.PI);
}
renderSourceButton(polyline) {
const { editor, target } = this.props;
const point = getPointAlongPolyline(polyline, 0);
const { x, y } = this.paperToScrollablePaneCoords(point);
const style = {
top: y - BUTTON_SIZE / 2,
left: x - BUTTON_SIZE / 2
};
return /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$7}__button`,
style,
disabled: isDeletedLink(editor.authoringState, target),
onMouseDown: this.onSourceMove
}, /* @__PURE__ */ React.createElement("svg", {
width: BUTTON_SIZE,
height: BUTTON_SIZE
}, /* @__PURE__ */ React.createElement("g", { transform: `scale(${BUTTON_SIZE})` }, /* @__PURE__ */ React.createElement("circle", {
r: .5,
cx: .5,
cy: .5,
fill: "#198AD3"
}))));
}
getButtonPosition(polyline, index) {
const point = getPointAlongPolyline(polyline, computePolylineLength(polyline) - 25 * index);
const { x, y } = this.paperToScrollablePaneCoords(point);
return {
top: y - BUTTON_SIZE / 2,
left: x - BUTTON_SIZE / 2
};
}
renderTargetButton(polyline) {
const { editor, target } = this.props;
const style = this.getButtonPosition(polyline, 0);
const { length } = polyline;
const degree = this.calculateDegree(polyline[length - 1], polyline[length - 2]);
return /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$7}__button`,
style,
disabled: isDeletedLink(editor.authoringState, target),
onMouseDown: this.onTargetMove
}, /* @__PURE__ */ React.createElement("svg", {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
style: { transform: `rotate(${degree}deg)` }
}, /* @__PURE__ */ React.createElement("g", { transform: `scale(${BUTTON_SIZE})` }, /* @__PURE__ */ React.createElement("polygon", {
points: "0,0.5 1,1 1,0",
fill: "#198AD3"
}))));
}
renderEditButton(polyline) {
const { canEdit } = this.state;
const style = this.getButtonPosition(polyline, 1);
if (canEdit === void 0) return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$7}__spinner`,
style
}, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
}));
const title = canEdit ? "Edit link" : "Editing is unavailable for the selected link";
return /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$7}__button ${CLASS_NAME$7}__edit`,
style,
title,
onClick: this.props.onEdit,
disabled: !canEdit
});
}
renderDeleteButton(polyline) {
const { canDelete } = this.state;
const style = this.getButtonPosition(polyline, 2);
if (canDelete === void 0) return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$7}__spinner`,
style
}, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 20,
height: 20
}));
const title = canDelete ? "Delete link" : "Deletion is unavailable for the selected link";
return /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$7}__button ${CLASS_NAME$7}__delete`,
style,
title,
onClick: this.props.onDelete,
disabled: !canDelete
});
}
renderEditLabelButton() {
const { view, target, paperArea, onEditLabel } = this.props;
const linkType = view.model.getLinkType(target.typeId);
if (!view.createLinkTemplate(linkType).setLinkLabel || !target.labelBounds) return null;
const { x, y, width, height } = target.labelBounds;
const { x: left, y: top } = paperArea.paperToScrollablePaneCoords(x + width, y + height / 2);
const size = {
width: 15,
height: 17
};
const style = {
width: size.width,
height: size.height,
top: top - size.height / 2,
left
};
return /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$7}__edit-label-button`,
style,
onClick: () => onEditLabel(),
title: "Edit Link Label"
});
}
render() {
const { editor, target, metadataApi } = this.props;
const polyline = this.computePolyline();
if (!polyline) return null;
const isAuthoringMode = Boolean(metadataApi);
const deleteButton = isDeletedByItself(editor.authoringState, target) || isSourceOrTargetDeleted(editor.authoringState, target) ? null : this.renderDeleteButton(polyline);
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$7}` }, isAuthoringMode ? this.renderTargetButton(polyline) : null, isAuthoringMode ? this.renderSourceButton(polyline) : null, !isAuthoringMode || isDeletedLink(editor.authoringState, target) ? null : this.renderEditButton(polyline), isAuthoringMode ? deleteButton : null, this.renderEditLabelButton());
}
};
function isDeletedLink(state, link) {
return isDeletedByItself(state, link) || isSourceOrTargetDeleted(state, link);
}
function isDeletedByItself(state, link) {
const event = state.links.get(link.data);
return event && event.deleted;
}
function isSourceOrTargetDeleted(state, link) {
const sourceEvent = state.elements.get(link.data.sourceId);
const targetEvent = state.elements.get(link.data.targetId);
return sourceEvent && sourceEvent.deleted || targetEvent && targetEvent.deleted;
}
//#endregion
//#region src/graph-explorer/editor/linkStateWidget.tsx
var CLASS_NAME$6 = `graph-explorer-authoring-state`;
var LINK_LABEL_MARGIN = 5;
var LinkStateWidget = class extends React.Component {
constructor(..._args) {
super(..._args);
this.listener = new EventObserver();
this.delayedUpdate = new Debouncer();
this.scheduleUpdate = () => {
this.delayedUpdate.call(this.performUpdate);
};
this.performUpdate = () => {
this.forceUpdate();
};
}
componentDidMount() {
this.listenEvents();
}
componentDidUpdate(prevProps) {
if (!(this.props.view === prevProps.view)) {
this.listener.stopListening();
this.listenEvents();
}
}
componentWillUnmount() {
this.listener.stopListening();
}
listenEvents() {
const { editor, view } = this.props;
this.listener.listen(editor.model.events, "elementEvent", ({ data }) => {
if (data.changeSize || data.changePosition) this.scheduleUpdate();
});
this.listener.listen(editor.model.events, "linkEvent", ({ data }) => {
if (data.changeVertices || data.changeLabelBounds) this.scheduleUpdate();
});
this.listener.listen(editor.model.events, "changeCells", this.scheduleUpdate);
this.listener.listen(editor.events, "changeAuthoringState", this.scheduleUpdate);
this.listener.listen(editor.events, "changeTemporaryState", this.scheduleUpdate);
this.listener.listen(editor.events, "changeValidationState", this.scheduleUpdate);
this.listener.listen(view.events, "syncUpdate", ({ layer }) => {
if (layer === RenderingLayer.Editor) this.delayedUpdate.runSynchronously();
});
}
calculateLinkPath(link) {
return "M" + this.calculatePolyline(link).map(({ x, y }) => `${x},${y}`).join(" L");
}
calculatePolyline(link) {
const { editor, view } = this.props;
const source = editor.model.getElement(link.sourceId);
const target = editor.model.getElement(link.targetId);
const route = view.getRouting(link.id);
const verticesDefinedByUser = link.vertices || [];
return computePolyline(source, target, route ? route.vertices : verticesDefinedByUser);
}
renderLinkStateLabels() {
const { editor } = this.props;
return editor.model.links.map((link) => {
let renderedState = null;
const state = editor.authoringState.links.get(link.data);
if (state) {
const onCancel = () => editor.discardChange(state);
let statusText;
let title;
if (state.deleted) {
statusText = "Delete";
title = "Revert deletion of the link";
} else if (!state.before) {
statusText = "New";
title = "Revert creation of the link";
} else {
statusText = "Change";
title = "Revert all changes in properties of the link";
}
if (statusText && title) renderedState = /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: `${CLASS_NAME$6}__state-label` }, statusText), "[", /* @__PURE__ */ React.createElement("span", {
className: `${CLASS_NAME$6}__state-cancel`,
onClick: onCancel,
title
}, "cancel"), "]");
}
const renderedErrors = this.renderLinkErrors(link.data);
if (renderedState || renderedErrors) {
const labelPosition = this.getLinkStateLabelPosition(link);
if (!labelPosition) return null;
const style = {
left: labelPosition.x,
top: labelPosition.y
};
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$6}__state-indicator`,
key: link.id,
style
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$6}__state-indicator-container` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$6}__state-indicator-body` }, renderedState, renderedErrors)));
} else return null;
});
}
renderLinkStateHighlighting() {
const { editor } = this.props;
return editor.model.links.map((link) => {
if (editor.temporaryState.links.has(link.data)) {
const path = this.calculateLinkPath(link);
return /* @__PURE__ */ React.createElement("path", {
key: link.id,
d: path,
fill: "none",
stroke: "grey",
strokeWidth: 5,
strokeOpacity: .5,
strokeDasharray: "8 8"
});
}
const event = editor.authoringState.links.get(link.data);
const isDeletedLink = AuthoringState.isDeletedLink(editor.authoringState, link.data);
const isUncertainLink = AuthoringState.isUncertainLink(editor.authoringState, link.data);
if (event || isDeletedLink || isUncertainLink) {
const path = this.calculateLinkPath(link);
let color;
if (isDeletedLink) color = "red";
else if (isUncertainLink) color = "blue";
else if (event && event.type === AuthoringKind.ChangeLink) color = event.before ? "blue" : "green";
return /* @__PURE__ */ React.createElement("path", {
key: link.id,
d: path,
fill: "none",
stroke: color,
strokeWidth: 5,
strokeOpacity: .5
});
}
return null;
});
}
getLinkStateLabelPosition(link) {
if (link.labelBounds) {
const { x, y } = link.labelBounds;
return {
x,
y: y - LINK_LABEL_MARGIN / 2
};
} else {
const polyline = this.calculatePolyline(link);
return getPointAlongPolyline(polyline, computePolylineLength(polyline) / 2);
}
}
renderLinkErrors(linkModel) {
const { editor } = this.props;
const { validationState } = editor;
const validation = validationState.links.get(linkModel);
if (!validation) return null;
const title = validation.errors.map((error) => error.message).join("\n");
return this.renderErrorIcon(title, validation);
}
renderErrorIcon(title, validation) {
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$6}__item-error`,
title
}, validation.loading ? /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 15,
height: 17
}) : /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$6}__item-error-icon` }), !validation.loading && validation.errors.length > 0 ? validation.errors.length : void 0);
}
render() {
const { editor, paperTransform } = this.props;
const { scale, originX, originY } = paperTransform;
if (!editor.inAuthoringMode) return null;
const htmlTransformStyle = {
position: "absolute",
left: 0,
top: 0,
transform: `scale(${scale},${scale})translate(${originX}px,${originY}px)`
};
return /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$6}` }, /* @__PURE__ */ React.createElement(TransformedSvgCanvas, {
paperTransform,
style: {
overflow: "visible",
pointerEvents: "none"
}
}, this.renderLinkStateHighlighting()), /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$6}__validation-layer`,
style: htmlTransformStyle
}, this.renderLinkStateLabels()));
}
};
//#endregion
//#region src/graph-explorer/editor/elementDecorator.tsx
var CLASS_NAME$5 = `graph-explorer-authoring-state`;
var ElementDecorator = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
const { model, editor } = props;
this.state = {
state: editor.authoringState.elements.get(model.iri),
validation: editor.validationState.elements.get(model.iri),
isTemporary: editor.temporaryState.elements.has(model.iri)
};
}
componentDidMount() {
const { model, editor } = this.props;
this.listener.listen(model.events, "changeSize", () => this.forceUpdate());
this.listener.listen(editor.events, "changeAuthoringState", (e) => {
const state = editor.authoringState.elements.get(model.iri);
if (state === e.previous.elements.get(model.iri)) return;
this.setState({ state });
});
this.listener.listen(editor.events, "changeValidationState", (e) => {
const validation = editor.validationState.elements.get(model.iri);
if (validation === e.previous.elements.get(model.iri)) return;
this.setState({ validation });
});
this.listener.listen(editor.events, "changeTemporaryState", (e) => {
const isTemporary = editor.temporaryState.elements.has(model.iri);
if (isTemporary === e.previous.elements.has(model.iri)) return;
this.setState({ isTemporary });
});
this.listener.listen(model.events, "changeData", (e) => {
if (e.previous.id !== model.iri) this.setState({
isTemporary: editor.temporaryState.elements.has(model.iri),
validation: editor.validationState.elements.get(model.iri),
state: editor.authoringState.elements.get(model.iri)
});
});
}
componentWillUnmount() {
this.listener.stopListening();
}
shouldComponentUpdate(nextProps, nextState) {
return this.state.state !== nextState.state || this.state.validation !== nextState.validation || this.state.isTemporary !== nextState.isTemporary || this.props.position !== nextProps.position;
}
renderElementOutlines() {
const { model } = this.props;
const { state, isTemporary } = this.state;
const { width, height } = model.size;
if (isTemporary) return [/* @__PURE__ */ React.createElement("rect", {
key: `${model.id}-opacity`,
x: 0,
y: 0,
width,
height,
fill: "rgba(255, 255, 255, 0.5)"
}), /* @__PURE__ */ React.createElement("rect", {
key: `${model.id}-stripes`,
x: 0,
y: 0,
width,
height,
fill: "url(#stripe-pattern)"
})];
if (state && state.deleted) {
const right = width;
const bottom = height;
return /* @__PURE__ */ React.createElement("g", { key: model.id }, /* @__PURE__ */ React.createElement("rect", {
x: 0,
y: 0,
width,
height,
fill: "white",
fillOpacity: .5
}), /* @__PURE__ */ React.createElement("line", {
x1: 0,
y1: 0,
x2: right,
y2: bottom,
stroke: "red"
}), /* @__PURE__ */ React.createElement("line", {
x1: right,
y1: 0,
x2: 0,
y2: bottom,
stroke: "red"
}));
}
return null;
}
renderErrorIcon(title, validation) {
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$5}__item-error`,
title
}, validation.loading ? /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 15,
height: 17
}) : /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$5}__item-error-icon` }), !validation.loading && validation.errors.length > 0 ? validation.errors.length : void 0);
}
renderElementErrors() {
const { view } = this.props;
const { validation } = this.state;
if (!validation) return null;
const title = validation.errors.map((error) => {
if (error.propertyType) {
const { id, label } = view.model.createProperty(error.propertyType);
return `${view.formatLabel(label, id)}: ${error.message}`;
} else return error.message;
}).join("\n");
return this.renderErrorIcon(title, validation);
}
renderElementState() {
const { model, editor } = this.props;
const { state } = this.state;
if (state) {
const onCancel = () => editor.discardChange(state);
let renderedState;
let statusText;
let title;
if (state.deleted) {
statusText = "Delete";
title = "Revert deletion of the element";
} else if (!state.before) {
statusText = "New";
title = "Revert creation of the element";
} else {
statusText = "Change";
title = "Revert all changes in properties of the element";
}
if (statusText && title) renderedState = /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("span", { className: `${CLASS_NAME$5}__state-label` }, statusText), "[", /* @__PURE__ */ React.createElement("span", {
className: `${CLASS_NAME$5}__state-cancel`,
onClick: onCancel,
title
}, "cancel"), "]");
const renderedErrors = this.renderElementErrors();
if (renderedState || renderedErrors) return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$5}__state-indicator`,
key: model.id,
style: {
left: 0,
top: 0
}
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$5}__state-indicator-container` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$5}__state-indicator-body` }, renderedState, renderedErrors)));
}
return null;
}
render() {
const { position, size } = this.props.model;
const transform = `translate(${position.x}px,${position.y}px)`;
const outlines = this.renderElementOutlines();
const state = this.renderElementState();
if (!outlines && !state) return null;
return /* @__PURE__ */ React.createElement("div", { style: {
position: "absolute",
transform
} }, outlines ? /* @__PURE__ */ React.createElement("svg", {
width: size.width,
height: size.height,
style: {
position: "absolute",
pointerEvents: "none",
overflow: "visible"
}
}, /* @__PURE__ */ React.createElement("defs", null, /* @__PURE__ */ React.createElement("pattern", {
id: "stripe-pattern",
patternUnits: "userSpaceOnUse",
width: 13,
height: 13,
patternTransform: "rotate(45)"
}, /* @__PURE__ */ React.createElement("line", {
x1: 0,
y: 0,
x2: 0,
y2: 13,
stroke: "#ddd",
strokeWidth: 10,
strokeOpacity: .2
}))), this.renderElementOutlines()) : null, state);
}
};
//#endregion
//#region src/graph-explorer/editor/temporaryState.ts
var TemporaryState = {
empty: {
elements: /* @__PURE__ */ new Map(),
links: new HashMap(hashLink, sameLink)
},
addElement(state, element) {
const elements = cloneMap(state.elements);
elements.set(element.id, element);
return {
...state,
elements
};
},
deleteElement(state, element) {
const elements = cloneMap(state.elements);
elements.delete(element.id);
return {
...state,
elements
};
},
addLink(state, link) {
const links = state.links.clone();
links.set(link, link);
return {
...state,
links
};
},
deleteLink(state, link) {
const links = state.links.clone();
links.delete(link);
return {
...state,
links
};
}
};
//#endregion
//#region src/graph-explorer/editor/editLayer.tsx
var EditLayerMode = /* @__PURE__ */ function(EditLayerMode) {
EditLayerMode[EditLayerMode["establishLink"] = 0] = "establishLink";
EditLayerMode[EditLayerMode["moveLinkSource"] = 1] = "moveLinkSource";
EditLayerMode[EditLayerMode["moveLinkTarget"] = 2] = "moveLinkTarget";
return EditLayerMode;
}({});
var EditLayer = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
this.cancellation = new Cancellation();
this.canDropOnElementCancellation = new Cancellation();
this.beginCreatingLink = (params) => {
const { editor } = this.props;
const { sourceId, point } = params;
const temporaryElement = this.createTemporaryElement(point);
const linkTemplate = new Link({
typeId: PLACEHOLDER_LINK_TYPE,
sourceId,
targetId: temporaryElement.id
});
const temporaryLink = editor.createNewLink({
link: linkTemplate,
temporary: true
});
editor.model.createLinkType(temporaryLink.typeId).setVisibility({
visible: true,
showLabel: false
});
this.temporaryElement = temporaryElement;
this.temporaryLink = temporaryLink;
};
this.onMouseMove = (e) => {
const { view, paperArea } = this.props;
const { targetElement, waitingForMetadata } = this.state;
e.preventDefault();
e.stopPropagation();
if (waitingForMetadata) return;
const point = paperArea.pageToPaperCoords(e.pageX, e.pageY);
this.temporaryElement.setPosition(point);
const newTargetElement = findElementAtPoint(view.model.elements, point);
if (newTargetElement !== targetElement) this.queryCanDropOnElement(newTargetElement);
this.setState({ targetElement: newTargetElement });
};
this.onMouseUp = (e) => {
if (this.state.waitingForMetadata) return;
this.setState({ waitingForMetadata: true });
const selectedPosition = this.props.paperArea.pageToPaperCoords(e.pageX, e.pageY);
this.executeEditOperation(selectedPosition);
};
this.createNewElement = async (source) => {
const { editor, metadataApi } = this.props;
if (!metadataApi) return;
const elementTypes = await CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.typesOfElementsDraggedFrom(source, this.cancellation.signal));
if (elementTypes === null) return;
const classId = elementTypes.length === 1 ? elementTypes[0] : PLACEHOLDER_ELEMENT_TYPE;
const elementModel = await CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.generateNewElement([classId], this.cancellation.signal));
if (elementModel === null) return;
return editor.createNewEntity({
elementModel,
temporary: true
});
};
this.state = {};
}
componentDidMount() {
const { mode, target, point } = this.props;
if (mode === 0) this.beginCreatingLink({
sourceId: target.id,
point
});
else if (mode === 1 || mode === 2) this.beginMovingLink(target, point);
else throw new Error("Unknown edit mode");
this.forceUpdate();
this.queryCanLinkFrom();
this.queryCanDropOnCanvas();
document.addEventListener("mousemove", this.onMouseMove);
document.addEventListener("mouseup", this.onMouseUp);
}
componentWillUnmount() {
this.listener.stopListening();
this.cancellation.abort();
this.canDropOnElementCancellation.abort();
document.removeEventListener("mousemove", this.onMouseMove);
document.removeEventListener("mouseup", this.onMouseUp);
}
beginMovingLink(target, startingPoint) {
const { editor, mode } = this.props;
if (!(mode === 1 || mode === 2)) throw new Error("Unexpected edit mode for moving link");
this.oldLink = target;
editor.model.removeLink(target.id);
const { id: _id, typeId, sourceId, targetId, ...otherProps } = target;
const temporaryElement = this.createTemporaryElement(startingPoint);
const linkTemplate = new Link({
typeId,
sourceId: mode === 1 ? temporaryElement.id : sourceId,
targetId: mode === 2 ? temporaryElement.id : targetId,
...otherProps
});
const temporaryLink = editor.createNewLink({
link: linkTemplate,
temporary: true
});
this.temporaryElement = temporaryElement;
this.temporaryLink = temporaryLink;
}
createTemporaryElement(point) {
const temporaryElement = this.props.view.model.createTemporaryElement();
temporaryElement.setPosition(point);
return temporaryElement;
}
queryCanLinkFrom() {
const { editor, metadataApi } = this.props;
if (!metadataApi) {
this.setState({ canLinkFrom: false });
return;
}
this.setState({ canLinkFrom: void 0 });
const source = editor.model.getElement(this.temporaryLink.sourceId);
CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.canLinkElement(source.data, this.cancellation.signal)).then((canLinkFrom) => {
if (canLinkFrom === null) return;
this.setState({ canLinkFrom });
}, (error) => {
console.error("Error calling canLinkElement:", error);
this.setState({ canLinkFrom: false });
});
}
queryCanDropOnCanvas() {
const { mode, editor, metadataApi } = this.props;
if (!metadataApi || mode !== 0) {
this.setState({ canDropOnCanvas: false });
return;
}
this.setState({ canDropOnCanvas: void 0 });
const source = editor.model.getElement(this.temporaryLink.sourceId);
CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.canDropOnCanvas(source.data, this.cancellation.signal)).then((canDropOnCanvas) => {
if (canDropOnCanvas === null) return;
this.setState({ canDropOnCanvas });
}, (error) => {
console.error("Error calling canDropOnCanvas:", error);
this.setState({ canDropOnCanvas: false });
});
}
queryCanDropOnElement(targetElement) {
const { mode, editor, metadataApi } = this.props;
this.canDropOnElementCancellation.abort();
this.canDropOnElementCancellation = new Cancellation();
if (!(metadataApi && targetElement)) {
this.setState({ canDropOnElement: false });
return;
}
this.setState({ canDropOnElement: void 0 });
let source;
let target;
if (mode === 0 || mode === 2) {
source = editor.model.getElement(this.temporaryLink.sourceId).data;
target = targetElement.data;
} else if (mode === 1) {
source = targetElement.data;
target = editor.model.getElement(this.temporaryLink.targetId).data;
}
const signal = this.canDropOnElementCancellation.signal;
CancellationToken.mapCancelledToNull(signal, metadataApi.canDropOnElement(source, target, signal)).then((canDropOnElement) => {
if (canDropOnElement === null) return;
this.setState({ canDropOnElement });
});
}
async executeEditOperation(selectedPosition) {
const { view, editor, mode } = this.props;
try {
const { targetElement, canLinkFrom, canDropOnCanvas, canDropOnElement } = this.state;
if (this.oldLink) editor.model.addLink(this.oldLink);
if (canLinkFrom && (targetElement ? canDropOnElement : canDropOnCanvas)) {
let modifiedLink;
let createdTarget = targetElement;
switch (mode) {
case 0: {
if (!createdTarget) {
const source = editor.model.getElement(this.temporaryLink.sourceId);
createdTarget = await this.createNewElement(source.data);
createdTarget.setPosition(selectedPosition);
view.performSyncUpdate();
centerElementAtPoint(createdTarget, selectedPosition);
}
const sourceElement = editor.model.getElement(this.temporaryLink.sourceId);
modifiedLink = await this.createNewLink(sourceElement, createdTarget);
break;
}
case 1:
modifiedLink = editor.moveLinkSource({
link: this.oldLink,
newSource: targetElement
});
break;
case 2:
modifiedLink = editor.moveLinkTarget({
link: this.oldLink,
newTarget: targetElement
});
break;
default: throw new Error("Unknown edit mode");
}
if (targetElement) {
const focusedLink = modifiedLink || this.oldLink;
editor.setSelection([focusedLink]);
editor.showEditLinkForm(focusedLink);
} else if (createdTarget && modifiedLink) {
editor.setSelection([createdTarget]);
const source = editor.model.getElement(modifiedLink.sourceId);
editor.showEditElementTypeForm({
link: modifiedLink,
source,
target: createdTarget,
targetIsNew: true
});
}
}
} finally {
this.cleanupAndFinish();
}
}
async createNewLink(source, target) {
const { editor, metadataApi } = this.props;
if (!metadataApi) return;
const linkTypes = await CancellationToken.mapCancelledToNull(this.cancellation.signal, metadataApi.possibleLinkTypes(source.data, target.data, this.cancellation.signal));
if (linkTypes === null) return;
const placeholder = {
linkTypeIri: PLACEHOLDER_LINK_TYPE,
direction: LinkDirection.out
};
const { linkTypeIri: typeId, direction } = linkTypes.length === 1 ? linkTypes[0] : placeholder;
const data = {
linkTypeId: typeId,
sourceId: source.iri,
targetId: target.iri
};
let [sourceId, targetId] = [source.id, target.id];
if (direction === LinkDirection.in) {
data.sourceId = target.iri;
data.targetId = source.iri;
[sourceId, targetId] = [targetId, sourceId];
}
const link = new Link({
typeId,
sourceId,
targetId,
data
});
return editor.model.findLink(link.typeId, link.sourceId, link.targetId) || editor.createNewLink({
link,
temporary: true
});
}
cleanupAndFinish() {
const { editor, onFinishEditing } = this.props;
const batch = editor.model.history.startBatch();
editor.model.removeElement(this.temporaryElement.id);
editor.model.removeLink(this.temporaryLink.id);
editor.setTemporaryState(TemporaryState.deleteLink(editor.temporaryState, this.temporaryLink.data));
batch.discard();
onFinishEditing();
}
render() {
const { view, paperTransform } = this.props;
const { waitingForMetadata } = this.state;
if (!this.temporaryLink) return null;
return /* @__PURE__ */ React.createElement(TransformedSvgCanvas, {
paperTransform,
style: { overflow: "visible" }
}, /* @__PURE__ */ React.createElement(LinkMarkers, { view }), this.renderHighlight(), this.renderCanDropIndicator(), waitingForMetadata ? null : /* @__PURE__ */ React.createElement(LinkLayer, {
view,
links: [this.temporaryLink]
}));
}
renderHighlight() {
const { targetElement, canLinkFrom, canDropOnElement, waitingForMetadata } = this.state;
if (!targetElement) return null;
const { x, y, width, height } = boundsOf(targetElement);
if (canLinkFrom === void 0 || canDropOnElement === void 0 || waitingForMetadata) return /* @__PURE__ */ React.createElement("g", { transform: `translate(${x},${y})` }, /* @__PURE__ */ React.createElement("rect", {
width,
height,
fill: "white",
fillOpacity: .5
}), /* @__PURE__ */ React.createElement(Spinner, {
size: 30,
position: {
x: width / 2,
y: height / 2
}
}));
const stroke = canLinkFrom && canDropOnElement ? "#5cb85c" : "#c9302c";
return /* @__PURE__ */ React.createElement("rect", {
x,
y,
width,
height,
fill: "transparent",
stroke,
strokeWidth: 3
});
}
renderCanDropIndicator() {
const { targetElement, canLinkFrom, canDropOnCanvas, waitingForMetadata } = this.state;
if (targetElement) return null;
const { x, y } = this.temporaryElement.position;
let indicator;
if (canLinkFrom === void 0 || canDropOnCanvas === void 0) indicator = /* @__PURE__ */ React.createElement(Spinner, {
size: 1.2,
position: {
x: .5,
y: -.5
}
});
else if (canLinkFrom && canDropOnCanvas) indicator = /* @__PURE__ */ React.createElement("path", {
d: "M0.5,0 L0.5,-1 M0,-0.5 L1,-0.5",
strokeWidth: .2,
stroke: "#5cb85c"
});
else indicator = /* @__PURE__ */ React.createElement("g", null, /* @__PURE__ */ React.createElement("circle", {
cx: "0.5",
cy: "-0.5",
r: "0.5",
fill: "none",
strokeWidth: .2,
stroke: "#c9302c"
}), /* @__PURE__ */ React.createElement("path", {
d: "M0.5,0 L0.5,-1",
strokeWidth: .2,
stroke: "#c9302c",
transform: "rotate(-45 0.5 -0.5)"
}));
return /* @__PURE__ */ React.createElement("g", { transform: `translate(${x} ${y})scale(40)` }, /* @__PURE__ */ React.createElement("rect", {
x: -.5,
y: -.5,
width: 1,
height: 1,
fill: "rgba(0, 0, 0, 0.1)",
rx: .25,
ry: .25
}), waitingForMetadata ? /* @__PURE__ */ React.createElement(Spinner, { size: .8 }) : /* @__PURE__ */ React.createElement("g", { transform: `translate(0.5, -0.5)scale(0.25)` }, indicator));
}
};
function centerElementAtPoint(element, point) {
element.setPosition({
x: point.x - element.size.width / 2,
y: point.y - element.size.height / 2
});
}
//#endregion
//#region src/graph-explorer/editor/validation.ts
var empty = createMutable();
var emptyElement = {
loading: false,
errors: []
};
var emptyLink = {
loading: false,
errors: []
};
function createMutable() {
return {
elements: /* @__PURE__ */ new Map(),
links: new HashMap(hashLink, sameLink)
};
}
function setElementErrors(state, target, errors) {
const elements = cloneMap(state.elements);
if (errors.length > 0) elements.set(target, {
loading: false,
errors
});
else elements.delete(target);
return {
...state,
elements
};
}
function setLinkErrors(state, target, errors) {
const links = state.links.clone();
if (errors.length > 0) links.set(target, {
loading: false,
errors
});
else links.delete(target);
return {
...state,
links
};
}
var ValidationState = {
empty,
emptyElement,
emptyLink,
createMutable,
setElementErrors,
setLinkErrors
};
function changedElementsToValidate(previousAuthoring, editor) {
const currentAuthoring = editor.authoringState;
const links = new HashMap(hashLink, sameLink);
previousAuthoring.links.forEach((e, model) => links.set(model, true));
currentAuthoring.links.forEach((e, model) => links.set(model, true));
const toValidate = /* @__PURE__ */ new Set();
links.forEach((value, linkModel) => {
if (currentAuthoring.links.get(linkModel) !== previousAuthoring.links.get(linkModel)) toValidate.add(linkModel.sourceId);
});
for (const element of editor.model.elements) {
const current = currentAuthoring.elements.get(element.iri);
const previous = previousAuthoring.elements.get(element.iri);
if (current !== previous) {
toValidate.add(element.iri);
if ((current || previous).deleted) {
for (const link of element.links) if (link.data.sourceId !== element.iri) toValidate.add(link.data.sourceId);
}
}
}
return toValidate;
}
function validateElements(targets, validationApi, editor, cancellationToken) {
const previousState = editor.validationState;
const newState = ValidationState.createMutable();
for (const element of editor.model.elements) {
if (newState.elements.has(element.iri)) continue;
const outboundLinks = element.links.reduce((acc, link) => {
if (link.sourceId === element.id) acc.push(link.data);
return acc;
}, []);
if (targets.has(element.iri)) {
const event = {
target: element.data,
outboundLinks,
state: editor.authoringState,
model: editor.model,
cancellation: cancellationToken
};
const result = CancellationToken.mapCancelledToNull(cancellationToken, validationApi.validate(event));
const loadingElement = {
loading: true,
errors: []
};
const loadingLink = {
loading: true,
errors: []
};
newState.elements.set(element.iri, loadingElement);
outboundLinks.forEach((link) => newState.links.set(link, loadingLink));
processValidationResult(result, loadingElement, loadingLink, event, editor);
} else {
newState.elements.set(element.iri, previousState.elements.get(element.iri));
for (const link of outboundLinks) newState.links.set(link, previousState.links.get(link));
}
}
editor.setValidationState(newState);
}
async function processValidationResult(result, previousElement, previousLink, e, editor) {
let allErrors;
try {
allErrors = await result;
if (allErrors === null) return;
} catch (err) {
console.error(`Failed to validate element`, e.target, err);
allErrors = [{
type: "element",
target: e.target.id,
message: `Failed to validate element`
}];
}
const elementErrors = [];
const linkErrors = new HashMap(hashLink, sameLink);
e.outboundLinks.forEach((link) => linkErrors.set(link, []));
for (const error of allErrors) if (error.type === "element" && error.target === e.target.id) elementErrors.push(error);
else if (error.type === "link" && linkErrors.has(error.target)) linkErrors.get(error.target).push(error);
let state = editor.validationState;
if (state.elements.get(e.target.id) === previousElement) state = ValidationState.setElementErrors(state, e.target.id, elementErrors);
linkErrors.forEach((errors, link) => {
if (state.links.get(link) === previousLink) state = ValidationState.setLinkErrors(state, link, errors);
});
editor.setValidationState(state);
}
//#endregion
//#region src/graph-explorer/editor/editorController.tsx
var EditorController = class {
constructor(props) {
this.listener = new EventObserver();
this.source = new EventSource();
this.events = this.source;
this._authoringState = AuthoringState.empty;
this._validationState = ValidationState.empty;
this._temporaryState = TemporaryState.empty;
this._selection = [];
this.cancellation = new Cancellation();
this.onKeyUp = (e) => {
if (e.keyCode === 46 && document.activeElement.localName !== "input") this.removeSelectedElements();
};
const { model, view, ...options } = props;
this.model = model;
this.view = view;
this.options = options;
this.listener.listen(this.events, "changeValidationState", (e) => {
for (const element of this.model.elements) {
const previous = e.previous.elements.get(element.iri);
if (this.validationState.elements.get(element.iri) !== previous) element.redraw();
}
});
this.listener.listen(this.events, "changeAuthoringState", (e) => {
if (this.options.validationApi) validateElements(changedElementsToValidate(e.previous, this), this.options.validationApi, this, this.cancellation.signal);
});
this.view._setElementDecorator((element) => /* @__PURE__ */ React.createElement(ElementDecorator, {
model: element,
view: this.view,
editor: this,
position: element.position
}));
}
_initializePaperComponents(paperArea) {
this.listener.listen(paperArea.events, "pointerUp", (e) => this.onPaperPointerUp(e));
this.listener.listen(this.model.events, "changeCells", () => this.onCellsChanged());
this.listener.listen(this.model.events, "elementEvent", (e) => {
if (e.key === "requestedGroupContent") this.loadGroupContent(e.data.requestedGroupContent.source);
});
this.listener.listen(this.model.events, "loadingStart", () => this.setSpinner({}));
this.listener.listen(this.model.events, "loadingSuccess", () => {
this.setSpinner(void 0);
const widget = /* @__PURE__ */ React.createElement(LinkStateWidget, {
view: this.view,
editor: this
});
this.view.setPaperWidget({
key: "states",
widget,
attachment: WidgetAttachment.OverLinks
});
});
this.listener.listen(this.model.events, "loadingError", ({ error }) => {
const statusText = error ? error.message : void 0;
this.setSpinner({
statusText,
errorOccured: true
});
});
if (!this.options.disableHalo) {
this.configureHalo();
document.addEventListener("keyup", this.onKeyUp);
this.listener.listen(this.view.events, "dispose", () => {
document.removeEventListener("keyup", this.onKeyUp);
});
}
}
get inAuthoringMode() {
return Boolean(this._metadataApi);
}
get metadataApi() {
return this._metadataApi;
}
setMetadataApi(value) {
const previous = this._metadataApi;
if (value === previous) return;
this._metadataApi = value;
if (Boolean(value) !== Boolean(previous)) this.source.trigger("changeMode", { source: this });
}
get authoringState() {
return this._authoringState;
}
setAuthoringState(value) {
if (this._authoringState === value) return;
this.model.history.execute(this.updateAuthoringState(value));
}
updateAuthoringState(state) {
const previous = this._authoringState;
return Command.create("Create or delete entities and links", () => {
this._authoringState = state;
this.source.trigger("changeAuthoringState", {
source: this,
previous
});
return this.updateAuthoringState(previous);
});
}
get validationState() {
return this._validationState;
}
setValidationState(value) {
const previous = this._validationState;
if (value === previous) return;
this._validationState = value;
this.source.trigger("changeValidationState", {
source: this,
previous
});
}
get temporaryState() {
return this._temporaryState;
}
setTemporaryState(value) {
const previous = this._temporaryState;
if (value === previous) return;
this._temporaryState = value;
this.source.trigger("changeTemporaryState", {
source: this,
previous
});
}
get selection() {
return this._selection;
}
setSelection(value) {
const previous = this._selection;
if (previous === value) return;
this._selection = value;
this.source.trigger("changeSelection", {
source: this,
previous
});
}
cancelSelection() {
this.setSelection([]);
}
removeSelectedElements() {
const itemsToRemove = this.selection;
if (itemsToRemove.length === 0) return;
this.cancelSelection();
this.removeItems(itemsToRemove);
}
removeItems(items) {
const batch = this.model.history.startBatch();
const deletedElementIris = /* @__PURE__ */ new Set();
for (const item of items) if (item instanceof Element$1) {
const event = this.authoringState.elements.get(item.iri);
if (event) this.discardChange(event);
this.model.removeElement(item.id);
deletedElementIris.add(item.iri);
} else if (item instanceof Link) {
if (AuthoringState.isNewLink(this.authoringState, item.data)) this.deleteLink(item.data);
}
if (deletedElementIris.size > 0) {
const newState = AuthoringState.deleteNewLinksConnectedToElements(this.authoringState, deletedElementIris);
this.setAuthoringState(newState);
}
batch.store();
}
onPaperPointerUp(event) {
if (this.options.disableHalo) return;
const { sourceEvent, target, triggerAsClick } = event;
if (sourceEvent.ctrlKey || sourceEvent.shiftKey || sourceEvent.metaKey) return;
if (this.dialogTarget && this.dialogType === 1) return;
if (target instanceof Element$1) {
this.setSelection([target]);
target.focus();
} else if (target instanceof Link) this.setSelection([target]);
else if (target instanceof LinkVertex) this.setSelection([target.link]);
else if (!target && triggerAsClick) {
this.setSelection([]);
this.hideDialog();
if (document.activeElement) document.activeElement.blur();
}
}
onCellsChanged() {
if (this.selection.length === 0) return;
const newSelection = this.selection.filter((item) => item instanceof Element$1 ? this.model.getElement(item.id) : item instanceof Link ? this.model.getLinkById(item.id) : false);
if (newSelection.length < this.selection.length) this.setSelection(newSelection);
}
setSpinner(props) {
const widget = props ? /* @__PURE__ */ React.createElement(LoadingWidget, { spinnerProps: props }) : void 0;
this.view.setPaperWidget({
key: LoadingWidget.Key,
widget,
attachment: WidgetAttachment.Viewport
});
}
configureHalo() {
if (this.options.disableHalo) return;
this.listener.listen(this.events, "changeSelection", () => {
const selected = this.selection.length === 1 ? this.selection[0] : void 0;
if (this.dialogTarget && selected !== this.dialogTarget) this.hideDialog();
this.bringSelectedElementsToFront();
this.renderDefaultHalo();
});
this.listener.listen(this.events, "toggleDialog", ({ isOpened: _isOpened }) => {
this.renderDefaultHalo();
});
this.renderDefaultHalo();
}
bringSelectedElementsToFront() {
if (this.selection.length === 0) return;
this.model.reorderElements(makeMoveComparator(this.model.elements, this.selection.filter((item) => item instanceof Element$1), MoveDirection.ToEnd));
}
renderDefaultHalo() {
const selected = this.selection.length === 1 ? this.selection[0] : void 0;
let halo;
if (selected instanceof Element$1) halo = /* @__PURE__ */ React.createElement(Halo, {
editor: this,
metadataApi: this.metadataApi,
target: selected,
onRemove: () => this.removeSelectedElements(),
onExpand: () => {
this.model.history.execute(setElementExpanded(selected, !selected.isExpanded));
},
navigationMenuOpened: this.dialogType === 0,
onToggleNavigationMenu: () => {
if (this.dialogTarget && this.dialogType === 0) this.hideDialog();
else this.showConnectionsMenu(selected);
this.renderDefaultHalo();
},
onAddToFilter: () => selected.addToFilter(),
onEstablishNewLink: (point) => this.startEditing({
target: selected,
mode: EditLayerMode.establishLink,
point
}),
onFollowLink: (element, e) => this.view.onIriClick(element.iri, element, IriClickIntent.JumpToEntity, e)
});
else if (selected instanceof Link) halo = /* @__PURE__ */ React.createElement(HaloLink, {
view: this.view,
editor: this,
metadataApi: this.metadataApi,
target: selected,
onEdit: () => this.showEditLinkForm(selected),
onDelete: () => this.deleteLink(selected.data),
onSourceMove: (point) => this.startEditing({
target: selected,
mode: EditLayerMode.moveLinkSource,
point
}),
onTargetMove: (point) => this.startEditing({
target: selected,
mode: EditLayerMode.moveLinkTarget,
point
}),
onEditLabel: () => this.showEditLinkLabelForm(selected)
});
this.view.setPaperWidget({
key: "halo",
widget: halo,
attachment: WidgetAttachment.OverElements
});
}
showConnectionsMenu(target) {
const dialogType = 0;
const onClose = () => this.hideDialog();
const content = /* @__PURE__ */ React.createElement(ConnectionsMenu, {
view: this.view,
editor: this,
target,
onAddElements: (iris, linkType) => this.onAddElementsInConnectionMenu(iris, target, linkType),
onClose,
suggestProperties: this.options.suggestProperties
});
this.showDialog({
target,
dialogType,
content,
onClose
});
}
showEditEntityForm(target) {
const { propertyEditor } = this.options;
const dialogType = 1;
const onSubmit = (newData) => {
this.hideDialog();
this.changeEntityData(target.data.id, newData);
};
const isIriModified = AuthoringState.isElementWithModifiedIri(this.authoringState, target.data.id);
let modelToEdit;
if (isIriModified) {
const relatedEvent = this.authoringState.elements.get(target.data.id);
modelToEdit = {
...target.data,
id: relatedEvent.newIri
};
} else modelToEdit = target.data;
const onCancel = () => this.hideDialog();
const content = propertyEditor ? propertyEditor({
elementData: target.data,
onSubmit,
onCancel
}) : /* @__PURE__ */ React.createElement(EditEntityForm, {
view: this.view,
entity: modelToEdit,
onApply: onSubmit,
onCancel
});
this.showDialog({
target,
dialogType,
content,
onClose: onCancel
});
}
showEditElementTypeForm({ link, source, target, targetIsNew }) {
const dialogType = 3;
const onCancel = () => {
this.removeTemporaryElement(target);
this.removeTemporaryLink(link);
this.hideDialog();
};
const content = /* @__PURE__ */ React.createElement(EditElementTypeForm, {
editor: this,
view: this.view,
metadataApi: this.metadataApi,
link: link.data,
source: source.data,
target: {
value: target.data,
isNew: targetIsNew
},
onChangeElement: (data) => {
const previous = target.data;
this.setTemporaryState(TemporaryState.deleteElement(this.temporaryState, previous));
target.setData(data);
this.setTemporaryState(TemporaryState.addElement(this.temporaryState, data));
},
onChangeLink: (data) => {
this.removeTemporaryLink(link);
const newLink = makeLinkWithDirection(new Link({
typeId: data.linkTypeId,
sourceId: source.id,
targetId: target.id,
data: {
...data,
sourceId: source.iri,
targetId: target.iri
}
}), data);
link = this.createNewLink({
link: newLink,
temporary: true
});
},
onApply: (elementData, isNewElement, linkData) => {
this.removeTemporaryElement(target);
this.removeTemporaryLink(link);
const batch = this.model.history.startBatch(isNewElement ? "Create new entity" : "Link to entity");
this.model.addElement(target);
if (isNewElement) {
target.setExpanded(true);
this.setAuthoringState(AuthoringState.addElement(this._authoringState, target.data));
} else this.model.requestLinksOfType();
const newLink = makeLinkWithDirection(new Link({
typeId: link.typeId,
sourceId: source.id,
targetId: target.id,
data: {
...link.data,
sourceId: source.iri,
targetId: target.iri
}
}), linkData);
this.createNewLink({ link: newLink });
batch.store();
this.hideDialog();
if (isNewElement) this.showEditEntityForm(target);
},
onCancel
});
this.showDialog({
target,
dialogType,
content,
caption: "Establish New Connection",
onClose: onCancel
});
}
showEditLinkForm(link) {
const dialogType = 2;
const source = this.model.getElement(link.sourceId).data;
const target = this.model.getElement(link.targetId).data;
const onCancel = () => {
if (this.temporaryState.links.has(link.data)) this.removeTemporaryLink(link);
this.hideDialog();
};
const content = /* @__PURE__ */ React.createElement(EditLinkForm, {
editor: this,
view: this.view,
metadataApi: this.metadataApi,
link: link.data,
source,
target,
onChange: (data) => {
if (this.temporaryState.links.has(link.data)) {
this.removeTemporaryLink(link);
const newLink = makeLinkWithDirection(link, data);
this.showEditLinkForm(this.createNewLink({
link: newLink,
temporary: true
}));
}
},
onApply: (data) => {
if (this.temporaryState.links.has(link.data)) {
this.removeTemporaryLink(link);
const newLink = makeLinkWithDirection(link, data);
this.createNewLink({ link: newLink });
} else this.changeLink(link.data, data);
this.hideDialog();
},
onCancel
});
const caption = this.temporaryState.links.has(link.data) ? "Establish New Connection" : "Edit Connection";
this.showDialog({
target: link,
dialogType,
content,
size: {
width: 300,
height: 160
},
caption,
onClose: onCancel
});
}
showEditLinkLabelForm(link) {
const linkType = this.view.model.getLinkType(link.typeId);
const template = this.view.createLinkTemplate(linkType);
const size = {
width: 300,
height: 145
};
const onCancel = () => this.hideDialog();
this.showDialog({
target: link,
dialogType: 4,
content: /* @__PURE__ */ React.createElement(EditLinkLabelForm, {
view: this.view,
link,
onApply: (label) => {
template.setLinkLabel(link, label);
this.hideDialog();
},
onCancel
}),
size,
caption: "Edit Link Label",
offset: {
x: 25,
y: -size.height / 2
},
calculatePosition: () => {
const { x, y, width, height } = link.labelBounds;
return {
x: x + width,
y: y + height / 2
};
},
onClose: onCancel
});
}
showDialog(params) {
const { target, dialogType, content, size, caption, offset, calculatePosition, onClose } = params;
this.dialogTarget = target;
this.dialogType = dialogType;
const dialog = /* @__PURE__ */ React.createElement(Dialog, {
view: this.view,
target,
size,
caption,
offset,
calculatePosition,
onClose
}, content);
this.view.setPaperWidget({
key: "dialog",
widget: dialog,
attachment: WidgetAttachment.OverElements
});
this.source.trigger("toggleDialog", { isOpened: true });
}
hideDialog() {
if (this.dialogTarget) {
const isTemporaryElement = this.dialogTarget instanceof Element$1 && this.temporaryState.elements.has(this.dialogTarget.iri);
const isTemporaryLink = this.dialogTarget instanceof Link && this.temporaryState.links.has(this.dialogTarget.data);
if (isTemporaryElement || isTemporaryLink) this.resetTemporaryState();
this.dialogType = void 0;
this.dialogTarget = void 0;
this.view.setPaperWidget({
key: "dialog",
widget: void 0,
attachment: WidgetAttachment.OverElements
});
this.source.trigger("toggleDialog", { isOpened: false });
}
}
onDragDrop(dragged, paperPosition) {
const batch = this.model.history.startBatch("Drag and drop onto diagram");
const placedElements = placeElements(this.view, dragged, paperPosition);
const irisToLoad = placedElements.map((elem) => elem.iri);
batch.history.execute(requestElementData(this.model, irisToLoad));
batch.history.execute(restoreLinksBetweenElements(this.model));
batch.store();
if (placedElements.length > 0) placedElements[placedElements.length - 1].focus();
this.setSelection(placedElements);
this.source.trigger("addElements", { elements: placedElements });
}
onAddElementsInConnectionMenu(elementIris, targetElement, linkType) {
const batch = this.view.model.history.startBatch();
const elements = elementIris.map((iri) => this.model.createElement(iri));
this.view.performSyncUpdate();
placeElementsAround({
model: this.model,
elements,
targetElement,
prefferedLinksLength: 300
}).then(() => {
this.source.trigger("addElements", { elements });
});
if (linkType && !linkType.visible) batch.history.execute(changeLinkTypeVisibility({
linkType,
visible: true,
showLabel: true,
preventLoading: true
}));
batch.history.execute(requestElementData(this.model, elementIris));
batch.history.execute(restoreLinksBetweenElements(this.model));
batch.store();
}
loadGroupContent(element) {
return this.model.loadEmbeddedElements(element.iri).then((models) => {
const batch = this.model.history.startBatch();
const elementIris = Object.keys(models);
elementIris.map((key) => this.model.createElement(models[key], element.id));
batch.discard();
return Promise.all([this.model.requestElementData(elementIris), this.model.requestLinksOfType()]).then(() => {
this.view.performSyncUpdate();
applyLayout(this.model, forceLayout({
model: this.model,
group: element.id
}));
this.model.triggerChangeGroupContent(element.id);
});
});
}
createNewEntity({ elementModel, temporary }) {
const batch = this.model.history.startBatch("Create new entity");
const element = this.model.createElement(elementModel);
element.setExpanded(true);
if (temporary) {
this.setTemporaryState(TemporaryState.addElement(this.temporaryState, element.data));
batch.discard();
} else {
this.setAuthoringState(AuthoringState.addElement(this._authoringState, element.data));
batch.store();
}
return element;
}
changeEntityData(targetIri, newData) {
const elements = this.model.elements.filter((el) => el.iri === targetIri);
if (elements.length === 0) return;
const oldData = elements[0].data;
const batch = this.model.history.startBatch("Edit entity");
const newState = AuthoringState.changeElement(this._authoringState, oldData, newData);
const event = newState.elements.get(targetIri) || newState.elements.get(newData.id);
this.model.history.execute(setElementData(this.model, targetIri, event.after));
this.setAuthoringState(newState);
batch.store();
}
deleteEntity(elementIri) {
const state = this.authoringState;
const elements = this.model.elements.filter((el) => el.iri === elementIri);
if (elements.length === 0) return;
const batch = this.model.history.startBatch("Delete entity");
const model = elements[0].data;
const event = state.elements.get(elementIri);
const linksToRemove = /* @__PURE__ */ new Set();
for (const element of elements) for (const link of element.links) if (link.data && AuthoringState.isNewLink(state, link.data)) linksToRemove.add(link.id);
linksToRemove.forEach((linkId) => this.model.removeLink(linkId));
if (event) this.discardChange(event);
this.setAuthoringState(AuthoringState.deleteElement(state, model));
batch.store();
}
createNewLink(params) {
const { link: base, temporary } = params;
if (this.model.findLink(base.typeId, base.sourceId, base.targetId)) throw Error("The link already exists");
const batch = this.model.history.startBatch("Create new link");
const addedLink = this.model.addLink(base);
if (!temporary) this.model.createLinks(base.data);
if (this.model.links.filter((link) => sameLink(link.data, base.data)).length > 0) if (temporary) {
this.setTemporaryState(TemporaryState.addLink(this.temporaryState, base.data));
batch.discard();
} else {
this.setAuthoringState(AuthoringState.addLink(this._authoringState, base.data));
batch.store();
}
else batch.discard();
return addedLink;
}
changeLink(oldData, newData) {
const batch = this.model.history.startBatch("Change link");
if (sameLink(oldData, newData)) {
this.model.history.execute(setLinkData(this.model, oldData, newData));
this.setAuthoringState(AuthoringState.changeLink(this._authoringState, oldData, newData));
} else {
let newState = this._authoringState;
newState = AuthoringState.deleteLink(newState, oldData);
newState = AuthoringState.addLink(newState, newData);
if (AuthoringState.isNewLink(this._authoringState, oldData)) this.model.links.filter((link) => sameLink(link.data, oldData)).forEach((link) => this.model.removeLink(link.id));
this.model.createLinks(newData);
this.setAuthoringState(newState);
}
batch.store();
}
moveLinkSource(params) {
const { link, newSource } = params;
const batch = this.model.history.startBatch("Move link to another element");
this.changeLink(link.data, {
...link.data,
sourceId: newSource.iri
});
const newLink = this.model.findLink(link.typeId, newSource.id, link.targetId);
newLink.setVertices(link.vertices);
batch.store();
return newLink;
}
moveLinkTarget(params) {
const { link, newTarget } = params;
const batch = this.model.history.startBatch("Move link to another element");
this.changeLink(link.data, {
...link.data,
targetId: newTarget.iri
});
const newLink = this.model.findLink(link.typeId, link.sourceId, newTarget.id);
newLink.setVertices(link.vertices);
batch.store();
return newLink;
}
deleteLink(model) {
const state = this.authoringState;
if (AuthoringState.isDeletedLink(state, model)) return;
const batch = this.model.history.startBatch("Delete link");
const newState = AuthoringState.deleteLink(state, model);
if (AuthoringState.isNewLink(state, model)) this.model.links.filter(({ data }) => sameLink(data, model)).forEach((link) => this.model.removeLink(link.id));
this.setAuthoringState(newState);
batch.store();
}
startEditing(params) {
const { target, mode, point } = params;
const onFinishEditing = () => {
this.view.setPaperWidget({
key: "editLayer",
widget: void 0,
attachment: WidgetAttachment.OverElements
});
};
const editLayer = /* @__PURE__ */ React.createElement(EditLayer, {
view: this.view,
editor: this,
metadataApi: this.metadataApi,
mode,
target,
point,
onFinishEditing
});
this.view.setPaperWidget({
key: "editLayer",
widget: editLayer,
attachment: WidgetAttachment.OverElements
});
}
resetTemporaryState() {
if (this.temporaryState.elements.size) this.model.elements.forEach((element) => {
if (this.temporaryState.elements.has(element.iri)) this.removeTemporaryElement(element);
});
if (this.temporaryState.links.size) this.model.links.forEach((link) => {
if (this.temporaryState.links.get(link.data)) this.removeTemporaryLink(link);
});
}
removeTemporaryElement(element) {
const batch = this.model.history.startBatch();
this.model.removeElement(element.id);
batch.discard();
this.setTemporaryState(TemporaryState.deleteElement(this.temporaryState, element.data));
}
removeTemporaryLink(link) {
this.model.removeLink(link.id);
this.setTemporaryState(TemporaryState.deleteLink(this.temporaryState, link.data));
}
discardChange(event) {
const newState = AuthoringState.discard(this._authoringState, event);
if (newState === this._authoringState) return;
const batch = this.model.history.startBatch("Discard change");
if (event.type === AuthoringKind.ChangeElement) if (event.deleted) {} else if (event.before) this.model.history.execute(setElementData(this.model, event.after.id, event.before));
else this.model.elements.filter((el) => el.iri === event.after.id).forEach((el) => this.model.removeElement(el.id));
else if (event.type === AuthoringKind.ChangeLink) if (event.deleted) {} else if (event.before) this.model.history.execute(setLinkData(this.model, event.after, event.before));
else this.model.links.filter(({ data }) => sameLink(data, event.after)).forEach((link) => this.model.removeLink(link.id));
this.setAuthoringState(newState);
batch.store();
}
};
var LoadingWidget = class extends React.Component {
static {
this.Key = "loadingWidget";
}
render() {
const { spinnerProps, paperArea } = this.props;
const areaMetrics = paperArea.getAreaMetrics();
const paneWidth = areaMetrics.clientWidth;
const paneHeight = areaMetrics.clientHeight;
const position = {
x: spinnerProps.statusText ? paneWidth / 3 : paneWidth / 2,
y: paneHeight / 2
};
return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-loading-widget" }, /* @__PURE__ */ React.createElement("svg", {
width: paneWidth,
height: paneHeight
}, /* @__PURE__ */ React.createElement(Spinner, {
position,
...spinnerProps
})));
}
};
function placeElements(view, dragged, position) {
const elements = dragged.map((item) => view.model.createElement(item));
for (const element of elements) element.setPosition(position);
view.performSyncUpdate();
let { x, y } = position;
let isFirst = true;
for (const element of elements) {
let { width, height } = boundsOf(element);
if (width === 0) width = 100;
if (height === 0) height = 50;
if (isFirst) {
isFirst = false;
x -= width / 2;
y -= height / 2;
}
element.setPosition({
x,
y
});
y += height + 20;
}
return elements;
}
function makeLinkWithDirection(original, data) {
if (!(data.sourceId === original.data.sourceId || data.sourceId === original.data.targetId)) throw new Error("New link source IRI is unrelated to original link");
if (!(data.targetId === original.data.sourceId || data.targetId === original.data.targetId)) throw new Error("New link target IRI is unrelated to original link");
const sourceId = data.sourceId === original.data.sourceId ? original.sourceId : original.targetId;
const targetId = data.targetId === original.data.targetId ? original.targetId : original.sourceId;
return new Link({
typeId: data.linkTypeId,
sourceId,
targetId,
data
});
}
//#endregion
//#region src/graph-explorer/workspace/toolbar.tsx
var CLASS_NAME$4 = "graph-explorer-toolbar";
var CLEAR_ALL_ARM_TIMEOUT = 4e3;
var DefaultToolbar = class extends React.Component {
constructor(..._args) {
super(..._args);
this.state = {};
this.onClearAllClick = () => {
if (this.state.clearAllArmed) {
this.clearClearAllTimeout();
this.setState({ clearAllArmed: false });
this.props.onClearAll();
} else {
this.clearClearAllTimeout();
this.clearAllTimeout = setTimeout(() => {
this.setState({ clearAllArmed: false });
}, CLEAR_ALL_ARM_TIMEOUT);
this.setState({ clearAllArmed: true });
}
};
this.onChangeLanguage = (event) => {
const value = event.currentTarget.value;
this.props.onChangeLanguage(value);
};
this.onExportSVG = () => {
this.props.onExportSVG();
};
this.onExportPNG = () => {
this.props.onExportPNG();
};
}
componentWillUnmount() {
this.clearClearAllTimeout();
}
clearClearAllTimeout() {
if (this.clearAllTimeout !== void 0) {
clearTimeout(this.clearAllTimeout);
this.clearAllTimeout = void 0;
}
}
renderSaveDiagramButton() {
if (!this.props.onSaveDiagram) return null;
return /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "saveDiagramButton graph-explorer-btn graph-explorer-btn-primary",
disabled: this.props.canSaveDiagram === false,
onClick: this.props.onSaveDiagram
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-floppy-o",
"aria-hidden": "true"
}), " Save diagram");
}
renderPersistAuthoredChangesButton() {
if (!this.props.onPersistChanges) return null;
return /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "saveDiagramButton graph-explorer-btn graph-explorer-btn-success",
disabled: this.props.canPersistChanges === false,
onClick: this.props.onPersistChanges
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-floppy-o",
"aria-hidden": "true"
}), " Save data");
}
renderLanguages() {
const { selectedLanguage, languages } = this.props;
if (languages.length <= 1) return null;
return /* @__PURE__ */ React.createElement("span", { className: `graph-explorer-btn-group ${CLASS_NAME$4}__language-selector` }, /* @__PURE__ */ React.createElement("label", { className: "graph-explorer-label" }, /* @__PURE__ */ React.createElement("span", null, "Data Language")), /* @__PURE__ */ React.createElement("select", {
value: selectedLanguage,
onChange: this.onChangeLanguage
}, languages.map(({ code, label }) => /* @__PURE__ */ React.createElement("option", {
key: code,
value: code
}, label))));
}
render() {
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$4 }, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-btn-group graph-explorer-btn-group-sm" }, this.renderSaveDiagramButton(), this.renderPersistAuthoredChangesButton(), this.props.onClearAll ? /* @__PURE__ */ React.createElement("button", {
type: "button",
className: this.state.clearAllArmed ? "graph-explorer-btn graph-explorer-btn-danger" : "graph-explorer-btn graph-explorer-btn-default",
title: this.state.clearAllArmed ? "Click again to confirm" : "Clear All",
onClick: this.onClearAllClick,
onBlur: () => this.setState({ clearAllArmed: false })
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-trash",
"aria-hidden": "true"
}), "\xA0", this.state.clearAllArmed ? "Are you sure?" : "Clear All") : null, /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Force layout",
onClick: this.props.onForceLayout
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-sitemap",
"aria-hidden": "true"
}), " Layout"), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Zoom In",
onClick: this.props.onZoomIn
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-search-plus",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Zoom Out",
onClick: this.props.onZoomOut
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-search-minus",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Fit to Screen",
onClick: this.props.onZoomToFit
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-arrows-alt",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Export diagram as PNG",
onClick: this.onExportPNG
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-picture-o",
"aria-hidden": "true"
}), " PNG"), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Export diagram as SVG",
onClick: this.onExportSVG
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-picture-o",
"aria-hidden": "true"
}), " SVG"), /* @__PURE__ */ React.createElement("button", {
type: "button",
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Print diagram",
onClick: this.props.onPrint
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-print",
"aria-hidden": "true"
})), this.renderLanguages()));
}
};
//#endregion
//#region src/graph-explorer/widgets/navigator.tsx
var CLASS_NAME$3 = "graph-explorer-navigator";
var MIN_SCALE = .25;
var Navigator = class extends React.Component {
static {
this.defaultProps = {
width: 300,
height: 160,
scalePadding: .2,
expanded: true
};
}
constructor(props, context) {
super(props, context);
this.delayedRedraw = new Debouncer();
this.listener = new EventObserver();
this.scheduleRedraw = () => {
if (this.state.expanded) this.delayedRedraw.call(this.draw);
};
this.draw = () => {
const { paperTransform: pt, width, height } = this.props;
this.calculateTransform();
const ctx = this.canvas.getContext("2d");
ctx.fillStyle = "#EEEEEE";
ctx.clearRect(0, 0, width, height);
ctx.fillRect(0, 0, width, height);
const paneStart = paneTopLeft(pt);
const paneSize = totalPaneSize(pt);
const paneEnd = {
x: paneStart.x + paneSize.x,
y: paneStart.y + paneSize.y
};
const start = canvasFromPaneCoords(paneStart, pt, this.transform);
const end = canvasFromPaneCoords(paneEnd, pt, this.transform);
ctx.fillStyle = "white";
ctx.fillRect(start.x, start.y, end.x - start.x, end.y - start.y);
ctx.save();
this.drawElements(ctx);
this.drawViewport(ctx);
ctx.restore();
};
this.onDragViewport = (e) => {
e.preventDefault();
if (this.isDraggingViewport) {
const paper = paperFromCanvasCoords(this.canvasFromPageCoords(e.pageX, e.pageY), this.props.paperTransform, this.transform);
this.props.paperArea.centerTo(paper);
}
};
this.onMouseUp = () => {
this.stopDraggingViewport();
};
this.onWheel = (e) => {
e.preventDefault();
const delta = Math.max(-1, Math.min(1, e.deltaY || e.deltaX));
this.props.paperArea.zoomBy(-delta * .1);
};
this.onToggleClick = () => {
this.setState((state) => ({ expanded: !state.expanded }), this.scheduleRedraw);
};
this.state = { expanded: this.props.expanded };
}
componentDidMount() {
const { view, paperArea } = this.props;
this.listener.listen(view.events, "changeHighlight", this.scheduleRedraw);
this.listener.listen(view.model.events, "changeCells", this.scheduleRedraw);
this.listener.listen(view.model.events, "elementEvent", this.scheduleRedraw);
this.listener.listen(paperArea.events, "pointerMove", this.scheduleRedraw);
this.listener.listen(paperArea.events, "scroll", this.scheduleRedraw);
}
shouldComponentUpdate(nextProps, nextState) {
return nextState !== this.state;
}
componentWillUnmount() {
this.delayedRedraw.dispose();
this.listener.stopListening();
this.stopDraggingViewport();
}
drawElements(ctx) {
const { view, paperTransform: pt } = this.props;
view.model.elements.forEach((element) => {
const { position, size } = element;
ctx.fillStyle = this.chooseElementColor(element);
const { x: x1, y: y1 } = canvasFromPaperCoords(position, pt, this.transform);
const { x: x2, y: y2 } = canvasFromPaperCoords({
x: position.x + size.width,
y: position.y + size.height
}, pt, this.transform);
ctx.fillRect(x1, y1, x2 - x1, y2 - y1);
});
}
chooseElementColor(element) {
const { view } = this.props;
if (view.highlighter && !view.highlighter(element)) return "lightgray";
const { color: { h, c, l } } = view.getTypeStyle(element.data.types);
return hcl(h, c, l).toString();
}
drawViewport(ctx) {
const { paperArea, paperTransform: pt, width, height } = this.props;
ctx.strokeStyle = "#337ab7";
ctx.lineWidth = 2;
const { clientWidth, clientHeight } = paperArea.getAreaMetrics();
const viewportStart = paperArea.clientToScrollablePaneCoords(0, 0);
const viewportEnd = paperArea.clientToScrollablePaneCoords(clientWidth, clientHeight);
const { x: x1, y: y1 } = canvasFromPaneCoords(viewportStart, pt, this.transform);
const { x: x2, y: y2 } = canvasFromPaneCoords(viewportEnd, pt, this.transform);
ctx.strokeRect(x1, y1, x2 - x1, y2 - y1);
ctx.beginPath();
if (x1 < 0) {
ctx.moveTo(0, y1);
ctx.lineTo(0, y2);
}
if (y1 < 0) {
ctx.moveTo(x1, 0);
ctx.lineTo(x2, 0);
}
if (x2 > width) {
ctx.moveTo(width, y1);
ctx.lineTo(width, y2);
}
if (y2 > height) {
ctx.moveTo(x1, height);
ctx.lineTo(x2, height);
}
ctx.lineWidth = 4;
ctx.strokeStyle = "#a0d2ff";
ctx.setLineDash([5, 5]);
ctx.stroke();
}
calculateTransform() {
const { paperArea, paperTransform: pt, width, height, scalePadding } = this.props;
const box = paperArea.getContentFittingBox();
const displayPadding = {
x: Math.max(box.width, width / MIN_SCALE) * scalePadding,
y: Math.max(box.height, height / MIN_SCALE) * scalePadding
};
const displayStart = paneFromPaperCoords({
x: box.x - displayPadding.x,
y: box.y - displayPadding.y
}, pt);
const displayEnd = paneFromPaperCoords({
x: box.x + box.width + displayPadding.x,
y: box.y + box.height + displayPadding.y
}, pt);
const displaySize = {
x: displayEnd.x - displayStart.x,
y: displayEnd.y - displayStart.y
};
const scale = Math.min(width / displaySize.x, height / displaySize.y);
const canvasOffset = {
x: (width - displaySize.x * scale) / 2,
y: (height - displaySize.y * scale) / 2
};
this.transform = {
scale,
canvasOffset,
paneOffset: displayStart
};
}
canvasFromPageCoords(pageX, pageY) {
const { top, left } = this.canvas.getBoundingClientRect();
return {
x: pageX - left - window.pageXOffset,
y: pageY - top - window.pageYOffset
};
}
render() {
const { width, height } = this.props;
const { expanded } = this.state;
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$3} ${CLASS_NAME$3}--${expanded ? "expanded" : "collapsed"}`,
style: expanded ? {
width,
height
} : void 0
}, /* @__PURE__ */ React.createElement("canvas", {
ref: (canvas) => {
this.canvas = canvas;
},
width,
height,
onMouseDown: (e) => {
this.startDragginViewport();
this.onDragViewport(e);
},
onWheel: this.onWheel
}), /* @__PURE__ */ React.createElement("button", {
className: `${CLASS_NAME$3}__toggle`,
title: expanded ? "Collapse navigator" : "Expand navigator",
onClick: this.onToggleClick
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$3}__toggle-icon` })));
}
startDragginViewport() {
if (!this.isDraggingViewport) {
this.isDraggingViewport = true;
document.addEventListener("mousemove", this.onDragViewport);
document.addEventListener("mouseup", this.onMouseUp);
}
}
stopDraggingViewport() {
if (this.isDraggingViewport) {
this.isDraggingViewport = false;
document.removeEventListener("mousemove", this.onDragViewport);
document.removeEventListener("mouseup", this.onMouseUp);
}
}
};
function canvasFromPaneCoords(pane, pt, nt) {
return {
x: nt.canvasOffset.x + (pane.x - nt.paneOffset.x) * nt.scale,
y: nt.canvasOffset.y + (pane.y - nt.paneOffset.y) * nt.scale
};
}
function canvasFromPaperCoords(paper, pt, nt) {
return canvasFromPaneCoords(paneFromPaperCoords(paper, pt), pt, nt);
}
function paperFromCanvasCoords(canvas, pt, nt) {
return paperFromPaneCoords({
x: nt.paneOffset.x + (canvas.x - nt.canvasOffset.x) / nt.scale,
y: nt.paneOffset.y + (canvas.y - nt.canvasOffset.y) / nt.scale
}, pt);
}
//#endregion
//#region src/graph-explorer/widgets/classTree/treeModel.ts
var TreeNode = { setDerived: (node, derived) => ({
...node,
derived
}) };
//#endregion
//#region images/tree/expand-toggle.svg
var expand_toggle_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='128'%20height='128'%20viewbox='0%200%20128%20128'%3e%3cellipse%20stroke='%23000'%20ry='60'%20rx='60'%20cy='64'%20cx='64'%20stroke-width='5'%20fill='none'/%3e%3cline%20y2='63.4375'%20x2='104.5'%20y1='63.4375'%20x1='22.5'%20stroke-width='5'%20stroke='%23000'%20fill='none'/%3e%3cline%20y2='102.444349'%20x2='63.5'%20y1='29.4375'%20x1='63.5'%20stroke-width='5'%20stroke='%23000'%20fill='none'/%3e%3c/svg%3e";
//#endregion
//#region images/tree/collapse-toggle.svg
var collapse_toggle_default = "data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='128'%20height='128'%20viewbox='0%200%20128%20128'%3e%3cellipse%20stroke='%23000'%20ry='60'%20rx='60'%20cy='64'%20cx='64'%20stroke-width='5'%20fill='none'/%3e%3cline%20y2='63.4375'%20x2='104.5'%20y1='63.4375'%20x1='22.5'%20stroke-width='5'%20stroke='%23000'%20fill='none'/%3e%3c/svg%3e";
//#endregion
//#region images/tree/leaf-default.svg
var leaf_default_default = "data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3csvg%20xmlns:svg='http://www.w3.org/2000/svg'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'%20xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'%20width='128'%20height='128'%20viewbox='0%200%20128%20128'%20version='1.1'%20id='svg4507'%20sodipodi:docname='tree-leaf.svg'%20inkscape:version='0.92.1%20r15371'%3e%3cdefs%20id='defs4511'%20/%3e%3csodipodi:namedview%20pagecolor='%23ffffff'%20bordercolor='%23666666'%20borderopacity='1'%20objecttolerance='10'%20gridtolerance='10'%20guidetolerance='10'%20inkscape:pageopacity='0'%20inkscape:pageshadow='2'%20inkscape:window-width='1920'%20inkscape:window-height='1017'%20id='namedview4509'%20showgrid='false'%20inkscape:zoom='0.90509668'%20inkscape:cx='-272.6847'%20inkscape:cy='-5.8596487'%20inkscape:window-x='-8'%20inkscape:window-y='-8'%20inkscape:window-maximized='1'%20inkscape:current-layer='svg4507'%20/%3e%3cg%20id='g4505'%3e%3ccircle%20id='svg_1'%20cy='64'%20cx='64'%20stroke-opacity='null'%20style='fill:none;stroke:%231573b0;stroke-width:5'%20r='60'%20/%3e%3ccircle%20id='svg_3'%20cy='64'%20cx='64'%20stroke-opacity='null'%20style='fill:%231573b0;stroke:%231573b0;stroke-width:5.71026325'%20r='37'%20/%3e%3c/g%3e%3c/svg%3e";
//#endregion
//#region images/tree/leaf-folder.svg
var leaf_folder_default = "data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'%20standalone='no'?%3e%3csvg%20xmlns:svg='http://www.w3.org/2000/svg'%20xmlns='http://www.w3.org/2000/svg'%20xmlns:sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd'%20xmlns:inkscape='http://www.inkscape.org/namespaces/inkscape'%20width='128'%20height='128'%20viewbox='0%200%20128%20128'%20version='1.1'%20id='svg5088'%20sodipodi:docname='tree-leaf-folder.svg'%20inkscape:version='0.92.1%20r15371'%3e%3cdefs%20id='defs5092'%20/%3e%3csodipodi:namedview%20pagecolor='%23ffffff'%20bordercolor='%23666666'%20borderopacity='1'%20objecttolerance='10'%20gridtolerance='10'%20guidetolerance='10'%20inkscape:pageopacity='0'%20inkscape:pageshadow='2'%20inkscape:window-width='1920'%20inkscape:window-height='1017'%20id='namedview5090'%20showgrid='false'%20inkscape:zoom='2.6074563'%20inkscape:cx='128.74614'%20inkscape:cy='30.905472'%20inkscape:window-x='-8'%20inkscape:window-y='-8'%20inkscape:window-maximized='1'%20inkscape:current-layer='svg5088'%20/%3e%3cg%20id='g5086'%3e%3ccircle%20id='svg_1'%20cy='64'%20cx='64'%20style='fill:none;stroke:%231573b0;stroke-width:5'%20r='60'%20/%3e%3ccircle%20id='svg_3'%20cy='64'%20cx='64'%20stroke-opacity='null'%20style='fill:%231573b0;stroke:%231573b0;stroke-width:5'%20r='37'%20/%3e%3cpath%20id='svg_5'%20d='m%20127.2258,86.546643%20c%20-0.0105,-5.971065%20-4.72314,-10.809034%20-10.54009,-10.817147%20v%20-0.0027%20H%2084.380152%20c%20-1.33102,-4.386353%20-5.300357,-7.593637%20-10.028771,-7.60175%20h%20-7.577587%20c%20-5.819586,0.0054%20-10.534822,4.846082%20-10.545365,10.817147%20v%2038.368417%20c%200.01054,5.96565%204.725778,10.80362%2010.545365,10.81444%20h%2049.914556%20c%205.81167,-0.0108%2010.52955,-4.84879%2010.54009,-10.81444%20V%2086.546643%20Z'%20inkscape:connector-curvature='0'%20style='fill:%230f5376'%20sodipodi:nodetypes='ccccccccccccc'%20/%3e%3c/g%3e%3c/svg%3e";
//#endregion
//#region src/graph-explorer/widgets/classTree/leaf.tsx
var LEAF_CLASS = "graph-explorer-class-leaf";
var Leaf = class extends React.Component {
constructor(props) {
super(props);
this.onClick = (e) => {
e.preventDefault();
const { node, onSelect } = this.props;
onSelect(node);
};
this.toggle = () => {
this.setState((state) => ({ expanded: !state.expanded }));
};
this.onClickCreate = () => {
this.props.onClickCreate(this.props.node);
};
this.onDragCreate = (e) => {
e.dataTransfer.setData("text", "");
this.props.onDragCreate(this.props.node);
};
this.state = { expanded: Boolean(this.props.searchText) };
}
componentDidUpdate(nextProps) {
if (this.props.searchText !== nextProps.searchText) this.setState({ expanded: Boolean(nextProps.searchText) });
}
render() {
const { node, ...otherProps } = this.props;
const { view, selectedNode, searchText, creatableClasses } = otherProps;
const { expanded } = this.state;
let toggleIcon;
if (node.derived.length > 0) toggleIcon = expanded ? collapse_toggle_default : expand_toggle_default;
let { icon } = view.getTypeStyle([node.model.id]);
if (!icon) icon = node.derived.length === 0 ? leaf_default_default : leaf_folder_default;
let bodyClass = `${LEAF_CLASS}__body`;
if (selectedNode && selectedNode.model === node.model) bodyClass += ` ${LEAF_CLASS}__body--selected`;
const label = highlightSubstring(node.label, searchText, { className: `${LEAF_CLASS}__highlighted-term` });
return /* @__PURE__ */ React.createElement("div", {
className: LEAF_CLASS,
role: "tree-item"
}, /* @__PURE__ */ React.createElement("div", { className: `${LEAF_CLASS}__row` }, /* @__PURE__ */ React.createElement("div", {
className: `${LEAF_CLASS}__toggle`,
onClick: this.toggle,
role: "button"
}, toggleIcon ? /* @__PURE__ */ React.createElement("img", {
className: `${LEAF_CLASS}__toggle-icon`,
src: toggleIcon
}) : null), /* @__PURE__ */ React.createElement("a", {
className: bodyClass,
href: node.model.id,
onClick: this.onClick
}, /* @__PURE__ */ React.createElement("div", { className: `${LEAF_CLASS}__icon-container` }, /* @__PURE__ */ React.createElement("img", {
className: `${LEAF_CLASS}__icon`,
src: icon
})), /* @__PURE__ */ React.createElement("span", { className: `${LEAF_CLASS}__label` }, label), node.model.count ? /* @__PURE__ */ React.createElement("span", { className: `${LEAF_CLASS}__count graph-explorer-badge` }, node.model.count) : null), creatableClasses.get(node.model.id) ? /* @__PURE__ */ React.createElement("div", { className: `${LEAF_CLASS}__create graph-explorer-btn-group graph-explorer-btn-group-xs` }, /* @__PURE__ */ React.createElement("button", {
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Click or drag to create new entity of this type",
draggable: true,
onClick: this.onClickCreate,
onDragStart: this.onDragCreate
}, "+")) : null), expanded && node.derived.length > 0 ? /* @__PURE__ */ React.createElement(Forest, {
className: `${LEAF_CLASS}__children`,
nodes: node.derived,
...otherProps
}) : null);
}
};
var Forest = class extends React.Component {
render() {
const { nodes, className, ...otherProps } = this.props;
return /* @__PURE__ */ React.createElement("div", {
className,
role: "tree"
}, nodes.map((node) => /* @__PURE__ */ React.createElement(Leaf, {
key: `node-${node.model.id}`,
node,
...otherProps
})));
}
};
//#endregion
//#region src/graph-explorer/widgets/classTree/classTree.tsx
var CLASS_NAME$2 = "graph-explorer-class-tree";
var MIN_TERM_LENGTH = 3;
var ClassTree = class extends React.Component {
constructor(props) {
super(props);
this.listener = new EventObserver();
this.delayedClassUpdate = new Debouncer();
this.delayedSearch = new Debouncer(200);
this.loadClassesOperation = new Cancellation();
this.refreshOperation = new Cancellation();
this.onSearchTextChange = (e) => {
const requestedSearchText = e.currentTarget.value;
this.setState({ requestedSearchText });
this.delayedSearch.call(this.performSearch);
};
this.performSearch = () => {
const { requestedSearchText } = this.state;
const requested = normalizeSearchText(requestedSearchText);
if (requested === this.state.appliedSearchText) return;
const appliedSearchText = requested.length < MIN_TERM_LENGTH ? void 0 : requested;
this.setState((state) => applyFilters({
...state,
appliedSearchText
}));
};
this.onShowOnlyCreatableChange = (_e) => {
this.setState((state) => applyFilters({
...state,
showOnlyConstructible: !state.showOnlyConstructible
}));
};
this.onSelectNode = (node) => {
const { onClassSelected } = this.props;
this.setState({ selectedNode: node });
onClassSelected(node.model.id);
};
this.onCreateInstance = (node) => {
const { onCreateInstance } = this.props;
onCreateInstance(node.model.id);
};
this.onDragCreate = (node) => {
const { view, onCreateInstance } = this.props;
view.setHandlerForNextDropOnPaper((e) => {
onCreateInstance(node.model.id, e.paperPosition);
});
};
this.refreshClassTree = () => {
const cancellation = new Cancellation();
const { editor } = this.props;
this.refreshOperation.abort();
this.refreshOperation = cancellation;
this.setState((state, props) => {
if (!this.classTree) return { refreshingState: ProgressState.none };
let refreshingState = ProgressState.none;
if (editor.inAuthoringMode) {
const newIris = getNewClassIris(state.constructibleClasses, this.classTree);
if (newIris.size > 0) {
refreshingState = ProgressState.loading;
this.queryCreatableTypes(newIris, cancellation.signal);
}
}
const roots = createRoots(this.classTree, props.view);
return applyFilters({
...state,
roots: sortTree(roots),
refreshingState
});
});
};
this.state = {
refreshingState: ProgressState.none,
roots: [],
filteredRoots: [],
requestedSearchText: "",
appliedSearchText: "",
constructibleClasses: /* @__PURE__ */ new Map(),
showOnlyConstructible: false
};
}
render() {
const { view, editor } = this.props;
const { refreshingState, requestedSearchText, appliedSearchText, filteredRoots, selectedNode, constructibleClasses, showOnlyConstructible } = this.state;
const normalizedSearchText = normalizeSearchText(requestedSearchText);
const searchText = appliedSearchText ? normalizedSearchText : void 0;
return /* @__PURE__ */ React.createElement("div", { className: CLASS_NAME$2 }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$2}__filter` }, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$2}__filter-group` }, /* @__PURE__ */ React.createElement("input", {
type: "text",
className: "search-input graph-explorer-form-control",
placeholder: "Search for...",
value: this.state.requestedSearchText,
onChange: this.onSearchTextChange
}), editor.inAuthoringMode ? /* @__PURE__ */ React.createElement("label", { className: `${CLASS_NAME$2}__only-creatable` }, /* @__PURE__ */ React.createElement("input", {
type: "checkbox",
checked: showOnlyConstructible,
onChange: this.onShowOnlyCreatableChange
}), " ", "Show only constructible") : null)), /* @__PURE__ */ React.createElement(ProgressBar, { state: refreshingState }), this.classTree ? /* @__PURE__ */ React.createElement(Forest, {
className: `${CLASS_NAME$2}__tree graph-explorer-scrollable`,
view,
nodes: filteredRoots,
searchText,
selectedNode,
onSelect: this.onSelectNode,
creatableClasses: constructibleClasses,
onClickCreate: this.onCreateInstance,
onDragCreate: this.onDragCreate
}) : /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME$2}__spinner` }, /* @__PURE__ */ React.createElement(HtmlSpinner, {
width: 30,
height: 30
})));
}
componentDidMount() {
const { view, editor } = this.props;
this.listener.listen(view.events, "changeLanguage", () => this.refreshClassTree());
this.listener.listen(editor.model.events, "loadingStart", () => {
this.initClassTree();
});
this.listener.listen(editor.model.events, "classEvent", ({ data }) => {
if (data.changeLabel || data.changeCount) this.delayedClassUpdate.call(this.refreshClassTree);
});
this.initClassTree();
}
componentWillUnmount() {
this.listener.stopListening();
this.delayedClassUpdate.dispose();
this.delayedSearch.dispose();
this.loadClassesOperation.abort();
this.refreshOperation.abort();
}
async initClassTree() {
if (this.dataProvider !== this.props.editor.model.dataProvider) {
this.dataProvider = this.props.editor.model.dataProvider;
this.classTree = void 0;
const cancellation = new Cancellation();
this.loadClassesOperation.abort();
this.loadClassesOperation = cancellation;
const classes = await this.dataProvider.classTree();
if (cancellation.signal.aborted) return;
this.setClassTree(classes);
}
this.refreshClassTree();
}
setClassTree(roots) {
const diagramModel = this.props.editor.model;
const visiting = /* @__PURE__ */ new Set();
const reduceNonCycle = (acc, model) => {
if (!visiting.has(model.id)) {
visiting.add(model.id);
const children = model.children.reduce(reduceNonCycle, []);
acc.push({
...model,
children
});
visiting.delete(model.id);
}
return acc;
};
this.classTree = roots.reduce(reduceNonCycle, []);
const addClass = (model) => {
if (!diagramModel.getClass(model.id)) {
const { id, label, count, children } = model;
const richClass = new FatClassModel({
id,
label: label.values,
count
});
diagramModel.addClass(richClass);
children.forEach(addClass);
}
};
this.classTree.forEach(addClass);
this.refreshClassTree();
}
async queryCreatableTypes(typeIris, ct) {
try {
const result = await CancellationToken.mapCancelledToNull(ct, this.props.editor.metadataApi.filterConstructibleTypes(typeIris, ct));
if (result === null) return;
this.setState((state) => {
const constructibleClasses = cloneMap(state.constructibleClasses);
typeIris.forEach((type) => {
constructibleClasses.set(type, result.has(type));
});
return applyFilters({
...state,
constructibleClasses,
refreshingState: ProgressState.completed
});
});
} catch (err) {
console.error(err);
if (ct.aborted) return;
this.setState((state) => applyFilters({
...state,
refreshingState: ProgressState.error
}));
}
}
};
function createRoots(classTree, view) {
const mapClass = (model) => {
const richClass = view.model.createClass(model.id);
return {
model: richClass,
label: view.formatLabel(richClass.label, richClass.id),
derived: model.children.map(mapClass)
};
};
return classTree.map(mapClass);
}
function getNewClassIris(existingClasses, classTree) {
const classIris = /* @__PURE__ */ new Set();
const visitClass = (model) => {
if (!existingClasses.has(model.id)) classIris.add(model.id);
model.children.forEach(visitClass);
};
classTree.forEach(visitClass);
return classIris;
}
function normalizeSearchText(text) {
return text.trim().toLowerCase();
}
function sortTree(roots) {
function mapNodes(nodes) {
if (nodes.length === 0) return nodes;
const mapped = nodes.map(mapNode);
mapped.sort(compareByLabel);
return mapped;
}
function mapNode(node) {
return TreeNode.setDerived(node, mapNodes(node.derived));
}
function compareByLabel(left, right) {
return left.label.localeCompare(right.label);
}
return mapNodes(roots);
}
function applyFilters(state) {
let filteredRoots = state.roots;
if (state.appliedSearchText) filteredRoots = filterByKeyword(filteredRoots, state.appliedSearchText);
if (state.showOnlyConstructible) filteredRoots = filterOnlyCreatable(filteredRoots, state.constructibleClasses);
return {
...state,
filteredRoots
};
}
function filterByKeyword(roots, searchText) {
if (roots.length === 0) return roots;
function collectByKeyword(acc, node) {
const derived = node.derived.reduce(collectByKeyword, []);
if (derived.length > 0 || node.label.toLowerCase().indexOf(searchText) >= 0) acc.push(TreeNode.setDerived(node, derived));
return acc;
}
return roots.reduce(collectByKeyword, []);
}
function filterOnlyCreatable(roots, creatableClasses) {
function collectOnlyCreatable(acc, node) {
const derived = node.derived.reduce(collectOnlyCreatable, []);
if (derived.length > 0 || creatableClasses.get(node.model.id)) acc.push(TreeNode.setDerived(node, derived));
return acc;
}
return roots.reduce(collectOnlyCreatable, []);
}
//#endregion
//#region src/graph-explorer/widgets/linksToolbox.tsx
var LinkInToolBox = class extends React.Component {
constructor(..._args) {
super(..._args);
this.onPressFilter = () => {
if (this.props.onPressFilter) this.props.onPressFilter(this.props.link);
};
this.changeState = (state) => {
const history = this.props.view.model.history;
changeLinkTypeState(history, state, [this.props.link]);
};
this.isChecked = (stateName) => {
let curState;
if (!this.props.link.visible) curState = "invisible";
else if (!this.props.link.showLabel) curState = "withoutLabels";
else curState = "allVisible";
return stateName === curState;
};
this.getText = () => {
const { link: linkType, view, filterKey } = this.props;
return highlightSubstring(view.formatLabel(linkType.label, linkType.id), filterKey);
};
}
render() {
const newIcon = this.props.link.isNew ? /* @__PURE__ */ React.createElement("span", { className: "linkInToolBox__new-tag" }, "new") : "";
const countIcon = this.props.count > 0 ? /* @__PURE__ */ React.createElement("span", { className: "graph-explorer-badge" }, this.props.count) : "";
const badgeContainer = newIcon || countIcon ? /* @__PURE__ */ React.createElement("div", null, newIcon, countIcon) : "";
return /* @__PURE__ */ React.createElement("li", {
"data-linktypeid": this.props.link.id,
className: "graph-explorer-list-group-item linkInToolBox clearfix"
}, /* @__PURE__ */ React.createElement("span", {
className: "graph-explorer-btn-group graph-explorer-btn-group-xs",
"data-toggle": "buttons"
}, /* @__PURE__ */ React.createElement("label", {
className: "graph-explorer-btn graph-explorer-btn-default" + (this.isChecked("invisible") ? " active" : ""),
id: "invisible",
title: "Hide links and labels",
onClick: () => this.changeState("invisible")
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-times",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("label", {
className: "graph-explorer-btn graph-explorer-btn-default" + (this.isChecked("withoutLabels") ? " active" : ""),
id: "withoutLabels",
title: "Show links without labels",
onClick: () => this.changeState("withoutLabels")
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-arrows-h",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("label", {
className: "graph-explorer-btn graph-explorer-btn-default" + (this.isChecked("allVisible") ? " active" : ""),
id: "allVisible",
title: "Show links with labels",
onClick: () => this.changeState("allVisible")
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-text-width",
"aria-hidden": "true"
}))), /* @__PURE__ */ React.createElement("div", { className: "link-title" }, this.getText()), badgeContainer, /* @__PURE__ */ React.createElement("div", {
className: "linkInToolBox__filter-button",
onClick: this.onPressFilter
}));
}
};
var LinkTypesToolboxView = class extends React.Component {
constructor(props) {
super(props);
this.compareLinks = (a, b) => {
const { view } = this.props;
const aText = view.formatLabel(a.label, a.id);
const bText = view.formatLabel(b.label, b.id);
return aText.localeCompare(bText);
};
this.onChangeInput = (e) => {
this.setState({ filterKey: e.currentTarget.value });
};
this.onDropFilter = () => {
this.setState({ filterKey: "" });
};
this.getLinks = () => {
const { view, links = [] } = this.props;
return links.filter((linkType) => {
const text = view.formatLabel(linkType.label, linkType.id).toLowerCase();
return !this.state.filterKey || text.indexOf(this.state.filterKey.toLowerCase()) >= 0;
}).sort(this.compareLinks);
};
this.getViews = (links) => {
const countMap = this.props.countMap || {};
const views = [];
for (const link of links) views.push(/* @__PURE__ */ React.createElement(LinkInToolBox, {
key: link.id,
view: this.props.view,
link,
onPressFilter: this.props.filterCallback,
count: countMap[link.id] || 0,
filterKey: this.state.filterKey
}));
return views;
};
this.state = { filterKey: "" };
}
render() {
const className = "link-types-toolbox";
const { view, dataState, selectedElement } = this.props;
const history = view.model.history;
const links = this.getLinks();
const views = this.getViews(links);
let connectedTo = null;
if (selectedElement) {
const selectedElementLabel = view.formatLabel(selectedElement.data.label.values, selectedElement.iri);
connectedTo = /* @__PURE__ */ React.createElement("h4", {
className: "links-heading",
style: { display: "block" }
}, "Connected to", "\xA0", /* @__PURE__ */ React.createElement("span", null, selectedElementLabel));
}
let dropButton = null;
if (this.state.filterKey) dropButton = /* @__PURE__ */ React.createElement("button", {
type: "button",
className: `${className}__clearSearch`,
onClick: this.onDropFilter
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-times",
"aria-hidden": "true"
}));
return /* @__PURE__ */ React.createElement("div", { className }, /* @__PURE__ */ React.createElement("div", { className: `${className}__heading` }, /* @__PURE__ */ React.createElement("div", { className: `${className}__searching-box` }, /* @__PURE__ */ React.createElement("input", {
className: "search-input graph-explorer-form-control",
type: "text",
value: this.state.filterKey,
onChange: this.onChangeInput,
placeholder: "Search for..."
}), dropButton), /* @__PURE__ */ React.createElement("div", { className: `${className}__switch-all` }, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-btn-group graph-explorer-btn-group-xs" }, /* @__PURE__ */ React.createElement("label", {
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Hide links and labels",
onClick: () => changeLinkTypeState(history, "invisible", links)
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-times",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("label", {
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Show links without labels",
onClick: () => changeLinkTypeState(history, "withoutLabels", links)
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-arrows-h",
"aria-hidden": "true"
})), /* @__PURE__ */ React.createElement("label", {
className: "graph-explorer-btn graph-explorer-btn-default",
title: "Show links with labels",
onClick: () => changeLinkTypeState(history, "allVisible", links)
}, /* @__PURE__ */ React.createElement("span", {
className: "fa fa-text-width",
"aria-hidden": "true"
}))), /* @__PURE__ */ React.createElement("span", null, "\xA0Switch all"))), /* @__PURE__ */ React.createElement(ProgressBar, { state: dataState }), /* @__PURE__ */ React.createElement("div", { className: `${className}__rest` }, connectedTo, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer-scrollable" }, /* @__PURE__ */ React.createElement("ul", { className: "graph-explorer-list-group connected-links" }, views))));
}
};
var LinkTypesToolbox = class extends React.Component {
constructor(props, context) {
super(props, context);
this.listener = new EventObserver();
this.linkListener = new EventObserver();
this.debounceSelection = new Debouncer(50);
this.updateOnCurrentSelection = () => {
const { editor } = this.props;
const single = editor.selection.length === 1 ? editor.selection[0] : null;
if (single !== this.state.selectedElement && single instanceof Element$1) this.requestLinksOf(single);
};
this.onLinkChanged = () => {
this.forceUpdate();
};
this.onAddToFilter = (linkType) => {
const { selectedElement } = this.state;
selectedElement.addToFilter(linkType);
};
const { view, editor } = this.props;
this.listener.listen(view.events, "changeLanguage", () => this.updateOnCurrentSelection());
this.listener.listen(editor.model.events, "loadingSuccess", () => this.updateOnCurrentSelection());
this.listener.listen(editor.events, "changeSelection", () => {
this.debounceSelection.call(this.updateOnCurrentSelection);
});
this.state = { dataState: ProgressState.none };
}
componentDidMount() {
this.updateOnCurrentSelection();
}
componentWillUnmount() {
this.listener.stopListening();
this.linkListener.stopListening();
this.debounceSelection.dispose();
}
requestLinksOf(selectedElement) {
if (selectedElement) {
const request = { elementId: selectedElement.iri };
this.currentRequest = request;
this.setState({
dataState: ProgressState.loading,
selectedElement
});
this.props.editor.model.dataProvider.linkTypesOf(request).then((linkTypes) => {
if (this.currentRequest !== request) return;
const { linksOfElement, countMap } = this.computeStateFromRequestResult(linkTypes);
this.subscribeOnLinksEvents(linksOfElement);
this.setState({
dataState: ProgressState.completed,
linksOfElement,
countMap
});
}).catch((error) => {
if (this.currentRequest !== request) return;
console.error(error);
this.setState({
dataState: ProgressState.error,
linksOfElement: void 0,
countMap: {}
});
});
} else {
this.currentRequest = null;
this.setState({
dataState: ProgressState.completed,
selectedElement,
linksOfElement: void 0,
countMap: {}
});
}
}
computeStateFromRequestResult(linkTypes) {
const linksOfElement = [];
const countMap = {};
const model = this.props.editor.model;
for (const linkType of linkTypes) {
const type = model.createLinkType(linkType.id);
linksOfElement.push(type);
countMap[linkType.id] = linkType.inCount + linkType.outCount;
}
return {
linksOfElement,
countMap
};
}
subscribeOnLinksEvents(linksOfElement) {
this.linkListener.stopListening();
const listener = this.linkListener;
for (const link of linksOfElement) {
listener.listen(link.events, "changeLabel", this.onLinkChanged);
listener.listen(link.events, "changeVisibility", this.onLinkChanged);
}
}
render() {
const { view } = this.props;
const { selectedElement, dataState, linksOfElement, countMap } = this.state;
return /* @__PURE__ */ React.createElement(LinkTypesToolboxView, {
view,
dataState,
links: linksOfElement,
countMap,
filterCallback: this.onAddToFilter,
selectedElement
});
}
};
function changeLinkTypeState(history, state, links) {
const batch = history.startBatch();
const { visible, showLabel } = state === "invisible" ? {
visible: false,
showLabel: false
} : state === "withoutLabels" ? {
visible: true,
showLabel: false
} : state === "allVisible" ? {
visible: true,
showLabel: true
} : void 0;
for (const linkType of links) history.execute(changeLinkTypeVisibility({
linkType,
visible,
showLabel
}));
batch.store();
}
//#endregion
//#region src/graph-explorer/workspace/accordion.tsx
var CLASS_NAME$1 = "graph-explorer-accordion";
var Accordion = class extends React.Component {
static {
this.defaultProps = {
direction: "vertical",
animationDuration: 300
};
}
constructor(props) {
super(props);
this.items = [];
this.isScrollable = false;
this.updateSizes = () => {
const { children } = this.props;
const defaultProps = /* @__PURE__ */ new Map();
React.Children.forEach(children, (child, index) => {
if (typeof child !== "object") return;
const { defaultSize, defaultCollapsed, collapsedSize, minSize } = child.props;
if (minSize !== void 0) this.isScrollable = true;
defaultProps.set(index, {
defaultSize,
defaultCollapsed,
collapsedSize,
minSize
});
});
this.defaultProps = defaultProps;
this.setState(({ collapsed }) => {
const totalSize = this.clientSize(this.element.parentElement);
let leftSize = totalSize;
let leftChildCount = React.Children.count(children);
const newCollapsed = [];
this.defaultProps.forEach(({ defaultSize, defaultCollapsed = false }, index) => {
let itemCollapsed = collapsed[index] === void 0 ? defaultCollapsed : collapsed[index];
const isLastItem = index === this.defaultProps.size - 1;
const noExpandedItems = newCollapsed.findIndex((c) => !c) < 0;
if (isLastItem && noExpandedItems) itemCollapsed = false;
const size = itemCollapsed ? this.sizeWhenCollapsed(index) : defaultSize;
if (size) {
leftSize = leftSize - size;
leftChildCount = leftChildCount - 1;
}
newCollapsed.push(itemCollapsed);
});
const newSizes = [];
const newPercents = [];
this.defaultProps.forEach(({ defaultSize, minSize }, index) => {
let size = leftSize / leftChildCount;
const collapsedSize = this.sizeWhenCollapsed(index);
if (newCollapsed[index]) size = collapsedSize;
else if (defaultSize !== void 0) size = defaultSize;
else if (size < minSize) size = collapsedSize + minSize;
if (size < collapsedSize) {
size = collapsedSize;
newCollapsed[index] = true;
}
const percent = `${100 * size / totalSize}%`;
newSizes.push(size);
newPercents.push(percent);
});
return {
sizes: newSizes,
percents: newPercents,
collapsed: newCollapsed
};
});
};
this.onBeginDragHandle = (_itemIndex) => {
this.originTotalSize = this.clientSize(this.element);
this.originSizes = this.computeEffectiveItemSizes();
this.originCollapsed = [...this.state.collapsed];
this.setState({ resizing: true }, () => {
const { direction, onStartResize } = this.props;
if (onStartResize) onStartResize(direction);
});
};
this.onEndDragHandle = () => {
this.setState({ resizing: false });
};
this.sizeWhenCollapsed = (index) => {
const item = this.items[index];
const { collapsedSize } = this.defaultProps.get(index);
if (collapsedSize !== void 0) return collapsedSize;
return (item.header ? this.clientSize(item.header) : 0) + (this.offsetSize(item.element) - this.clientSize(item.element));
};
this.onDragHandle = (itemIndex, dx, dy) => {
const sizes = [...this.originSizes];
const collapsed = [...this.originCollapsed];
new SizeDistributor(sizes, collapsed, this.originTotalSize, this.sizeWhenCollapsed).distribute(itemIndex + 1, this.isVertical ? dy : dx);
const percents = sizes.map((size) => `${100 * size / this.originTotalSize}%`);
this.setState({
sizes,
percents,
collapsed
});
};
const childCount = React.Children.count(this.props.children);
this.state = {
collapsed: [],
resizing: false,
percents: React.Children.map(this.props.children, () => `${100 / childCount}%`)
};
}
componentDidMount() {
this.updateSizes();
}
componentDidUpdate(prevProps, prevState) {
const { direction, children, onResize, animationDuration } = this.props;
if (React.Children.count(children) !== React.Children.count(prevProps.children)) this.updateSizes();
const { sizes, resizing } = this.state;
if (sizes !== prevState.sizes && onResize) if (resizing) onResize(direction);
else setTimeout(() => onResize(direction), animationDuration);
}
get isVertical() {
return this.props.direction === "vertical";
}
clientSize(element) {
const { clientWidth, clientHeight } = element;
return this.isVertical ? clientHeight : clientWidth;
}
offsetSize(element) {
const { offsetWidth, offsetHeight } = element;
return this.isVertical ? offsetHeight : offsetWidth;
}
render() {
const { direction } = this.props;
const { resizing } = this.state;
const resizingClassName = resizing ? `${CLASS_NAME$1}--resizing` : "";
const scrollableClassName = this.isScrollable ? `${CLASS_NAME$1}--scrollable` : "";
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME$1} ${CLASS_NAME$1}--${direction} ${resizingClassName} ${scrollableClassName}`,
ref: (element) => {
this.element = element;
}
}, this.renderItems());
}
renderItems() {
const { sizes, percents, collapsed } = this.state;
const { children, direction } = this.props;
return React.Children.map(children, (child, index) => {
if (typeof child !== "object") throw new Error("Accordion should have only AccordionItem elements as children");
const lastChild = index === React.Children.count(children) - 1;
const size = collapsed[index] ? sizes[index] : percents[index];
const additionalProps = {
ref: (element) => {
this.items[index] = element;
},
collapsed: collapsed[index],
size,
direction,
onChangeCollapsed: (itemCollapsed) => this.onItemChangeCollapsed({
itemIndex: index,
itemCollapsed
}),
onBeginDragHandle: lastChild ? void 0 : () => this.onBeginDragHandle(index),
onDragHandle: lastChild ? void 0 : (dx, dy) => this.onDragHandle(index, dx, dy),
onEndDragHandle: this.onEndDragHandle
};
return React.cloneElement(child, additionalProps);
});
}
computeEffectiveItemSizes() {
const sizes = [];
this.items.forEach((item, index) => {
if (!item) return;
if (this.state.collapsed[index]) {
const collapsedSize = this.sizeWhenCollapsed(index);
sizes.push(collapsedSize);
} else sizes.push(this.offsetSize(item.element));
});
return sizes;
}
onItemChangeCollapsed({ itemIndex, itemCollapsed }) {
const totalSize = this.clientSize(this.element);
const sizes = this.computeEffectiveItemSizes();
if (sizes.length === 1) return;
const collapsed = [...this.state.collapsed];
const effectiveSize = sizes[itemIndex];
const collapsedSize = this.sizeWhenCollapsed(itemIndex);
const distributor = new SizeDistributor(sizes, collapsed, totalSize, this.sizeWhenCollapsed);
if (itemCollapsed) {
const splitShift = Math.max(effectiveSize - collapsedSize, 0);
sizes[itemIndex] = collapsedSize;
if (itemIndex === sizes.length - 1) distributor.expand(splitShift, itemIndex - 1);
else distributor.expand(splitShift, itemIndex + 1);
} else {
const { defaultSize, minSize } = this.defaultProps.get(itemIndex);
const shift = (defaultSize || totalSize / sizes.length) - collapsedSize;
let freeSize;
if (itemIndex === sizes.length - 1) freeSize = distributor.collapseForward({
shift,
from: itemIndex - 1,
to: itemIndex
});
else freeSize = distributor.collapseForward({
shift,
from: itemIndex + 1,
to: sizes.length
});
freeSize = Math.max(freeSize, distributor.leftoverSize());
if (freeSize < shift) freeSize += distributor.collapseForward({
shift: shift - freeSize,
from: 0,
to: itemIndex
});
if (freeSize < minSize) freeSize = minSize;
sizes[itemIndex] = Math.round(collapsedSize + freeSize);
}
collapsed[itemIndex] = itemCollapsed;
const percents = sizes.map((size) => `${100 * size / totalSize}%`);
this.setState({
sizes,
percents,
collapsed
});
}
};
var SizeDistributor = class {
constructor(sizes, collapsed, totalSize, sizeWhenCollapsed) {
this.sizes = sizes;
this.collapsed = collapsed;
this.totalSize = totalSize;
this.sizeWhenCollapsed = sizeWhenCollapsed;
}
distribute(splitIndex, splitShift) {
if (splitShift > 0) {
let freeSize = this.collapseForward({
shift: splitShift,
from: splitIndex,
to: this.sizes.length
});
freeSize = Math.max(freeSize, this.leftoverSize());
this.expand(freeSize, splitIndex - 1);
} else {
let freeSize = this.collapseBackward({
shift: -splitShift,
from: 0,
to: splitIndex
});
freeSize = Math.max(freeSize, this.leftoverSize());
this.expand(freeSize, splitIndex);
}
}
collapseForward({ shift, from, to }) {
if (shift <= 0) return 0;
let shiftLeft = shift;
for (let i = from; i < to; i++) shiftLeft = this.collapse(shiftLeft, i);
return shift - shiftLeft;
}
collapseBackward({ shift, from, to }) {
if (shift <= 0) return 0;
let shiftLeft = shift;
for (let i = to - 1; i >= from; i--) shiftLeft = this.collapse(shiftLeft, i);
return shift - shiftLeft;
}
collapse(shift, index) {
if (this.collapsed[index]) return shift;
const size = this.sizes[index];
const collapsedSize = this.sizeWhenCollapsed(index);
const newSize = Math.round(Math.max(size - shift, collapsedSize));
this.sizes[index] = newSize;
this.collapsed[index] = newSize <= collapsedSize;
return shift - (size - newSize);
}
expand(shift, index) {
if (shift <= 0) return 0;
const oldSize = this.sizes[index];
const newSize = Math.round(oldSize + shift);
this.sizes[index] = newSize;
this.collapsed[index] = newSize <= this.sizeWhenCollapsed(index);
return newSize - oldSize;
}
leftoverSize() {
const sizeSum = this.sizes.reduce((sum, size) => sum + size, 0);
return Math.max(this.totalSize - sizeSum, 0);
}
};
//#endregion
//#region src/graph-explorer/workspace/accordionItem.tsx
var DockSide = /* @__PURE__ */ function(DockSide) {
DockSide[DockSide["Left"] = 1] = "Left";
DockSide[DockSide["Right"] = 2] = "Right";
return DockSide;
}({});
var CLASS_NAME = "graph-explorer-accordion-item";
var AccordionItem = class extends React.Component {
static {
this.defaultProps = { direction: "vertical" };
}
constructor(props) {
super(props);
this.state = { resizing: false };
}
get element() {
return this._element;
}
get header() {
return this._header;
}
get isVertical() {
return this.props.direction === "vertical";
}
renderToggleButton() {
const { collapsed, dockSide, onChangeCollapsed } = this.props;
if (!dockSide) return null;
const side = dockSide === 1 ? "left" : "right";
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME}__handle-btn ${CLASS_NAME}__handle-btn-${side}`,
onClick: () => onChangeCollapsed(!collapsed)
});
}
render() {
const { heading, bodyClassName, children, bodyRef, collapsed, size, direction, onBeginDragHandle, onDragHandle, onEndDragHandle, dockSide } = this.props;
const { resizing } = this.state;
const shouldRenderHandle = onBeginDragHandle && onDragHandle && onEndDragHandle;
const style = this.isVertical ? { height: size } : { width: size };
const isMounted = !(collapsed && dockSide);
return /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME} ${CLASS_NAME}--${collapsed ? "collapsed" : "expanded"} ${CLASS_NAME}--${direction}
${resizing ? `${CLASS_NAME}--resizing` : ""}`,
ref: (element) => {
this._element = element;
},
style
}, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME}__inner` }, heading ? /* @__PURE__ */ React.createElement("div", {
className: `${CLASS_NAME}__header`,
ref: (header) => {
this._header = header;
},
onClick: () => this.props.onChangeCollapsed(!collapsed)
}, heading) : null, /* @__PURE__ */ React.createElement("div", { className: `${CLASS_NAME}__body` }, children && isMounted ? children : /* @__PURE__ */ React.createElement("div", {
ref: bodyRef,
className: `${bodyClassName || ""}`
}))), shouldRenderHandle ? /* @__PURE__ */ React.createElement(DraggableHandle, {
className: `${CLASS_NAME}__handle ${CLASS_NAME}__handle-${direction}`,
onBeginDragHandle: (_e) => {
this.setState({ resizing: true });
onBeginDragHandle();
},
onDragHandle: (_e, x, y) => onDragHandle(x, y),
onEndDragHandle: (_e) => {
this.setState({ resizing: false });
onEndDragHandle();
}
}) : null, this.renderToggleButton());
}
};
//#endregion
//#region src/graph-explorer/workspace/layout/layout.tsx
var DEFAULT_HORIZONTAL_COLLAPSED_SIZE = 28;
var WorkspaceLayoutType = /* @__PURE__ */ function(WorkspaceLayoutType) {
WorkspaceLayoutType["Row"] = "row";
WorkspaceLayoutType["Column"] = "column";
WorkspaceLayoutType["Component"] = "component";
return WorkspaceLayoutType;
}({});
var WorkspaceLayout = class extends React.Component {
renderAccordion({ children, direction, animationDuration }) {
const { _onStartResize, _onResize } = this.props;
const items = children.map((child, index) => {
let dockSide;
if (direction === "horizontal" && !child.undocked) {
if (index === 0) dockSide = DockSide.Left;
else if (index === children.length - 1) dockSide = DockSide.Right;
}
let collapsedSize = child.collapsedSize;
if (collapsedSize === void 0 && direction === "horizontal") collapsedSize = DEFAULT_HORIZONTAL_COLLAPSED_SIZE;
return /* @__PURE__ */ React.createElement(AccordionItem, {
key: child.type === "component" ? child.id : index,
heading: child.type === "component" ? child.heading : void 0,
dockSide,
defaultSize: child.defaultSize,
defaultCollapsed: child.defaultCollapsed,
collapsedSize,
minSize: child.minSize
}, this.renderLayout(child));
});
return /* @__PURE__ */ React.createElement(Accordion, {
direction,
onStartResize: _onStartResize,
onResize: _onResize,
animationDuration
}, items);
}
renderLayout(layout) {
if (layout.type === "row") return this.renderAccordion({
children: layout.children,
direction: "horizontal",
animationDuration: layout.animationDuration
});
if (layout.type === "column") return this.renderAccordion({
children: layout.children,
direction: "vertical",
animationDuration: layout.animationDuration
});
if (layout.type === "component") return React.Children.only(layout.content);
return null;
}
render() {
const { layout } = this.props;
return this.renderLayout(layout);
}
};
//#endregion
//#region src/graph-explorer/workspace/workspaceMarkup.tsx
var WorkspaceMarkup = class extends React.Component {
constructor(..._args) {
super(..._args);
this.untilMouseUpClasses = [];
this.cancellation = new Cancellation();
this.triggerWorkspaceEvent = (key) => {
const { onWorkspaceEvent } = this.props;
if (onWorkspaceEvent) onWorkspaceEvent(key);
};
this.onCreateInstance = async (classId, position) => {
const { editor, view, model, metadataApi } = this.props;
await forceNonReactExecutionContext();
const batch = model.history.startBatch();
const signal = this.cancellation.signal;
const elementModel = await CancellationToken.mapCancelledToNull(signal, metadataApi.generateNewElement([classId], signal));
if (elementModel === null) return;
const element = editor.createNewEntity({ elementModel });
const targetPosition = position || getViewportCenterInPaperCoords(this.paperArea);
element.setPosition(targetPosition);
view.performSyncUpdate();
centerElementToPosition(element, targetPosition);
batch.store();
editor.setSelection([element]);
editor.showEditEntityForm(element);
};
this.onDocumentMouseUp = () => {
for (const className of this.untilMouseUpClasses) this.element.classList.remove(className);
this.untilMouseUpClasses = [];
};
this.onDropOnPaper = (e, paperPosition) => {
e.preventDefault();
const event = {
dragEvent: e,
paperPosition
};
if (this.props.view._tryHandleDropOnPaper(event)) return;
const iris = tryParseDefaultDragAndDropData(e);
if (iris.length > 0) this.props.editor.onDragDrop(iris, paperPosition);
};
}
addToolbarWidgetToPaper() {
const { hideToolbar, view, toolbar } = this.props;
if (!hideToolbar) view.setPaperWidget({
key: "toolbar",
widget: /* @__PURE__ */ React.createElement(ToolbarWidget, null, toolbar),
attachment: WidgetAttachment.Viewport
});
}
getLeftPanelLayout() {
const { view, editor, model, searchCriteria, onSearchCriteriaChanged } = this.props;
const classTree = /* @__PURE__ */ React.createElement(ClassTree, {
view,
editor,
onClassSelected: (classId) => {
const elementType = model.createClass(classId);
onSearchCriteriaChanged({ elementType });
},
onCreateInstance: this.onCreateInstance
});
const instancesSearch = /* @__PURE__ */ React.createElement(InstancesSearch, {
view,
model,
criteria: searchCriteria || {},
onCriteriaChanged: onSearchCriteriaChanged
});
return {
type: WorkspaceLayoutType.Column,
children: [{
id: "classes",
type: WorkspaceLayoutType.Component,
content: classTree,
heading: "Classes"
}, {
id: "instances",
type: WorkspaceLayoutType.Component,
content: instancesSearch,
heading: "Instances"
}],
defaultSize: 275,
defaultCollapsed: !this.props.isLeftPanelOpen
};
}
getRightPanelLayout() {
const { view, editor, elementsSearchPanel } = this.props;
const rightPanel = {
type: WorkspaceLayoutType.Column,
children: [{
id: "connections",
type: WorkspaceLayoutType.Component,
content: /* @__PURE__ */ React.createElement(LinkTypesToolbox, {
view,
editor
}),
heading: "Connections"
}],
defaultSize: 275,
defaultCollapsed: !this.props.isRightPanelOpen
};
if (elementsSearchPanel) rightPanel.children = [...rightPanel.children, {
id: "search",
type: WorkspaceLayoutType.Component,
content: React.cloneElement(elementsSearchPanel, {
view,
editor
}),
heading: "Search in diagram"
}];
return rightPanel;
}
render() {
const paper = {
id: "paper",
type: WorkspaceLayoutType.Component,
content: /* @__PURE__ */ React.createElement("div", {
className: "graph-explorer__main-panel",
style: {
flex: "1 1 0px",
width: "100%"
}
}, /* @__PURE__ */ React.createElement(PaperArea, {
ref: (el) => {
this.paperArea = el;
},
view: this.props.view,
zoomOptions: this.props.zoomOptions,
hideScrollBars: this.props.hideScrollBars,
watermarkSvg: this.props.watermarkSvg,
watermarkUrl: this.props.watermarkUrl,
onDragDrop: this.onDropOnPaper,
onZoom: this.props.onZoom
}))
};
const workspaceLayout = this.props.hidePanels ? paper : {
type: WorkspaceLayoutType.Row,
children: [
this.getLeftPanelLayout(),
paper,
this.getRightPanelLayout()
]
};
return /* @__PURE__ */ React.createElement(WorkspaceContext.Provider, { value: { workspace: {
editor: this.props.editor,
triggerWorkspaceEvent: this.triggerWorkspaceEvent
} } }, /* @__PURE__ */ React.createElement("div", {
ref: (e) => {
this.element = e;
},
className: "graph-explorer"
}, /* @__PURE__ */ React.createElement("div", { className: "graph-explorer__workspace" }, /* @__PURE__ */ React.createElement(WorkspaceLayout, {
layout: workspaceLayout,
_onStartResize: (direction) => this.untilMouseUp({
preventTextSelection: true,
verticalResizing: direction === "vertical",
horizontalResizing: direction === "horizontal"
})
}))));
}
componentDidMount() {
document.addEventListener("mouseup", this.onDocumentMouseUp);
this.addToolbarWidgetToPaper();
}
componentWillUnmount() {
document.removeEventListener("mouseup", this.onDocumentMouseUp);
this.cancellation.abort();
}
preventTextSelection() {
this.untilMouseUp({ preventTextSelection: true });
}
untilMouseUp(params) {
this.untilMouseUpClasses = [];
if (params.preventTextSelection) this.untilMouseUpClasses.push("graph-explorer--unselectable");
if (params.horizontalResizing) this.untilMouseUpClasses.push("graph-explorer--horizontal-resizing");
if (params.verticalResizing) this.untilMouseUpClasses.push("graph-explorer--vertical-resizing");
for (const className of this.untilMouseUpClasses) this.element.classList.add(className);
}
};
var ToolbarWidget = class extends React.Component {
render() {
return /* @__PURE__ */ React.createElement("div", { className: "graph-explorer__toolbar-widget" }, this.props.children);
}
};
function forceNonReactExecutionContext() {
return Promise.resolve();
}
function getViewportCenterInPaperCoords(paperArea) {
const viewport = paperArea.getAreaMetrics();
return paperArea.clientToPaperCoords(viewport.clientWidth / 2, viewport.clientHeight / 2);
}
function centerElementToPosition(element, center) {
const position = {
x: center.x - element.size.width / 2,
y: center.y - element.size.height / 2
};
element.setPosition(position);
}
function tryParseDefaultDragAndDropData(e) {
const tryGetIri = (type, decode = false) => {
try {
const iriString = e.dataTransfer.getData(type);
if (!iriString) return;
let iris;
try {
iris = JSON.parse(iriString);
} catch (_e) {
iris = [decode ? decodeURI(iriString) : iriString];
}
return iris.length === 0 ? void 0 : iris;
} catch (_e) {
return;
}
};
return tryGetIri("application/x-graph-explorer-elements") || tryGetIri("text/uri-list", true) || tryGetIri("text") || [];
}
//#endregion
//#region src/graph-explorer/workspace/workspace.ts
var GRAPH_EXPLORER_WEBSITE = "https://graph-explorer.org/";
var GRAPH_EXPLORER_LOGO_SVG = void 0;
var Workspace = class extends Component {
static {
this.defaultProps = {
hideTutorial: true,
collapseNavigator: false,
leftPanelInitiallyOpen: true,
rightPanelInitiallyOpen: false,
languages: [{
code: "en",
label: "English"
}, {
code: "ru",
label: "Russian"
}],
language: "en"
};
}
constructor(props) {
super(props);
this.listener = new EventObserver();
this._watermarkSvg = GRAPH_EXPLORER_LOGO_SVG;
this._watermarkUrl = GRAPH_EXPLORER_WEBSITE;
this.zoomToFit = () => {
this.markup.paperArea.zoomToFit();
};
this.zoomToFitRect = (bbox) => {
this.markup.paperArea.zoomToFitRect(bbox);
};
this.clearAll = () => {
this.editor.removeItems([...this.model.elements]);
};
this.forceLayout = () => {
const batch = this.model.history.startBatch("Force layout");
batch.history.registerToUndo(RestoreGeometry.capture(this.model));
applyLayout(this.model, forceLayout({ model: this.model }));
for (const link of this.model.links) link.setVertices([]);
batch.store();
};
this.exportSvg = (fileName) => {
this.markup.paperArea.exportSVG().then((svg) => {
fileName = fileName || "diagram.svg";
saveAs(new Blob(["<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + svg], { type: "image/svg+xml" }), fileName);
});
};
this.exportPng = (fileName) => {
fileName = fileName || "diagram.png";
this.markup.paperArea.exportPNG({ backgroundColor: "white" }).then((dataUri) => {
saveAs(dataURLToBlob(dataUri), fileName);
});
};
this.undo = () => {
this.model.history.undo();
};
this.redo = () => {
this.model.history.redo();
};
this.zoomBy = (value) => {
this.markup.paperArea.zoomBy(value);
};
this.zoomIn = () => {
this.markup.paperArea.zoomIn();
};
this.zoomOut = () => {
this.markup.paperArea.zoomOut();
};
this.print = () => {
this.markup.paperArea.exportSVG().then((svg) => {
const printWindow = window.open("", void 0, "width=1280,height=720");
printWindow.document.write(svg);
printWindow.document.close();
printWindow.print();
});
};
this.changeLanguage = (language) => {
const { onLanguageChange } = this.props;
if (onLanguageChange) onLanguageChange(language);
else {
this.view.setLanguage(language);
this.forceUpdate();
}
};
this.centerTo = (paperPosition) => {
this.markup.paperArea.centerTo(paperPosition);
};
const { hideHalo, history, viewOptions = {}, metadataApi, validationApi, propertyEditor, elementTemplateResolver, linkTemplateResolver, typeStyleResolver, selectLabelLanguage } = this.props;
const { linkRouter, onIriClick, disableDefaultHalo, suggestProperties, groupBy } = viewOptions;
this.model = new AsyncModel(history || new NonRememberingHistory(), groupBy || []);
this.view = new DiagramView(this.model, {
elementTemplateResolver,
linkTemplateResolver,
typeStyleResolver,
selectLabelLanguage,
linkRouter,
onIriClick
});
this.editor = new EditorController({
model: this.model,
view: this.view,
disableHalo: hideHalo || disableDefaultHalo,
suggestProperties,
validationApi,
propertyEditor
});
this.editor.setMetadataApi(metadataApi);
this.view.setLanguage(this.props.language);
this.state = {};
}
_getPaperArea() {
return this.markup ? this.markup.paperArea : void 0;
}
_setWatermark(watermarkSvg, watermarkUrl) {
this._watermarkSvg = watermarkSvg;
this._watermarkUrl = watermarkUrl;
this.forceUpdate();
}
render() {
const { hidePanels, hideToolbar, metadataApi, hideScrollBars, onWorkspaceEvent, _elementsSearchPanel } = this.props;
return createElement(WorkspaceMarkup, {
ref: (markup) => {
this.markup = markup;
},
hidePanels,
hideToolbar,
hideScrollBars,
model: this.model,
view: this.view,
editor: this.editor,
metadataApi,
leftPanelInitiallyOpen: this.props.leftPanelInitiallyOpen,
rightPanelInitiallyOpen: this.props.rightPanelInitiallyOpen,
searchCriteria: this.state.criteria,
onSearchCriteriaChanged: (criteria) => this.setState({ criteria }),
zoomOptions: this.props.zoomOptions,
onZoom: this.props.onZoom,
isLeftPanelOpen: this.props.leftPanelInitiallyOpen,
isRightPanelOpen: this.props.rightPanelInitiallyOpen,
toolbar: createElement(ToolbarWrapper, { workspace: this }),
onWorkspaceEvent,
watermarkSvg: this._watermarkSvg,
watermarkUrl: this._watermarkUrl,
elementsSearchPanel: _elementsSearchPanel
});
}
componentDidMount() {
const { onWorkspaceEvent } = this.props;
this.editor._initializePaperComponents(this.markup.paperArea);
this.updateNavigator(!this.props.hideNavigator);
this.listener.listen(this.model.events, "loadingSuccess", () => {
this.view.performSyncUpdate();
this.markup.paperArea.centerContent();
});
this.listener.listen(this.model.events, "elementEvent", ({ key: _key, data }) => {
if (!data.requestedAddToFilter) return;
const { source, linkType, direction } = data.requestedAddToFilter;
this.setState({ criteria: {
refElement: source,
refElementLink: linkType,
linkDirection: direction
} });
if (onWorkspaceEvent) onWorkspaceEvent(WorkspaceEventKey.searchUpdateCriteria);
});
this.listener.listen(this.markup.paperArea.events, "pointerUp", (e) => {
if (this.props.onPointerUp) this.props.onPointerUp(e);
});
this.listener.listen(this.markup.paperArea.events, "pointerMove", (e) => {
if (this.props.onPointerMove) this.props.onPointerMove(e);
});
this.listener.listen(this.markup.paperArea.events, "pointerDown", (e) => {
if (this.props.onPointerDown) this.props.onPointerDown(e);
});
if (onWorkspaceEvent) {
this.listener.listen(this.editor.events, "changeSelection", () => onWorkspaceEvent(WorkspaceEventKey.editorChangeSelection));
this.listener.listen(this.editor.events, "toggleDialog", () => onWorkspaceEvent(WorkspaceEventKey.editorToggleDialog));
this.listener.listen(this.editor.events, "addElements", () => onWorkspaceEvent(WorkspaceEventKey.editorAddElements));
}
}
componentDidUpdate(nextProps) {
if (Boolean(nextProps.onLanguageChange) && nextProps.language !== this.view.getLanguage()) this.view.setLanguage(nextProps.language);
if (nextProps.metadataApi !== this.editor.metadataApi) this.editor.setMetadataApi(nextProps.metadataApi);
if (nextProps.hideNavigator !== this.props.hideNavigator) this.updateNavigator(!nextProps.hideNavigator);
}
componentWillUnmount() {
this.listener.stopListening();
this.view.dispose();
}
updateNavigator(showNavigator) {
if (showNavigator) {
const widget = createElement(Navigator, {
view: this.view,
expanded: !this.props.collapseNavigator
});
this.view.setPaperWidget({
key: "navigator",
widget,
attachment: WidgetAttachment.Viewport
});
} else this.view.setPaperWidget({
key: "navigator",
widget: void 0,
attachment: WidgetAttachment.Viewport
});
}
getModel() {
return this.model;
}
getDiagram() {
return this.view;
}
getEditor() {
return this.editor;
}
preventTextSelectionUntilMouseUp() {
this.markup.preventTextSelection();
}
showWaitIndicatorWhile(operation) {
this.editor.setSpinner({});
if (operation) operation.then(() => {
this.editor.setSpinner(void 0);
}).catch((error) => {
console.error(error);
this.editor.setSpinner({
statusText: "Unknown error occured",
errorOccured: true
});
});
}
};
var ToolbarWrapper = class extends Component {
constructor(..._args) {
super(..._args);
this.listener = new EventObserver();
}
render() {
const { workspace } = this.props;
const view = workspace.getDiagram();
const editor = workspace.getEditor();
const { languages, onSaveDiagram, onPersistChanges, hidePanels, toolbar } = workspace.props;
const canPersistChanges = onPersistChanges ? !AuthoringState.isEmpty(editor.authoringState) : void 0;
const canSaveDiagram = !canPersistChanges;
const defaultToolbarProps = {
onZoomIn: workspace.zoomIn,
onZoomOut: workspace.zoomOut,
onZoomToFit: workspace.zoomToFit,
onPrint: workspace.print,
onExportSVG: workspace.exportSvg,
onExportPNG: workspace.exportPng,
canSaveDiagram,
onSaveDiagram: onSaveDiagram ? () => onSaveDiagram(workspace) : void 0,
canPersistChanges,
onPersistChanges: onPersistChanges ? () => onPersistChanges(workspace) : void 0,
onForceLayout: () => {
workspace.forceLayout();
workspace.getDiagram().performSyncUpdate();
workspace.zoomToFit();
},
onClearAll: workspace.clearAll,
languages,
selectedLanguage: view.getLanguage(),
onChangeLanguage: workspace.changeLanguage,
hidePanels
};
if (toolbar) return cloneElement(toolbar, {
...defaultToolbarProps,
...toolbar.props
});
else return createElement(DefaultToolbar, defaultToolbarProps);
}
componentDidMount() {
const { workspace } = this.props;
const editor = workspace.getEditor();
const view = workspace.getDiagram();
this.listener.listen(editor.events, "changeAuthoringState", () => {
this.forceUpdate();
});
this.listener.listen(view.events, "changeLanguage", () => {
this.forceUpdate();
});
}
componentWillUnmount() {
this.listener.stopListening();
}
};
function renderTo(workspace, container, props) {
createRoot(container).render(createElement(workspace, props));
}
//#endregion
//#region src/graph-explorer/internalApi.ts
var internalApi_exports = /* @__PURE__ */ __exportAll({
BatchingScheduler: () => BatchingScheduler,
BufferingQueue: () => BufferingQueue,
Cancellation: () => Cancellation,
CancellationToken: () => CancellationToken,
CancelledError: () => CancelledError,
Debouncer: () => Debouncer,
HashMap: () => HashMap,
HtmlSpinner: () => HtmlSpinner,
KeyedObserver: () => KeyedObserver,
LINK_SHOW_IRI: () => LINK_SHOW_IRI,
ListElementView: () => ListElementView,
MoveDirection: () => MoveDirection,
OrderedMap: () => OrderedMap,
Paper: () => Paper,
PaperArea: () => PaperArea,
PaperAreaContext: () => PaperAreaContext,
SearchResults: () => SearchResults,
Spinner: () => Spinner,
TemplateProperties: () => TemplateProperties,
TransformedSvgCanvas: () => TransformedSvgCanvas,
WorkspaceContext: () => WorkspaceContext,
animateInterval: () => animateInterval,
biasFreePadded: () => biasFreePadded,
cloneMap: () => cloneMap,
cloneSet: () => cloneSet,
delay: () => delay,
easeInOutBezier: () => easeInOutBezier,
getContentFittingBox: () => getContentFittingBox,
getContentFittingBoxForLayout: () => getContentFittingBoxForLayout,
getOrCreateArrayInMap: () => getOrCreateArrayInMap,
getOrCreateSetInMap: () => getOrCreateSetInMap,
groupForceLayout: () => groupForceLayout,
groupRemoveOverlaps: () => groupRemoveOverlaps,
highlightSubstring: () => highlightSubstring,
isEmptyMap: () => isEmptyMap,
makeMoveComparator: () => makeMoveComparator,
objectValues: () => objectValues,
observeElementTypes: () => observeElementTypes,
observeLinkTypes: () => observeLinkTypes,
observeProperties: () => observeProperties,
padded: () => padded,
paneFromPaperCoords: () => paneFromPaperCoords,
paneTopLeft: () => paneTopLeft,
paperFromPaneCoords: () => paperFromPaneCoords,
startDragElements: () => startDragElements,
totalPaneSize: () => totalPaneSize
});
//#endregion
export { AsyncModel, AuthoredEntity, AuthoringKind, AuthoringState, Cancellation, CancellationToken, CancelledError, Command, CompositeDataProvider, DBPediaSettings, DIAGRAM_CONTEXT_URL_V1, DefaultElementTemplate, DefaultElementTemplateBundle, DefaultToolbar, DemoDataProvider, DiagramModel, DiagramView, DraggableHandle, EditorController, Element$1 as Element, EmbeddedLayer, EventObserver, EventSource, GraphBuilder, GroupTemplate, internalApi_exports as InternalApi, IriClickIntent, Link, LinkDirection, LinkVertex, NonRememberingHistory, OWLRDFSSettings, OWLRDFSSettingsOverride, OWLStatsSettings, PLACEHOLDER_ELEMENT_TYPE, PLACEHOLDER_LINK_TYPE, QLeverSettings, RDFSettings, Rect, RenderingLayer, RestoreGeometry, SparqlDataProvider, SparqlGraphBuilder, SparqlQueryMethod, StandardTemplate, ValidationState, Vector, WidgetAttachment, WikidataSettings, Workspace, WorkspaceEventKey, WorkspaceLayout, WorkspaceLayoutType, applyLayout, boundsOf, calculateLayout, computeGrouping, computePolyline, computePolylineLength, convertToSerializedDiagram, executeSparqlConstruct, executeSparqlQuery, findElementAtPoint, findNearestSegmentIndex, forceLayout, getContentFittingBox, getPointAlongPolyline, hashLink, isIriProperty, isLinkConnectedToElement, isLiteralProperty, isPolylineEqual, makeGraphItems, makeLayout, makeLayoutData, makeSerializedDiagram, removeOverlaps, renderTo, requestElementData, restoreLinksBetweenElements, sameElement, sameLink, setElementData, setElementExpanded, setLinkData };
//# sourceMappingURL=graph-explorer.mjs.map