SpaSca : open SCAffolding to SPAcially and textualy explore interfaces https://fabien.benetou.fr/pub/home/future_of_text_demo/engine/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
text-code-xr-engine/index.html

288 lines
15 KiB

<!DOCTYPE html>
<html>
<title>SpaSca : Spatial Scaffolding</title>
<head>
<!-- Suggestions? https://git.benetou.fr/utopiah/text-code-xr-engine/issues/ -->
<script src='dependencies/aframe.offline.min.js'></script>
<script src="dependencies/a-console.js"></script>
<script src='dependencies/aframe-troika-text.min.js'></script>
<script src='dependencies/webdav.js'></script>
<script src='jxr-core.js?12345'></script>
<script src='jxr-postitnote.js?13235'></script>
</head>
<body>
<script>
/*
SpaSca circular menu inspired by https://www.olegfrolov.design/spatialcomputing or the Meta OS menu
on empty pinch moving, if (moving duration above threshold 500ms)
then
show menu nearby position with JXR commands that hides on released,
on moving
highlight closest snippet
rewrite nearby getClosestTargetElements() to be only over a subset or targets, i.e the ones from active menu
on release
if (nearby one of the created JXR snippets), execute it
remove those JXR commands
<a-troika-text id="spatial-introspection-test" anchor=left value="console.log('executing from secondary pinch');"
onreleased="console.log('run on released')"
onpicked="console.log('run on picked')"
target position=" -0.3 1.35 0" rotation="0 40 0" scale="0.1 0.1 0.1">
</a-troika-text>
*/
var library
AFRAME.registerComponent('library-load', {
init: function(){
//fetch("../content/library-acm22.json").then( res => res.json() ).then( res => {
fetch("https://webdav.benetou.fr/fotsave/test_newfot.json").then( res => res.json() ).then( res => {
library = res
console.log(res)
res.documents.map( (doc,i) => {
if (doc.customData[0].position)
addNewNote( doc.title, doc.customData[0].position )
else
addNewNote( doc.title, '.5 '+(1+i/50)+' -.5' )
.setAttribute("onreleased", "updateDatasetThenSave(e.detail.element)")
})
})
}
})
function updateDatasetThenSave(element){
if (!library) return
let pos = element.getAttribute('position')
let title = element.getAttribute('value')
console.log(pos, title)
library.documents.filter( el => el.title == title ).map( found => found.customData[0].position = pos )
//library.documents.filter( el => el.title == title ).map( found => console.log( found.customData[0] ) )
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/test_newfot.json", JSON.stringify(library) )
setFeedbackHUD( "file saved" )
}
function cloneTarget(target){
let el = target.cloneNode(true)
if (!el.id)
el.id = "clone_" + crypto.randomUUID()
else
el.id += "_clone_" + crypto.randomUUID()
AFRAME.scenes[0].appendChild(el)
}
function deleteTarget(target){
targets = targets.filter( e => e != target)
target.remove()
}
function runClosestJXR(){
// ideally this would come from event details
let latest = selectedElements[selectedElements.length-1].element
let el = getClosestTargetElement( latest.getAttribute('position') )
if (el) interpretJXR( el.getAttribute("value") )
}
// should move to jxr-core.js
AFRAME.registerComponent('onemptypinch', { // changed from ondrop to be coherent with event name
init: function(){
AFRAME.scenes[0].addEventListener('enter-vr', e => {
console.log('entered vr')
document.querySelector("[cursor]").setAttribute("visible", "true")
document.querySelector("[camera]").setAttribute("cursor", "")
})
this.menuTargets = []
this.menuEl = document.createElement("a-box")
this.menuEl.setAttribute("width", ".01")
this.menuEl.setAttribute("height", ".01")
this.menuEl.setAttribute("depth", ".01")
//this.menuEl.setAttribute("scale", ".01 .01 .01") // breaking moving targets as children
this.menuEl.setAttribute("opacity", 0)
this.menuEl.setAttribute("color", "red")
this.menuEl.setAttribute("wireframe", "true")
this.menuEl.id = "handprimarymenu"
this.menuOpacity = 0
AFRAME.scenes[0].appendChild(this.menuEl)
let namedCSSColors = [ "aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige", "bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown", "burlywood", "cadetblue", "chartreuse", "chocolate", "coral", "cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan", "darkgoldenrod", "darkgray", "darkgreen", "darkkhaki", "darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred", "darksalmon", "darkseagreen", "darkslateblue", "darkslategray", "darkturquoise", "darkviolet", "deeppink", "deepskyblue", "dimgray", "dodgerblue", "firebrick", "floralwhite", "forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod", "gray", "green", "greenyellow", "honeydew", "hotpink", "indianred", "indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen", "lemonchiffon", "lightblue", "lightcoral", "lightcyan", "lightgoldenrodyellow", "lightgreen", "lightgrey", "lightpink", "lightsalmon", "lightseagreen", "lightskyblue", "lightslategray", "lightsteelblue", "lightyellow", "lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine", "mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen", "mediumslateblue", "mediumspringgreen", "mediumturquoise", "mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin", "navajowhite", "navy", "navyblue", "oldlace", "olive", "olivedrab", "orange", "orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise", "palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum", "powderblue", "purple", "red", "rosybrown", "royalblue", "saddlebrown", "salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver", "skyblue", "slateblue", "slategray", "snow", "springgreen", "steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet", "wheat", "white", "whitesmoke", "yellow", "yellowgreen"]
//let colors =["green", "blue", "orange", "yellow", "brown", "black"].map( (c,i) => {
let colors = namedCSSColors.slice(0,20).map( (c,i) => {
let newEl = document.createElement("a-box")
let x = (i/100+.01)
let z = -.1 // i%10
newEl.setAttribute("position", "" + x + " 0 " + z)
newEl.setAttribute("opacity", 0)
newEl.setAttribute("scale", ".01 .01 .01")
newEl.setAttribute("wireframe", "true")
newEl.setAttribute("color", c )
newEl.setAttribute("target", "true" ) // does not work as on same hand
// requires to enter a mode, i.e edit mode, that would let it be visible and movable
// seems to lose position while rotation does work
// wondering if truly empty, should be exclusive events, can't have emptypinch and pinched at the same time
// note that getClosestElements() filter out non visible element, but not when opacity is 0
// currently we can move now elements that are with opacity 0 yet visible
// maybe position works but scale is not taking into account the parent scale, only its position, so we move too little
// try to then stick to scale 1 for now then
this.menuEl.appendChild( newEl )
this.menuTargets.push( newEl )
})
this.menuTargets.push( this.menuEl )
},
// could support multi
events: {
emptypinch: function (e) {
this.menuEl.setAttribute("position", e.detail.position)
// works with AFRAME.scenes[0].emit('emptypinch', {position:"0 0 0"})
let code = this.el.getAttribute('onemptypinch')
// if multi, should also look for onreleased__ not just onreleased
try {
eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt)
} catch (error) {
console.error(`Evaluation failed with ${error}`);
}
},
emptypinchmoved: function (e) {
let foundElement = getClosestElement( e.detail.position, threshold=0.01, this.menuTargets )
this.menuTargets.map( mt => mt.setAttribute("wireframe", "true") )
if ( foundElement ) foundElement.setAttribute("wireframe", "false")
if (this.menuOpacity < 1) {
this.menuOpacity += .01
this.menuTargets.map( mt => mt.setAttribute("opacity", this.menuOpacity) )
}
let code = this.el.getAttribute('onemptypinchmoved')
// if multi, should also look for onreleased__ not just onreleased
try {
eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt)
} catch (error) {
console.error(`Evaluation failed with ${error}`);
}
},
emptypinchreleased: function (e) {
// could also check if above this.menuOpacity threshold, e.g not doing below .3 to avoid unexpected action
let foundElement = getClosestElement( e.detail.position, threshold=0.02, this.menuTargets )
// does not seem to execute anymore after teleporting, did work before though
if ( foundElement ){
document.getElementById("box").setAttribute("color", foundElement.getAttribute("color") )
}
this.menuOpacity = 0
this.menuEl.setAttribute("opacity", this.menuOpacity)
this.menuTargets.map( mt => mt.setAttribute("opacity", this.menuOpacity) )
let code = this.el.getAttribute('onemptypinchreleased')
// if multi, should also look for onreleased__ not just onreleased
try {
eval( code ) // should be jxr too e.g if (txt.match(prefix)) interpretJXR(txt)
} catch (error) {
console.error(`Evaluation failed with ${error}`);
}
},
}
})
function onHoveredTeleport(){
// iterate over targets
// see instead of teleportable https://aframe.io/docs/1.5.0/components/cursor.html#configuring-the-cursor-through-the-raycaster-component
Array.from( document.querySelectorAll("[teleporter]") ).map( target => {
if ( target.states.includes( "cursor-hovered" ) ){
target.setAttribute("material", "color", "magenta") // visited
document.getElementById('rig').setAttribute('position', target.getAttribute("position") )
}
})
}
AFRAME.registerComponent('teleporter', {
init: function(){
this.el.setAttribute("opacity", .5)
},
events: {
mouseenter: function (e) { this.el.setAttribute("opacity", .8) },
mouseleave: function (e) { this.el.setAttribute("opacity", .5) },
click: function (e) { document.getElementById('rig').setAttribute('position', this.el.getAttribute("position") ) }
// makes it compatible with mouse on desktop ... but also somehow enable the wrist shortcut?!
}
});
</script>
<div style='position:fixed;z-index:1; top: 0%; left: 0%; border-bottom: 70px solid transparent; border-left: 70px solid #eee; '>
<a href="https://git.benetou.fr/utopiah/text-code-xr-engine/issues/">
<img style='position:fixed;left:10px;' title='code repository' src='gitea_logo.svg'>
</a>
</div>
<a-scene startfunctions library-load onemptypinch="onHoveredTeleport()" >
<a-gltf-model hide-on-enter-ar="" id="environment" src="../content/CubeRoom.glb" rotation="0 -90 0" position="0 0 1" scale="" ></a-gltf-model>
<!-- Cube Room by Anonymous [CC-BY] via Poly Pizza -->
<a-entity id="rig">
<a-entity id="player" networked="template:#avatar-template;attachTemplateToLocal:false;"
hud camera look-controls wasd-controls position="0 1.6 0">
<a-entity cursor position="0 0 -1"
geometry="primitive: ring; radiusInner: 0.005; radiusOuter: 0.01"
material="color: black; shader: flat; opacity:.05;"
></a-entity>
</a-entity>
<a-entity id="rightHand" pinchprimary hand-tracking-controls="hand: right;"></a-entity>
<a-entity id="leftHand" pinchsecondary wristattachsecondary="target: #box" hand-tracking-controls="hand: left;"></a-entity>
<a-console position="2 2 0" rotation="0 -45 0" font-size="34" height=1 skip-intro=true></a-console>
</a-entity>
<a-box pressable start-on-press id="box" scale="0.05 0.05 0.05" color="pink"></a-box>
<a-troika-text value="SpaSca : Spatial Scaffolding" anchor="left" outline-width="5%" font="../content/ChakraPetch-Regular.ttf" position="-3 5 -2"
scale="3 3 3" rotation="80 0 0" troika-text="outlineWidth: 0.01; strokeColor: #ffffff" material="flatShading: true; blending: additive; emissive: #c061cb"></a-troika-text>
<a-sky hide-on-enter-ar color="lightgray"></a-sky>
<a-troika-text anchor=left target value="instructions : \n--right pinch to move\n--left pinch to execute" position="0 0.65 -0.2" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="jxr onNextPrimaryPinch(deleteTarget)" target position=" -0.3 1.50 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="jxr onNextPrimaryPinch(cloneTarget)" target position=" -0.3 1.60 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="jxr location.reload()" target position=" -0.3 1.30 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="jxr makeAnchorsVisibleOnTargets()" target position=" -0.3 1.20 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="forceXaxis toggling"
onreleased="console.log('run on released');forceXaxis=!forceXaxis"
onpicked="console.log('run on picked');forceXaxis=!forceXaxis"
target position=" -0.3 1.45 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="translate targets"
onreleased="toggleTranslateTargets()"
onpicked="toggleTranslateTargets()"
target position=" 1 1.45 -.2" rotation="0 -40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-troika-text anchor=left value="jxr setTimeout( _ => toggleAttachToSelf(), 1000); toggleAttachToSelf()" target position=" -0.3 1.25 0" rotation="0 40 0" scale="0.1 0.1 0.1"></a-troika-text>
<a-box teleporter height=".1" class="teleportable" material="color: cyan" position="3.5 0 -3.5" ></a-box>
<a-box teleporter height=".1" class="teleportable" material="color: cyan" position="-4 0 4" ></a-box>
<a-box teleporter height=".1" class="teleportable" material="color: cyan" position="3 3 4" >
<a-troika-text anchor=left value="jxr location.reload()" target position="0 1.30 -.5" rotation="0 0 0" scale="0.1 0.1 0.1"></a-troika-text>
<!-- works to execute but not to move, should either reparent or take into account the parent offset while moving -->
<!-- see pinchmoved in primary pinch in jxr-core.js as potential solution -->
</a-box>
<a-box teleporter height=".1" class="teleportable" material="color: cyan" position="0 0 0" ></a-box>
<a-troika-text id="spatial-introspection-test" anchor=left value="console.log('executing from secondary pinch');"
onreleased="console.log('run on released')"
onpicked="console.log('run on picked')"
target position=" -0.3 1.35 0" rotation="0 40 0" scale="0.1 0.1 0.1">
</a-troika-text>
<!-- should probably do so for the menu root too -->
<a-troika-text anchor=left value="jxr Array.from( document.getElementById('handprimarymenu').children ).map( el => el.setAttribute('opacity', 0))"
target position=" -0.3 1.53 0" rotation="0 40 0" scale="0.1 0.1 0.1">
</a-troika-text>
<a-troika-text anchor=left value="jxr Array.from( document.getElementById('handprimarymenu').children ).map( el => el.setAttribute('opacity', 1))"
target position=" -0.3 1.55 0" rotation="0 40 0" scale="0.1 0.1 0.1">
</a-troika-text>
</a-scene>
</body>
</script>
</html>