- Jump To … +
  ./demo/modules/canvas-minimap.js ./demo/modules/canvas-scene-editor.js ./demo/modules/demo-m006-c1.js ./demo/modules/demo-m006-c2.js ./demo/modules/demo-m006-c3.js ./demo/modules/demo-m006-c4.js ./demo/modules/demo-m006-utils.js ./demo/modules/dom-entity-editor.js ./demo/modules/entity-copy-paste.js ./demo/modules/entity-manipulation-gui.js ./demo/modules/entity-navigation.js ./demo/modules/entity-ring-builder.js ./demo/modules/london-crime-lines.js ./demo/modules/london-crime-stacked-bars.js ./demo/modules/lottie-loader.js ./demo/modules/simple-chart-frame.js ./demo/modules/simple-graph-lines.js ./demo/modules/simple-graph-stacked-bars.js ./demo/modules/wikipedia-views-spiral-chart.js ./demo/snippets/animated-highlight-gradient-text-snippet.js ./demo/snippets/animated-hover-gradient-snippet.js ./demo/snippets/animated-word-gradient-snippet.js ./demo/snippets/before-after-slider-infographic.js ./demo/snippets/bubbles-text-snippet.js ./demo/snippets/green-box-snippet.js ./demo/snippets/jazzy-button-snippet.js ./demo/snippets/page-performance-snippet-test.js ./demo/snippets/page-performance-snippet.js ./demo/snippets/pan-image-snippet.js ./demo/snippets/placeholder-effect-snippet.js ./demo/snippets/ripple-effect-snippet.js ./demo/snippets/risograph-text-gradient-snippet.js ./demo/snippets/spotlight-text-snippet-test.js ./demo/snippets/spotlight-text-snippet.js ./demo/snippets/swirling-stripes-text-snippet.js ./demo/snippets/text-snippet-helper.js ./demo/snippets/word-highlighter-snippet.js ./demo/snippets/worley-text-gradient-snippet.js

- §

  # Demo Modules 005

  Accessible GUI-based simple canvas editor

  Related files:

  - Accessible GUI-based simple canvas editor - main demo

  #### Usage

  This module creates a large Cell wrapper which we can use to host the entitys to be used in the scene, alongside a draggable, keyboard accessible minimap for navigating around the Cell. The module will also add the following zones to the supplied Canvas wrapper:

  - A frame drag zone, to drag the frame around the map - the main navigation functionality (processingOrder: 20)
  - A map drag zone, to drag the map around the Canvas wrapper’s base Cell (processingOrder: 21)
  - A keyboard zone - the frame and map can be moved using `ALT + arrows` keystrokes, and can be hidden/revealed using the `ALT + backspace` keystroke

  Inputs to the `initializeMinimap` function

  - `canvas` - SC canvas wrapper object (required)
  - `mainDimensions` - a Number, or an array of `[width, height]` Numbers, setting the size of the scene Cell which will be returned to the calling code (default: 1600)
  - `mapDimensions` - a Number, or an array of `[width, height]` Numbers, setting the size of minimap that appears on the scene Cell (default: 200)

  Output from the `initializeMinimap` function - is an object containing the following attributes:

  - `checkForMinimapChanges` - a function that can be invoked by an animation object, to adjust responsively to changes in the host DOM canvas element’s dimensions (responsive canvas)
  - `mainCell` - the scene Cell wrapper object
  - `killCanvasMinimap` - kill function, to remove everything associated with the scene Cell and minimap from the SC library
