visbug-lib
Version:
<p align="center"> <img src="./assets/visbug.png" width="300" height="300" alt="visbug"> <br> <a href="https://www.npmjs.org/package/visbug"><img src="https://img.shields.io/npm/v/visbug.svg?style=flat" alt="npm latest version number"></a> <a href
279 lines (224 loc) • 6.73 kB
JavaScript
import $ from 'blingblingjs'
import { getStyle } from '../utilities/'
let imgs = []
, overlays = []
, dragItem
const state = {
watching: true,
}
export function watchImagesForUpload() {
imgs = $([
...document.images,
...$('picture'),
...findBackgroundImages(document),
])
clearWatchers(imgs)
initWatchers(imgs)
}
export function toggleWatching({watch}) {
state.watching = watch
}
const initWatchers = imgs => {
imgs.on('dragover', onDragEnter)
imgs.on('dragleave', onDragLeave)
imgs.on('drop', onDrop)
$(document.body).on('dragover', onDragEnter)
$(document.body).on('dragleave', onDragLeave)
$(document.body).on('drop', onDrop)
$(document.body).on('dragstart', onDragStart)
$(document.body).on('dragend', onDragEnd)
}
const clearWatchers = imgs => {
imgs.off('dragenter', onDragEnter)
imgs.off('dragleave', onDragLeave)
imgs.off('drop', onDrop)
$(document.body).off('dragenter', onDragEnter)
$(document.body).off('dragleave', onDragLeave)
$(document.body).off('drop', onDrop)
$(document.body).on('dragstart', onDragStart)
$(document.body).on('dragend', onDragEnd)
imgs = []
}
const previewFile = file => {
return new Promise((resolve, reject) => {
let reader = new FileReader()
reader.readAsDataURL(file)
reader.onloadend = () => resolve(reader.result)
})
}
// only fired for in-page drag events, track what the user picked up
const onDragStart = ({target}) =>
dragItem = target
const onDragEnd = e =>
dragItem = undefined
const onDragEnter = async e => {
e.preventDefault()
e.stopPropagation()
const pre_selected = $('img[data-selected=true], [data-selected=true] > img')
if (imgs.some(img => img === e.target)) {
if (!pre_selected.length) {
if (!isFileEvent(e))
previewDrop(e.target)
showOverlay(e.currentTarget, 0)
}
else {
if (pre_selected.some(node => node == e.target) && !isFileEvent(e))
pre_selected.forEach(node =>
previewDrop(node))
pre_selected.forEach((img, i) =>
showOverlay(img, i))
}
}
}
const onDragLeave = e => {
e.stopPropagation()
const pre_selected = $('img[data-selected=true], [data-selected=true] > img')
if (!pre_selected.some(node => node === e.target))
resetPreviewed(e.target)
else
pre_selected.forEach(node => resetPreviewed(node))
hideOverlays()
}
const onDrop = async e => {
e.stopPropagation()
e.preventDefault()
const srcs = await getTransferData(dragItem, e)
if (srcs.length) {
const selectedImages = $('img[data-selected=true], [data-selected=true] > img')
const targetImages = getTargetContentImages(selectedImages, e)
if (targetImages.length) {
updateContentImages(targetImages, srcs)
}
else {
const bgImages = getTargetBackgroundImages(imgs, e)
updateBackgroundImages(bgImages, srcs[0])
}
}
hideOverlays()
}
const getTransferData = async (dragItem, e) => {
if (dragItem)
return [dragItem.currentSrc]
return e.dataTransfer.files.length
? await Promise.all([...e.dataTransfer.files]
.filter(file => file.type.includes('image'))
.map(previewFile))
: []
}
const getTargetContentImages = (selected, e) =>
selected.length ? selected
: e.target.nodeName === 'IMG' && !selected.length ? [e.target]
: []
const updateContentImages = (images, srcs) => {
let i = 0
images.forEach(img => {
clearDragHistory(img)
updateContentImage(img, srcs[i])
i = ++i % srcs.length
})
}
const updateContentImage = (img, src) => {
img.src = src
if (img.srcset !== '')
img.srcset = src
const sources = getPictureSourcesToUpdate(img)
if (sources.length)
sources.forEach(source =>
source.srcset = src)
}
const getTargetBackgroundImages = (images, e) =>
images.filter(img =>
img.contains(e.target))
const updateBackgroundImages = (images, src) =>
images.forEach(img => {
clearDragHistory(img)
if (window.getComputedStyle(img).backgroundImage != 'none')
img.style.backgroundImage = `url(${src})`
})
const getPictureSourcesToUpdate = img =>
Array.from(img.parentElement.children)
.filter(sibling =>
sibling.nodeName === 'SOURCE')
.filter(source =>
!source.media || window.matchMedia(source.media).matches)
const showOverlay = (node, i) => {
if (!state.watching) return
const rect = node.getBoundingClientRect()
const overlay = overlays[i]
if (overlay) {
overlay.update = rect
}
else {
overlays[i] = document.createElement('visbug-overlay')
overlays[i].position = rect
document.body.appendChild(overlays[i])
}
}
const hideOverlays = () => {
overlays.forEach(overlay =>
overlay.remove())
overlays = []
}
const findBackgroundImages = el => {
const src_regex = /url\(\s*?['"]?\s*?(\S+?)\s*?["']?\s*?\)/i
return $('*').reduce((collection, node) => {
const prop = getStyle(node, 'background-image')
const match = src_regex.exec(prop)
// if (match) collection.push(match[1])
if (match) collection.push(node)
return collection
}, [])
}
const previewDrop = async (node) => {
if (!['lastSrc','lastSrcset','lastSiblings','lastBackgroundImage'].some(prop => node[prop])){
const setSrc = dragItem.currentSrc
if (window.getComputedStyle(node).backgroundImage !== 'none'){
node.lastBackgroundImage = window.getComputedStyle(node).backgroundImage
node.style.backgroundImage = `url(${setSrc})`
}else{
cacheImageState(node)
updateContentImage(node, setSrc)
}
}
}
const cacheImageState = image => {
image.lastSrc = image.src
image.lastSrcset = image.srcset
const sibSource = getPictureSourcesToUpdate(image)
if (sibSource.length) {
sibSource.forEach(sib => {
sib.lastSrcset = sib.srcset
sib.lastSrc = sib.src
})
}
}
const resetPreviewed = node => {
if (node.lastSrc)
node.src = node.lastSrc
if (node.lastSrcset)
node.srcset = node.lastSrcset
const sources = getPictureSourcesToUpdate(node)
if (sources.length)
sources.forEach(source => {
if (source.lastSrcset)
source.srcset = source.lastSrcset
if (source.lastSrc)
source.src = source.lastSrc
})
if (node.lastBackgroundImage)
node.style.backgroundImage = node.lastBackgroundImage
clearDragHistory(node)
}
const clearDragHistory = node => {
['lastSrc','lastSrcset','lastBackgroundImage'].forEach(prop =>
node[prop] = null)
sources = getPictureSourcesToUpdate(node)
if (sources){
sources.forEach(source => {
source.lastSrcset = null
source.lastSrc = null
})
}
}
const isFileEvent = e =>
e.dataTransfer.types.some(type => type === 'Files')