Skip to content

Commit

Permalink
feat: auto load missing resources
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinchappell committed Nov 7, 2024
1 parent 2edd580 commit 46771a8
Show file tree
Hide file tree
Showing 13 changed files with 134 additions and 75 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npx semantic-release
- run: npm run build
if: success()
- name: Deploy - https://draggable.github.io/formeo/
if: success()
uses: peaceiris/actions-gh-pages@v4
Expand Down
4 changes: 3 additions & 1 deletion src/demo/js/actionButtons.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ const editorActions = (editor, renderer) => ({
},
logJSON: () => console.log(editor.json),
viewData: () => {
Object.entries(editor.formData).forEach(([key, val]) => console.log(key, val))
for (const [key, val] of Object.entries(editor.formData)) {
console.log(key, val)
}
},
resetEditor: () => {
window.sessionStorage.removeItem('formeo-formData')
Expand Down
8 changes: 4 additions & 4 deletions src/lib/js/common/animation.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const animate = {
/**
* Gets the computed style for an element
* @param {[type]} elem [description]
* @param {Boolean} property [description]
* @return {[type]} [description]
* Get the computed style for DOM element
* @param {Object} elem dom element
* @param {Boolean} property style eg. width, height, opacity
* @return {String} computed style
*/
getStyle: (elem, property = false) => {
let style
Expand Down
4 changes: 3 additions & 1 deletion src/lib/js/common/dom.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
CONTROL_GROUP_CLASSNAME,
CHILD_CLASSNAME_MAP,
iconPrefix,
formeoSpriteId,
} from '../constants.js'

const iconFontTemplates = {
Expand Down Expand Up @@ -277,7 +278,8 @@ class DOM {
return this.iconSymbols
}

const iconSymbolNodes = document.querySelectorAll('#formeo-sprite svg symbol')
const formeoSprite = document.getElementById(formeoSpriteId)
const iconSymbolNodes = formeoSprite.querySelectorAll('svg symbol')

const createSvgIconConfig = symbolId => ({
tag: 'svg',
Expand Down
2 changes: 0 additions & 2 deletions src/lib/js/common/helpers.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
'use strict'
import { unique } from './utils/index.mjs'
import { get } from './utils/object.mjs'
import { slugify } from './utils/string.mjs'

/**
* Tests if is whole number. returns false if n is Float
Expand Down
104 changes: 77 additions & 27 deletions src/lib/js/common/loaders.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import dom from './dom.js'
import { POLYFILLS } from '../constants.js'
import {
CSS_URL,
FALLBACK_CSS_URL,
FALLBACK_SVG_SPRITE_URL,
POLYFILLS,
SVG_SPRITE_URL,
formeoSpriteId,
} from '../constants.js'
import { noop } from './utils/index.mjs'

/* global fetch */
Expand All @@ -9,6 +16,19 @@ const loaded = {
css: new Set(),
}

export const ajax = (fileUrl, callback, onError = noop) => {
return new Promise(resolve => {
return fetch(fileUrl)
.then(data => {
if (!data.ok) {
return resolve(onError(data))
}
resolve(callback ? callback(data) : data)
})
.catch(err => onError(err))
})
}

const onLoadStylesheet = (elem, cb) => {
elem.removeEventListener('load', onLoadStylesheet)
elem.rel = 'stylesheet'
Expand Down Expand Up @@ -82,31 +102,51 @@ export const insertScripts = srcs => {
return Promise.all(promises)
}

/**
* Inserts multiple stylesheets into the document.
*
* @param {string|string[]} srcs - A single stylesheet URL or an array of stylesheet URLs to be inserted.
* @returns {Promise<void[]>} A promise that resolves when all stylesheets have been inserted.
*/
export const insertStyles = srcs => {
srcs = Array.isArray(srcs) ? srcs : [srcs]
const promises = srcs.map(src => insertStyle(src))
return Promise.all(promises)
}

export const insertIcons = resp => {
const spritePromise = typeof resp === 'string' ? Promise.resolve(resp) : resp.text()
return spritePromise.then(iconSvgStr => {
const id = 'formeo-sprite'
let iconSpriteWrap = document.getElementById(id)
if (!iconSpriteWrap) {
iconSpriteWrap = dom.create({
id,
children: iconSvgStr,
attrs: {
hidden: true,
style: 'display: none;',
},
})
/**
* Inserts SVG icons into the document if they are not already present.
*
* @param {string} iconSvgStr - A string containing SVG icons.
* @returns {HTMLElement} The element wrapping the inserted SVG icons.
*/
export const insertIcons = iconSvgStr => {
let iconSpriteWrap = document.getElementById(formeoSpriteId)
if (!iconSpriteWrap) {
iconSpriteWrap = dom.create({
id: formeoSpriteId,
children: iconSvgStr,
attrs: {
hidden: true,
style: 'display: none;',
},
})

document.body.insertBefore(iconSpriteWrap, document.body.childNodes[0])
}
return iconSpriteWrap
})
document.body.insertBefore(iconSpriteWrap, document.body.childNodes[0])
}
return iconSpriteWrap
}

/**
* Fetches icons from the provided URL and inserts them into the document.
* If the primary URL fails, it attempts to fetch from a fallback URL.
*
* @param {string} iconSpriteUrl - The URL to fetch the icon sprite from.
* @returns {Promise<void>} A promise that resolves when the icons are fetched and inserted.
*/
export const fetchIcons = async (iconSpriteUrl = SVG_SPRITE_URL) => {
const parseResp = async resp => insertIcons(await resp.text())
return ajax(iconSpriteUrl, parseResp, () => ajax(FALLBACK_SVG_SPRITE_URL, parseResp))
}

export const loadPolyfills = polyfillConfig => {
Expand All @@ -116,14 +156,6 @@ export const loadPolyfills = polyfillConfig => {
return Promise.all(polyfills.map(({ src }) => insertScript(src)))
}

export const ajax = (file, callback, onError = noop) => {
return new Promise((resolve, reject) => {
return fetch(file)
.then(data => resolve(callback ? callback(data) : data))
.catch(err => reject(new Error(onError(err))))
})
}

export const LOADER_MAP = {
js: insertScripts,
css: insertStyles,
Expand All @@ -135,3 +167,21 @@ export const fetchDependencies = dependencies => {
})
return Promise.all(promises)
}

export const isCssLoaded = () => {
const formeoSprite = document.getElementById(formeoSpriteId)
const computedStyle = window.getComputedStyle(formeoSprite)

return computedStyle.visibility === 'hidden'
}

export const fetchFormeoStyle = async () => {
// check if necessary styles were loaded
if (!isCssLoaded()) {
await insertStyle(CSS_URL)
// check again and use fallback if necessary styles were not loaded
if (!isCssLoaded()) {
return await insertStyle(FALLBACK_CSS_URL)
}
}
}
10 changes: 2 additions & 8 deletions src/lib/js/components/controls/html/header.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const headerTags = Array.from(Array(5).keys())
const headerKey = 'controls.html.header'

class HeaderControl extends Control {

constructor() {
const header = {
tag: headerTags[0],
Expand All @@ -32,13 +31,8 @@ class HeaderControl extends Control {
},
content: i18n.get(headerKey),
action: {
onRender: evt => {
console.log('evt', evt)
},
click: evt => {
// debugger
console.log('evt', evt)
},
// onRender: evt => {},
// click: evt => {},
},
}
super(header)
Expand Down
4 changes: 1 addition & 3 deletions src/lib/js/components/controls/html/tinymce.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ class TinyMCEControl extends Control {
},
controlAction: {
// callback when control is clicked
click: () => {
console.log('window.tinymce control clicked')
},
click: () => {},
// callback for when control is rendered
onRender: () => {},
},
Expand Down
7 changes: 4 additions & 3 deletions src/lib/js/config.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import mi18n from '@draggable/i18n'
import { isIE } from './common/helpers'
const EN_US = import.meta.env.EN_US
import { SVG_SPRITE_URL } from './constants'
const enUS = import.meta.env.enUS

mi18n.addLanguage('en-US', EN_US)
mi18n.addLanguage('en-US', enUS)

export const defaults = {
get editor() {
Expand All @@ -14,7 +15,7 @@ export const defaults = {
sessionStorage: false,
editorContainer: null, // element or selector to attach editor to
external: {}, // assign external data to be used in conditions autolinker
svgSprite: null, // change to null
svgSprite: SVG_SPRITE_URL, // change to null
iconFont: null, // 'glyphicons' || 'font-awesome' || 'fontello'
config: {}, // stages, rows, columns, fields
events: {},
Expand Down
11 changes: 8 additions & 3 deletions src/lib/js/constants.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import pkg from '../../../package.json' with { type: 'json' }

const isProd = import.meta.env.PROD

const name = pkg.name
const version = pkg.version
export const PACKAGE_NAME = name
export const formeoSpriteId = 'formeo-sprite'

export const POLYFILLS = [
{ name: 'cssPreload', src: '//cdnjs.cloudflare.com/ajax/libs/loadCSS/2.0.1/cssrelpreload.min.js' },
{ name: 'mutationObserver', src: '//cdn.jsdelivr.net/npm/mutationobserver-shim/dist/mutationobserver.min.js' },
{ name: 'fetch', src: 'https://unpkg.com/unfetch/polyfill' },
]

export const FALLBACK_SVG_SPRITE = 'https://draggable.github.io/formeo/assets/img/formeo-sprite.svg'
export const SVG_SPRITE_URL = isProd ? `https://cdn.jsdelivr.net/npm/formeo@${version}/dist/${formeoSpriteId}.svg` : `assets/img/${formeoSpriteId}.svg`
export const FALLBACK_SVG_SPRITE_URL = `https://draggable.github.io/formeo/assets/img/${formeoSpriteId}.svg`
export const CSS_URL = `https://cdn.jsdelivr.net/npm/formeo@${version}/dist/formeo.min.css`
export const FALLBACK_CSS_URL = 'https://draggable.github.io/formeo/assets/css/formeo.min.css'

export const CONTROL_GROUP_CLASSNAME = 'control-group'
export const STAGE_CLASSNAME = `${PACKAGE_NAME}-stage`
Expand All @@ -21,7 +27,6 @@ export const CUSTOM_COLUMN_OPTION_CLASSNAME = 'custom-column-widths'
export const COLUMN_PRESET_CLASSNAME = 'column-preset'
export const COLUMN_RESIZE_CLASSNAME = 'resizing-columns'


export const CHILD_CLASSNAME_MAP = new Map([
[STAGE_CLASSNAME, ROW_CLASSNAME],
[ROW_CLASSNAME, COLUMN_CLASSNAME],
Expand Down
34 changes: 17 additions & 17 deletions src/lib/js/editor.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import '../sass/formeo.scss'
import i18n from '@draggable/i18n'
import dom from './common/dom.js'
import Events from './common/events.js'
import Actions from './common/actions.js'
import Controls from './components/controls/index.js'
import Components from './components/index.js'
import { loadPolyfills, insertStyle, insertIcons, ajax } from './common/loaders.js'
import { SESSION_LOCALE_KEY, FALLBACK_SVG_SPRITE } from './constants.js'
import { loadPolyfills, insertStyle, fetchIcons, isCssLoaded, fetchFormeoStyle } from './common/loaders.js'
import { SESSION_LOCALE_KEY, CSS_URL } from './constants.js'
import { merge } from './common/utils/index.mjs'
import { defaults } from './config.js'
import sprite from '../icons/formeo-sprite.svg?raw'
import '../sass/formeo.scss'

/**
* Main class
Expand Down Expand Up @@ -40,12 +39,7 @@ export class FormeoEditor {
Actions.init({ debug, sessionStorage: opts.sessionStorage, ...actions })

// Load remote resources such as css and svg sprite
this.loadResources().then(() => {
if (opts.allowEdit) {
// this.edit = this.init.bind(this)
this.init()
}
})
document.addEventListener('DOMContentLoaded', this.loadResources.bind(this))
}

get formData() {
Expand All @@ -62,7 +56,9 @@ export class FormeoEditor {
* Load remote resources
* @return {Promise} asynchronously loaded remote resources
*/
loadResources() {
async loadResources() {
document.removeEventListener('DOMContentLoaded', this.loadResources)

const promises = []

if (this.opts.polyfills) {
Expand All @@ -74,15 +70,19 @@ export class FormeoEditor {
}

// Ajax load svgSprite and inject into markup.
if (this.opts.svgSprite) {
promises.push(ajax(this.opts.svgSprite, insertIcons, () => ajax(FALLBACK_SVG_SPRITE, insertIcons)))
} else {
promises.push(insertIcons(sprite))
}
promises.push(fetchIcons(this.opts.svgSprite))

promises.push(i18n.init({ ...this.opts.i18n, locale: window.sessionStorage?.getItem(SESSION_LOCALE_KEY) }))

return Promise.all(promises)
const resolvedPromises = await Promise.all(promises)

fetchFormeoStyle()

if (this.opts.allowEdit) {
this.init()
}

return resolvedPromises
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/lib/sass/formeo.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@
@use "components/autocomplete";
@use "components/panels";

.formeo-sprite {
#formeo-sprite {
display: none !important;
visibility: hidden !important;
}

.formeo {
Expand Down
Loading

0 comments on commit 46771a8

Please sign in to comment.