comprehensive-rust icon indicating copy to clipboard operation
comprehensive-rust copied to clipboard

Keep state of the Rust Playgrounds when navigating between slides

Open mgeisler opened this issue 2 years ago • 31 comments

Today, the embedded Playgrounds are reset when you navigate between slides. This has caused problems: if people navigate away from a slide to look something up, they've suddenly lost their work.

This is actually the reason why we don't make the exercises editable directly in the slides: too many people have lost work because of this.

We should be able to store the current state in the browser's local storage.

mgeisler avatar Nov 13 '23 15:11 mgeisler

I would like to help with it.

jnovikov avatar Jan 12 '24 16:01 jnovikov

I would like to help with it.

Hi @jnovikov, that would be awesome! I hope the idea is somewhat clear, otherwise please let me know :smile: The problem right now is that using Arrow-Left or Arrow-Right (or clicking links) navigates away from the current slide: when you navigate back, the playground is back at its initial state. The idea is to prevent this by storing the state in local storage.

I don't know much about the editor here, except that mdbook uses this one: https://ace.c9.io/.

mgeisler avatar Jan 15 '24 21:01 mgeisler

Hey @mgeisler , I would like to give a try .If it Is still open.

mani-chand avatar Mar 06 '24 18:03 mani-chand

It's all yours! I'm not sure anyone has in mind a good way to do this, so the first step is coming up with an idea..

djmitche avatar Mar 07 '24 17:03 djmitche

It's all yours! I'm not sure anyone has in mind a good way to do this, so the first step is coming up with an idea..

I looked at it there is a div which as has some child html elements in them all our code lies as innerHTML/innerText.

I will store all children's of that parent div in an array using Arraysfrom(document.getElementsByClassName('{that parent div class name}')[0].children).

Then I will make a object with the URL as key and array of html elements as value.

I will store that array of objects (Assuming user make changes in all playgrounds) in browser local storage.

Every time loads the page, I will check the object is that url present in that key.

If present then I will display object value code.

else I will start assigning that code to new key in the object.

Process of displaying code : Delete all children of parent div add all childrens in object one by one using loop.

mani-chand avatar Mar 07 '24 17:03 mani-chand

That sounds like it will work -- try it out!

djmitche avatar Mar 07 '24 18:03 djmitche

It's all yours! I'm not sure anyone has in mind a good way to do this, so the first step is coming up with an idea..

I looked at it there is a div which as has some child html elements in them all our code lies as innerHTML/innerText.

I will store all children's of that parent div in an array using Arraysfrom(document.getElementsByClassName('{that parent div class name}')[0].children).

Then I will make a object with the URL as key and array of html elements as value.

I will store that array of objects (Assuming user make changes in all playgrounds) in browser local storage.

Every time loads the page, I will check the object is that url present in that key.

If present then I will display object value code.

else I will start assigning that code to new key in the object.

Process of displaying code : Delete all children of parent div add all childrens in object one by one using loop.

May be few steps will change. I will come up with code tommarow.

mani-chand avatar Mar 07 '24 18:03 mani-chand

It's all yours! I'm not sure anyone has in mind a good way to do this, so the first step is coming up with an idea..

I looked at it there is a div which as has some child html elements in them all our code lies as innerHTML/innerText.

I will store all children's of that parent div in an array using Arraysfrom(document.getElementsByClassName('{that parent div class name}')[0].children).

Then I will make a object with the URL as key and array of html elements as value.

I will store that array of objects (Assuming user make changes in all playgrounds) in browser local storage.

Every time loads the page, I will check the object is that url present in that key.

If present then I will display object value code.

else I will start assigning that code to new key in the object.

Process of displaying code : Delete all children of parent div add all childrens in object one by one using loop.

Is it okay to use indexedDb(Browser storage) to store data.

Because , I cannot store HTML tags in local storage.

mani-chand avatar Mar 08 '24 13:03 mani-chand

I think either IndexdDB or LocalStorage is fine. I suspect it only needs to store text (the code in the playground) and not HTML, right?

djmitche avatar Mar 08 '24 15:03 djmitche

I think either IndexdDB or LocalStorage is fine. I suspect it only needs to store text (the code in the playground) and not HTML, right?

Yes, now, I am storing text instead of html elements.

mani-chand avatar Mar 08 '24 16:03 mani-chand


function setCodeToPlayground(){
  const code = JSON.parse(localStorage.getItem(window.location.href));
  console.log(code)
  if(code){
    const playground = document.getElementsByClassName('ace_text-layer')[0]
    while (playground.lastElementChild) {
      console.log(playground.lastElementChild.innerHTML.replace('<span>','^').replace('</span>',"^").replace(/\s+/g, '').split('^'))
      playground.removeChild(playground.lastElementChild);
    }
    console.log(playground,"after removal")
    for(let i = 0; i < code.length; i++){
      let parentDiv = code[i][0]
      let spanChild = code[i][1]
      let div = document.createElement(parentDiv.tag)
      div.style.height = "17.5938px"
      div.style.top = `${17.5938*i}px`
      for(let cls in parentDiv.classes){
        div.classList.add(parentDiv.classes[cls])
      }
      for(let j = 0;j<spanChild.length;j++){
        //console.log(spanChild[j].styles,typeof(spanChild[j].styles))
        let span = document.createElement(spanChild[j].tag)
        //span.classList = spanChild[j].classes
        for(let cls in spanChild[j].classes){
          span.classList.add(spanChild[j].classes[cls])
        }
        span.innerText = spanChild[j].text
        div.insertBefore(span,div.lastChild)
      }
      playground.insertBefore(div,playground.lastChild)
    }
  }
  localStorage.removeItem(window.location.href)
}

