This commit is contained in:
Vendored
+89
@@ -0,0 +1,89 @@
|
||||
/** @module AssetLoader */
|
||||
|
||||
/**
|
||||
* Loads a data url and returns the resulting element, font face, or when the type is unrecognized, a fetch response.
|
||||
* @param {String} dataURL
|
||||
* @param {String} id
|
||||
* @param {Boolean} unsafe load assets directly into the DOM
|
||||
*/
|
||||
export async function loadAsset(dataURL,id=undefined,unsafe=false){
|
||||
const type=dataURL.split(';')[0].split(':')[1]
|
||||
switch(type){
|
||||
case 'text/css':{
|
||||
const style=document.createElement('style')
|
||||
style.id=id
|
||||
style.innerHTML=atob(dataURL.split(',')[1])
|
||||
if(unsafe){
|
||||
return document.body.appendChild(style)
|
||||
}
|
||||
return style
|
||||
}
|
||||
case 'text/html':{
|
||||
const element=document.createElement('span')
|
||||
element.innerHTML=atob(dataURL.split(',')[1])
|
||||
element.id=id
|
||||
if(unsafe){
|
||||
for(const script of element.querySelectorAll('script')){
|
||||
import('data:text/javascript,'+script.innerHTML)
|
||||
}
|
||||
return document.body.appendChild(element)
|
||||
}
|
||||
return element
|
||||
}
|
||||
case 'text/javascript':{
|
||||
if(unsafe){
|
||||
return import(dataURL)
|
||||
}
|
||||
const script=document.createElement('script')
|
||||
script.id=id
|
||||
script.innerHTML=atob(dataURL.split(',')[1])
|
||||
return script
|
||||
}
|
||||
default:{
|
||||
const mtype=type.split('/')[0]
|
||||
switch(mtype){
|
||||
case 'audio':{
|
||||
const audio=document.createElement('audio')
|
||||
audio.id=id
|
||||
audio.src=dataURL
|
||||
if(unsafe){
|
||||
return document.body.appendChild(audio)
|
||||
}
|
||||
return audio
|
||||
}
|
||||
case 'font':{
|
||||
if(unsafe){
|
||||
document.fonts.add(await new FontFace(id,dataURL).load())
|
||||
}
|
||||
return new FontFace(id,dataURL)
|
||||
}
|
||||
case 'image':{
|
||||
const img=document.createElement('img')
|
||||
img.id=id
|
||||
img.src=dataURL
|
||||
if(unsafe){
|
||||
return document.body.appendChild(img)
|
||||
}
|
||||
return img
|
||||
}
|
||||
case 'video':{
|
||||
const video=document.createElement('video')
|
||||
video.id=id
|
||||
video.src=dataURL
|
||||
if(unsafe){
|
||||
return document.body.appendChild(video)
|
||||
}
|
||||
return video
|
||||
}
|
||||
default:{
|
||||
if(unsafe){
|
||||
window[id]=await fetch(dataURL)
|
||||
}
|
||||
return await fetch(dataURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default loadAsset
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* On import, sets the global css variable `--random` to `Math.random()` before each draw
|
||||
* @module CSSRandom
|
||||
*/
|
||||
(function CSSRandom() {
|
||||
document.documentElement.style.setProperty('--random', Math.random())
|
||||
requestAnimationFrame(CSSRandom)
|
||||
})()
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
/** @module FileReaderAsync */
|
||||
|
||||
/** @class */
|
||||
export class FileReaderAsync {
|
||||
/**
|
||||
* @param {String} type The return type (ArrayBuffer, BinaryString, Text, DataURL)
|
||||
* @param {Blob} blob The `Blob` to read
|
||||
* @returns {Promise<ArrayBuffer>} The `Blob` data
|
||||
*/
|
||||
static readAs(type,blob){
|
||||
return new Promise((resolve,reject)=>{
|
||||
const fileReader=new FileReader()
|
||||
fileReader.onload=(event)=>resolve(event.target.result)
|
||||
fileReader.onabort=(event)=>reject(event)
|
||||
fileReader.onerror=(event)=>reject(event)
|
||||
fileReader['readAs'+type](blob)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @param {Blob} blob The `Blob` to read
|
||||
* @returns {Promise<ArrayBuffer>} The `Blob` data
|
||||
*/
|
||||
readAsArrayBuffer(blob) {
|
||||
return FileReaderAsync.readAs('ArrayBuffer',blob)
|
||||
}
|
||||
/**
|
||||
* @depreciated see https://developer.mozilla.org/en-US/docs/Web/API/FileReaderSync/readAsBinaryString
|
||||
* @param {Blob} blob The `Blob` to read
|
||||
* @returns {Promise<String>} The `Blob` data
|
||||
*/
|
||||
readAsBinaryString(blob) {
|
||||
return FileReaderAsync.readAs('BinaryString',blob)
|
||||
}
|
||||
/**
|
||||
* @param {Blob} blob The `Blob` to read
|
||||
* @returns {Promise<String>} The `Blob` data
|
||||
*/
|
||||
readAsText(blob) {
|
||||
return FileReaderAsync.readAs('Text',blob)
|
||||
}
|
||||
/**
|
||||
* @param {Blob} blob The `Blob` to read
|
||||
* @returns {Promise<String>} The `Blob` data
|
||||
*/
|
||||
readAsDataURL(blob) {
|
||||
return FileReaderAsync.readAs('DataURL',blob)
|
||||
}
|
||||
}
|
||||
|
||||
export default FileReaderAsync
|
||||
Vendored
+169
@@ -0,0 +1,169 @@
|
||||
/** @module FormDataDeep */
|
||||
|
||||
/**
|
||||
* Usage: `Array<HTMLElement>.filter(removeChildren)`
|
||||
*
|
||||
* Removes elements from the array which are children of other elements in the array.
|
||||
*/
|
||||
export function removeChildren(element,index,array){
|
||||
for(const sibling of array){
|
||||
if(!sibling.isSameNode(element) && sibling.contains(element)){
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
/**
|
||||
* Appends a new value onto an existing key inside an object, or adds the key if it does not already exist.
|
||||
* @param {Object} object
|
||||
* @param {String} name
|
||||
* @param {*} value
|
||||
*/
|
||||
export function append(object,name,value){
|
||||
if(name in object){
|
||||
if(object[name] instanceof Array){
|
||||
object[name].push(value)
|
||||
}else{
|
||||
object[name]=[object[name],value]
|
||||
}
|
||||
}else{
|
||||
object[name]=value
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get `form` data as an object, including nested forms.
|
||||
* @param {HTMLFormElement} form
|
||||
* @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
|
||||
*/
|
||||
export function getFormDataDeep(form,primitives=false){
|
||||
const formDataDeep={}
|
||||
const formData=new FormData(form)
|
||||
for(const name of formData.keys()){
|
||||
const value=formData.getAll(name)
|
||||
if(value.length==0){
|
||||
continue
|
||||
}
|
||||
if(primitives){
|
||||
for(let i=0;i<value.length;i++){
|
||||
if(!isNaN(+value[i])){
|
||||
value[i]=+value[i]
|
||||
}
|
||||
if(value[i]=='on'){
|
||||
value[i]=true
|
||||
}
|
||||
}
|
||||
}
|
||||
if(value.length==1){
|
||||
formDataDeep[name]=value[0]
|
||||
}else{
|
||||
formDataDeep[name]=value
|
||||
}
|
||||
}
|
||||
for(const child of [...form.querySelectorAll('form')].filter(removeChildren)){
|
||||
const value=getFormDataDeep(child,primitives)
|
||||
if(Object.keys(value)==0){
|
||||
continue
|
||||
}
|
||||
const name=child.name
|
||||
append(formDataDeep,name,value)
|
||||
}
|
||||
return formDataDeep
|
||||
}
|
||||
/**
|
||||
* Convenience class to have a similar interface to FormData
|
||||
*/
|
||||
export class FormDataDeep extends FormData {
|
||||
/**
|
||||
* Get `form` data as an object, including nested forms.
|
||||
* @param {HTMLFormElement | Object} form the root form element, or a JSON Object containing form data.
|
||||
* @param {Boolean} primitives whether to convert string values to primitives. Defaults `false`.
|
||||
*/
|
||||
constructor(form,primitives=false){
|
||||
super()
|
||||
if(form instanceof HTMLFormElement){
|
||||
Object.assign(this,getFormDataDeep(form,primitives))
|
||||
}else{
|
||||
Object.assign(this,form)
|
||||
}
|
||||
}
|
||||
append(name,value,filename=undefined){
|
||||
if(filename!==undefined){
|
||||
value=new File([value],filename)
|
||||
}
|
||||
append(this,name,value)
|
||||
}
|
||||
delete(name){
|
||||
delete this[name]
|
||||
}
|
||||
entries(){
|
||||
return Object.entries(this).filter((value)=>!value instanceof Function)
|
||||
}
|
||||
get(name){
|
||||
if(this[name] instanceof Array){
|
||||
return this[name].at(-1)
|
||||
}else{
|
||||
return this[name]
|
||||
}
|
||||
}
|
||||
getAll(name){
|
||||
if(this[name] instanceof Array){
|
||||
return this[name]
|
||||
}else{
|
||||
return [this[name]]
|
||||
}
|
||||
}
|
||||
has(name){
|
||||
return name in this
|
||||
}
|
||||
keys(){
|
||||
return this.entries().keys()
|
||||
}
|
||||
set(name,value,filename=undefined){
|
||||
if(filename!==undefined){
|
||||
value=new File([value],filename)
|
||||
}
|
||||
this[name]=value
|
||||
}
|
||||
values(){
|
||||
return this.entries().values()
|
||||
}
|
||||
/**
|
||||
* Applies the values in this object to the target form.
|
||||
* @param {HTMLFormElement} form
|
||||
*/
|
||||
apply(form){
|
||||
for(const element of form.elements){
|
||||
const name=element.name
|
||||
if(! name in this){
|
||||
continue
|
||||
}
|
||||
let value
|
||||
if(this[name] instanceof Array){
|
||||
value=this[name].shift()
|
||||
}else{
|
||||
value=this[name]
|
||||
delete this[name]
|
||||
}
|
||||
switch(element.type){
|
||||
case 'radio':
|
||||
case 'checkbox':{
|
||||
element.checked=true
|
||||
break
|
||||
}
|
||||
default:{
|
||||
element.value=value
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
for(const child of [...form.querySelectorAll('form')].filter(removeChildren)){
|
||||
const name=child.name
|
||||
if(! name in this){
|
||||
continue
|
||||
}
|
||||
new FormDataDeep(this[name]).apply(form)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FormDataDeep
|
||||
Vendored
+41
@@ -0,0 +1,41 @@
|
||||
/** @module fontScale */
|
||||
|
||||
/**
|
||||
* Scale an element's font size so that it doesn't overflow the viewport.
|
||||
* Note that some css may be required for this to work,
|
||||
* for example setting `display: flex;` on the parent element,
|
||||
* and this function relies on the element being visible.
|
||||
* If you want to keep your element invisible until font scaling finishes,
|
||||
* use `opacity: 0;`
|
||||
* @param {HTMLElement} element the element to be scaled
|
||||
* @param {Number} maxHeight maximum font size (which is equivalent to pixel hight)
|
||||
* @returns {Number} the resulting font size
|
||||
*/
|
||||
export function fontScale(element,maxHeight=innerHeight){
|
||||
return new Promise(function(resolve){
|
||||
function scaleup(){
|
||||
let size=parseInt(getComputedStyle(element).fontSize)
|
||||
let bounds=element.getBoundingClientRect()
|
||||
if(bounds.right<innerWidth && size<maxHeight){
|
||||
element.style.fontSize=size+1+'px'
|
||||
setTimeout(scaleup)
|
||||
}else{
|
||||
setTimeout(scaledown)
|
||||
}
|
||||
|
||||
}
|
||||
function scaledown(){
|
||||
let size=parseInt(getComputedStyle(element).fontSize)
|
||||
let bounds=element.getBoundingClientRect()
|
||||
if(bounds.right>innerWidth || size>maxHeight){
|
||||
element.style.fontSize=size-1+'px'
|
||||
setTimeout(scaledown)
|
||||
}else{
|
||||
resolve(size)
|
||||
}
|
||||
}
|
||||
setTimeout(scaleup)
|
||||
})
|
||||
}
|
||||
|
||||
export default fontScale
|
||||
Reference in New Issue
Block a user