Skip to content

Instantly share code, notes, and snippets.

@myobie
Last active April 15, 2020 11:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save myobie/f4e26d1ffbaf472f81920e427d0f3448 to your computer and use it in GitHub Desktop.
Save myobie/f4e26d1ffbaf472f81920e427d0f3448 to your computer and use it in GitHub Desktop.
Gives one a div which will overlay the page when a file is dragged onto the window and lets one specify a callback which will receive the files which were dropped if any (using a new as-of-yet-unpublished state library)
import { html, css, globalCSS } from './html.js'
import { State } from './state.js'
const isDraggingOver = new State(false)
let runOnce = false
isDraggingOver.onUpdate(newState => {
const root = document.documentElement
const dropzoneDisplay = newState ? 'block' : 'none'
root.style.setProperty('--dropzone-display', dropzoneDisplay)
})
/** @typedef { import('./html').TemplateResult } TemplateResult */
/**
* @param {function(File[]): void} onDropCallback
* @returns {function(): TemplateResult}
*/
export function createDropzone (onDropCallback) {
if (!runOnce) {
window.addEventListener('dragenter', e => {
windowdragenter(e)
})
globalCSS`
:root {
--dropzone-display: none;
}
`
runOnce = true
}
/** @type {function(DragEvent): void} */
const combinedOnDrop = e => {
ondrop(e)
if (e.dataTransfer) {
/** @type {File[]} */
const files = []
for (let i = 0, len = e.dataTransfer.items.length; i < len; ++i) {
const item = e.dataTransfer.items[i]
if (item.kind === 'file') {
const file = item.getAsFile()
if (file) {
files.push(file)
}
}
}
if (files.length > 0) {
onDropCallback(files)
}
}
}
return () => html`
<div
@drop=${combinedOnDrop}
@dragenter=${ondragenter}
@dragover=${ondragover}
@dragleave=${ondragleave}
class=${css`
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(179, 219, 255, 0.3);
border: 10px solid rgba(179, 219, 255, 0.8);
z-index: 999;
display: var(--dropzone-display);
`}
></div>
`
}
/**
* @param {DragEvent} e
* @returns {void}
*/
function windowdragenter (e) {
isDraggingOver.update(true)
}
/**
* @param {DragEvent} e
* @returns {void}
*/
function ondragenter (e) {
e.preventDefault()
isDraggingOver.update(true)
}
/**
* @param {DragEvent} e
* @returns {void}
*/
function ondragleave (e) {
e.preventDefault()
isDraggingOver.update(false)
}
/**
* @param {DragEvent} e
* @returns {void}
*/
function ondragover (e) {
e.preventDefault()
if (e.dataTransfer === null) {
return
}
e.dataTransfer.dropEffect = 'copy'
}
/**
* @param {DragEvent} e
* @returns {void}
*/
function ondrop (e) {
e.preventDefault()
isDraggingOver.update(false)
if (e.dataTransfer === null) {
return
}
e.dataTransfer.effectAllowed = 'copy'
}
import {
html as litHTML,
render as litRender
// @ts-ignore
} from 'https://cdn.pika.dev/lit-html@^1.1.2'
import {
css as emotion,
injectGlobal
// @ts-ignore
} from 'https://cdn.pika.dev/emotion@^10.0.27'
// These type imports are hacks – I don't want to use npm to load these, but I
// have not found a way to load them yet unless I want to submodule these
// (which I might want to do)
// https://github.com/Polymer/lit-html/blob/master/src/lib/template-result.ts
/** @typedef { import('../../node_modules/lit-html/lit-html').TemplateResult } TemplateResult */
// https://github.com/Polymer/lit-html/blob/master/src/lib/render-options.ts
/** @typedef { import('../../node_modules/lit-html/lit-html').RenderOptions } RenderOptions */
/**
* @param {TemplateStringsArray} strings
* @param {unknown[]} values
* @returns {TemplateResult}
*/
export function html (strings, ...values) {
return litHTML(strings, ...values)
}
/**
* @param {TemplateResult} result
* @param {Element | DocumentFragment} container
* @param {Partial<RenderOptions>} [options]
* @returns {void}
*/
export function render (result, container, options) {
return litRender(result, container, options)
}
/**
* @param {TemplateStringsArray} strings
* @param {unknown[]} args
* @returns {string}
*/
export function css (strings, ...args) {
return emotion(strings, ...args)
}
/**
* @param {TemplateStringsArray} strings
* @param {unknown[]} args
* @returns {void}
*/
export function globalCSS (strings, ...args) {
return injectGlobal(strings, ...args)
}
// Fake immer
/** @template T */
export class State {
/** @typedef {function(T):void} Callback */
/** @typedef {function(): T} InitialValueFactory */
/**
* @param {T | InitialValueFactory} initialValue
*/
constructor (initialValue) {
if (initialValue instanceof Function) {
/** @type {T} */
this.value = initialValue()
} else {
/** @type {T} */
this.value = initialValue
}
deepFreeze(this.value)
/** @type Callback[] */
this._callbacks = []
}
/**
* @param {T | function(T): T} newValue
*/
update (newValue) {
const originalValue = this.value
const mutableValue = deepCopy(this.value)
if (newValue instanceof Function) {
this.value = newValue(mutableValue)
} else {
this.value = newValue
}
deepFreeze(this.value)
if (originalValue !== this.value) {
this._callbacks.forEach(cb => {
cb(this.value)
})
}
}
/**
* @param {Callback} cb
* @returns {void}
*/
onUpdate (cb) {
this._callbacks.push(cb)
}
}
/**
* @param {any} po - possible object
* @returns {boolean}
*/
function isObject (po) {
return (
po !== null &&
typeof po === 'object' &&
po.constructor === Object &&
Object.prototype.toString.call(po) === '[object Object]'
)
}
/**
* @template O
* @param {O} o
* @param {number} [level=0]
* @returns {O}
*/
function deepCopy (o, level = 0) {
if (level > 20) {
return o
}
if (isObject(o)) {
o = Object.assign({}, o)
for (const key in o) {
o[key] = deepCopy(o[key], level + 1)
}
return o
} else if (Array.isArray(o)) {
// @ts-ignore
o = Array.from(o)
for (const i in o) {
o[i] = deepCopy(o[i], level + 1)
}
return o
} else {
return o
}
}
/**
* @param {any} o
* @param {number} [level=0]
* @returns {void}
*/
function deepFreeze (o, level = 0) {
if (level > 20) {
return
}
Object.freeze(o)
if (isObject(o)) {
Object.values(o).forEach(v => {
deepFreeze(v, level + 1)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment