editor-split
Fabien Benetou 2 years ago
parent b8cdd97aac
commit 5240ab9ff9
  1. 121
      index.html

@ -2214,27 +2214,23 @@ function startExperience(){
document.querySelector("#mainbutton").style.display = "none" document.querySelector("#mainbutton").style.display = "none"
} }
let codeEditor
// should consider multiple instances instead // should consider multiple instances instead
let codeEditor = { let editors = []
element: null, // track created editors then apply actions to the currently selected one
line: 0, // problems happen when relying on querySelector/getElementById rather than a specific editor
page: null, // do these based on codeEditor.element rather than document
startWindowRange: 0, // still probably problematic for interactions
lengthWindowRange: 20, // consider for now only the currentEditor
scrollInterval: null,
currentlyDisplayedText: "", function nextLineCodeEditor(codeEditor, lines=1){ // can be negative to scroll up
caret: null,
language: null,
}
function nextLineCodeEditor(lines=1){ // can be negative to scroll up
if (codeEditor.line+lines < 0) return if (codeEditor.line+lines < 0) return
codeEditor.line+=lines codeEditor.line+=lines
let content=codeEditor.page.split("\n").slice(codeEditor.line,codeEditor.line+codeEditor.lengthWindowRange).join("\n"); let content=codeEditor.page.split("\n").slice(codeEditor.line,codeEditor.line+codeEditor.lengthWindowRange).join("\n");
codeEditor.currentlyDisplayedText=content codeEditor.currentlyDisplayedText=content
codeEditor.element.setAttribute("troika-text", {value: content}) codeEditor.element.setAttribute("troika-text", {value: content})
if (codeEditor.language) codeEditor.element.setAttribute("troika-text", {colorRanges: highlight(content, language='javascript')}) if (codeEditor.language) codeEditor.element.setAttribute("troika-text", {colorRanges: highlight(content, language='javascript')})
let gutterEl = document.getElementById("leftgutter") let gutterEl = codeEditor.element.querySelector(".leftgutter")
if (gutterEl){ if (gutterEl){
let lineNumbers = "\n" let lineNumbers = "\n"
for (let i=codeEditor.line+1;i<=codeEditor.line+codeEditor.lengthWindowRange;i++){ for (let i=codeEditor.line+1;i<=codeEditor.line+codeEditor.lengthWindowRange;i++){
@ -2245,7 +2241,7 @@ function nextLineCodeEditor(lines=1){ // can be negative to scroll up
gutterEl.setAttribute("troika-text", {value: lineNumbers}) gutterEl.setAttribute("troika-text", {value: lineNumbers})
} }
let rightGutterEl = document.getElementById("rightgutter") let rightGutterEl = codeEditor.element.querySelector(".rightgutter")
if (rightGutterEl){ if (rightGutterEl){
b = rightGutterEl.parentElement.object3D.children[0]._textRenderInfo.blockBounds b = rightGutterEl.parentElement.object3D.children[0]._textRenderInfo.blockBounds
w = b[2]-b[0] w = b[2]-b[0]
@ -2257,23 +2253,23 @@ function nextLineCodeEditor(lines=1){ // can be negative to scroll up
} }
} }
function nextPageCodeEditor(){ function nextPageCodeEditor(codeEditor, ){
nextLineCodeEditor(codeEditor.lengthWindowRange) nextLineCodeEditor(codeEditor, codeEditor.lengthWindowRange)
} }
function previousPageCodeEditor(){ function previousPageCodeEditor(codeEditor, ){
nextLineCodeEditor(-codeEditor.lengthWindowRange) nextLineCodeEditor(codeEditor, -codeEditor.lengthWindowRange)
} }
function stopScrollCodeEditor(){ function stopScrollCodeEditor(codeEditor, ){
codeEditor.scrollInterval = clearInterval( codeEditor.scrollInterval ) codeEditor.scrollInterval = clearInterval( codeEditor.scrollInterval )
} }
function startScrollCodeEditor(){ function startScrollCodeEditor(codeEditor, ){
if (!codeEditor.scrollInterval) codeEditor.scrollInterval = setInterval( _ => nextLineCodeEditor(), 100) if (!codeEditor.scrollInterval) codeEditor.scrollInterval = setInterval( _ => nextLineCodeEditor(codeEditor, ), 100)
} }
function highlightAllOccurences(keyword="function"){ function highlightAllOccurences(codeEditor, keyword="function"){
let indices = [] let indices = []
let lastfound = codeEditor.currentlyDisplayedText.indexOf(keyword,0) let lastfound = codeEditor.currentlyDisplayedText.indexOf(keyword,0)
while (lastfound>-1) { while (lastfound>-1) {
@ -2287,7 +2283,7 @@ function highlightAllOccurences(keyword="function"){
}) })
} }
function hightlightNextKeyword(keyword="function"){ function hightlightNextKeyword(codeEditor, keyword="function"){
let pos = codeEditor.currentlyDisplayedText.indexOf(keyword) let pos = codeEditor.currentlyDisplayedText.indexOf(keyword)
// invisible characters... some still left // invisible characters... some still left
let offset = (codeEditor.currentlyDisplayedText.slice(0,pos).match(/[\n\t ]/g)||[]).length let offset = (codeEditor.currentlyDisplayedText.slice(0,pos).match(/[\n\t ]/g)||[]).length
@ -2295,13 +2291,13 @@ function hightlightNextKeyword(keyword="function"){
highlightString(pos, keyword.length) highlightString(pos, keyword.length)
} }
function highlightString(pos, length){ function highlightString(codeEditor, pos, length){
for (let c=pos;c<pos+length;c++) highlightChar( c ) for (let c=pos;c<pos+length;c++) highlightChar(codeEditor, c )
} }
// WARNING this is limited to visible characters, i.e not " " or "\t" or "\n" // WARNING this is limited to visible characters, i.e not " " or "\t" or "\n"
// should instead allow to highlight " " and "\t" both looking the same // should instead allow to highlight " " and "\t" both looking the same
function highlightChar(pos=0, name=null){ // could have multiple selection function highlightChar(codeEditor, pos=0, name=null){ // could have multiple selection
let b = Array.from( codeEditor.element.object3D.children[0].geometry.attributes.aTroikaGlyphBounds.array ).slice(pos*4,pos*4+4) let b = Array.from( codeEditor.element.object3D.children[0].geometry.attributes.aTroikaGlyphBounds.array ).slice(pos*4,pos*4+4)
let w = b[2]-b[0] let w = b[2]-b[0]
let h = b[3]-b[1] // could be used to check for same line, if so could make a single block from beginning to end let h = b[3]-b[1] // could be used to check for same line, if so could make a single block from beginning to end
@ -2317,7 +2313,7 @@ function highlightChar(pos=0, name=null){ // could have multiple selection
c.position.z= .01 c.position.z= .01
} }
function highlightUnderChar(pos=0, name=null){ function highlightUnderChar(codeEditor, pos=0, name=null){
let b = Array.from( codeEditor.element.object3D.children[0].geometry.attributes.aTroikaGlyphBounds.array ).slice(pos*4,pos*4+4) let b = Array.from( codeEditor.element.object3D.children[0].geometry.attributes.aTroikaGlyphBounds.array ).slice(pos*4,pos*4+4)
// note that this skips invisible char and thus desync codeEditor.caret from actual position // note that this skips invisible char and thus desync codeEditor.caret from actual position
// but, for now at least, " " and "\t" seems to be of equal value and "\n" does not shift on the current line // but, for now at least, " " and "\t" seems to be of equal value and "\n" does not shift on the current line
@ -2350,45 +2346,45 @@ function highlightUnderChar(pos=0, name=null){
c.position.z= .01 c.position.z= .01
} }
function moveCaretToNextVisibleChar(){ function moveCaretToNextVisibleChar(codeEditor, ){
addCaretToCodeEditor( ++codeEditor.caret ) addCaretToCodeEditor( ++codeEditor.caret )
// might be able to reach non visible one by remove an offset // might be able to reach non visible one by remove an offset
} }
function addCaretToCodeEditor(pos=0){ function addCaretToCodeEditor(codeEditor, pos=0){
if (codeEditor.caret) removeCaretFromCodeEditor() if (codeEditor.caret) removeCaretFromCodeEditor(codeEditor, )
highlightUnderChar(pos, "caret") highlightUnderChar(codeEditor, pos, "caret")
codeEditor.caret = pos codeEditor.caret = pos
} }
function removeCaretFromCodeEditor(){ function removeCaretFromCodeEditor(codeEditor, ){
codeEditor.element.object3D.getObjectByName("caret").removeFromParent() codeEditor.element.object3D.getObjectByName("caret").removeFromParent()
} }
function clearCodeEditorContent(){ function clearCodeEditorContent(codeEditor, ){
updateCodeEditorWithContent( "" ) updateCodeEditorWithContent( "" )
} }
// should support a range, note the entire document (or window?) // should support a range, note the entire document (or window?)
function searchAndReplaceInCodeEditor(before, after){ function searchAndReplaceInCodeEditor(codeEditor, before, after){
updateCodeEditorWithContent( codeEditor.currentlyDisplayedText.replaceAll(before, after)) updateCodeEditorWithContent( codeEditor, codeEditor.currentlyDisplayedText.replaceAll(before, after))
// note that it desyncs from page so page should only be seen as the initial source // note that it desyncs from page so page should only be seen as the initial source
// this though would break scrolling which is based on page // this though would break scrolling which is based on page
// consequently page should be modified // consequently page should be modified
} }
function updateCodeEditorWithContent(content){ function updateCodeEditorWithContent(codeEditor, content){
if (!codeEditor.element) return if (!codeEditor.element) return
codeEditor.currentlyDisplayedText=content codeEditor.currentlyDisplayedText=content
codeEditor.element.setAttribute("troika-text", {value: content}) codeEditor.element.setAttribute("troika-text", {value: content})
if (codeEditor.language) codeEditor.element.setAttribute("troika-text", {colorRanges: highlight(content, language='javascript')}) if (codeEditor.language) codeEditor.element.setAttribute("troika-text", {colorRanges: highlight(content, language='javascript')})
} }
function addBackdropToTroikaElement( el ){ function addBackdropToTroikaElement( codeEditor, el ){
el.addEventListener("object3dset", e => { el.addEventListener("object3dset", e => {
el.object3D.children[0].addEventListener("synccomplete", e => { el.object3D.children[0].addEventListener("synccomplete", e => {
// this can be used for resizing but without add the element // this can be used for resizing but without add the element
if (document.getElementById("leftgutter")) return // already added, should unregister if (codeEditor.element.querySelector(".leftgutter")) return // already added, should unregister
b = el.object3D.children[0]._textRenderInfo.blockBounds b = el.object3D.children[0]._textRenderInfo.blockBounds
w = b[2]-b[0] w = b[2]-b[0]
@ -2405,10 +2401,10 @@ function addBackdropToTroikaElement( el ){
}) })
} }
function addGuttersToTroikaElement( el ){ function addGuttersToTroikaElement( codeEditor, el ){
el.addEventListener("object3dset", e => { el.addEventListener("object3dset", e => {
el.object3D.children[0].addEventListener("synccomplete", e => { el.object3D.children[0].addEventListener("synccomplete", e => {
if (document.getElementById("leftgutter")) return if (codeEditor.element.querySelector(".leftgutter")) return
// already added, should unregister, can be removed to allow dynamic resizing BUT should skip adding element // already added, should unregister, can be removed to allow dynamic resizing BUT should skip adding element
b = el.object3D.children[0]._textRenderInfo.blockBounds b = el.object3D.children[0]._textRenderInfo.blockBounds
@ -2421,7 +2417,6 @@ function addGuttersToTroikaElement( el ){
m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} ); m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} );
c = new THREE.Mesh( g, m ); c = new THREE.Mesh( g, m );
el.object3D.add( c ); el.object3D.add( c );
c.name = "leftgutter"
c.position.z=-.01 c.position.z=-.01
c.position.x= -gutterWidth/2 c.position.x= -gutterWidth/2
//c.rotation.y= .2 // looks nice but have to consider text on top first, could apply rotation to text too //c.rotation.y= .2 // looks nice but have to consider text on top first, could apply rotation to text too
@ -2438,7 +2433,7 @@ function addGuttersToTroikaElement( el ){
lineNumbers.slice(0,-1) lineNumbers.slice(0,-1)
leftGutter.setAttribute("troika-text", {value: lineNumbers}) leftGutter.setAttribute("troika-text", {value: lineNumbers})
leftGutter.setAttribute("troika-text", {textIndent: -.5}) leftGutter.setAttribute("troika-text", {textIndent: -.5})
leftGutter.id = "leftgutter" leftGutter.className = "leftgutter"
codeEditor.element.appendChild( leftGutter ) codeEditor.element.appendChild( leftGutter )
// should be updated when scrolling // should be updated when scrolling
@ -2447,19 +2442,18 @@ function addGuttersToTroikaElement( el ){
m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} ); m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} );
c = new THREE.Mesh( g, m ); c = new THREE.Mesh( g, m );
el.object3D.add( c ); el.object3D.add( c );
c.name = "rightgutter"
c.position.z=-.01 c.position.z=-.01
c.position.x= w+gutterWidth/2 c.position.x= w+gutterWidth/2
//c.rotation.y= -.2 // looks nice but have to consider text on top first //c.rotation.y= -.2 // looks nice but have to consider text on top first
var rightGutter = document.createElement("a-cylinder") var rightGutter = document.createElement("a-cylinder")
// height proportional to the visible content to the terminal size // height proportional to the visible content to the terminal size
let scrollBarHeight = codeEditor.lengthWindowRange/getNumberOfLinesFromCodeEditor() * h let scrollBarHeight = codeEditor.lengthWindowRange/getNumberOfLinesFromCodeEditor(codeEditor, ) * h
let scrollBarVerticalOffset = codeEditor.line/getNumberOfLinesFromCodeEditor() * h let scrollBarVerticalOffset = codeEditor.line/getNumberOfLinesFromCodeEditor(codeEditor, ) * h
if (scrollBarHeight < .1) scrollBarHeight = .1 if (scrollBarHeight < .1) scrollBarHeight = .1
rightGutter.setAttribute("height", scrollBarHeight ) rightGutter.setAttribute("height", scrollBarHeight )
rightGutter.setAttribute("radius", .01 ) rightGutter.setAttribute("radius", .01 )
rightGutter.id = "rightgutter" rightGutter.className = "rightgutter"
// should become a constrained target (moving only on y axis and clamped) // should become a constrained target (moving only on y axis and clamped)
codeEditor.element.appendChild( rightGutter ) codeEditor.element.appendChild( rightGutter )
// so... rightgutter vs rightGutter ... somehow changing to the "correct" one breaks the editor itself (?!) // so... rightgutter vs rightGutter ... somehow changing to the "correct" one breaks the editor itself (?!)
@ -2472,7 +2466,6 @@ function addGuttersToTroikaElement( el ){
m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} ); m = new THREE.MeshBasicMaterial( {color: 0x333333, opacity: 0.9, transparent: true} );
c = new THREE.Mesh( g, m ); c = new THREE.Mesh( g, m );
el.object3D.add( c ); el.object3D.add( c );
c.name = "middlegutter"
c.position.z=-.01 c.position.z=-.01
c.position.y= -h/2-gutterHeight/2 c.position.y= -h/2-gutterHeight/2
c.position.x= w/2 c.position.x= w/2
@ -2484,7 +2477,7 @@ function addGuttersToTroikaElement( el ){
middleGutter.setAttribute("outline-color", "black" ) middleGutter.setAttribute("outline-color", "black" )
middleGutter.setAttribute("troika-text", {value: ":(will add commands here)"}) middleGutter.setAttribute("troika-text", {value: ":(will add commands here)"})
//middleGutter.setAttribute("troika-text", {textIndent: -.3}) //middleGutter.setAttribute("troika-text", {textIndent: -.3})
middleGutter.id = "middlegutter" middleGutter.className = "middlegutter"
codeEditor.element.appendChild( middleGutter ) codeEditor.element.appendChild( middleGutter )
middleGutter.object3D.position.y= -h/2-gutterHeight/2 middleGutter.object3D.position.y= -h/2-gutterHeight/2
// should disable the overlay first, see parseKeys // should disable the overlay first, see parseKeys
@ -2517,7 +2510,7 @@ function addGuttersToTroikaElement( el ){
}) })
} }
function getNumberOfLinesFromCodeEditor(){ function getNumberOfLinesFromCodeEditor(codeEditor, ){
let newLines = codeEditor.page.match(/\n/g) let newLines = codeEditor.page.match(/\n/g)
if (!newLines) return 1 // undefined or 0 if (!newLines) return 1 // undefined or 0
return newLines.length return newLines.length
@ -2528,6 +2521,17 @@ function getNumberOfLinesFromCodeEditor(){
// should also support clipboard or even a more direct way to have impact // should also support clipboard or even a more direct way to have impact
// could save remotely (e.g wiki) or locally in localStorage // could save remotely (e.g wiki) or locally in localStorage
function addCodeEditor(page="jxr console.log('hello world')", language="javascript", position="-.5 1.6 -.7", name="codeditor" ){ function addCodeEditor(page="jxr console.log('hello world')", language="javascript", position="-.5 1.6 -.7", name="codeditor" ){
let codeEditor = {
element: null,
line: 0,
page: null,
startWindowRange: 0,
lengthWindowRange: 20,
scrollInterval: null,
currentlyDisplayedText: "",
caret: null,
language: null,
}
// could also add empty but with column and row for sizing // could also add empty but with column and row for sizing
// for now supporting only 1 scode editor, despite the name parameter // for now supporting only 1 scode editor, despite the name parameter
@ -2550,7 +2554,7 @@ function addCodeEditor(page="jxr console.log('hello world')", language="javascri
codeEditor.page = forcedLines codeEditor.page = forcedLines
codeEditor.line = codeEditor.startWindowRange codeEditor.line = codeEditor.startWindowRange
let numberOfLines = getNumberOfLinesFromCodeEditor() let numberOfLines = getNumberOfLinesFromCodeEditor(codeEditor, )
if (numberOfLines<codeEditor.lengthWindowRange) codeEditor.lengthWindowRange = numberOfLines if (numberOfLines<codeEditor.lengthWindowRange) codeEditor.lengthWindowRange = numberOfLines
content=codeEditor.page.split("\n").slice(codeEditor.line,codeEditor.line+codeEditor.lengthWindowRange).join("\n"); content=codeEditor.page.split("\n").slice(codeEditor.line,codeEditor.line+codeEditor.lengthWindowRange).join("\n");
@ -2566,7 +2570,7 @@ function addCodeEditor(page="jxr console.log('hello world')", language="javascri
codeEditor.language = language codeEditor.language = language
} }
addBackdropToTroikaElement( codeEditor.element ) addBackdropToTroikaElement( codeEditor, codeEditor.element )
//addGuttersToTroikaElement( codeEditor.element ) //addGuttersToTroikaElement( codeEditor.element )
let scrollbarPicked = false let scrollbarPicked = false
@ -2592,13 +2596,13 @@ function addCodeEditor(page="jxr console.log('hello world')", language="javascri
} }
p.addEventListener('pinchstarted', pinchPrimaryScrollbarStarted ); p.addEventListener('pinchstarted', pinchPrimaryScrollbarStarted );
function pinchPrimaryScrollbarStarted(event){ function pinchPrimaryScrollbarStarted(event){
let rightGutterEl = document.getElementById("rightgutter") let rightGutterEl = codeEditor.element.querySelector(".rightgutter")
previousPosition = event.detail.position.clone() previousPosition = event.detail.position.clone()
rightGutterEl.object3D.getWorldPosition( target ); rightGutterEl.object3D.getWorldPosition( target );
if (previousPosition.distanceTo(target)<0.1) scrollbarPicked = true if (previousPosition.distanceTo(target)<0.1) scrollbarPicked = true
} }
return codeEditor.element return codeEditor
} }
// could change model opacity based on hand position, fading out when within a (very small here) safe space // could change model opacity based on hand position, fading out when within a (very small here) safe space
@ -3041,7 +3045,14 @@ function tiltId(id, value){
// used for testing // used for testing
AFRAME.registerComponent('startfunctions', { AFRAME.registerComponent('startfunctions', {
init: function () { init: function () {
fetch( 'https://webdav.benetou.fr/fot-demo-day/mobydick-extract.txt').then(r=>r.text()).then( page => { addCodeEditor( page, '', '-0.22 1.4 -.4' ) })
fetch( 'https://webdav.benetou.fr/fot-demo-day/mobydick-extract.txt').then(r=>r.text()).then( src => {
let parts = 4 // can't handle odd splits...
let pl = src.length/parts
for (let n=0; n<parts; n++ ){
addCodeEditor( src.slice(n*pl,(n+1)*pl), '', '-0.22 '+(2-n/10)+' -.4' )
}
})
// should become a component instead // should become a component instead
//startExperience() //startExperience()
//doublePinchToScale() //doublePinchToScale()

Loading…
Cancel
Save