window.onunload = setTimeout(setCodeToPlayground,5000)

function getCodeFromPlayground() {
  console.log("getCodeFromPlayground")
  const playground = document.getElementsByClassName('ace_text-layer')[0].children
  var code = []
    for (let i = 0; i < playground.length; i++) {
      let parentCodeList = {
        tag : playground[i].tagName,
        classes : playground[i].classList,
        styles : playground[i].style
      }
      var line = []
      for(let j = 0; j < playground[i].children.length; j++) {
          console.log(playground[i].innerHTML)
          let codeList = {
            tag : playground[i].children[j].tagName,
            text: playground[i].children[j].innerText,
            classes : playground[i].children[j].classList,
            styles : playground[i].children[j].style,
          }
          line.push(codeList)
      }
      code.push([parentCodeList,line])
  }
  console.log(code)
  //localStorage.removeItem(window.location.href)
  localStorage.setItem(window.location.href,JSON.stringify(code))
}
addEventListener('beforeunload',getCodeFromPlayground())

Need some help with this code . If any one can.

mani-chand avatar Mar 09 '24 12:03 mani-chand

function setCodeToPlayground(){
  const code = JSON.parse(localStorage.getItem(window.location.href));
  console.log(code)
  if(code){
    const playground = document.getElementsByClassName('ace_text-layer')[0]
    while (playground.lastElementChild) {
      console.log(playground.lastElementChild.innerHTML.replace('<span>','^').replace('</span>',"^").replace(/\s+/g, '').split('^'))
      playground.removeChild(playground.lastElementChild);
    }
    console.log(playground,"after removal")
    for(let i = 0; i < code.length; i++){
      let parentDiv = code[i][0]
      let spanChild = code[i][1]
      let div = document.createElement(parentDiv.tag)
      div.style.height = "17.5938px"
      div.style.top = `${17.5938*i}px`
      for(let cls in parentDiv.classes){
        div.classList.add(parentDiv.classes[cls])
      }
      for(let j = 0;j<spanChild.length;j++){
        //console.log(spanChild[j].styles,typeof(spanChild[j].styles))
        let span = document.createElement(spanChild[j].tag)
        //span.classList = spanChild[j].classes
        for(let cls in spanChild[j].classes){
          span.classList.add(spanChild[j].classes[cls])
        }
        span.innerText = spanChild[j].text
        div.insertBefore(span,div.lastChild)
      }
      playground.insertBefore(div,playground.lastChild)
    }
  }
  localStorage.removeItem(window.location.href)
}

window.onunload = setTimeout(setCodeToPlayground,5000)

function getCodeFromPlayground() {
  console.log("getCodeFromPlayground")
  const playground = document.getElementsByClassName('ace_text-layer')[0].children
  var code = []
    for (let i = 0; i < playground.length; i++) {
      let parentCodeList = {
        tag : playground[i].tagName,
        classes : playground[i].classList,
        styles : playground[i].style
      }
      var line = []
      for(let j = 0; j < playground[i].children.length; j++) {
          console.log(playground[i].innerHTML)
          let codeList = {
            tag : playground[i].children[j].tagName,
            text: playground[i].children[j].innerText,
            classes : playground[i].children[j].classList,
            styles : playground[i].children[j].style,
          }
          line.push(codeList)
      }
      code.push([parentCodeList,line])
  }
  console.log(code)
  //localStorage.removeItem(window.location.href)
  localStorage.setItem(window.location.href,JSON.stringify(code))
}
addEventListener('beforeunload',getCodeFromPlayground())

Need some help with this code . If any one can.

Updated code.

mani-chand avatar Mar 10 '24 10:03 mani-chand

Getting problem with word without span tag. every word as a specific span tag except variables and print keyword.Missing those words without span tag.

mani-chand avatar Mar 10 '24 10:03 mani-chand

Can you push a draft PR that we could experiment with?

djmitche avatar Mar 11 '24 17:03 djmitche

Sure on it.

mani-chand avatar Mar 11 '24 17:03 mani-chand

It seems like the text editor widget (ace_text) should have some API for getting/setting text contents, rather than digging around in its internal markup.

From the docs, it looks like these suffice: editors[0].getValue();/editors[0].setValue("fn main(){\n\n}", -1);.

While teaching I rely on being able to reset the code samples to their original contents (both while discussing a slide and before teaching the course a second time), so I'd like to make sure there's still a straightforward way to do so before this lands.

fw-immunant avatar Mar 11 '24 18:03 fw-immunant

