Cross-platform Image Viewer With Angular 4+

Author: yyosifov
Publish Date:
Official Page: Go to website
Views Total: 185
License: MIT

Description:

A Cross-Platform, blazing fast, extendable image Viewer based on Electron, Angular 4, and TypeScript.

More Features:

  • Loads a directory of images in a second
  • Let’s you manipulate image with the keyboard – next,previous,rotate,copy,delete
  • Easily extendable – new functionality can be added at any time
  • Supports all image files a browser does
  • Runs cross-platform. Mainly useful for OS X though.

Installation:

# NPM
$ npm install image-viewer --save

Usage:

The markup.

<div class="div-image">
  <div id='div-center-container' class='div-center-container'>
    <button id="open-file"  type="button" class="btn btn-secondary-outline btn-lg">Open</button>

    <img id="currentImage" />
  </div>

  <div id="control-panel">
      <div class="control-buttons text-center">
        <div>
          <button type="button" class="btn btn-primary" id="rotate-left"><i class="fa fa-undo fa-1x"></i></button>
          <button type="button" class="btn btn-success" id="previous"><i class="fa fa-arrow-circle-left fa-1x"></i></button>
          <label style="margin-left: 5px; margin-right: 5px; color: white;" id="directoryStats"></label>
          <button type="button" class="btn btn-success" id="next"><i class="fa fa-arrow-circle-right fa-1x"></i></button>
          <button type="button" class="btn btn-primary" id="rotate-right"><i class="fa fa-repeat fa-1x"></i></button>
        </div>
      </div>
    </div>
</div>

The example app.js.

'use strict';

var fs = require('fs');
var path = require('path');
var _ = require('lodash');
var remote = require('electron').remote;
const { dialog } = require('electron').remote;
var ipcRenderer = require('electron').ipcRenderer;

var fileSystem = require('./js/file-system');
var constants = require('./js/constants');

// jquery selectors
var $currentImage = $('#currentImage'),
  $previous = $('#previous'),
  $next = $('#next'),
  $directoryStats = $('#directoryStats'),
  $openFile = $('#open-file'),
  $controlPanel = $('#control-panel'),
  $rotateLeft = $('#rotate-left'),
  $rotateRight = $('#rotate-right'),
  $divCenterImage = $('#div-center-container'),
  containerDimensions = {};

// the list of all retrieved files
var imageFiles = [],
  currentImageFile = '',
  currentDir = '';

// migrated
var toggleButtons = function(hasSelectedImage) {
  // disable buttons?
  if(hasSelectedImage) {
    $openFile.hide();
    $currentImage.show();
    $controlPanel.show();
  } else {
    $openFile.show();
    $currentImage.hide();
    $controlPanel.hide();
  }
};

// Shows an image on the page.
var showImage = function(index) {
  toggleButtons(true);

  setRotateDegrees(0);
  // clear the CSS 
  $currentImage.css({
    height: '100%',
    width: '100%'
  });
  // clean up
  containerDimensions = {};

  $currentImage.data('currentIndex', index);
  $currentImage.attr('src', imageFiles[index]);
  currentImageFile = imageFiles[index];

  // Hide show previous/next if there are no more/less files.
  // $next.toggle(!(index + 1 === imageFiles.length));
  // $previous.toggle(!(index === 0));

  // set the stats text
  var statsText = (index + 1) + ' / ' + imageFiles.length;
  $directoryStats.text(statsText);

  ipcRenderer.send('image-changed', currentImageFile);
};

var hasImages = function() {
  return imageFiles && imageFiles.length > 0;
}

var onPreviousClick = function() {
  if(!hasImages()) {
    return;
  }

  var currentImageId = $currentImage.data('currentIndex');
  if(currentImageId > 0) {
    showImage(--currentImageId);
  } else {
    // we're at 0 -> move to the end.
    showImage(imageFiles.length - 1);
  }
};

$previous.click(onPreviousClick);

var onNextClick = function() {
  if(!hasImages()) {
    return;
  }

  var currentImageId = $currentImage.data('currentIndex');
  if(currentImageId + 1 < imageFiles.length) {
    showImage(++currentImageId);
  } else {
    // we're at the end - next is the beginning
    showImage(0);
  }
};

$next.click(onNextClick);

// Show image in Full screen on double click
var fullscreenButton = document.getElementById("currentImage");
fullscreenButton.addEventListener("dblclick", toggleFullScreen, false);

function toggleFullScreen() {
  //console.log('double click...');
  ipcRenderer.send('toggle-full-screen');
}

var _loadDir = function(dir, fileName) {
  currentDir = dir;
  imageFiles = fileSystem.getDirectoryImageFiles(dir);

  var selectedImageIndex = imageFiles.indexOf(fileName);
  if(selectedImageIndex === -1) {
    selectedImageIndex = 0;
  }

  if(selectedImageIndex < imageFiles.length) {
    showImage(selectedImageIndex);  
  }
  else {
    alert('No image files found in this directory.');
  }
};