- §

  #### Initialization function (exported)

      const initializeMinimap = (args = {}, scrawl) => {

- §

  Check we have required arguments/values

          if (scrawl == null) throw new Error('SC entity navigation module error: missing Scrawl-canvas object argument');

      /* eslint-disable-next-line */
          let { mainDimensions, mapDimensions, supportedKey, canvas } = args;

          let argsCheck = '';

          if (canvas == null) argsCheck += ' Canvas wrapper;';

          if (argsCheck.length) throw new Error(`SC canvas minimap module error: missing arguments -${argsCheck}`);

- §

  Magic numbers

          let mainDimensionX = 1600,
              mainDimensionY = 1600,
              mapDimensionX = 200,
              mapDimensionY = 200;

          if (mainDimensions) {

              if (Array.isArray(mainDimensions)){

                  mainDimensionX = mainDimensions[0] || 1600;
                  mainDimensionY = mainDimensions[1] || 1600;
              }
              else if (mainDimensions.toFixed) {

                  mainDimensionX = mainDimensions || 1600;
                  mainDimensionY = mainDimensions || 1600;
              }
          }

          if (mapDimensions) {

              if (Array.isArray(mapDimensions)){

                  mapDimensionX = mapDimensions[0] || 200;
                  mapDimensionY = mapDimensions[1] || 200;
              }
              else if (mapDimensions.toFixed) {

                  mapDimensionX = mapDimensions || 200;
                  mapDimensionY = mapDimensions || 200;
              }
          }

          const mainMapRatioX = mainDimensionX / mapDimensionX,
              mainMapRatioY = mainDimensionY / mapDimensionY;

          let [displayWidth, displayHeight] = canvas.get("dimensions");

          let frameWidth = (displayWidth / mainDimensionX) * mapDimensionX;
          let frameHeight = (displayHeight / mainDimensionY) * mapDimensionY;

          canvas.set({

              includeInTabNavigation: true,

          }).setBase({

- §

  The base Cell needs to compile after the other Cells

              compileOrder: 2
          });

- §

  Build out the main Cell (default: 1600px x 1600px)

  - we don’t actually display this Cell

          const mainCell = canvas.buildCell({

              name: "main-cell",
              dimensions: [mainDimensionX, mainDimensionY],
              shown: false,
              compileOrder: 0,
          });

- §

  Rely on the main code to populate the main Cell with entitys, and the functionality required to interact with them. We only care here about displaying the main map in the canvas element

- §

  Display the main Cell in the base Cell

          const mainCellPicture = scrawl.makePicture({

              name: "main-cell-picture",
              group: canvas.base.name,
              asset: "main-cell",
              dimensions: [displayWidth, displayHeight],
              copyDimensions: [displayWidth, displayHeight]
          });

- §

  Build out the smaller map Cell (default: 200px x 200px)

  - this will initially display in the top right corner
  - it will be draggable too

          const pivotGroup = scrawl.makeGroup({

              name: "map-cell-pivot-group",
              host: canvas.base.name
          });

          const mapPivot = scrawl.makeBlock({

              name: "map-cell-pivot",
              group: "map-cell-pivot-group",
              start: [displayWidth - mapDimensionX, 0],
              dimensions: [mapDimensionX, mapDimensionY],
              method: "none"
          });

- §

  Functionality so we can drag-drop the map Cell around the base Cell

          const mapDragZone = scrawl.makeDragZone({
              zone: canvas,
              collisionGroup: "map-cell-pivot-group",
              coordinateSource: canvas.base,
              endOn: ["up", "leave"],
              preventTouchDefaultWhenDragging: true,
              processingOrder: 21,
          });

          const mapCell = canvas.buildCell({

              name: "map-cell",
              dimensions: [mapDimensionX, mapDimensionY],

- §

  We pivot the map Cell to the draggable Block entity

  - wherever the Block goes, the map Cell will follow

              pivot: "map-cell-pivot",
              lockTo: "pivot",
              backgroundColor: "white",

- §

  The map Cell needs to compile after the large Cell

              compileOrder: 1
          });

- §

  Now we can copy the large Cell into the map Cell

          scrawl.makePicture({

              name: "map-cell-picture",
              group: "map-cell",
              asset: "main-cell",
              dimensions: ["100%", "100%"],
              copyDimensions: ["100%", "100%"],
              lineWidth: 4,
              method: "fillThenDraw",
          });

- §

  Add the draggable map frame

          const myFrameGroup = scrawl.makeGroup({

              name: "map-cell-frame-group",
              host: mapCell.name
          });

          const frame = scrawl.makeBlock({

              name: "map-cell-frame",
              group: "map-cell-frame-group",
              dimensions: [frameWidth, frameHeight],
              strokeStyle: "red",
              lineWidth: 2,
              method: "draw"
          });

- §

  Functionality so we can drag-drop the frame around the map Cell

          const checkPermittedFramePosition = (x, y) => {

              if (x < 0 || y < 0) return false;
              if (x + frameWidth > mapDimensionX) return false;
              if (y + frameHeight > mapDimensionY) return false;

              return true;
          };

          const updateFrames = (x, y) => {

              const newX = x * mainMapRatioX,
              newY = y * mainMapRatioY;

- §

  Adjust the position of the Picture wrt to the frame in the map

              mainCellPicture.set({
                  copyStartX: newX,
                  copyStartY: newY
              });

- §

  Adjust the position of the large Cell wrt the base Cell

              mainCell.set({
                  startX: -newX,
                  startY: -newY
              });
          };

          const exitDrag = () => {

              let [x, y] = frame.get('position');

              if (x < 0) x = 0;
              if (y < 0) y = 0;
              if (x + frameWidth > mapDimensionX) x = mapDimensionX - frameWidth;
              if (y + frameHeight > mapDimensionY) y = mapDimensionY - frameHeight;

              frame.set({
                  start: [x, y],
              });

              updateFrames(x, y);
          };

          const checkMinimapFrameDrag = () => {

              const [x, y] = frame.get('position');

              if (checkPermittedFramePosition(x, y)) updateFrames(x, y);
              else frameDragZone('exit');
          };

          const frameDragZone = scrawl.makeDragZone({

              zone: canvas,
              collisionGroup: "map-cell-frame-group",
              coordinateSource: mapCell,
              endOn: ["up", "leave"],
              updateWhileMoving: checkMinimapFrameDrag,
              updateOnPrematureExit: exitDrag,
              preventTouchDefaultWhenDragging: true,
              exposeCurrentArtefact: true,
              processingOrder: 20,
          });

          const checkForMinimapChanges = () => {

              const [w, h] = canvas.get("dimensions");

              if (w !== displayWidth || h !== displayHeight) {

                  displayWidth = w;
                  displayHeight = h;

                  frameWidth = (displayWidth / mainDimensionX) * mapDimensionX;
                  frameHeight = (displayHeight / mainDimensionY) * mapDimensionY;

                  mainCellPicture.set({
                      dimensions: [displayWidth, displayHeight],
                      copyDimensions: [displayWidth, displayHeight]
                  });

                  frame.set({
                      dimensions: [frameWidth, frameHeight]
                  });

                  mapPivot.set({
                      start: [displayWidth - mapDimensionX, 0]
                  });
              }

              mainCell.updateHere();
              mapCell.updateHere();
          };

- §

  Keyboard navigation

          const moveFrame = (direction) => {

              let [x, y] = frame.get('position');

              switch (direction) {

                  case 'left' :
                      x -= 1;
                      break;
                  case 'up' :
                      y -= 1;
                      break;
                  case 'right' :
                      x += 1;
                      break;
                  case 'down' :
                      y += 1;
                      break;
              }

              if(checkPermittedFramePosition(x, y)) {

                  const newX = x * mainMapRatioX,
                      newY = y * mainMapRatioY;

                  frame.set({
                      startX: x,
                      startY: y,
                  });

- §

  Adjust the position of the Picture wrt to the frame in the map

                  mainCellPicture.set({
                      copyStartX: newX,
                      copyStartY: newY
                  });

- §

  Adjust the position of the large Cell wrt the base Cell

                  mainCell.set({
                      startX: -newX,
                      startY: -newY
                  });
              }
          };

          const moveMinimap = (direction) => {

              let [x, y] = mapPivot.get('position');

              switch (direction) {

                  case 'left' :
                      x -= 2;
                      break;
                  case 'up' :
                      y -= 2;
                      break;
                  case 'right' :
                      x += 2;
                      break;
                  case 'down' :
                      y += 2;
                      break;
              }

              mapPivot.set({
                  startX: x,
                  startY: y,
              });
          };

- §

  Show/hide minimap

          let minimapIsShowing = true;
          const displayMinimap = () => {

              minimapIsShowing = !minimapIsShowing;

              mapPivot.set({
                  visibility: minimapIsShowing,
              });

              mapCell.set({
                  shown: minimapIsShowing,
              });
          };

- §

  Keyboard event listener functions

          const keyboard = scrawl.makeKeyboardZone({

              zone: canvas,

              shiftAlt: {
                  Backspace: () => displayMinimap(),
                  ArrowLeft: () => moveMinimap('left'),
                  ArrowUp: () => moveMinimap('up'),
                  ArrowRight: () => moveMinimap('right'),
                  ArrowDown: () => moveMinimap('down'),
              },

              altOnly: {
                  Backspace: () => displayMinimap(),
                  ArrowLeft: () => moveFrame('left'),
                  ArrowUp: () => moveFrame('up'),
                  ArrowRight: () => moveFrame('right'),
                  ArrowDown: () => moveFrame('down'),
              },
          });

- §

  #### Cleanup and return

          const killCanvasMinimap = () => {
              pivotGroup.kill(true);
              myFrameGroup.kill(true);
              mainCell.get('group').kill(true);
              mapCell.get('group').kill(true);
              keyboard.kill();
              frameDragZone();
              mapDragZone();
          };

- §

  Return object

          return {
              mainCell,
              checkForMinimapChanges,
              killCanvasMinimap,
          };
      };

- §

  #### Export

      export {
          initializeMinimap,
      };