Repository: j-mie6/parsley-debug-app
Pull request #128: Help section with performant children
Author: login: aniketguptax
Created: 2025-03-02T17:54:04Z | Merged: 2025-03-10T12:27:20Z



Diff:
diff --git a/frontend/src/assets/help/attachParser.gif b/frontend/src/assets/help/attachParser.gif
new file mode 100644
index 00000000..2eb67cc0
Binary files /dev/null and b/frontend/src/assets/help/attachParser.gif differ
diff --git a/frontend/src/assets/help/breakpoints.gif b/frontend/src/assets/help/breakpoints.gif
new file mode 100644
index 00000000..e73509d9
Binary files /dev/null and b/frontend/src/assets/help/breakpoints.gif differ
diff --git a/frontend/src/assets/help/codeView.gif b/frontend/src/assets/help/codeView.gif
new file mode 100644
index 00000000..f1afc046
Binary files /dev/null and b/frontend/src/assets/help/codeView.gif differ
diff --git a/frontend/src/assets/help/dillSummary.gif b/frontend/src/assets/help/dillSummary.gif
new file mode 100644
index 00000000..710b1f24
Binary files /dev/null and b/frontend/src/assets/help/dillSummary.gif differ
diff --git a/frontend/src/assets/help/downloadTree.gif b/frontend/src/assets/help/downloadTree.gif
new file mode 100644
index 00000000..eefc1fc3
Binary files /dev/null and b/frontend/src/assets/help/downloadTree.gif differ
diff --git a/frontend/src/assets/help/inputView.gif b/frontend/src/assets/help/inputView.gif
new file mode 100644
index 00000000..d90876a6
Binary files /dev/null and b/frontend/src/assets/help/inputView.gif differ
diff --git a/frontend/src/assets/help/iterativeNodes.gif b/frontend/src/assets/help/iterativeNodes.gif
new file mode 100644
index 00000000..55d0c212
Binary files /dev/null and b/frontend/src/assets/help/iterativeNodes.gif differ
diff --git a/frontend/src/assets/help/settings.gif b/frontend/src/assets/help/settings.gif
new file mode 100644
index 00000000..224e7395
Binary files /dev/null and b/frontend/src/assets/help/settings.gif differ
diff --git a/frontend/src/assets/help/stateManagement.gif b/frontend/src/assets/help/stateManagement.gif
new file mode 100644
index 00000000..1fc4f4f7
Binary files /dev/null and b/frontend/src/assets/help/stateManagement.gif differ
diff --git a/frontend/src/assets/help/traverseTree.gif b/frontend/src/assets/help/traverseTree.gif
new file mode 100644
index 00000000..1f61c7e4
Binary files /dev/null and b/frontend/src/assets/help/traverseTree.gif differ
diff --git a/frontend/src/assets/help/treeColours.gif b/frontend/src/assets/help/treeColours.gif
new file mode 100644
index 00000000..06aa9480
Binary files /dev/null and b/frontend/src/assets/help/treeColours.gif differ
diff --git a/frontend/src/assets/help/uploadTree.gif b/frontend/src/assets/help/uploadTree.gif
new file mode 100644
index 00000000..7fa63a2d
Binary files /dev/null and b/frontend/src/assets/help/uploadTree.gif differ
diff --git a/frontend/src/main/scala/controller/viewControllers/HelpViewController.scala b/frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
new file mode 100644
index 00000000..c60604cb
--- /dev/null
+++ b/frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
@@ -0,0 +1,45 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import com.raquo.laminar.api.L.*
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val currentIndex: Var[Int] = Var(0)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        currentIndex.set(0)
+    }
+
+    def getActiveSection: Signal[HelpSection] = currentIndex.signal.map(getHelpSectionFromIndex)
+    
+    def isFirstSection: Signal[Boolean] = getActiveSection.map(_.index == 0)
+    
+    def isLastSection: Signal[Boolean] = getActiveSection.map(_.index == allHelpSections.length - 1)
+
+    def getHelpSectionFromIndex(index: Int): HelpSection = allHelpSections(index)
+
+    def nextSection(): Unit = {
+        currentIndex.update(currIndex => (currIndex + 1) % allHelpSections.length)
+    }
+
+    def prevSection(): Unit = {
+        currentIndex.update(currIndex => if (currIndex == 0) allHelpSections.length - 1 else currIndex - 1)
+    }
+
+    def progressPercentage: Signal[Double] = currentIndex.signal.map { index =>
+        (index.toDouble / (allHelpSections.length - 1)) * 100
+    }
+}
\ No newline at end of file
diff --git a/frontend/src/main/scala/model/HelpPopup.scala b/frontend/src/main/scala/model/HelpPopup.scala
new file mode 100644
index 00000000..4688ed95
--- /dev/null
+++ b/frontend/src/main/scala/model/HelpPopup.scala
@@ -0,0 +1,127 @@
+package model.help
+
+import com.raquo.laminar.api.L.*
+
+import controller.viewControllers.HelpViewController
+
+/**
+  * HelpSection is the generic representation of each section in the help popup for Dill
+  * 
+  * @param title Title of the section
+  * @param infoText Supplementary text that further describes an action
+  * @visualSource The relative path to an image that shows the action
+  * @index The index of this section amongst all help sections
+  */
+sealed trait HelpSection {
+    def title: String
+    def infoText: String
+    def visualSource: String
+    def index: Int
+}
+
+case object BaseHelpSection extends HelpSection {
+    override def title: String = "How to Dill"
+    override def infoText: String = "Getting started"
+    override def visualSource: String = "frontend/styles/icons/dill-logo.png"
+    override def index: Int = 0
+}
+
+case object HelpAttachParser extends HelpSection {
+    override def title: String = "1. Attach a Parser"
+    override def infoText: String = "Don't forget to use the @experimental and @parsley.debuggable tags."
+    override def visualSource: String = "frontend/src/assets/help/attachParser.gif"
+    override def index: Int = 1
+}
+
+case object HelpInputView extends HelpSection {
+    override def title: String = "2. Input View"
+    override def infoText: String = "Use the Input View to check thing string being parsed."
+    override def visualSource: String = "frontend/src/assets/help/inputView.gif"
+    override def index: Int = 2
+}
+
+case object HelpCodeView extends HelpSection {
+    override def title: String = "3. Code View"
+    override def infoText: String = "Use the Code View to inspect the file a parser was written in."
+    override def visualSource: String = "frontend/src/assets/help/codeView.gif"
+    override def index: Int = 3
+}
+
+case object HelpTraverseTrees extends HelpSection {
+    override def title: String = "4. Traversing Trees"
+    override def infoText: String = "Click once to expand nodes and click again to compress nodes."
+    override def visualSource: String = "frontend/src/assets/help/traverseTree.gif"
+    override def index: Int = 4
+}
+
+case object HelpTreeColours extends HelpSection {
+    override def title: String = "5. Tree Colours"
+    override def infoText: String = "For a basic Dill tree, green indicates parse success and red indicates a failure."
+    override def visualSource: String = "frontend/src/assets/help/treeColours.gif"
+    override def index: Int = 5
+}
+
+case object HelpIterativeNodes extends HelpSection {
+    override def title: String = "6. Iterative Nodes"
+    override def infoText: String = "Iterative nodes are blue and display one child that can be scrolled through by default. Clicking an iterative node expands all its children."
+    override def visualSource: String = "frontend/src/assets/help/iterativeNodes.gif"
+    override def index: Int = 6
+}
+
+case object HelpDownloadingTrees extends HelpSection {
+    override def title: String = "7. Downloading Trees"
+    override def infoText: String = "Download trees for storage and easy collaboration."
+    override def visualSource: String = "frontend/src/assets/help/downloadTree.gif"
+    override def index: Int = 7
+}
+
+case object HelpUploadingTrees extends HelpSection {
+    override def title: String = "8. Uploading Trees"
+    override def infoText: String = "Upload trees generated by any user and view them."
+    override def visualSource: String = "frontend/src/assets/help/uploadTree.gif"
+    override def index: Int = 8
+}
+
+case object HelpRefManagement extends HelpSection {
+    override def title: String = "9. Reference Management"
+    override def infoText: String = "Use the References panel to modify a parser's state when a reference is passed."
+    override def visualSource: String = "frontend/src/assets/help/stateManagement.gif"
+    override def index: Int = 9
+}
+
+case object HelpBreakpoints extends HelpSection {
+    override def title: String = "10. Breakpoints"
+    override def infoText: String = "Use breakpoints to pause execution and inspect a debug tree. Skip through various breakpoints to explore advancing outputs."
+    override def visualSource: String = "frontend/src/assets/help/breakpoints.gif"
+    override def index: Int = 10
+}
+
+case object HelpSettings extends HelpSection {
+    override def title: String = "11. Settings"
+    override def infoText: String = "Modify to customise your Dill experience."
+    override def visualSource: String = "frontend/src/assets/help/settings.gif"
+    override def index: Int = 11
+}
+
+case object HelpSummary extends HelpSection {
+    override def title: String = "That's it!"
+    override def infoText: String = "Debug interactively with Dill"
+    override def visualSource: String = "frontend/src/assets/help/dillSummary.gif"
+    override def index: Int = 12
+}
+
+val allHelpSections: List[HelpSection] = List(
+    BaseHelpSection,
+    HelpAttachParser, 
+    HelpInputView, 
+    HelpCodeView,
+    HelpTraverseTrees,
+    HelpTreeColours,
+    HelpIterativeNodes,
+    HelpDownloadingTrees,
+    HelpUploadingTrees,
+    HelpRefManagement,
+    HelpBreakpoints,
+    HelpSettings,
+    HelpSummary
+)
\ No newline at end of file
diff --git a/frontend/src/main/scala/view/DebugViewPage.scala b/frontend/src/main/scala/view/DebugViewPage.scala
index be28f93e..d3be9be0 100644
--- a/frontend/src/main/scala/view/DebugViewPage.scala
+++ b/frontend/src/main/scala/view/DebugViewPage.scala
@@ -13,16 +13,19 @@ import com.raquo.laminar.codecs.*
 import com.raquo.laminar.api.features.unitArrows
 import org.scalajs.dom
 
