diff --git a/css/app.css b/css/app.css
index 615d383..2b76c52 100644
--- a/css/app.css
+++ b/css/app.css
@@ -57,6 +57,16 @@ input[type="checkbox"] {
padding-left: 0;
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 */
span:after {
text-align: left;
diff --git a/scenes/ssgp/index.html b/scenes/ssgp/index.html
index 47b19d3..a8126c2 100644
--- a/scenes/ssgp/index.html
+++ b/scenes/ssgp/index.html
@@ -58,7 +58,7 @@
-
+
+
+
+
+
+
+
+
+
+
Sono inesorabilmente vuoto...
+
+
+
+
diff --git a/src/controllers/menu_controller.js b/src/controllers/menu_controller.js
index cfe911e..2d9e75b 100644
--- a/src/controllers/menu_controller.js
+++ b/src/controllers/menu_controller.js
@@ -14,6 +14,7 @@ export default class extends Controller {
async connect() {
console.log('#menu controller connected');
this.ontologyObject = await loadOntology(ontologyJsonPath);
+ AppState.ontology = this.ontologyObject;
}
/**
* Open settings panel
@@ -240,7 +241,6 @@ export default class extends Controller {
tab.appendChild(content);
const classesTab = content.querySelector('#classes');
- const instancesTab = content.querySelector('#instances');
for (let domain of domains) {
const heading = document.createElement('span');
@@ -266,17 +266,20 @@ export default class extends Controller {
classesTab.appendChild(domainList);
}
+ const instancesTab = content.querySelector('#instances');
+ const navList = instancesTab.querySelector('ul');
const sortedItLabels = ontology.getInstanceLabels().map(l => l.it).sort();
for (let label of sortedItLabels) {
- const instanceItem = document.createElement('div');
- instanceItem.className = 'border-bottom ps-3 py-2 c-hand';
- instanceItem.textContent = label;
- instanceItem.setAttribute('data-controller', 'node');
- instanceItem.setAttribute('data-node-id-value', label);
- instanceItem.setAttribute('data-action', 'click->node#transition');
+ const instanceItem = document.createElement('li');
+ instanceItem.className = 'nav-item border-bottom ps-3 py-2 c-hand';
+ instanceItem.innerHTML = html`
+ ${label}
+ `;
+ 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`
`;
diff --git a/src/controllers/node_controller.js b/src/controllers/node_controller.js
index f5b8517..161c4b7 100644
--- a/src/controllers/node_controller.js
+++ b/src/controllers/node_controller.js
@@ -1,13 +1,16 @@
// Global ATON
import { Controller } from "@hotwired/stimulus"
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,
* related to the clipping module
*/
export default class extends Controller {
- static targets = ['instance', 'node'];
+ static targets = ['instance', 'node', 'panel'];
static values = {
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() {
- const node = ATON.getSceneNode(this.idValue);
+ open({ params: {id} }) {
+ const node = ATON.getSceneNode(id);
+ const normNode = AppState.normalizedNodes.find(n => n.id === id);
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`
+ ${prop.propertyLabel.it ?? prop.propertyLabel.en}
+
+ ${prop.value.value}
+ `;
+
+ list.appendChild(item);
+ }
+ }
+
+ panel.classList.remove('d-none');
+ }
+
+ closeInstancePanel() {
+
+ }
+}
diff --git a/src/ontology.js b/src/ontology.js
index 6afe7c4..db2b9cf 100644
--- a/src/ontology.js
+++ b/src/ontology.js
@@ -19,6 +19,31 @@ export class Ontology {
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
*/
diff --git a/src/scene.js b/src/scene.js
index 44052cc..c906b39 100644
--- a/src/scene.js
+++ b/src/scene.js
@@ -107,12 +107,6 @@ function loadNodes(nodes) {
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({
id: n.label,
active: n.isInvisible ? false: true,
@@ -130,14 +124,19 @@ function loadNodes(nodes) {
* @param {string} id The original scene node ID (3D object)
* @returns {ATON.SceneNode}
*/
-function createSemanticNode(model, id) {
+export function createSemanticNode(model, id) {
// Default/highlight materials for semantic node
let matSemDef = new THREE.MeshStandardMaterial({
- color: '#f7e6af',
+ color: '#81eb81',
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)
.setCloneOnLoadHit(false)
diff --git a/src/state.js b/src/state.js
index 973615c..4f55810 100644
--- a/src/state.js
+++ b/src/state.js
@@ -23,6 +23,10 @@ let AppState = {
* @type {NormalizedSceneNode[]} normalizedNodes
*/
normalizedNodes: [],
+ /**
+ * The loaded ontology object
+ */
+ ontology: {},
treeNodes: {},
semanticNodes: new Map,
mainNodeId: null,
diff --git a/src/utils/nodeUtils.js b/src/utils/nodeUtils.js
index 46b24cc..e3adc6e 100644
--- a/src/utils/nodeUtils.js
+++ b/src/utils/nodeUtils.js
@@ -20,6 +20,7 @@
* @property {Boolean} isMain
* @property {Number|null} opacity
* @property {Boolean} active
+ * @property {String} content
*/
/**