text: import {
    CLASS_LOADING,
    CLASS_TRANSITION,
    EVENT_LOAD,
    EVENT_TRANSITION_END,
    EVENT_VIEWED,
  } from './constants'
  import {
    addClass,
    addListener,
    assign,
    forEach,
    getImageNameFromURL,
    getImageNaturalSizes,
    getTransforms,
    hasClass,
    removeClass,
    removeListener,
    setData,
    setStyle,
  } from './utilities'
  
  export default {
    render() {
      this.initContainer()
      this.initViewer()
      this.initList()
      this.renderViewer()
    },
  
    initBody() {
      const { ownerDocument } = this.element
      const body = ownerDocument.body || ownerDocument.documentElement
  
      this.body = body
      this.scrollbarWidth =
        window.innerWidth - ownerDocument.documentElement.clientWidth
      this.initialBodyPaddingRight = body.style.paddingRight
      this.initialBodyComputedPaddingRight =
        window.getComputedStyle(body).paddingRight
    },
  
    initContainer() {
      this.containerData = {
        width: window.innerWidth,
        height: window.innerHeight,
      }
    },
  
    initViewer() {
      const { options, parent } = this
      let viewerData
  
      if (options.inline) {
        viewerData = {
          width: Math.max(parent.offsetWidth, options.minWidth),
          height: Math.max(parent.offsetHeight, options.minHeight),
        }
  
        this.parentData = viewerData
      }
  
      if (this.fulled || !viewerData) {
        viewerData = this.containerData
      }
  
      this.viewerData = assign({}, viewerData)
    },
  
    renderViewer() {
      if (this.options.inline && !this.fulled) {
        setStyle(this.viewer, this.viewerData)
      }
    },
  
    initList() {
      const { element, options, list } = this
      const items = []
  
      // initList may be called in this.update, so should keep idempotent
      list.innerHTML = ''
  
      forEach(this.images, (image, index) => {
        const { src } = image
        const alt = image.alt || getImageNameFromURL(src)
        const url = this.getImageURL(image)
  
        if (src || url) {
          const item = document.createElement('li')
          const img = document.createElement('img')
  
          forEach(options.inheritedAttributes, (name) => {
            const value = image.getAttribute(name)
  
            if (value !== null) {
              img.setAttribute(name, value)
            }
          })
  
          img.src = src || url
          img.alt = alt
          img.setAttribute('data-original-url', url || src)
          item.setAttribute('data-index', index)
          item.setAttribute('data-viewer-action', 'view')
          item.setAttribute('role', 'button')
  
          if (options.keyboard) {
            item.setAttribute('tabindex', 0)
          }
  
          item.appendChild(img)
          list.appendChild(item)
          items.push(item)
        }
      })
  
      this.items = items
  
      forEach(items, (item) => {
        const image = item.firstElementChild
  
        setData(image, 'filled', true)
  
        if (options.loading) {
          addClass(item, CLASS_LOADING)
        }
  
        addListener(
          image,
          EVENT_LOAD,
          (event) => {
            if (options.loading) {
              removeClass(item, CLASS_LOADING)
            }
  
            this.loadImage(event)
          },
          {
            once: true,
          }
        )
      })
  
      if (options.transition) {
        addListener(
          element,
          EVENT_VIEWED,
          () => {
            addClass(list, CLASS_TRANSITION)
          },
          {
            once: true,
          }
        )
      }
    },
  
    renderList(index) {
      const i = index || this.index
      const width = this.items[i].offsetWidth || 30
      const outerWidth = width + 1 // 1 pixel of `margin-left` width
  
      // Place the active item in the center of the screen
      setStyle(
        this.list,
        assign(
          {
            width: outerWidth * this.length,
          },
          getTransforms({
            translateX: (this.viewerData.width - width) / 2 - outerWidth * i,
          })
        )
      )
    },
  
    resetList() {
      const { list } = this
  
      list.innerHTML = ''
      removeClass(list, CLASS_TRANSITION)
      setStyle(
        list,
        getTransforms({
          translateX: 0,
        })
      )
    },
  
    initImage(done) {
      const { options, image, viewerData } = this
      const footerHeight = this.footer.offsetHeight
      const viewerWidth = viewerData.width
      const viewerHeight = Math.max(
        viewerData.height - footerHeight,
        footerHeight
      )
      const oldImageData = this.imageData || {}
      let sizingImage
  
      this.imageInitializing = {
        abort: () => {
          sizingImage.onload = null
        },
      }
  
      sizingImage = getImageNaturalSizes(
        image,
        options,
        (naturalWidth, naturalHeight) => {
          const aspectRatio = naturalWidth / naturalHeight
          let width = viewerWidth
          let height = viewerHeight
  
          this.imageInitializing = false
  
          if (viewerHeight * aspectRatio > viewerWidth) {
            height = viewerWidth / aspectRatio
          } else {
            width = viewerHeight * aspectRatio
          }
  
          width = Math.min(width * 0.9, naturalWidth)
          height = Math.min(height * 0.9, naturalHeight)
  
          const left = (viewerWidth - width) / 2
          const top = (viewerHeight - height) / 2
  
          const imageData = {
            left,
            top,
            x: left,
            y: top,
            width,
            height,
            ratio: width / naturalWidth,
            aspectRatio,
            naturalWidth,
            naturalHeight,
          }
          const initialImageData = assign({}, imageData)
  
          if (options.rotatable) {
            imageData.rotate = oldImageData.rotate || 0
            initialImageData.rotate = 0
          }
  
          if (options.scalable) {
            imageData.scaleX = oldImageData.scaleX || 1
            imageData.scaleY = oldImageData.scaleY || 1
            initialImageData.scaleX = 1
            initialImageData.scaleY = 1
          }
  
          this.imageData = imageData
          this.initialImageData = initialImageData
  
          if (done) {
            done()
          }
        }
      )
    },
  
    renderImage(done) {
      const { image, imageData } = this
  
      setStyle(
        image,
        assign(
          {
            width: imageData.width,
            height: imageData.height,
  
            // XXX: Not to use translateX/Y to avoid image shaking when zooming
            marginLeft: imageData.x,
            marginTop: imageData.y,
          },
          getTransforms(imageData)
        )
      )
  
      if (done) {
        if (
          (this.viewing ||
            this.moving ||
            this.rotating ||
            this.scaling ||
            this.zooming) &&
          this.options.transition &&
          hasClass(image, CLASS_TRANSITION)
        ) {
          const onTransitionEnd = () => {
            this.imageRendering = false
            done()
          }
  
          this.imageRendering = {
            abort: () => {
              removeListener(image, EVENT_TRANSITION_END, onTransitionEnd)
            },
          }
  
          addListener(image, EVENT_TRANSITION_END, onTransitionEnd, {
            once: true,
          })
        } else {
          done()
        }
      }
    },
  
    resetImage() {
      // this.image only defined after viewed
      if (this.viewing || this.viewed) {
        const { image } = this
  
        if (this.viewing) {
          this.viewing.abort()
        }
  
        image.parentNode.removeChild(image)
        this.image = null
      }
    },
  }
  