Changed around line 15: class MitoSiteApp {
-
+ // Modified toggleHelpCommand method
+ toggleHelpCommand(container) {
+ let helpModal = container.querySelector(".help-modal")
+
+ if (helpModal) {
+ // If modal exists, toggle its visibility
+ const isVisible = helpModal.style.display !== "none"
+ helpModal.style.display = isVisible ? "none" : "block"
+ return
+ }
+
+ // Create help modal
+ helpModal = document.createElement("div")
+ helpModal.className = "help-modal"
+ helpModal.style.cssText = `
+ position: fixed;
+ top: 0;
+ right: 0;
+ width: 300px;
+ height: 100vh;
+ background: rgba(0, 0, 0, 0.85);
+ color: white;
+ padding: 20px;
+ overflow-y: auto;
+ z-index: 1000;
+ box-shadow: -2px 0 10px rgba(0, 0, 0, 0.3);
+ `
+
+ // Create heading
+ const heading = document.createElement("h2")
+ heading.textContent = "Keyboard Shortcuts"
+ heading.style.marginBottom = "20px"
+ helpModal.appendChild(heading)
+
+ // Parse shortcuts and create clickable elements
+ const shortcuts = this.keyboardShortcuts
+ .split("\n")
+ .slice(1) // Skip header row
+ .map((line) => {
+ const [key, command, category] = line.split(" ")
+ return { key, command, category }
+ })
+
+ // Group shortcuts by category
+ const categorizedShortcuts = shortcuts.reduce(
+ (acc, { key, command, category }) => {
+ if (!acc[category]) {
+ acc[category] = []
+ }
+ acc[category].push({ key, command })
+ return acc
+ },
+ {},
+ )
+
+ // Create sections for each category
+ Object.entries(categorizedShortcuts).forEach(([category, shortcuts]) => {
+ const categorySection = document.createElement("div")
+ categorySection.style.marginBottom = "20px"
+
+ const categoryHeading = document.createElement("h3")
+ categoryHeading.textContent = category
+ categoryHeading.style.marginBottom = "10px"
+ categorySection.appendChild(categoryHeading)
+
+ shortcuts.forEach(({ key, command }) => {
+ const shortcutElement = document.createElement("div")
+ shortcutElement.style.cssText = `
+ display: flex;
+ align-items: center;
+ margin-bottom: 10px;
+ cursor: pointer;
+ padding: 5px;
+ border-radius: 4px;
+ `
+
+ // Add hover effect
+ shortcutElement.addEventListener("mouseenter", () => {
+ shortcutElement.style.background = "rgba(255, 255, 255, 0.1)"
+ })
+ shortcutElement.addEventListener("mouseleave", () => {
+ shortcutElement.style.background = "none"
+ })
+
+ const keyElement = document.createElement("kbd")
+ keyElement.textContent = key
+ keyElement.style.cssText = `
+ background: rgba(255, 255, 255, 0.2);
+ padding: 2px 6px;
+ border-radius: 3px;
+ margin-right: 10px;
+ min-width: 20px;
+ text-align: center;
+ `
+
+ const commandName = command.replace("Command", "")
+ const displayName = commandName
+ .split(/(?=[A-Z])/)
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(" ")
+
+ const descElement = document.createElement("span")
+ descElement.textContent = displayName
+
+ shortcutElement.appendChild(keyElement)
+ shortcutElement.appendChild(descElement)
+
+ // Add click handler to trigger the command
+ shortcutElement.addEventListener("click", () => {
+ if (typeof this[command] === "function") {
+ this[command](container)
+ }
+ })
+
+ categorySection.appendChild(shortcutElement)
+ })
+
+ helpModal.appendChild(categorySection)
+ })
+
+ // Add close button
+ const closeButton = document.createElement("button")
+ closeButton.textContent = "×"
+ closeButton.style.cssText = `
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ background: none;
+ border: none;
+ color: white;
+ font-size: 24px;
+ cursor: pointer;
+ padding: 5px;
+ `
+ closeButton.addEventListener("click", () => {
+ helpModal.style.display = "none"
+ })
+ helpModal.appendChild(closeButton)
+
+ // Add to document body instead of container
+ document.body.appendChild(helpModal)
+ }
+
+ keyboardShortcuts = `key command category
+ d toggleDetectionCommand Detection
+ t drawRandomBoxesCommand Detection
+ h flipHorizontalCommand Camera
+ v flipVerticalCommand Camera
+ c takeSnapshotCommand Camera
+ z changeZoomLevelDisplayCommand Overlay
+ ? toggleHelpCommand Overlay`
+
+ setupKeyboardShortcuts(container) {
+ // Parse the keyboard shortcuts string into a map
+ const shortcuts = new Map(
+ this.keyboardShortcuts
+ .split("\n")
+ .slice(1) // Skip header row
+ .map((line) => {
+ const [key, command] = line.split(" ")
+ return [key, command]
+ }),
+ )
+
+ // Add keyboard event listener
+ document.addEventListener("keydown", (e) => {
+ // Ignore if user is typing in an input field
+ if (e.target.tagName.toLowerCase() === "input") return
+
+ const command = shortcuts.get(e.key.toLowerCase())
+ if (command && typeof this[command] === "function") {
+ e.preventDefault()
+ this[command](container)
+ }
+ })
+ }
+
+ toggleDetectionCommand(container) {
+ this.isDetecting = !this.isDetecting
+ if (this.isDetecting) {
+ this.startDetection(container)
+ console.log("Detection enabled")
+ } else {
+ // Clear all boxes when detection is disabled
+ const data = this.instances.get(container)
+ data.boxes = []
+ this.clearCanvas(container)
+
+ // Update cell counter
+ const hud = container.querySelector(".scrollWebcamHud")
+ const cellCounter = hud.querySelector(
+ '.scrollWebcamHudElement[data-type="cellCount"]',
+ )
+ if (cellCounter) {
+ cellCounter.textContent = "Cells Counted: 0"
+ }
+
+ console.log("Detection disabled")
+ }
+ }
+
+ flipHorizontalCommand(container) {
+ const data = this.instances.get(container)
+ const { video } = data
+
+ // Toggle the transform style
+ const currentTransform = video.style.transform || ""
+ if (currentTransform.includes("scaleX(-1)")) {
+ video.style.transform = currentTransform.replace("scaleX(-1)", "")
+ } else {
+ video.style.transform = `${currentTransform} scaleX(-1)`
+ }
+
+ // Also flip the canvas to match
+ const { canvas } = data
+ if (currentTransform.includes("scaleX(-1)")) {
+ canvas.style.transform = currentTransform.replace("scaleX(-1)", "")
+ } else {
+ canvas.style.transform = `${currentTransform} scaleX(-1)`
+ }
+ }
+
+ flipVerticalCommand(container) {
+ const data = this.instances.get(container)
+ const { video } = data
+
+ // Toggle the transform style
+ const currentTransform = video.style.transform || ""
+ if (currentTransform.includes("scaleY(-1)")) {
+ video.style.transform = currentTransform.replace("scaleY(-1)", "")
+ } else {
+ video.style.transform = `${currentTransform} scaleY(-1)`
+ }
+
+ // Also flip the canvas to match
+ const { canvas } = data
+ if (currentTransform.includes("scaleY(-1)")) {
+ canvas.style.transform = currentTransform.replace("scaleY(-1)", "")
+ } else {
+ canvas.style.transform = `${currentTransform} scaleY(-1)`
+ }
+ }
+
+ takeSnapshotCommand(container) {
+ const data = this.instances.get(container)
+ const { video, canvas } = data
+
+ // Create a temporary canvas to capture the full state
+ const tempCanvas = document.createElement("canvas")
+ tempCanvas.width = video.videoWidth
+ tempCanvas.height = video.videoHeight
+ const tempCtx = tempCanvas.getContext("2d")
+
+ // 1. Draw the video frame
+ tempCtx.drawImage(video, 0, 0)
+
+ // 2. Draw the detection boxes from the canvas
+ tempCtx.drawImage(canvas, 0, 0)
+
+ // 3. Draw HUD elements
+ const hud = container.querySelector(".scrollWebcamHud")
+ if (hud) {
+ // Get all HUD elements
+ const hudElements = hud.querySelectorAll(".scrollWebcamHudElement")
+
+ hudElements.forEach((element) => {
+ // Get computed styles
+ const style = window.getComputedStyle(element)
+ const rect = element.getBoundingClientRect()
+ const containerRect = container.getBoundingClientRect()
+
+ // Calculate relative position within container
+ const x = rect.left - containerRect.left
+ const y = rect.top - containerRect.top
+
+ // Set up text styling
+ tempCtx.font = style.font || "30px Arial"
+ tempCtx.fillStyle = style.color || "white"
+ tempCtx.textBaseline = "top"
+
+ // Apply text shadow if present
+ if (style.textShadow && style.textShadow !== "none") {
+ tempCtx.shadowColor = "black"
+ tempCtx.shadowBlur = 2
+ tempCtx.shadowOffsetX = 1
+ tempCtx.shadowOffsetY = 1
+ }
+
+ // Draw the text
+ tempCtx.fillText(
+ element.textContent,
+ (x / containerRect.width) * video.videoWidth,
+ (y / containerRect.height) * video.videoHeight,
+ )
+
+ // Reset shadow
+ tempCtx.shadowColor = "transparent"
+ tempCtx.shadowBlur = 0
+ tempCtx.shadowOffsetX = 0
+ tempCtx.shadowOffsetY = 0
+ })
+ }
+
+ // 4. Draw zoom level indicator
+ const zoomDisplay = container.querySelector(".top-left")
+ if (zoomDisplay) {
+ const style = window.getComputedStyle(zoomDisplay)
+ tempCtx.font = style.font || "16px Arial"
+ tempCtx.fillStyle = style.color || "white"
+ tempCtx.textBaseline = "top"
+ tempCtx.fillText(zoomDisplay.textContent, 10, 10)
+ }
+
+ // Create thumbnail container if it doesn't exist
+ let thumbContainer = container.querySelector(".snapshot-thumbnails")
+ if (!thumbContainer) {
+ thumbContainer = document.createElement("div")
+ thumbContainer.className = "snapshot-thumbnails"
+ thumbContainer.style.position = "fixed"
+ thumbContainer.style.left = "10px"
+ thumbContainer.style.top = "10px"
+ thumbContainer.style.width = "120px"
+ thumbContainer.style.zIndex = "1000"
+ thumbContainer.style.maxHeight = "100vh"
+ thumbContainer.style.overflowY = "auto"
+ thumbContainer.style.padding = "10px"
+ thumbContainer.style.background = "rgba(0, 0, 0, 0.5)"
+ thumbContainer.style.borderRadius = "5px"
+ container.appendChild(thumbContainer)
+ }
+
+ // Create and add thumbnail
+ const thumbnail = document.createElement("div")
+ thumbnail.style.marginBottom = "10px"
+ thumbnail.style.cursor = "pointer"
+ thumbnail.style.position = "relative"
+
+ // Add timestamp to thumbnail
+ const timestamp = document.createElement("div")
+ timestamp.style.position = "absolute"
+ timestamp.style.bottom = "0"
+ timestamp.style.left = "0"
+ timestamp.style.right = "0"
+ timestamp.style.background = "rgba(0,0,0,0.5)"
+ timestamp.style.color = "white"
+ timestamp.style.fontSize = "10px"
+ timestamp.style.padding = "2px"
+ timestamp.textContent = new Date().toLocaleTimeString()
+
+ const img = document.createElement("img")
+ img.src = tempCanvas.toDataURL("image/png")
+ img.style.width = "100%"
+ img.style.border = "2px solid white"
+
+ thumbnail.appendChild(img)
+ thumbnail.appendChild(timestamp)
+ thumbContainer.appendChild(thumbnail)
+
+ // Add click handler to open full size in new window
+ thumbnail.addEventListener("click", () => {
+ const win = window.open()
+ win.document.write(`
+
+
+
Snapshot - ${new Date().toLocaleString()}+
+ body { margin: 0; background: #000; display: flex; justify-content: center; align-items: center; min-height: 100vh; }
+ img { max-width: 100%; max-height: 100vh; object-fit: contain; }
+
+
+
+
+
+
+ `)
+ })
+ }
+
+ changeZoomLevelDisplayCommand(container) {
+ // Get next zoom level
+ const currentIndex = this.zoomLevels.indexOf(this.currentZoomLevel)
+ const nextIndex = (currentIndex + 1) % this.zoomLevels.length
+ this.currentZoomLevel = this.zoomLevels[nextIndex]
+
+ // Update or create zoom display element
+ let zoomDisplay = container.querySelector(".top-left")
+ // Update display text
+ zoomDisplay.textContent = `${this.microscopeName} ${this.currentZoomLevel}`
+ }
+
+ microscopeName = "Swift SW200DL"
+ zoomLevels = ["40x", "100x", "400x"]
+ currentZoomLevel = "100x"
+
Changed around line 714: class MitoSiteApp {
- setupKeyboardShortcuts(container) {
- document.addEventListener("keydown", (e) => {
- const data = this.instances.get(container)
-
- if (e.key === "Escape") {
- data.isDrawing = false
- this.redrawBoxes(container)
- } else if (e.key === "z" && e.ctrlKey) {
- data.boxes.pop()
- this.redrawBoxes(container)
- } else if (e.key === "Delete") {
- this.clearAllBoxes(container)
- } else if (e.key.toLowerCase() === "r") {
- // Toggle automatic box drawing
- if (data.autoDrawInterval) {
- clearInterval(data.autoDrawInterval)
- data.autoDrawInterval = null
- console.log("Stopped auto drawing")
+ drawRandomBoxesCommand(container) {
+ const data = this.instances.get(container)
+ // Toggle automatic box drawing
+ if (data.autoDrawInterval) {
+ clearInterval(data.autoDrawInterval)
+ data.autoDrawInterval = null
+ console.log("Stopped auto drawing")
+ } else {
+ console.log("Started auto drawing")
+ let max = 253
+ data.autoDrawInterval = setInterval(() => {
+ if (max) {
+ this.drawRandomBox(container)
+ max--
- console.log("Started auto drawing")
- let max = 253
- data.autoDrawInterval = setInterval(() => {
- if (max) {
- this.drawRandomBox(container)
- max--
- } else {
- if (Math.random() > 0.92) this.drawRandomBox(container)
- }
- }, 25) // Draw every 500ms
+ if (Math.random() > 0.92) this.drawRandomBox(container)
- }
- })
+ }, 25) // Draw every 500ms
+ }