+
 import model.Page
 import model.errors.DillException
 import model.toast.TreeUploadFailed
 import model.toast.TreeUploaded
 import model.toast.TreeDownloaded
 import model.toast.TreeDownloadFailed
+import view.HelpView
 import view.SettingsView
 import view.StateManagementView
 import controller.AppStateController
 import controller.errors.ErrorController
+import controller.viewControllers.HelpViewController
 import controller.viewControllers.InputViewController
 import controller.viewControllers.MainViewController
 import controller.viewControllers.MainViewController.View
@@ -114,10 +117,11 @@ abstract class DebugViewPage extends Page {
     }
 
                 
-    /* Overview information tab. */
-    private lazy val infoButton: HtmlElement = button(
+    /* Overview help popup */
+    private lazy val helpButton: HtmlElement = button(
         className := "debug-view-button debug-view-button-info",
-        i(className := "bi bi-info-circle-fill"),
+        i(className := "bi bi-question-circle-fill"),
+        onClick --> (_ => HelpViewController.openPopup())
     )
 
     private lazy val stateButton: HtmlElement = button(
@@ -227,7 +231,7 @@ abstract class DebugViewPage extends Page {
             child(downloadButton) <-- TreeViewController.treeExists,
             uploadButton,
             stateButton,
-            infoButton,
+            helpButton,
         )
     )   
 
@@ -245,7 +249,7 @@ abstract class DebugViewPage extends Page {
             headerView,
             div(
                 className := "debug-view-body",
-                
+                child(HelpView(HelpViewController.getActiveSection)) <-- HelpViewController.isPopupOpen,
                 child(SettingsView()) <-- SettingsViewController.isSettingsOpen,
                 
                 div(
diff --git a/frontend/src/main/scala/view/HelpView.scala b/frontend/src/main/scala/view/HelpView.scala
new file mode 100644
index 00000000..b11e366a
--- /dev/null
+++ b/frontend/src/main/scala/view/HelpView.scala
@@ -0,0 +1,42 @@
+package view
+
+import com.raquo.laminar.api.L.*
+import model.help.*
+import controller.viewControllers.HelpViewController
+
+object HelpView {
+    def apply(activeHelpSection: Signal[HelpSection]): HtmlElement = {
+        div(
+            cls("help-popup-container"),
+            hidden <-- HelpViewController.isPopupOpen,
+
+            div(
+                cls("help-popup"),
+                
+                div(cls("help-header"),
+                    div(cls("help-close-button"), "✖", onClick --> (_ => HelpViewController.closePopup())),
+                    h2("Help Guide")
+                ),
+                
+                div(cls("help-content"),
+                    h2(cls("help-title"), text <-- activeHelpSection.map(_.title)),
+                    div(cls("help-section-text"), text <-- activeHelpSection.map (_.infoText)),
+                    img(src <-- activeHelpSection.map(_.visualSource), cls("help-image"))
+                ),
+                
+                div(cls("help-footer"),
+                    child(button(cls("help-prev-button"), "Previous", onClick --> (_ => HelpViewController.prevSection()))) <-- HelpViewController.isFirstSection.not,
+                    child(button(cls("help-next-button"), "Next", onClick --> (_ => HelpViewController.nextSection()))) <-- HelpViewController.isLastSection.not,
+                    child(button(cls("help-done-button"), "Done", onClick --> (_ => HelpViewController.closePopup()))) <-- HelpViewController.isLastSection
+                ),
+
+                div(cls("help-progress-container"),
+                    div(
+                        cls("help-progress-fill"),
+                        width <-- HelpViewController.progressPercentage.map(p => s"${p}%")
+                    )
+                )
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/frontend/styles/pages/help-section.css b/frontend/styles/pages/help-section.css
new file mode 100644
index 00000000..24c75d35
--- /dev/null
+++ b/frontend/styles/pages/help-section.css
@@ -0,0 +1,127 @@
+.help-popup-container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100vw;
+    height: 100vh;
+    background: rgba(0, 0, 0, 0.3);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 2000;
+}
+
+.help-popup {
+    background: var(--background-secondary);
+    padding: 3vh 3vw;
+    border-radius: 12px;
+    width: 50vw;
+    max-width: 600px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    position: relative;
+}
+
+.help-header {
+    width: 100%;
+    text-align: center;
+    position: relative;
+    margin-bottom: 1.5vh;
+}
+
+.help-header h2 {
+    font-size: 1.8rem;
+    font-weight: bold;
+    margin: 0 auto;
+}
+
+.help-close-button {
+    position: absolute;
+    top: 0;
+    right: 0;
+    padding: 0.5vh 1vw;
+
+    cursor: pointer;
+    background: none;
+    border: none;
+
+    font-size: 1.8rem;
+    color: black;
+}
+
+.help-close-button:hover {
+    color: var(--font-color-error);
+}
+
+.help-content {
+    text-align: center;
+    width: 100%;
+    padding: 1vh 0;
+}
+
+.help-title {
+    font-size: 1.5rem;
+    font-weight: bold;
+    margin-bottom: 0.5vh;
+    color: var(--font-color-primary)
+}
+
+.help-section-text {
+    font-size: 1.1rem;
+    line-height: 1.5;
+    color: var(--font-color-tertiary);
+    margin-bottom: 2vh;
+}
+
+.help-image {
+    width: 100%;
+    max-height: 250px;
+    object-fit: contain;
+    border-radius: 8px;
+    margin-top: 1vh;
+}
+
+.help-footer {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    margin-top: 2vh;
+}
+
+.help-prev-button,
+.help-next-button,
+.help-done-button {
+    display: table-cell;
+    text-align: center;
+    margin-inline: 20px;
+    padding: 0.8vh 2vw;
+    font-size: 1rem;
+    border: none;
+    border-radius: 6px;
+    cursor: pointer;
+    color: var(--font-color-primary);
+    background-color: var(--background-primary);
+}
+
+.help-prev-button:hover,
+.help-next-button:hover,
+.help-done-button {
+    background: var(--background-tertiary-20);
+}
+
+.help-progress-container {
+    width: 100%;
+    height: 6px;
+    border-radius: 3px;
+    overflow: hidden;
+    margin-top: 1.5vh;
+}
+
+.help-progress-fill {
+    height: 100%;
+    width: 0%;
+    background: var(--background-tertiary-40);
+    transition: width 0.3s ease-in-out;
+}
diff --git a/styles.css b/styles.css
index 2e235b52..d7ca4f1f 100644
--- a/styles.css
+++ b/styles.css
@@ -7,6 +7,7 @@
 @import url('frontend/styles/displays/debug-tree.css');
 @import url('frontend/styles/displays/popup.css');
 @import url('frontend/styles/displays/toast.css');
+@import url('frontend/styles/pages/help-section.css');
 @import url('frontend/styles/icons/bootstrap-icons.min.css');
 
 @font-face {


Reviews:
user: login: AdamW1087
state: CHANGES_REQUESTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T08:37:25Z
user: login: AdamW1087
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T09:14:13Z
user: login: j-mie6
state: COMMENTED
author_association: OWNER
submitted_at: 2025-03-10T09:15:48Z
user: login: aniketguptax
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T10:17:18Z
user: login: Riley-horrix
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-03-10T12:25:39Z
user: login: AdamW1087
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-03-10T12:27:16Z

Review comments:
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: AdamW1087
body: Ordering
created_at: 2025-03-10T08:33:38Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: AdamW1087
body: No brace 
created_at: 2025-03-10T08:34:21Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
+
+    def isFirstSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == 0)
+    }
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: AdamW1087
body: No brace
created_at: 2025-03-10T08:34:39Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
+
+    def isFirstSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == 0)
+    }
+
+    def isLastSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == allHelpSections.length - 1)
+    }
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: AdamW1087
body: Same thing
created_at: 2025-03-10T08:34:52Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
+
+    def isFirstSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == 0)
+    }
+
+    def isLastSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == allHelpSections.length - 1)
+    }
+
+    def nextSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val nextIndex = (currentIndex + 1) % allHelpSections.length
+        activeSection.set(allHelpSections(nextIndex))
+    }
+
+    def prevSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val prevIndex = if (currentIndex == 0) allHelpSections.length - 1 else currentIndex - 1
+        activeSection.set(allHelpSections(prevIndex))
+    }
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: AdamW1087
body: Could you not make a variable for current?
created_at: 2025-03-10T08:35:51Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
+
+    def isFirstSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == 0)
+    }
+
+    def isLastSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == allHelpSections.length - 1)
+    }
+
+    def nextSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val nextIndex = (currentIndex + 1) % allHelpSections.length
+        activeSection.set(allHelpSections(nextIndex))
+    }
+
+    def prevSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val prevIndex = if (currentIndex == 0) allHelpSections.length - 1 else currentIndex - 1
+        activeSection.set(allHelpSections(prevIndex))
+    }
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: AdamW1087
body: Or pass as arg
created_at: 2025-03-10T09:14:13Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
+
+    def isFirstSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == 0)
+    }
+
+    def isLastSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == allHelpSections.length - 1)
+    }
+
+    def nextSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val nextIndex = (currentIndex + 1) % allHelpSections.length
+        activeSection.set(allHelpSections(nextIndex))
+    }
+
+    def prevSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val prevIndex = if (currentIndex == 0) allHelpSections.length - 1 else currentIndex - 1
+        activeSection.set(allHelpSections(prevIndex))
+    }
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: j-mie6
body: There should be no need for an explicit set here, realistically 
created_at: 2025-03-10T09:15:48Z
author_association: OWNER
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
+
+    def isFirstSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == 0)
+    }
+
+    def isLastSection: Signal[Boolean] = {
+        getActiveSection.map(_.index).map(_ == allHelpSections.length - 1)
+    }
+
+    def nextSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val nextIndex = (currentIndex + 1) % allHelpSections.length
+        activeSection.set(allHelpSections(nextIndex))
+    }
+
+    def prevSection(): Unit = {
+        val currentIndex = activeSection.now().index
+        val prevIndex = if (currentIndex == 0) allHelpSections.length - 1 else currentIndex - 1
+        activeSection.set(allHelpSections(prevIndex))
+    }
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: aniketguptax
body: Yeah sorry I made this a while back and forgot to change it :(
created_at: 2025-03-10T10:17:18Z
author_association: COLLABORATOR
diff_hunk: @@ -0,0 +1,52 @@
+package controller.viewControllers
+
+import scala.scalajs.js
+
+import com.raquo.laminar.api.L.*
+import scala.scalajs.js.internal.UnitOps.unitOrOps
+
+import model.help.*
+
+object HelpViewController {
+    private val activeSection: Var[HelpSection] = Var(BaseHelpSection)
+
+    private val popupOpen: Var[Boolean] = Var(false)
+
+    def isPopupOpen: Signal[Boolean] = popupOpen.signal
+
+    def openPopup(): Unit = popupOpen.set(true)
+    def closePopup(): Unit = {
+        popupOpen.set(false)
+        activeSection.set(BaseHelpSection)
+    }
+
+    def getActiveSection: Signal[HelpSection] = {
+        activeSection.signal
+    } 
path: frontend/src/main/scala/controller/viewControllers/HelpViewController.scala
user: login: Riley-horrix
body: Should also be a val

created_at: 2025-03-10T12:25:39Z
author_association: COLLABORATOR

PR comments:
user: login: AdamW1087
created_at: 2025-03-10T10:40:42Z
body: also next and previous buttons are really long still
![image](https://github.com/user-attachments/assets/e7a8f3f1-ed13-430c-88fd-be241503a0e2)

get next on the right side and wrap the box around it?
author_association: COLLABORATOR
user: login: AdamW1087
created_at: 2025-03-10T10:44:18Z
body: just went through it. i really like it but just a couple of things, for the upload gif could you move your files so you can do it without a blur, it takes away from the feeling of before. also there isnt one for settings (can put it last or with the skipping iterative bit)
author_association: COLLABORATOR
user: login: aniketguptax
created_at: 2025-03-10T10:48:21Z
body: > just went through it. i really like it but just a couple of things, for the upload gif could you move your files so you can do it without a blur, it takes away from the feeling of before. also there isnt one for settings (can put it last or with the skipping iterative bit)

Bro it's my downloads folder 😭😭
author_association: COLLABORATOR
user: login: AdamW1087
created_at: 2025-03-10T10:48:54Z
body: > > just went through it. i really like it but just a couple of things, for the upload gif could you move your files so you can do it without a blur, it takes away from the feeling of before. also there isnt one for settings (can put it last or with the skipping iterative bit)
> 
> Bro it's my downloads folder 😭😭

yeah i just mean take everything out temporaily, record it then put things back, it just looks a bit too much
author_association: COLLABORATOR
user: login: aniketguptax
created_at: 2025-03-10T11:08:41Z
body: > > > just went through it. i really like it but just a couple of things, for the upload gif could you move your files so you can do it without a blur, it takes away from the feeling of before. also there isnt one for settings (can put it last or with the skipping iterative bit)
> 
> > 
> 
> > Bro it's my downloads folder 😭😭
> 
> 
> 
> yeah i just mean take everything out temporaily, record it then put things back, it just looks a bit too much

I can try it'll just take a lil time cuz I probably have a few gig on there
author_association: COLLABORATOR
user: login: AdamW1087
created_at: 2025-03-10T11:20:52Z
body: > > > > just went through it. i really like it but just a couple of things, for the upload gif could you move your files so you can do it without a blur, it takes away from the feeling of before. also there isnt one for settings (can put it last or with the skipping iterative bit)
> > 
> > 
> > > 
> > 
> > 
> > > Bro it's my downloads folder 😭😭
> > 
> > 
> > yeah i just mean take everything out temporaily, record it then put things back, it just looks a bit too much
> 
> I can try it'll just take a lil time cuz I probably have a few gig on there

thanks, i would do it but no mac :(
author_association: COLLABORATOR