File: src/lib/UI/NativeDOM/List/Item.js

Recommend this page to a friend!
  Classes of Dom Hastings  >  JS Webdav Client  >  src/lib/UI/NativeDOM/List/Item.js  >  Download  
File: src/lib/UI/NativeDOM/List/Item.js
Role: Class source
Content type: text/plain
Description: Class source
Class: JS Webdav Client
Access files of a Webdav server
Author: By
Last change:
Date: 1 month ago
Size: 8,889 bytes
 

Contents

Class file image Download
import * as BasicLightbox from 'basiclightbox';
import Element from '../Element.js';
import Prism from 'prismjs';
import joinPath from '../../../joinPath.js';

export default class Item extends Element {
  #base64Encoder;
  #entry;
  #templates = Object.freeze({
    video: (entry) => `<video autoplay controls><source src="${entry.fullPath}"/></video>`,
    audio: (entry) => `<audio autoplay controls><source src="${entry.fullPath}"/></audio>`,
    image: (entry) => `<img alt="${entry.title}" src="${entry.fullPath}"/>`,
    font: (entry) => {
      const formats = {
          eot: 'embedded-opentype',
          otf: 'opentype',
          ttf: 'truetype'
        },
        extension = entry.name.replace(/^.+\.([^.]+)$/, '$1').toLowerCase(),
        fontName = entry.fullPath.replace(/\W+/g, '_'),
        demoText = `The quick brown fox jumps over the lazy dog. 0123456789<br/>
        Aa Bb Cc Dd Ee Ff Gg Hh Ii Jj Kk Ll Mm Nn Oo Pp Qq Rr Ss Tt Uu Vv Ww Xx Yy Zz`
      ;

      return `<style type="text/css">@font-face{font-family:"${fontName}";src:url("${entry.fullPath}") format("${formats[extension] || extension}")}</style>
<h1 style="font-family:'${fontName}'">${entry.title}</h1>
<p style="font-family:'${fontName}';font-size:1.5em">${demoText}</p>
<p style="font-family:'${fontName}'">${demoText}</p>
<p style="font-family:'${fontName}'"><strong>${demoText}</strong></p>
<p style="font-family:'${fontName}'"><em>${demoText}</em></p>`;
    },
    text: (entry, content) => `<pre><code class="language-${entry.extension}">${content.replace(/[<>]/g, (c) => ({'<': '&lt;', '>': '&gt;'}[c]))}</code></pre>`,
    pdf: (entry) => `<iframe src="${entry.fullPath}" frameborder="0" border="0" height="100%" width="100%"></iframe>`
  });

  constructor(entry, base64Encoder = btoa) {
    const template = `<li tabindex="0" data-full-path=${entry.fullPath}">
  <span class="title">${entry.title}</span>
  <input type="text" name="rename" class="hidden" readonly>
  <span class="size">${entry.displaySize}</span>
  <a href="#" title="Delete" class="delete"></a>
  <!--<a href="#" title="Move" class="move"></a>-->
  <a href="#" title="Rename" class="rename"></a>
  <!--<a href="#" title="Copy" class="copy"></a>-->
  <a href="${entry.fullPath}" download="${entry.name}" title="Download"></a>
</li>`;

    super(template);

    this.#base64Encoder = base64Encoder;
    this.#entry = entry;

    this.element.classList.add(
      ...[
        entry.directory ? 'directory' : 'file',
        entry.type      ? entry.type  : 'unknown'
      ]
    );

    if (entry.placeholder) {
      this.element.classList.add('loading');
    }

    if (! entry.del) {
      this.element.querySelector('.delete').setAttribute('hidden', '');
    }

    if (! entry.rename) {
      this.element.querySelector('.rename').setAttribute('hidden', '');
    }

    this.bindEvents();
  }

  bindEvents(element = this.element) {
    this.on('entry:update', (entry) => {
      if (entry === this.#entry) {
        this.update();
      }
    });

    this.on('move:failed', (sourcePath) => {
      if (sourcePath === this.#entry.fullPath) {
        this.loading(false);
      }
    });

    this.on('delete:failed', (sourcePath) => {
      if (sourcePath === this.#entry.fullPath) {
        this.loading(false);
      }
    });

    element.addEventListener('click', () => this.open());

    element.querySelector('[download]').addEventListener('click', (event) => event.stopPropagation());

    element.querySelector('.delete').addEventListener('click', (event) => {
      event.preventDefault();
      event.stopPropagation();

      this.del();
    });

    element.querySelector('.rename').addEventListener('click', (event) => {
      event.stopPropagation();
      event.preventDefault();

      this.rename();
    });

    element.addEventListener('keydown', (event) => {
      if ([113, 46, 13].includes(event.which)) { // if (['F2', 'Delete', 'Enter'].includes(event.key)) {
        event.preventDefault();

        if (event.which === 113) { // if (event.key === 'F2') {
          if (this.#entry.rename) {
            this.rename();
          }
        }
        else if (event.which === 46) { // else if (event.key === 'Delete') {
          if (this.#entry.del) {
            this.del();
          }
        }
        else if (event.which === 13 && ! this.#entry.directory) { // else if (event.key === 'Enter' && ! this.#entry.directory) {
          if (event.shiftKey) {
            return this.download();
          }

          this.open();
        }
      }
    });
  }

  del() {
    const entry = this.#entry;

    if (! entry.del) {
      throw new TypeError(`'${entry.name}' is read only.`);
    }

    this.loading();

    // TODO: i18n
    if (! confirm(`Are you sure you want to delete '${entry.title}?'`)) {
      return this.loading(false);
    }

    this.trigger('delete', entry.fullPath, entry);
  }

  download() {
    if (this.#entry.directory) {
      return;
    }

    this.element.querySelector('[download]').click();
  }

  loading(loading = true) {
    if (loading) {
      return this.element.classList.add('loading');
    }

    this.element.classList.remove('loading');
  }

  open() {
    const entry = this.#entry;

    this.loading();

    if (entry.directory) {
      return this.trigger('go', entry.fullPath, {
        failure: () => this.loading(false)
      });
    }

    const launchLightbox = (lightboxContent, onShow) => {
      const escapeListener = (event) => {
          if (event.which === 27) { // if (event.key === 'Escape') {
            lightbox.close();
          }
        },
        lightbox = BasicLightbox.create(lightboxContent, {
          className: entry.type,
          onShow: () => {
            this.loading(false);
            document.addEventListener('keydown', escapeListener);

            if (onShow) {
              onShow(lightbox);
            }
          },
          onClose: () => document.removeEventListener('keydown', escapeListener)
        })
      ;

      lightbox.show();
    };

    if (['video', 'audio', 'image', 'font', 'pdf'].includes(entry.type)) {
      this.trigger('check', entry.fullPath, () => {
        launchLightbox(this.#templates[entry.type](entry));
      }, () => this.loading(false));
    }
    else {
      this.trigger('get', entry.fullPath, (content) => {
        if (! content) {
          return this.loading(false);
        }

        if (entry.type !== 'text') {
          return this.download();
        }

        launchLightbox(this.#templates.text(entry, content), (lightbox) => Prism.highlightAllUnder(lightbox.element()));
      });

      this.loading(false);
    }

    event.preventDefault();
  }

  rename() {
    const entry = this.#entry;

    if (! entry.rename) {
      throw new TypeError(`'${entry.name}' cannot be renamed.`);
    }

    const node = this.element,
      title = node.querySelector('.title'),
      input = node.querySelector('input'),
      setInputSize = () => {
        title.innerText = input.value;
        input
          .style
          .setProperty(
            'width',
            `${title.scrollWidth}px`
          )
        ;
      },
      save = () => {
        // don't process if there's no name change
        if (input.value !== entry.title) {
          this.loading();

          unbindListeners();

          return this.trigger('move', entry.fullPath, joinPath(entry.path, input.value), entry);
        }

        revert();
      },
      unbindListeners = () => {
        input.removeEventListener('blur', blurListener);
        input.removeEventListener('keydown', keyDownListener);
        input.removeEventListener('input', inputListener);
      },
      revert = () => {
        title.classList.remove('invisible');
        input.classList.add('hidden');
        input.value = entry.title;
        setInputSize();
        unbindListeners();

        return node.focus();
      },
      blurListener = () => {
        save();
      },
      keyDownListener = (event) => {
        if (event.which === 13) { // if (event.key === 'Enter') {
          event.stopPropagation();
          event.preventDefault();

          save();
        }
        else if (event.which === 27) { // else if (event.key === 'Escape') {
          revert();
        }
      },
      inputListener = () => {
        return setInputSize();
      }
    ;

    title.classList.add('invisible');

    input.classList.remove('hidden');
    input.value = entry.title;
    setInputSize();
    input.removeAttribute('readonly');
    input.addEventListener('blur', blurListener);
    input.addEventListener('keydown', keyDownListener);
    input.addEventListener('input', inputListener);
    input.focus();
  }

  update() {
    if (this.#entry.placeholder && this.element.classList.contains('placeholder')) {
      this.element.classList.remove('placeholder');
    }
  }
}