Last active
April 15, 2020 11:32
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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