Source: LensDashboardNavigator.js

import { Util } from "./Util"
import { LensDashboard } from "./LensDashboard"

/*
 * @fileoverview
 * LensDashboardNavigator module provides an enhanced lens dashboard with navigation controls and tools.
 * Extends the base LensDashboard with additional UI elements for camera control, lighting, and annotation navigation.
 */

/**
 * LensDashboardNavigator class creates an interactive lens dashboard with navigation controls.
 * Provides:
 * - Camera movement control
 * - Light direction control
 * - Annotation switching and navigation
 * - Toolbar UI elements positioned around the lens
 * @extends LensDashboard
 */
class LensDashboardNavigator extends LensDashboard {
   /**
    * Creates a new LensDashboardNavigator instance.
    * @param {Viewer} viewer - The OpenLIME viewer instance
    * @param {Object} [options] - Configuration options
    * @param {number} [options.toolboxHeight=22] - Height of the toolbox UI elements in pixels
    * @param {number} [options.toolboxGap=5] - Gap (in px) between left and roght toolboxes
    * @param {number} [options.angleToolbar=30] - Angle of toolbar position in degrees
    * @param {Object} [options.actions] - Configuration for toolbar actions
    * @param {Object} [options.actions.camera] - Camera control action
    * @param {string} options.actions.camera.label - Action identifier
    * @param {Function} options.actions.camera.task - Callback for camera action
    * @param {Object} [options.actions.light] - Light control action
    * @param {string} options.actions.light.label - Action identifier
    * @param {Function} options.actions.light.task - Callback for light action
    * @param {Object} [options.actions.annoswitch] - Annotation toggle action
    * @param {string} options.actions.annoswitch.label - Action identifier
    * @param {string} options.actions.annoswitch.type - Action type ('toggle')
    * @param {string} options.actions.annoswitch.toggleClass - CSS class for toggle element
    * @param {Function} options.actions.annoswitch.task - Callback for annotation toggle
    * @param {Object} [options.actions.prev] - Previous annotation action
    * @param {string} options.actions.prev.label - Action identifier
    * @param {Function} options.actions.prev.task - Callback for previous action
    * @param {Object} [options.actions.down] - Download annotation action
    * @param {string} options.actions.down.label - Action identifier
    * @param {Function} options.actions.down.task - Callback for download action
    * @param {Object} [options.actions.next] - Next annotation action
    * @param {string} options.actions.next.label - Action identifier
    * @param {Function} options.actions.next.task - Callback for next action
    * @param {Function} [options.updateCb] - Callback fired during lens updates
    * @param {Function} [options.updateEndCb] - Callback fired when lens movement ends
    */
   constructor(viewer, options) {
      super(viewer, options);
      options = Object.assign({
         toolboxHeight: 22,
         toolboxGap: 5,
         actions: {
            camera: { label: 'camera', cb_task: (() => { }), task: (event) => { if (!this.actions.camera.active) this.toggleLightController(); this.actions.camera.cb_task() } },
            light: { label: 'light', cb_task: (() => { }), task: (event) => { if (!this.actions.light.active) this.toggleLightController(); this.actions.light.cb_task() } },
            annoswitch: { label: 'annoswitch', type: 'toggle', toggleClass: '.openlime-lens-dashboard-annoswitch-bar', task: (event) => { } },
            prev: { label: 'prev', task: (event) => { } },
            down: { label: 'down', task: (event) => { } },
            next: { label: 'next', task: (event) => { } },
         },
         updateCb: null,
         updateEndCb: null
      }, options);
      Object.assign(this, options);

      this.moving = false;
      this.delay = 400;
      this.timeout = null; // Timeout for moving
      this.noupdate = false;

      this.angleToolbar = 30.0 * (Math.PI / 180.0);

      this.container.style.display = 'block';
      this.container.style.margin = '0';

      const h1 = document.createElement('div');
      h1.style = `text-align: center; color: #fff`;
      h1.classList.add('openlime-lens-dashboard-toolbox-header');
      h1.innerHTML = 'MOVE';

      const h2 = document.createElement('div');
      h2.style = `text-align: center; color: #fff`;
      h2.classList.add('openlime-lens-dashboard-toolbox-header');
      h2.innerHTML = 'INFO';

      this.toolbox1 = document.createElement('div');
      this.toolbox1.style = `z-index: 10; position: absolute; padding: 4px; left: 0px; width: fit-content; background-color: rgb(20, 20, 20, 1.0); border-radius: 10px; gap: 8px`;
      this.toolbox1.classList.add('openlime-lens-dashboard-toolbox');
      this.container.appendChild(this.toolbox1);
      this.toolbox1.appendChild(h1);

      this.toolbox2 = document.createElement('div');
      this.toolbox2.style = `z-index: 10; position: absolute; padding: 4px; right: 0px; width: fit-content; background-color: rgb(20, 20, 20, 1.0); border-radius: 10px; gap: 8px`;
      this.toolbox2.classList.add('openlime-lens-dashboard-toolbox');
      this.container.appendChild(this.toolbox2);
      this.toolbox2.appendChild(h2);

      this.tools1 = document.createElement('div');
      this.tools1.style = `display: flex; justify-content: center; height: ${this.toolboxHeight}px`;
      this.tools1.classList.add('openlime-lens-dashboard-toolbox-tools');
      this.toolbox1.appendChild(this.tools1);

      this.tools2 = document.createElement('div');
      this.tools2.style = `display: flex; justify-content: center; height: ${this.toolboxHeight}px`;
      this.tools2.classList.add('openlime-lens-dashboard-toolbox-tools');
      this.toolbox2.appendChild(this.tools2);

      // TOOLBOX ITEMS

      this.actions.camera.svg = `<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   viewBox="0 0 83.319054 83.319054"
   version="1.1"
   id="svg2495"
   xml:space="preserve"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg"><defs
     id="defs2492" /><path
     d="m 83.319059,41.66005 c 0,23.007824 -18.651718,41.659533 -41.659532,41.659533 C 18.651716,83.319583 -4.9557762e-6,64.667874 -4.9557762e-6,41.66005 -4.9557762e-6,18.651185 18.651716,-5.2882463e-4 41.659527,-5.2882463e-4 64.667341,-5.2882463e-4 83.319059,18.651185 83.319059,41.66005 Z"
     style="fill:#fbfbfb;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778"
     id="path74"
     class="openlime-lens-dashboard-button-bkg" /><g
     id="g1"
     class="openlime-lens-dashboard-camera"><path
       stroke="#000000"
       stroke-width="9.03222"
       d="M 41.659527,5.5306402 V 32.627305 m 0,18.064443 v 27.096665"
       id="path1"
       style="fill:none" /><path
       stroke="#000000"
       stroke-linecap="round"
       stroke-linejoin="round"
       stroke-width="9.03222"
       d="M 30.36925,16.820917 41.659527,5.5306402 52.949804,16.820917 M 30.36925,66.498136 41.659527,77.788413 52.949804,66.498136 M 16.820917,30.36925 5.5306402,41.659527 16.820917,52.949804 M 66.498136,30.36925 77.788413,41.659527 66.498136,52.949804 M 12.304806,41.659527 h 58.709441"
       id="path2"
       style="fill:none" /></g></svg>`;

      this.actions.light.svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->

<svg
   viewBox="0 0 83.319054 83.320114"
   version="1.1"
   id="svg5698"
   xmlns="http://www.w3.org/2000/svg"
   xmlns:svg="http://www.w3.org/2000/svg">
  <defs
     id="defs5695" />
  <path
     d="m 83.319055,41.660582 c 0,23.00782 -18.651715,41.659529 -41.659525,41.659529 C 18.65172,83.320111 -8.5009768e-7,64.668402 -8.5009768e-7,41.660582 -8.5009768e-7,18.651717 18.65172,3.1357422e-6 41.65953,3.1357422e-6 64.66734,3.1357422e-6 83.319055,18.651717 83.319055,41.660582 Z"
     style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778"
     id="path74"
     class="openlime-lens-dashboard-button-bkg" />
  <g
     id="g1"
     transform="matrix(1.4106801,0,0,1.4106801,-164.24813,-100.38311)"
     class="openlime-lens-dashboard-light">
    <path
       d="m 137.44618,117.65204 c 0.139,1.31022 5.28885,5.23911 7.37659,5.43772 2.08632,0.19826 1.80798,0.31679 3.29353,-0.15981 1.48413,-0.47554 6.21488,-3.25367 6.44772,-3.72921 0.59266,-1.21814 0.97296,-2.46098 0.46319,-3.21487 -0.51117,-0.7567 -1.39206,-0.19861 -2.31916,0.0801 -0.92745,0.27693 -4.87009,2.02282 -6.16973,2.4197 -1.01952,0.3115 -3.24661,-0.19862 -3.24661,-0.19862 0,0 11.29312,-3.73168 11.7355,-4.56247 0.46426,-0.87383 0.0924,-2.46133 -0.97437,-2.65959 -0.67945,-0.127 -15.44637,4.72228 -15.81714,5.31777 -0.37218,0.59549 -0.78952,1.26929 -0.78952,1.26929 z"
       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.352778"
       id="path90"/>
    <path
       d="m 138.37505,106.50074 c 0,0 -0.76236,-0.18874 -1.29964,0.0801 -0.53728,0.26882 -0.27834,3.09492 -0.27834,3.09492 l 5.75204,-0.0783 c 0,0 -4.96393,1.26894 -5.42678,2.22109 -0.46461,0.9525 0.58985,2.39395 1.53106,2.61973 0.75353,0.18203 16.15475,-4.7364 16.51282,-5.15938 0.3115,-0.36653 0.51012,-2.38125 0.18627,-2.69804 -0.32527,-0.31715 -1.62349,-0.27834 -1.62349,-0.27834 0,0 -0.51117,0.0399 -0.0469,-1.38783 0.46461,-1.4291 5.38128,-7.103886 6.58707,-10.675761 1.1617,-3.440642 0.4893,-7.948436 -0.83503,-10.118725 -1.39876,-2.294466 -3.38596,-3.770489 -6.12281,-4.841169 -2.74778,-1.076325 -7.82849,-1.683808 -11.87591,-0.556683 -4.03048,1.124655 -7.16844,3.170766 -8.9983,5.873397 -1.64289,2.426405 -1.57797,7.302147 -1.02129,9.206441 0.55668,1.906058 6.2163,8.96973 6.77298,10.39742 0.55668,1.4291 0.18627,2.30117 0.18627,2.30117 z"
       style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.352778"
       id="path96"/>
  </g>
</svg>`;

      this.actions.annoswitch.svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
      <!-- Created with Inkscape (http://www.inkscape.org/) -->
      
      <svg
         viewBox="0 0 83.319054 83.320114"
         version="1.1"
         id="svg11415"
         xml:space="preserve"
         xmlns="http://www.w3.org/2000/svg"
         xmlns:svg="http://www.w3.org/2000/svg"><defs
           id="defs11412"><marker
             style="overflow:visible"
             id="TriangleStart"
             refX="0"
             refY="0"
             orient="auto-start-reverse"
             markerWidth="5.3244081"
             markerHeight="6.155385"
             viewBox="0 0 5.3244081 6.1553851"
             preserveAspectRatio="xMidYMid"><path
               transform="scale(0.5)"
               style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
               d="M 5.77,0 -2.88,5 V -5 Z"
               id="path135" /></marker><marker
             style="overflow:visible"
             id="TriangleStart-5"
             refX="0"
             refY="0"
             orient="auto-start-reverse"
             markerWidth="5.3244081"
             markerHeight="6.155385"
             viewBox="0 0 5.3244081 6.1553851"
             preserveAspectRatio="xMidYMid"><path
               transform="scale(0.5)"
               style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
               d="M 5.77,0 -2.88,5 V -5 Z"
               id="path135-3" /></marker></defs><g
           id="g327"
           transform="translate(129.83427,13.264356)"><g
             id="g346"><path
               d="m -46.51522,28.396234 c 0,23.007813 -18.65172,41.659526 -41.65953,41.659526 -23.00782,0 -41.65952,-18.651713 -41.65952,-41.659526 0,-23.00887 18.6517,-41.66059 41.65952,-41.66059 23.00781,0 41.65953,18.65172 41.65953,41.66059 z"
               style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778"
               id="path68"
               class="openlime-lens-dashboard-button-bkg" /><g
               aria-label="i"
               id="text430"
               style="font-size:50.8px;line-height:1.25;font-family:'Palace Script MT';-inkscape-font-specification:'Palace Script MT';font-variant-ligatures:none;letter-spacing:0px;word-spacing:0px;stroke-width:0.264583"
               transform="matrix(1.9896002,0,0,1.9896002,-378.32178,-41.782121)"><path
                 d="m 149.74343,19.295724 c -1.4224,1.1176 -2.5908,2.032 -3.5052,2.6416 0.3556,1.0668 0.8128,1.9304 1.9304,3.556 1.4224,-1.27 1.5748,-1.4224 3.302,-2.7432 -0.1524,-0.3048 -0.254,-0.508 -0.6604,-1.1684 -0.3048,-0.6096 -0.3556,-0.6096 -0.762,-1.6256 z m 1.9304,25.4 -0.8636,0.4572 c -3.5052,1.9304 -4.1148,2.1844 -4.7244,2.1844 -0.5588,0 -0.9144,-0.5588 -0.9144,-1.4224 0,-0.8636 0,-0.8636 1.6764,-7.5692 1.8796,-7.7216 1.8796,-7.7216 1.8796,-8.128 0,-0.3048 -0.254,-0.508 -0.6096,-0.508 -0.8636,0 -3.8608,1.6764 -8.0264,4.4704 l -0.1016,1.4224 c 3.0988,-1.6764 3.2512,-1.7272 3.7084,-1.7272 0.4064,0 0.6096,0.3048 0.6096,0.8636 0,0.7112 -0.1524,1.4224 -0.9144,4.318 -2.3876,8.8392 -2.3876,8.8392 -2.3876,10.16 0,1.2192 0.4572,2.032 1.2192,2.032 0.8636,0 2.2352,-0.6604 4.9276,-2.3876 0.9652,-0.6096 1.9304,-1.2192 2.8956,-1.8796 0.4572,-0.254 0.8128,-0.508 1.4224,-0.8636 z"
                 style="font-weight:bold;font-family:Z003;-inkscape-font-specification:'Z003 Bold'"
                 id="path495" /></g><path
               style="fill:none;stroke:#000000;stroke-width:17.09477;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
               d="M -66.121922,49.608737 -110.22757,7.1826674"
               id="path465"
               class="openlime-lens-dashboard-annoswitch-bar" /></g></g></svg>`;

      this.actions.prev.svg = `<svg
               viewBox="0 0 83.319054 83.320114"
               version="1.1"
               id="svg11415"
               xml:space="preserve"
               xmlns="http://www.w3.org/2000/svg"
               xmlns:svg="http://www.w3.org/2000/svg"><defs
                 id="defs11412"><marker
                   style="overflow:visible"
                   id="TriangleStart"
                   refX="0"
                   refY="0"
                   orient="auto-start-reverse"
                   markerWidth="5.3244081"
                   markerHeight="6.155385"
                   viewBox="0 0 5.3244081 6.1553851"
                   preserveAspectRatio="xMidYMid"><path
                     transform="scale(0.5)"
                     style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
                     d="M 5.77,0 -2.88,5 V -5 Z"
                     id="path135" /></marker><marker
                   style="overflow:visible"
                   id="TriangleStart-5"
                   refX="0"
                   refY="0"
                   orient="auto-start-reverse"
                   markerWidth="5.3244081"
                   markerHeight="6.155385"
                   viewBox="0 0 5.3244081 6.1553851"
                   preserveAspectRatio="xMidYMid"><path
                     transform="scale(0.5)"
                     style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
                     d="M 5.77,0 -2.88,5 V -5 Z"
                     id="path135-3" /></marker></defs><g
                 id="g417"
                 transform="matrix(3.3565779,0,0,3.3565779,129.92814,-51.220758)"><g
                   id="g335"><path
                     d="m -172.71351,100.60243 c 0,23.00781 -18.65172,41.65952 -41.65953,41.65952 -23.00782,0 -41.65952,-18.65171 -41.65952,-41.65952 0,-23.00887 18.6517,-41.66059 41.65952,-41.66059 23.00781,0 41.65953,18.65172 41.65953,41.66059 z"
                     style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778"
                     id="path68"
                     class="openlime-lens-dashboard-button-bkg"
                     transform="matrix(0.29792248,0,0,0.29792248,37.569341,-2.3002842)" /><path
                     style="fill:#030104"
                     d="m -35.494703,28.624414 c 0,-0.264 0.213,-0.474 0.475,-0.474 h 2.421 c 0.262,0 0.475,0.21 0.475,0.474 0,3.211 2.615,5.826 5.827,5.826 3.212,0 5.827,-2.615 5.827,-5.826 0,-3.214 -2.614,-5.826 -5.827,-5.826 -0.34,0 -0.68,0.028 -1.016,0.089 v 1.647 c 0,0.193 -0.116,0.367 -0.291,0.439 -0.181,0.073 -0.383,0.031 -0.521,-0.104 l -4.832,-3.273 c -0.184,-0.185 -0.184,-0.482 0,-0.667 l 4.833,-3.268 c 0.136,-0.136 0.338,-0.176 0.519,-0.104 0.175,0.074 0.291,0.246 0.291,0.438 v 1.487 c 0.34,-0.038 0.68,-0.057 1.016,-0.057 5.071,0 9.198,4.127 9.198,9.198 0,5.07 -4.127,9.197 -9.198,9.197 -5.07,10e-4 -9.197,-4.126 -9.197,-9.196 z"
                     id="path415" /></g></g></svg>`;

      this.actions.down.svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
        <!-- Created with Inkscape (http://www.inkscape.org/) -->
        
        <svg
           viewBox="0 0 83.319054 83.320114"
           version="1.1"
           id="svg11415"
           xml:space="preserve"
           xmlns="http://www.w3.org/2000/svg"
           xmlns:svg="http://www.w3.org/2000/svg"><defs
             id="defs11412"><marker
               style="overflow:visible"
               id="TriangleStart"
               refX="0"
               refY="0"
               orient="auto-start-reverse"
               markerWidth="5.3244081"
               markerHeight="6.155385"
               viewBox="0 0 5.3244081 6.1553851"
               preserveAspectRatio="xMidYMid"><path
                 transform="scale(0.5)"
                 style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
                 d="M 5.77,0 -2.88,5 V -5 Z"
                 id="path135" /></marker><marker
               style="overflow:visible"
               id="TriangleStart-5"
               refX="0"
               refY="0"
               orient="auto-start-reverse"
               markerWidth="5.3244081"
               markerHeight="6.155385"
               viewBox="0 0 5.3244081 6.1553851"
               preserveAspectRatio="xMidYMid"><path
                 transform="scale(0.5)"
                 style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
                 d="M 5.77,0 -2.88,5 V -5 Z"
                 id="path135-3" /></marker></defs><g
             id="g4652"
             transform="translate(145.46385,95.197966)"><g
               id="g4846"
               transform="translate(-126.60931,52.756264)"><path
                 d="m 64.464511,-106.29364 c 0,23.007813 -18.65172,41.659526 -41.65953,41.659526 -23.0078196,0 -41.659526,-18.651713 -41.659526,-41.659526 0,-23.00887 18.6517064,-41.66059 41.659526,-41.66059 23.00781,0 41.65953,18.65172 41.65953,41.66059 z"
                 style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778"
                 id="path68"
                 class="openlime-lens-dashboard-button-bkg" /><g
                 id="g2392-5"
                 transform="matrix(0.26458333,0,0,0.26458333,-283.58108,-263.57207)"><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
                   d="m 1072.4033,509.27736 h 171.1826"
                   id="path351-6" /><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:30;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
                   d="m 1185.0215,568.3701 h 59.6026"
                   id="path351-3-2" /><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:30;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
                   d="m 1184.2167,621.15576 h 59.6026"
                   id="path351-3-2-0" /><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:40;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
                   d="m 1072.4033,679.59496 h 171.1826"
                   id="path351-3-6-7-1" /><path
                   style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:11.4448;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#TriangleStart-5)"
                   d="m 1074.9115,570.87447 54.1203,-0.0275"
                   id="path1366-2" /><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:14;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
                   d="m 1080.0425,521.28147 v 54.87857"
                   id="path1402-7" /><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
                   d="m 1150.8866,623.00688 0.3956,-5.02729"
                   id="path2545" /><path
                   style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:30;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
                   d="m 1185.0215,567.71656 h 59.6026"
                   id="path2720" /></g></g></g></svg>`;

      this.actions.next.svg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
      <!-- Created with Inkscape (http://www.inkscape.org/) -->
      
      <svg
         viewBox="0 0 83.319054 83.320114"
         version="1.1"
         id="svg11415"
         xml:space="preserve"
         xmlns="http://www.w3.org/2000/svg"
         xmlns:svg="http://www.w3.org/2000/svg"><defs
           id="defs11412"><marker
             style="overflow:visible"
             id="TriangleStart"
             refX="0"
             refY="0"
             orient="auto-start-reverse"
             markerWidth="5.3244081"
             markerHeight="6.155385"
             viewBox="0 0 5.3244081 6.1553851"
             preserveAspectRatio="xMidYMid"><path
               transform="scale(0.5)"
               style="fill:context-stroke;fill-rule:evenodd;stroke:context-stroke;stroke-width:1pt"
               d="M 5.77,0 -2.88,5 V -5 Z"
               id="path135" /></marker></defs><g
           id="g4652"
           transform="translate(-12.647874,74.762541)"><path
             d="m 95.96693,-33.101955 c 0,23.007813 -18.65172,41.6595258 -41.65953,41.6595258 -23.00782,0 -41.659526,-18.6517128 -41.659526,-41.6595258 0,-23.008872 18.651706,-41.660586 41.659526,-41.660586 23.00781,0 41.65953,18.651714 41.65953,41.660586 z"
             style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:0.352778"
             id="path68"
             class="openlime-lens-dashboard-button-bkg" /><g
             id="g4636"
             transform="translate(173.74831,-50.897484)"><path
               style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10.5833;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
               d="m -142.08694,-4.7366002 h 45.292059"
               id="path351" /><path
               style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:10.5833;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
               d="m -142.08694,40.326598 h 45.292059"
               id="path351-3-6-7" /><path
               style="display:inline;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.20746;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1;marker-end:url(#TriangleStart)"
               d="m -136.09942,8.7192481 0.008,14.9721889"
               id="path1366" /><path
               style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.70417;stroke-linecap:butt;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
               d="M -136.07283,-1.5605128 V 24.204958"
               id="path1402" /><path
               style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:7.9375;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
               d="m -111.69142,24.864565 h 15.76985"
               id="path351-3-2-0-3" /><path
               style="fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:7.9375;stroke-linecap:round;stroke-linejoin:miter;stroke-dasharray:none;stroke-opacity:1"
               d="m -111.37623,10.725444 h 15.76986"
               id="path2720-9" /></g></g></svg>`;

      for (let [name, action] of Object.entries(this.actions)) {
         action.element = Util.SVGFromString(action.svg);
         action.element.style = `height: 100%; margin: 0 5px`;
         action.element.classList.add('openlime-lens-dashboard-button');
         if (action.type == 'toggle') {
            const toggleElm = action.element.querySelector(action.toggleClass);
            toggleElm.style.visibility = `hidden`;
            action.active = false;
         }
         action.element.addEventListener('pointerdown', (e) => {
            if (action.type == 'toggle') {
               action.active = !action.active;
               const toggleElm = action.element.querySelector(action.toggleClass);
               if (action.active) {
                  toggleElm.style.visibility = `visible`;
               } else {
                  toggleElm.style.visibility = `hidden`;
               }
               this.noupdate = true;
            }
            action.task(e);
            e.preventDefault();
         });
      }

      this.tools1.appendChild(this.actions.camera.element);
      this.tools1.appendChild(this.actions.light.element);
      this.tools2.appendChild(this.actions.annoswitch.element);
      this.tools2.appendChild(this.actions.prev.element);
      this.tools2.appendChild(this.actions.down.element);
      this.tools2.appendChild(this.actions.next.element);

      // Set Camera movement active
      this.actions.camera.active = this.actions.camera.element.classList.toggle('openlime-lens-dashboard-camera-active');
      this.actions.light.active = false;

      // Enable camera, light, next buttons
      this.setActionEnabled('camera');
      this.setActionEnabled('light');
      this.setActionEnabled('annoswitch');
      this.setActionEnabled('next');
   }

   /**
    * Retrieves an action configuration by its label.
    * @param {string} label - The action label to find
    * @returns {Object|null} The action configuration object or null if not found
    * @private
    */
   getAction(label) {
      let result = null;
      for (let [name, action] of Object.entries(this.actions)) {
         if (action.label === label) {
            result = action;
            break;
         }
      }
      return result;
   }

   /**
    * Enables or disables a specific action button.
    * @param {string} label - The action label to modify
    * @param {boolean} [enable=true] - Whether to enable or disable the action
    */
   setActionEnabled(label, enable = true) {
      const action = this.getAction(label);
      if (action) {
         action.element.classList.toggle('enabled', enable);
      }
   }

   /**
    * Toggles between camera and light control modes.
    * When light control is active, modifies controller behavior for light direction adjustment.
    * @private
    */
   toggleLightController() {
      let active = this.actions.light.element.classList.toggle('openlime-lens-dashboard-light-active');
      this.actions.light.active = active;
      this.actions.camera.active = this.actions.camera.element.classList.toggle('openlime-lens-dashboard-camera-active');

      for (let layer of Object.values(this.viewer.canvas.layers))
         for (let c of layer.controllers)
            if (c.control == 'light') {
               c.active = true;
               c.activeModifiers = active ? [0, 2, 4] : [2, 4];  //nothing, shift and alt
            }
   }

   /**
    * Updates the dashboard position and UI elements.
    * @private
    * @param {number} x - Center X coordinate in scene space
    * @param {number} y - Center Y coordinate in scene space
    * @param {number} r - Lens radius in scene space
    */
   update(x, y, r) {
      if (this.noupdate) {
         this.noupdate = false;
         return;
      }
      super.update(x, y, r);
      const center = {
         x: this.lensBox.x,
         y: this.lensBox.y
      }
      const radius = this.lensBox.r;
      const sizew = this.lensBox.w;
      const sizeh = this.lensBox.h;

      // Set toolbox position
      const tbw1 = this.toolbox1.clientWidth;
      const tbh1 = this.toolbox1.clientHeight;
      const tbw2 = this.toolbox2.clientWidth;
      const tbh2 = this.toolbox2.clientHeight;
      let cbx = radius * Math.sin(this.angleToolbar);
      let cby = radius * Math.cos(this.angleToolbar);

      let bx1 = this.containerSpace + radius - cbx - tbw1 / 2 - this.toolboxGap;
      let by1 = this.containerSpace + radius + cby - tbh1 / 2;
      this.toolbox1.style.left = `${bx1}px`;
      this.toolbox1.style.top = `${by1}px`;

      let bx2 = this.containerSpace + radius + cbx - tbw2 / 2 + this.toolboxGap;
      let by2 = this.containerSpace + radius + cby - tbh2 / 2;
      this.toolbox2.style.left = `${bx2}px`;
      this.toolbox2.style.top = `${by2}px`;

      if (this.updateCb) {
         // updateCb(c.x, c.y, r, dashboard.w, dashboard.h, canvas.w, canvas.h) all params in canvas coordinates
         this.updateCb(center.x, center.y, radius, sizew, sizeh, this.viewer.camera.viewport.w, this.viewer.camera.viewport.h);
      }

      if (!this.moving) {
         this.toggle();
         this.moving = true;
      }
      if (this.timeout) clearTimeout(this.timeout);
      this.timeout = setTimeout(() => {
         this.toggle();
         this.moving = false;
         if (this.updateEndCb) this.updateEndCb(center.x, center.y, radius, sizew, sizeh, this.viewer.camera.viewport.w, this.viewer.camera.viewport.h);
      }, this.delay);
   }
}

export { LensDashboardNavigator }