snippet selection (and switchWireframe bugfix)

keyboardless-jxr-editing
Fabien Benetou 9 months ago
parent eb8c9e3636
commit f2a20c291c
  1. 141
      index.html
  2. 4
      jxr.js

@ -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>

@ -1034,8 +1034,8 @@ function switchToWireframe(){
// should actually be just for src, not for text notes... even though could be interesting // should actually be just for src, not for text notes... even though could be interesting
}) })
*/ */
var model = document.querySelector("#environment").object3D var model = document.querySelector("#environment")?.object3D
model.traverse( o => { if (o.material) { if (model) model.traverse( o => { if (o.material) {
o.material.wireframe = visible; o.material.wireframe = visible;
o.material.opacity = visible ? 0.05 : 1; o.material.opacity = visible ? 0.05 : 1;
o.material.transparent = visible; o.material.transparent = visible;

Loading…
Cancel
Save