(moving this conversation to the PR, #1917)

djmitche avatar Mar 11 '24 19:03 djmitche

Hello @djmitche , still any thing needed to add to close this issue.

mani-chand avatar Mar 22 '24 17:03 mani-chand

I don't think so!

djmitche avatar Mar 22 '24 20:03 djmitche

Cc @djmitche, @fw-immunant, and @mani-chand. I merged this now so I can ensure the course works for the class I'm teaching in ~12 hours.

I only saw this after browsing around on the site for 30 minutes or so. I don't really know why it would happen, but I guess the event that saves the playgrounds is unreliable. I would suggest looking into saving the state on changes to the playgrounds instead of trying to detect navigating away from the pages.

I've seen problems with detecting the pagehide event in the past: the event is not reliably fired because it has been misused in the past.

I ran into another problem: clearing saved playground data doesn't fully work. Navigating away from the current page will save the playgrounds on the page again.

Originally posted by @mgeisler in https://github.com/google/comprehensive-rust/issues/1935#issuecomment-2016969529

djmitche avatar Mar 25 '24 13:03 djmitche

LocalState is limited in the amount of storage a site is allowed. I think that's 5MB? That might be part of the issue.

Docs on pagehide agree that it's unreliable, but I think it's better than onunload?

djmitche avatar Mar 25 '24 13:03 djmitche

on 2nd point.

saving the current page after reset of playground.

When you reset the playground it will clear all including the current page but when you leave the current page pagehide event triggers and saves the state even there is no change in the code.

mani-chand avatar Mar 25 '24 13:03 mani-chand

Cc @djmitche, @fw-immunant, and @mani-chand. I merged this now so I can ensure the course works for the class I'm teaching in ~12 hours.

I only saw this after browsing around on the site for 30 minutes or so. I don't really know why it would happen, but I guess the event that saves the playgrounds is unreliable. I would suggest looking into saving the state on changes to the playgrounds instead of trying to detect navigating away from the pages.

I've seen problems with detecting the pagehide event in the past: the event is not reliably fired because it has been misused in the past.

I ran into another problem: clearing saved playground data doesn't fully work. Navigating away from the current page will save the playgrounds on the page again.

Originally posted by @mgeisler in https://github.com/google/comprehensive-rust/issues/1935#issuecomment-2016969529

1st point I think pagehide event doesn't trigger on closing the window directly.

mani-chand avatar Mar 25 '24 13:03 mani-chand

Cc @djmitche, @fw-immunant, and @mani-chand. I merged this now so I can ensure the course works for the class I'm teaching in ~12 hours.

I only saw this after browsing around on the site for 30 minutes or so. I don't really know why it would happen, but I guess the event that saves the playgrounds is unreliable. I would suggest looking into saving the state on changes to the playgrounds instead of trying to detect navigating away from the pages.

I've seen problems with detecting the pagehide event in the past: the event is not reliably fired because it has been misused in the past.

I ran into another problem: clearing saved playground data doesn't fully work. Navigating away from the current page will save the playgrounds on the page again.

Originally posted by @mgeisler in https://github.com/google/comprehensive-rust/issues/1935#issuecomment-2016969529

For onchange of innerText https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver. It looks good. What do you say? @djmitche @mgeisler

mani-chand avatar Mar 25 '24 13:03 mani-chand

LocalState is limited in the amount of storage a site is allowed. I think that's 5MB? That might be part of the issue.

Docs on pagehide agree that it's unreliable, but I think it's better than onunload?

I think all our codes doesn't consume even 3mb because they are only strings.

mani-chand avatar Mar 25 '24 13:03 mani-chand

Yeah, my quic mental math also suggested we weren't using 5MB.

I think the ACE editor has its own events which would probably be better to listen to instead of the underlying DOM events. Will updating LocalStorage on every keypress be too slow?

djmitche avatar Mar 25 '24 13:03 djmitche

Yeah, my quic mental math also suggested we weren't using 5MB.

I think the ACE editor has its own events which would probably be better to listen to instead of the underlying DOM events. Will updating LocalStorage on every keypress be too slow?

This is great. I think we can trust it.

mani-chand avatar Mar 25 '24 13:03 mani-chand

Yeah, my quic mental math also suggested we weren't using 5MB.

I think the ACE editor has its own events which would probably be better to listen to instead of the underlying DOM events. Will updating LocalStorage on every keypress be too slow?

You really think my brain will know or understand quic 🤔. I went to Google for 🧐and understood what it is . My brain is still in completing bachelor of technology in computer science. Even I am doing an internship at a startup.

mani-chand avatar Mar 25 '24 14:03 mani-chand

I ran into another problem: clearing saved playground data doesn't fully work. Navigating away from the current page will save the playgrounds on the page again.

I think this problem will also gets solved. If someone clears the playground and navigate to another page without changing playground code then it will won't save.

mani-chand avatar Mar 25 '24 14:03 mani-chand

Sorry! I've been working on QUIC things for a few months now and my fingers type that instead of "quick". It is, indeed, kind of 🤯!

djmitche avatar Mar 25 '24 16:03 djmitche