var onOpen = function(filePath) {
  filePath = filePath + ''; // convert to string
  var stat = fs.lstatSync(filePath);
  if(stat.isDirectory()) {
    onDirOpen(filePath);
  } else {
    onFileOpen(filePath);
  }
};

var onFileOpen = function(fileName) {
  fileName = fileName + ''; // convert to string.
  var dirName = path.dirname(fileName);

  _loadDir(dirName, fileName);
};

var onDirOpen = function(dir) {
  _loadDir(dir + ''); // convert to string
};

var onFileDelete = function() {
  // file has been deleted, show previous or next...
  var index = imageFiles.indexOf(currentImageFile);
  if(index > -1) {
    imageFiles.splice(index, 1);
  }
  if(index === imageFiles.length) index--;
  if(index < 0) {
    // no more images in this directory - it's empty...
    toggleButtons(false);
  } else {
    showImage(index);
  }
};

var getCurrentFile = function() {
  return currentImageFile;
};

var getPreviousDegrees = function() {
  var deg = $currentImage.data('rotateDegree') || 0;
  return deg;
};

var getDimensionIndex = function(deg) {
  //console.log(`get ${deg}`);
  return (deg == 0 || Math.abs(deg) == 180) ? 0 : 1;
}

var setRotateDegrees = function(deg) {
  if (!currentImage.src) {
    // nothing to rotate
    return;
  }

  const imgHeight = $currentImage.height(),
    imgWidth = $currentImage.width();

  const containerHeight = $divCenterImage.height(),
    containerWidth = $divCenterImage.width();
  //console.log(`imgHeight = ${imgHeight}, imgWidth = ${imgWidth}`);

  const prevDegrees = getPreviousDegrees();
  const dimensionsIndex = getDimensionIndex(prevDegrees);
  if(!containerDimensions[dimensionsIndex]) {
    // persist them
    containerDimensions[dimensionsIndex] = {
      height: containerHeight,
      width: containerWidth
    };

    //console.log(`set ${dimensionsIndex} in container dimensions: h:` + containerDimensions[dimensionsIndex].height + ' and w:' + containerDimensions[dimensionsIndex].width);
  }

  const otherIndex = (dimensionsIndex + 1) % 2;
  if(containerDimensions[otherIndex]) {
    //console.log('in container dimensions: h:' + containerDimensions[otherIndex].height + ' and w:' + containerDimensions[otherIndex].width);
    $currentImage.css({
      height: containerDimensions[otherIndex].height,
      width: containerDimensions[otherIndex].width
    });
  } else {
    containerDimensions[otherIndex] = {
      height: containerWidth,
      width: containerHeight
    }
    //console.log(`set ${otherIndex} in container dimensions: h:` + containerDimensions[otherIndex].height + ' and w:' + containerDimensions[otherIndex].width);

    $currentImage.css({
      height: containerWidth,
      width: containerHeight
    });
  }
  
  $currentImage.css({
     '-webkit-transform' : 'rotate('+deg+'deg)',
       '-moz-transform' : 'rotate('+deg+'deg)',  
        '-ms-transform' : 'rotate('+deg+'deg)',  
         '-o-transform' : 'rotate('+deg+'deg)',  
            'transform' : 'rotate('+deg+'deg)',  
                 'zoom' : 1
  });

  $currentImage.data('rotateDegree', deg);
};

var onRotate = function(rotationDegrees) {
  // get current degree and rotationDegrees
  var deg = $currentImage.data('rotateDegree') || 0;
  deg -= rotationDegrees;
  deg = deg % 360;

  setRotateDegrees(deg);
};

$rotateLeft.click(function() {
  onRotate(-90);
});

$rotateRight.click(function() {
  onRotate(90);
});

// Initialize the app
var initialize = function() {
  var appMenu = require('./js/app-menu'); 
  appMenu.initialize({
    onOpen: onOpen,
    onFileDelete: onFileDelete,
    getCurrentFile: getCurrentFile
  });

  // no files selected
  toggleButtons(false);

  //ipcRenderer.send('log', '$openFile = ' + $openFile);
  $openFile.click(function() {
    //ipcRenderer.send('log', 'dialog = ' + dialog);
    // TODO: Refactor this... code duplication
    dialog.showOpenDialog({
      properties: [
        'openFile',
        'openDirectory'
      ],
      filters: [
        {
          name: 'Images',
          extensions: constants.SupportedImageExtensions  
        }
      ]
    },
    function(fileName) {
      if(fileName) {
        onOpen(fileName);
      }
    });
  });

  // handle navigation from left/right clicks
  $(window).keydown(function(ev) {
    switch(ev.keyCode) {
      case constants.LeftKey:
        onPreviousClick();
        break;

      case constants.RightKey:
        onNextClick();
        break;

      case constants.UpKey:
        onRotate(-90);
        break;

      case constants.DownKey:
        onRotate(90);
        break;

      case constants.EscapeKey:
        ipcRenderer.send('exit-full-screen', currentImageFile);
        break;
    }
  });
};
initialize();

Preview:

Image Viewer

Leave a Reply