Idiotic instance panel...
This commit is contained in:
10
css/app.css
10
css/app.css
@@ -57,6 +57,16 @@ input[type="checkbox"] {
|
|||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
list-style-position: outside;
|
list-style-position: outside;
|
||||||
}
|
}
|
||||||
|
#instances li > a {
|
||||||
|
color: #dee2e6;
|
||||||
|
}
|
||||||
|
#instances li > a:hover {
|
||||||
|
color: var(--bs-nav-link-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
#instance-panel {
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
/* Tooltips from https://codepen.io/pure-css/pen/bddggP */
|
/* Tooltips from https://codepen.io/pure-css/pen/bddggP */
|
||||||
span:after {
|
span:after {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|||||||
@@ -58,7 +58,7 @@
|
|||||||
<script type="module" src="./index.js"></script>
|
<script type="module" src="./index.js"></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-bs-theme="light" data-controller="menu modal" data-action="semantic-modal-show@document->modal#showSemanticModal">
|
<body data-bs-theme="light" data-controller="menu modal node" data-action="semantic-modal-show@document->modal#showSemanticModal">
|
||||||
<div id="toolbar" class="aton-toolbar-top w-100"
|
<div id="toolbar" class="aton-toolbar-top w-100"
|
||||||
data-controller="toolbar clipper" data-clipper-enabled-value="false">
|
data-controller="toolbar clipper" data-clipper-enabled-value="false">
|
||||||
<div class="bg-dark-subtle position-absolute opacity-50 mt-1 ms-1 rounded">
|
<div class="bg-dark-subtle position-absolute opacity-50 mt-1 ms-1 rounded">
|
||||||
@@ -174,6 +174,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Instance panel (template it...)-->
|
||||||
|
<div class="position-absolute top-0 start-0 w-25 h-100 container instance-panel d-none bg-dark" data-node-target="panel">
|
||||||
|
<div class="container border-bottom border-secondary-subtle w-100">
|
||||||
|
<h1 class="fs-4 py-2 text-center"></h1>
|
||||||
|
</div>
|
||||||
|
<div class="row h-100">
|
||||||
|
<div class="col-5 border-end border-secondary-subtle h-100">
|
||||||
|
<ul id="properties"></ul>
|
||||||
|
</div>
|
||||||
|
<div class="col-7">
|
||||||
|
<p class="text-center fs-4 pt-4 mt-4">Sono inesorabilmente vuoto...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="aton-poweredby" >
|
<div class="aton-poweredby" >
|
||||||
<a href="https://aton.ispc.cnr.it/site/" target="_blank"><img src="/res/aton-logo.png"></a>
|
<a href="https://aton.ispc.cnr.it/site/" target="_blank"><img src="/res/aton-logo.png"></a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ export default class extends Controller {
|
|||||||
async connect() {
|
async connect() {
|
||||||
console.log('#menu controller connected');
|
console.log('#menu controller connected');
|
||||||
this.ontologyObject = await loadOntology(ontologyJsonPath);
|
this.ontologyObject = await loadOntology(ontologyJsonPath);
|
||||||
|
AppState.ontology = this.ontologyObject;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Open settings panel
|
* Open settings panel
|
||||||
@@ -240,7 +241,6 @@ export default class extends Controller {
|
|||||||
tab.appendChild(content);
|
tab.appendChild(content);
|
||||||
|
|
||||||
const classesTab = content.querySelector('#classes');
|
const classesTab = content.querySelector('#classes');
|
||||||
const instancesTab = content.querySelector('#instances');
|
|
||||||
|
|
||||||
for (let domain of domains) {
|
for (let domain of domains) {
|
||||||
const heading = document.createElement('span');
|
const heading = document.createElement('span');
|
||||||
@@ -266,17 +266,20 @@ export default class extends Controller {
|
|||||||
classesTab.appendChild(domainList);
|
classesTab.appendChild(domainList);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const instancesTab = content.querySelector('#instances');
|
||||||
|
const navList = instancesTab.querySelector('ul');
|
||||||
const sortedItLabels = ontology.getInstanceLabels().map(l => l.it).sort();
|
const sortedItLabels = ontology.getInstanceLabels().map(l => l.it).sort();
|
||||||
|
|
||||||
for (let label of sortedItLabels) {
|
for (let label of sortedItLabels) {
|
||||||
const instanceItem = document.createElement('div');
|
const instanceItem = document.createElement('li');
|
||||||
instanceItem.className = 'border-bottom ps-3 py-2 c-hand';
|
instanceItem.className = 'nav-item border-bottom ps-3 py-2 c-hand';
|
||||||
instanceItem.textContent = label;
|
instanceItem.innerHTML = html`
|
||||||
instanceItem.setAttribute('data-controller', 'node');
|
<a class="nav-link">${label}</a>
|
||||||
instanceItem.setAttribute('data-node-id-value', label);
|
`;
|
||||||
instanceItem.setAttribute('data-action', 'click->node#transition');
|
instanceItem.setAttribute('data-node-id-param', label);
|
||||||
|
instanceItem.setAttribute('data-action', 'click->node#open');
|
||||||
|
|
||||||
instancesTab.appendChild(instanceItem);
|
navList.appendChild(instanceItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -299,7 +302,9 @@ export default class extends Controller {
|
|||||||
const content = html`
|
const content = html`
|
||||||
<div class="tab-content" id="ontoTabsContent">
|
<div class="tab-content" id="ontoTabsContent">
|
||||||
<div class="tab-pane fade p-3 show active" id="classes" role="tabpanel" aria-labelledby="classes-tab" tabindex="0"></div>
|
<div class="tab-pane fade p-3 show active" id="classes" role="tabpanel" aria-labelledby="classes-tab" tabindex="0"></div>
|
||||||
<div class="tab-pane fade" id="instances" role="tabpanel" aria-labelledby="instances-tab" tabindex="0"></div>
|
<div class="tab-pane fade" id="instances" role="tabpanel" aria-labelledby="instances-tab" tabindex="0">
|
||||||
|
<ul class="nav flex-column"></ul>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
// Global ATON
|
// Global ATON
|
||||||
import { Controller } from "@hotwired/stimulus"
|
import { Controller } from "@hotwired/stimulus"
|
||||||
import AppState from "../state.js";
|
import AppState from "../state.js";
|
||||||
|
import { createSemanticNode } from "../scene.js";
|
||||||
|
import { Ontology } from "../ontology.js";
|
||||||
|
|
||||||
|
const html = String.raw;
|
||||||
/**
|
/**
|
||||||
* Handle events for the clipper toolbar,
|
* Handle events for the clipper toolbar,
|
||||||
* related to the clipping module
|
* related to the clipping module
|
||||||
*/
|
*/
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ['instance', 'node'];
|
static targets = ['instance', 'node', 'panel'];
|
||||||
static values = {
|
static values = {
|
||||||
id: String,
|
id: String,
|
||||||
};
|
};
|
||||||
@@ -17,13 +20,75 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Transition to this ATON node (request POV)
|
* Transition to ATON node (request POV)
|
||||||
|
* if instance is architectural and
|
||||||
|
* open the instance panel
|
||||||
*/
|
*/
|
||||||
transition() {
|
open({ params: {id} }) {
|
||||||
const node = ATON.getSceneNode(this.idValue);
|
const node = ATON.getSceneNode(id);
|
||||||
|
const normNode = AppState.normalizedNodes.find(n => n.id === id);
|
||||||
|
|
||||||
if (node) ATON.Nav.requestPOVbyNode(node);
|
if (node) ATON.Nav.requestPOVbyNode(node);
|
||||||
|
if (normNode) this.addSemanticNode(normNode);
|
||||||
|
|
||||||
|
const ontology = new Ontology;
|
||||||
|
ontology.data = AppState.ontology;
|
||||||
|
|
||||||
|
const instance = ontology.getInstanceByLabel(id, 'it');
|
||||||
|
if (instance) this.openInstancePanel(instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* Attaches a semantic node to
|
||||||
|
* an existing scene node using
|
||||||
|
* its 3D model for the semantic shape
|
||||||
|
* @param {import("../state.js").NormalizedSceneNode} normNode
|
||||||
|
*/
|
||||||
|
addSemanticNode(normNode) {
|
||||||
|
if (!AppState.semanticNodes.get(normNode.label)) {
|
||||||
|
createSemanticNode(normNode.model, normNode.label);
|
||||||
|
AppState.semanticNodes.set(normNode.label, normNode.content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populates an instance panel based on the
|
||||||
|
* instance data and opens it
|
||||||
|
* @param {Object} instance
|
||||||
|
*/
|
||||||
|
openInstancePanel(instance) {
|
||||||
|
const properties = instance.literalProperties;
|
||||||
|
// Populate left side properties column
|
||||||
|
/**
|
||||||
|
* @type {HTMLDivElement}
|
||||||
|
*/
|
||||||
|
const panel = this.panelTarget;
|
||||||
|
const list = panel.querySelector('#properties');
|
||||||
|
list.innerHTML = '';
|
||||||
|
|
||||||
|
panel.querySelector('h1').textContent = instance.label.it;
|
||||||
|
|
||||||
|
for (const prop of properties) {
|
||||||
|
// Include only DC properties (OK?)
|
||||||
|
const schema = prop.property.split(':')[0];
|
||||||
|
if (schema === 'dcterms' && Object.keys(prop.propertyLabel).length !== 0) {
|
||||||
|
const item = document.createElement('li');
|
||||||
|
item.className = 'py-2';
|
||||||
|
|
||||||
|
item.innerHTML = html`
|
||||||
|
<span class="text-primary-emphasis d-inline-block">${prop.propertyLabel.it ?? prop.propertyLabel.en}</span>
|
||||||
|
<br />
|
||||||
|
${prop.value.value}
|
||||||
|
`;
|
||||||
|
|
||||||
|
list.appendChild(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panel.classList.remove('d-none');
|
||||||
|
}
|
||||||
|
|
||||||
|
closeInstancePanel() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -19,6 +19,31 @@ export class Ontology {
|
|||||||
return this.#data.instancesById[id];
|
return this.#data.instancesById[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo Revise loop approach...
|
||||||
|
* @param {String} label
|
||||||
|
* @param {String} lang The language identifier, either 'en' or 'it'
|
||||||
|
* @returns {Object} The instance object as per onto JSON... (document it!!)
|
||||||
|
*/
|
||||||
|
getInstanceByLabel(label, lang) {
|
||||||
|
if (!['en','it'].includes(lang)) {
|
||||||
|
throw new Error(`Invalid language string provided: '${lang}'`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let instance = {};
|
||||||
|
|
||||||
|
for (const key of Object.keys(this.#data.instancesById)) {
|
||||||
|
const currentInstance = this.#data.instancesById[key];
|
||||||
|
const currentLabel = currentInstance.label[lang];
|
||||||
|
if (currentLabel === label) {
|
||||||
|
instance = currentInstance;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {Array<{en: string, it: string}>} The bloody array of label objects
|
* @returns {Array<{en: string, it: string}>} The bloody array of label objects
|
||||||
*/
|
*/
|
||||||
|
|||||||
19
src/scene.js
19
src/scene.js
@@ -107,12 +107,6 @@ function loadNodes(nodes) {
|
|||||||
AppState.clipping.boundingSphere = node.getBound();
|
AppState.clipping.boundingSphere = node.getBound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (n.isSemantic) {
|
|
||||||
createSemanticNode(n.model, n.label);
|
|
||||||
AppState.semanticNodes.set(n.label, n.content);
|
|
||||||
//ATON.getSemanticRoot().attach(semNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
AppState.nodes.push({
|
AppState.nodes.push({
|
||||||
id: n.label,
|
id: n.label,
|
||||||
active: n.isInvisible ? false: true,
|
active: n.isInvisible ? false: true,
|
||||||
@@ -130,14 +124,19 @@ function loadNodes(nodes) {
|
|||||||
* @param {string} id The original scene node ID (3D object)
|
* @param {string} id The original scene node ID (3D object)
|
||||||
* @returns {ATON.SceneNode}
|
* @returns {ATON.SceneNode}
|
||||||
*/
|
*/
|
||||||
function createSemanticNode(model, id) {
|
export function createSemanticNode(model, id) {
|
||||||
// Default/highlight materials for semantic node
|
// Default/highlight materials for semantic node
|
||||||
let matSemDef = new THREE.MeshStandardMaterial({
|
let matSemDef = new THREE.MeshStandardMaterial({
|
||||||
color: '#f7e6af',
|
color: '#81eb81',
|
||||||
transparent: true,
|
transparent: true,
|
||||||
opacity: 0.2,
|
opacity: 0.4,
|
||||||
});
|
});
|
||||||
let matSemHL = ATON.MatHub.materials.semanticShapeHL;
|
let matSemHL = new THREE.MeshStandardMaterial({
|
||||||
|
color: '#e9757b',
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const semNode = ATON.createSemanticNode(id)
|
const semNode = ATON.createSemanticNode(id)
|
||||||
.setCloneOnLoadHit(false)
|
.setCloneOnLoadHit(false)
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ let AppState = {
|
|||||||
* @type {NormalizedSceneNode[]} normalizedNodes
|
* @type {NormalizedSceneNode[]} normalizedNodes
|
||||||
*/
|
*/
|
||||||
normalizedNodes: [],
|
normalizedNodes: [],
|
||||||
|
/**
|
||||||
|
* The loaded ontology object
|
||||||
|
*/
|
||||||
|
ontology: {},
|
||||||
treeNodes: {},
|
treeNodes: {},
|
||||||
semanticNodes: new Map,
|
semanticNodes: new Map,
|
||||||
mainNodeId: null,
|
mainNodeId: null,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
* @property {Boolean} isMain
|
* @property {Boolean} isMain
|
||||||
* @property {Number|null} opacity
|
* @property {Number|null} opacity
|
||||||
* @property {Boolean} active
|
* @property {Boolean} active
|
||||||
|
* @property {String} content
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user