Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep state of the Rust Playgrounds when navigating between slides #1476

Open
mgeisler opened this issue Nov 13, 2023 · 31 comments
Open

Keep state of the Rust Playgrounds when navigating between slides #1476

mgeisler opened this issue Nov 13, 2023 · 31 comments
Assignees
Labels
enhancement New feature or request good first issue Good for newcomers

Comments

@mgeisler
Copy link
Collaborator

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 mgeisler added enhancement New feature or request good first issue Good for newcomers labels Nov 13, 2023
@jnovikov
Copy link
Member

I would like to help with it.

@mgeisler
Copy link
Collaborator Author

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 😄 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/.

@mani-chand
Copy link
Contributor

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

@djmitche
Copy link
Collaborator

djmitche commented Mar 7, 2024

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

@mani-chand
Copy link
Contributor

mani-chand commented Mar 7, 2024

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.

@djmitche
Copy link
Collaborator

djmitche commented Mar 7, 2024

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

@mani-chand
Copy link
Contributor

mani-chand commented Mar 7, 2024

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
Copy link
Contributor

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.

@djmitche
Copy link
Collaborator

djmitche commented Mar 8, 2024

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?

@mani-chand
Copy link
Contributor

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
Copy link
Contributor

mani-chand commented Mar 9, 2024

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
Copy link
Contributor

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
Copy link
Contributor

mani-chand commented Mar 10, 2024

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.

@djmitche
Copy link
Collaborator

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

@mani-chand
Copy link
Contributor

Sure on it.

@fw-immunant
Copy link
Collaborator

fw-immunant commented Mar 11, 2024

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.

@djmitche
Copy link
Collaborator

(moving this conversation to the PR, #1917)

@mani-chand
Copy link
Contributor

mani-chand commented Mar 22, 2024

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

@djmitche
Copy link
Collaborator

I don't think so!

@djmitche djmitche reopened this Mar 25, 2024
@djmitche
Copy link
Collaborator

djmitche commented Mar 25, 2024

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 #1935 (comment)

@djmitche
Copy link
Collaborator

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?

@mani-chand
Copy link
Contributor

mani-chand commented Mar 25, 2024

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
Copy link
Contributor

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 #1935 (comment)

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

@mani-chand
Copy link
Contributor

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 #1935 (comment)

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
Copy link
Contributor

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.

@djmitche
Copy link
Collaborator

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?

@mani-chand
Copy link
Contributor

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
Copy link
Contributor

mani-chand commented Mar 25, 2024

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
Copy link
Contributor

mani-chand commented Mar 25, 2024

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.

@djmitche
Copy link
Collaborator

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 🤯!

@mani-chand
Copy link
Contributor

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 🤯!

Leave it, Atleast I have learnt about it because of that message.

mgeisler added a commit that referenced this issue May 28, 2024
#1476 issue. Updated the function call(`getCodeFromPlayground`) in
`save-playground.js` file from `pagehide` event to change event in ace
editor.

---------

Co-authored-by: Martin Geisler <mgeisler@google.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request good first issue Good for newcomers
Projects
None yet
Development

No branches or pull requests

5 participants