clean up project for use with npm install
Release / release (push) Successful in 2m6s

This commit is contained in:
2025-12-15 19:48:56 -08:00
parent b6a681338a
commit 4fe053c324
50 changed files with 391 additions and 15409 deletions
+89
View File
@@ -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
+8
View File
@@ -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)
})()
+50
View File
@@ -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
+169
View File
@@ -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
+41
View File
@@ -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