|
|
@ -31,7 +31,7 @@ |
|
|
|
<!-- still experimenting, see webdav.html --> |
|
|
|
<!-- still experimenting, see webdav.html --> |
|
|
|
<script src='dependencies/webdav.js'></script> |
|
|
|
<script src='dependencies/webdav.js'></script> |
|
|
|
|
|
|
|
|
|
|
|
<script src='jxr.js?212345'></script> |
|
|
|
<script src='jxr.js?12345'></script> |
|
|
|
<!-- replacing with local copies as CDNs are like unpkg tend to be slow |
|
|
|
<!-- replacing with local copies as CDNs are like unpkg tend to be slow |
|
|
|
<script type="module" src="https://unpkg.com/immers-client/dist/destination.bundle.js"></script> |
|
|
|
<script type="module" src="https://unpkg.com/immers-client/dist/destination.bundle.js"></script> |
|
|
|
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script> |
|
|
|
<script src="https://aframe.io/releases/1.3.0/aframe.min.js"></script> |
|
|
@ -55,66 +55,91 @@ |
|
|
|
<body> |
|
|
|
<body> |
|
|
|
|
|
|
|
|
|
|
|
<script> |
|
|
|
<script> |
|
|
|
function loadFile(element){ |
|
|
|
/* |
|
|
|
const file = element.files[0] |
|
|
|
motivated composability sketches done the 29/3/2024 with igloo example |
|
|
|
const reader = new FileReader(); |
|
|
|
|
|
|
|
reader.addEventListener( "load", () => { |
|
|
|
see past branches indluding |
|
|
|
writecsljsonback(reader.result) |
|
|
|
bind-jxr-target |
|
|
|
}, false,); |
|
|
|
gltf-jxr |
|
|
|
|
|
|
|
but also possibly |
|
|
|
if (file) { |
|
|
|
visual-tension |
|
|
|
reader.readAsText(file); |
|
|
|
nodal |
|
|
|
|
|
|
|
ideally not full on editors e.g |
|
|
|
|
|
|
|
editor-split |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
highlight could start as |
|
|
|
|
|
|
|
x use a jxr command to pre-select an existing jxr snippet (already exists somewhere in past work, at least for classes...) |
|
|
|
|
|
|
|
x place a cursor e.g | at the beginning of the snippet copied in the feedback HUD |
|
|
|
|
|
|
|
x moving the index along the x axis moves the cursor within that snippet on HUD |
|
|
|
|
|
|
|
x pinching starts the selection, all characters within that selected are bolden |
|
|
|
|
|
|
|
x if not (yet) supported by troika then consider another color, e.g unselected text as white, selected as grey |
|
|
|
|
|
|
|
x releasing the pinch complete the selection |
|
|
|
|
|
|
|
new affordance can thus be proposed as jxr snippets applying not to the entire jxr snippet pre-selected but selection only |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
cf e.g |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
tensionVisualized() |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// generalized version of past onNextPinchSplitReader() |
|
|
|
|
|
|
|
function onNextPinch(callback){ |
|
|
|
|
|
|
|
let lastPrimary = selectedElements.filter( e => e.primary ).length |
|
|
|
|
|
|
|
let checkForNewPinches = setInterval( _ => { |
|
|
|
|
|
|
|
if (selectedElements.filter( e => e.primary ).length > lastPrimary){ |
|
|
|
|
|
|
|
let id = getIdFromPick() // applies on primary only |
|
|
|
|
|
|
|
if (id) { callback(id) } |
|
|
|
|
|
|
|
clearInterval(checkForNewPinches) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
}, 50) // relatively cheap check, filtering on small array |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const libraryURL = 'https://webdav.benetou.fr/fotsave/ExportedItems-FromZoteroAsCSLJSON.json' |
|
|
|
let selection = '' |
|
|
|
AFRAME.registerComponent('getcsljson', { |
|
|
|
function highlightSnippet(id){ |
|
|
|
|
|
|
|
let value = document.getElementById(id).getAttribute('value') |
|
|
|
|
|
|
|
// perdiodically check on x position of left index and move | accordingly |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let p = document.querySelector('[pinchprimary]') |
|
|
|
|
|
|
|
let target = new THREE.Vector3(); // create once an reuse it |
|
|
|
|
|
|
|
let currentCharPos = -1 |
|
|
|
|
|
|
|
let pinchStartedPos = -1 |
|
|
|
|
|
|
|
let pinchEndedPos = -1 |
|
|
|
|
|
|
|
p.addEventListener('pinchstarted', event => { pinchEndedPos = -1; pinchStartedPos = currentCharPos } ) |
|
|
|
|
|
|
|
p.addEventListener('pinchended', event => pinchEndedPos = currentCharPos) |
|
|
|
|
|
|
|
let indexTipTracking = setInterval( _ => { |
|
|
|
|
|
|
|
target = p.components['hand-tracking-controls'].indexTipPosition |
|
|
|
|
|
|
|
let position = target.x * 100 // should use relative position but easier to start with |
|
|
|
|
|
|
|
currentCharPos = position |
|
|
|
|
|
|
|
let nvalue = value.slice(0,position) + '|' + value.slice(position) |
|
|
|
|
|
|
|
// setFeedbackHUD( nvalue ) not good as it autoclears after |
|
|
|
|
|
|
|
// console.log(pinchStartedPos, pinchEndedPos) |
|
|
|
|
|
|
|
// should offset based on past modification |
|
|
|
|
|
|
|
if (pinchStartedPos>-1) nvalue = nvalue.slice(0,pinchStartedPos) + '<' + nvalue.slice(pinchStartedPos) |
|
|
|
|
|
|
|
if (pinchEndedPos>-1) nvalue = nvalue.slice(0,pinchEndedPos) + '>' + nvalue.slice(pinchEndedPos) |
|
|
|
|
|
|
|
document.querySelector("#feedbackhud").setAttribute("value",nvalue) |
|
|
|
|
|
|
|
// works but could also consider cloning the initial element from id then moving it slight forward and changing color on the cloned one |
|
|
|
|
|
|
|
let newSelection = '' |
|
|
|
|
|
|
|
if (pinchStartedPos > -1 && pinchEndedPos > -1) newSelection = value.slice(pinchStartedPos,pinchEndedPos) |
|
|
|
|
|
|
|
if (newSelection.length > 0 && selection != newSelection ){ |
|
|
|
|
|
|
|
AFRAME.scenes[0].emit( 'selectionchanged' , {id: id, selection: newSelection} ) |
|
|
|
|
|
|
|
// works... yet emits on pinchstarted too |
|
|
|
|
|
|
|
selection = newSelection |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
}, 20) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
AFRAME.registerComponent('onselectionchange', { |
|
|
|
schema: { |
|
|
|
schema: { |
|
|
|
url: {type: 'string', default: libraryURL }, |
|
|
|
// callback: {type: 'string', default: console.log }, // doubt functions can be a type |
|
|
|
}, |
|
|
|
}, |
|
|
|
init: function(){ |
|
|
|
init: function(){ |
|
|
|
let generatorName = this.attrName |
|
|
|
let generatorName = this.attrName |
|
|
|
fetch(this.data.url).then(res => res.json() ).then(res => { notesFromArray(res, generatorName, "title", 2, -1/10) }) |
|
|
|
AFRAME.scenes[0].addEventListener('selectionchanged', event => console.log( 'selected text in snippet', event.detail.id, ':', event.detail.selection ) ) |
|
|
|
//fetch(this.data.url).then(res => res.json() ).then(res => { notesFromArray(res, generatorName) }) |
|
|
|
// here console.log could be callback |
|
|
|
// could use citeproc instead |
|
|
|
|
|
|
|
// see also https://citation.js.org |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
function notesFromArray(data, generatorName="", field="title", offset=1, ratio=1/10, depth=-.5 ){ |
|
|
|
|
|
|
|
data.slice(0,maxItemsFromSources).map( (n,i) => addNewNote( n[field], "0 "+(offset+i*ratio)+" "+depth, ".1 .1 .1", null, generatorName ) ) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
function getDataToSaveBack(){ |
|
|
|
|
|
|
|
let dataToSave = [] |
|
|
|
|
|
|
|
let unsorted = [] |
|
|
|
|
|
|
|
fetch(libraryURL).then(res => res.json() ).then(res => { |
|
|
|
|
|
|
|
// assume unique title always present |
|
|
|
|
|
|
|
getArrayFromClass('getcsljson').map(i=>{ |
|
|
|
|
|
|
|
unsorted.push( {data: res.filter(citation=>citation.title==i.getAttribute('value'))[0], |
|
|
|
|
|
|
|
position: i.getAttribute('position') |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
dataToSave = unsorted.sort((a,b)=>b.position.y-a.position.y) |
|
|
|
|
|
|
|
.map(i=> { if (!i.data.note) { i.data.note = JSON.stringify(i.position) } else {i.data.note += '\n' + JSON.stringify(i.position) } ; return i.data } ) |
|
|
|
|
|
|
|
// this is also where piggy-backing on CSL-JSON could be tested, e.g spatial position (or stringifoied pose) field added |
|
|
|
|
|
|
|
// could consider appending to the .note field instead |
|
|
|
|
|
|
|
// .position does get saved, and does not prevent to be loaded from Zotero, but does get lost after when exported again |
|
|
|
|
|
|
|
// cf https://citeproc-js.readthedocs.io/en/latest/csl-json/markup.html#cheater-syntax-for-odd-fields |
|
|
|
|
|
|
|
writecsljsonback(JSON.stringify(dataToSave)) |
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// data could come from parsing back order from getArrayFromClass('getcsljson').map(i=>i.getAttribute('position').y) |
|
|
|
|
|
|
|
// cf https://gist.github.com/Utopiah/26bae9fecc7a921f8bfd38cf5fc91612#file-logo_vr_hubs-js-L44 |
|
|
|
|
|
|
|
// yet still needs the actual data itself and adding a comment field for position if to be used back here rather than e.g Zotero |
|
|
|
|
|
|
|
function writecsljsonback(data){ |
|
|
|
|
|
|
|
const webdavurl = "https://webdav.benetou.fr"; |
|
|
|
|
|
|
|
const client = window.WebDAV.createClient(webdavurl) |
|
|
|
|
|
|
|
async function w(path, data){ return await client.putFileContents(path, data); } |
|
|
|
|
|
|
|
w("/fotsave/ExportedItems-FromZoteroAsCSLJSON.json", data ) |
|
|
|
|
|
|
|
setFeedbackHUD( "file saved" ) |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
</script> |
|
|
|
</script> |
|
|
|
<input style='position:fixed;z-index:1; top: 0%; left: 20%; display:float' |
|
|
|
<input style='position:fixed;z-index:1; top: 0%; left: 20%; display:float' |
|
|
|
type="file" name="file-input" accept=".json" id="file-input" onchange="loadFile(this)" /> |
|
|
|
type="file" name="file-input" accept=".json" id="file-input" onchange="loadFile(this)" /> |
|
|
@ -126,7 +151,7 @@ function writecsljsonback(data){ |
|
|
|
|
|
|
|
|
|
|
|
<button id=mainbutton style="display:none; z-index: 1; position: absolute; width:50%; margin: auto; text-align:center; top:45%; left:30%; height:30%;" onclick="startExperience()">Start the experience (hand tracking recommended)</button> |
|
|
|
<button id=mainbutton style="display:none; z-index: 1; position: absolute; width:50%; margin: auto; text-align:center; top:45%; left:30%; height:30%;" onclick="startExperience()">Start the experience (hand tracking recommended)</button> |
|
|
|
|
|
|
|
|
|
|
|
<a-scene startfunctions getcsljson> |
|
|
|
<a-scene startfunctions onselectionchange> |
|
|
|
<!-- screenstack dynamic-view selectionboxonpinches glossary timeline issues fot |
|
|
|
<!-- screenstack dynamic-view selectionboxonpinches glossary timeline issues fot |
|
|
|
toolbox commands-from-external-json disable-components-via-url enable-components-via-url |
|
|
|
toolbox commands-from-external-json disable-components-via-url enable-components-via-url |
|
|
|
physics="debug:true; friction: 0.01;" |
|
|
|
physics="debug:true; friction: 0.01;" |
|
|
@ -147,8 +172,14 @@ function writecsljsonback(data){ |
|
|
|
<!-- from https://poly.pizza/m/8cWuXx5BASV --> |
|
|
|
<!-- from https://poly.pizza/m/8cWuXx5BASV --> |
|
|
|
<!-- alt https://poly.pizza/m/cA_lcvRC4NA --> |
|
|
|
<!-- alt https://poly.pizza/m/cA_lcvRC4NA --> |
|
|
|
|
|
|
|
|
|
|
|
<a-troika-text anchor=left target annotation="content:saves data back to Zotero library over WebDAV backend" |
|
|
|
<a-troika-text anchor=left target annotation="content:could be used to then edit that snippet" |
|
|
|
value="jxr getDataToSaveBack()" position=" -0.3 0.60 -.5" rotation="0 0 0" scale="0.1 0.1 0.1"></a-troika-text> |
|
|
|
value="jxr onNextPinch(setFeedbackHUD)" position=" -0.3 0.60 -.5" rotation="0 0 0" scale="0.1 0.1 0.1"></a-troika-text> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-troika-text anchor=left target annotation="content:then edit that snippet" |
|
|
|
|
|
|
|
value="jxr onNextPinch(highlightSnippet)" position=" -0.3 1.20 -.5" rotation="0 0 0" scale="0.1 0.1 0.1"></a-troika-text> |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<a-troika-text anchor=left target annotation="content:refresh page while keeping XR on" |
|
|
|
|
|
|
|
value="jxr location.reload()" position=" -0.3 1.50 -.5" rotation="0 0 0" scale="0.1 0.1 0.1"></a-troika-text> |
|
|
|
|
|
|
|
|
|
|
|
<a-entity id="rig"> |
|
|
|
<a-entity id="rig"> |
|
|
|
<a-entity id="player" networked="template:#avatar-template;attachTemplateToLocal:false;" |
|
|
|
<a-entity id="player" networked="template:#avatar-template;attachTemplateToLocal:false;" |
|
|
@ -181,8 +212,8 @@ function writecsljsonback(data){ |
|
|
|
|
|
|
|
|
|
|
|
<!-- somehow disable hand interaction despite, according to the documentation, it should rely on world position |
|
|
|
<!-- somehow disable hand interaction despite, according to the documentation, it should rely on world position |
|
|
|
<a-text target value="jxr qs #rig sa position 0 0 10" position="0 1.55 .5" rotation="0 180 0" scale="0.1 0.1 0.1"></a-text> |
|
|
|
<a-text target value="jxr qs #rig sa position 0 0 10" position="0 1.55 .5" rotation="0 180 0" scale="0.1 0.1 0.1"></a-text> |
|
|
|
<a-console position="2 2 0" rotation="0 -45 0" font-size="34" height=1 skip-intro=true></a-console> |
|
|
|
|
|
|
|
--> |
|
|
|
--> |
|
|
|
|
|
|
|
<a-console position="2 2 -1" rotation="0 -45 0" font-size="34" height=1 skip-intro=true></a-console> |
|
|
|
|
|
|
|
|
|
|
|
<!-- for Wolvic on Lynx support test --> |
|
|
|
<!-- for Wolvic on Lynx support test --> |
|
|
|
<a-entity thumbstick-shifting oculus-touch-controls="hand: left"></a-entity> |
|
|
|
<a-entity thumbstick-shifting oculus-touch-controls="hand: left"></a-entity> |
|
|
|