-
Notifications
You must be signed in to change notification settings - Fork 137
feat: add lazy component #615
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
select 'lazy' as component; | ||
|
||
select | ||
'/chart-example.sql?_sqlpage_embed' as embed, | ||
'card my-2' as class, | ||
'height:340px' as style; | ||
|
||
select '/map-example.sql' as embed; | ||
|
||
select '/table-example.sql?_sqlpage_embed' as embed; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,47 +3,53 @@ | |
|
||
const nonce = document.currentScript.nonce; | ||
|
||
function sqlpage_card() { | ||
for (const c of document.querySelectorAll("[data-pre-init=card]")) { | ||
const source = c.dataset.embed; | ||
fetch(c.dataset.embed) | ||
.then(res => res.text()) | ||
.then(html => { | ||
const body = c.querySelector(".card-content"); | ||
body.innerHTML = html; | ||
c.removeAttribute("data-pre-init"); | ||
const spinner = c.querySelector(".card-loading-placeholder"); | ||
if (spinner) { | ||
spinner.parentNode.removeChild(spinner); | ||
} | ||
const fragLoadedEvt = new CustomEvent("fragment-loaded", { | ||
bubbles: true | ||
}); | ||
c.dispatchEvent(fragLoadedEvt); | ||
}) | ||
function sqlpage_embed() { | ||
for (const c of document.querySelectorAll("[data-embed]")) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe this should be done in parallel rather than sequentially ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure if I understand you. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is the query better ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes it's better ! |
||
if (c.ariaBusy === "true") continue; | ||
c.ariaBusy = true; | ||
let url; | ||
try { | ||
url = new URL(c.dataset.embed, window.location.href) | ||
} catch { | ||
console.erreur(`'${c.dataset.embed}' is not a valid url`) | ||
mtt-artis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue; | ||
} | ||
url.searchParams.set("_sqlpage_embed", ""); | ||
|
||
fetch(url) | ||
.then(res => res.text()) | ||
.then(html => { | ||
c.innerHTML = html; | ||
c.ariaBusy = false; | ||
delete c.dataset.embed; | ||
c.dispatchEvent(new CustomEvent("fragment-loaded", { | ||
bubbles: true | ||
})); | ||
}) | ||
.catch(err => console.error("Fetch error: ", err)); | ||
} | ||
} | ||
|
||
function sqlpage_table(){ | ||
// Tables | ||
for (const r of document.querySelectorAll("[data-pre-init=table]")) { | ||
new List(r, { | ||
valueNames: [...r.getElementsByTagName("th")].map(t => t.textContent), | ||
searchDelay: 100, | ||
// Hurts performance, but prevents https://github.com/lovasoa/SQLpage/issues/542 | ||
// indexAsync: true | ||
}); | ||
r.removeAttribute("data-pre-init"); | ||
} | ||
function sqlpage_table() { | ||
// Tables | ||
for (const r of document.querySelectorAll("[data-pre-init=table]")) { | ||
new List(r, { | ||
valueNames: [...r.getElementsByTagName("th")].map(t => t.textContent), | ||
searchDelay: 100, | ||
// Hurts performance, but prevents https://github.com/lovasoa/SQLpage/issues/542 | ||
// indexAsync: true | ||
}); | ||
r.removeAttribute("data-pre-init"); | ||
} | ||
} | ||
|
||
function sqlpage_select_dropdown(){ | ||
function sqlpage_select_dropdown() { | ||
const selects = document.querySelectorAll("[data-pre-init=select-dropdown]"); | ||
if (!selects.length) return; | ||
const src = "https://cdn.jsdelivr.net/npm/tom-select@2.3.1/dist/js/tom-select.popular.min.js"; | ||
if (!window.TomSelect) { | ||
const script = document.createElement("script"); | ||
script.src= src; | ||
script.src = src; | ||
script.integrity = "sha384-aAqv9vleUwO75zAk1sGKd5VvRqXamBXwdxhtihEUPSeq1HtxwmZqQG/HxQnq7zaE"; | ||
script.crossOrigin = "anonymous"; | ||
script.nonce = nonce; | ||
|
@@ -52,137 +58,142 @@ function sqlpage_select_dropdown(){ | |
return; | ||
} | ||
for (const s of selects) { | ||
new TomSelect(s, { | ||
create: s.dataset.create_new | ||
}); | ||
new TomSelect(s, { | ||
create: s.dataset.create_new | ||
}); | ||
} | ||
} | ||
|
||
let is_leaflet_injected = false; | ||
let is_leaflet_loaded = false; | ||
|
||
function sqlpage_map() { | ||
const first_map = document.querySelector("[data-pre-init=map]"); | ||
if (first_map && !is_leaflet_injected) { | ||
// Add the leaflet js and css to the page | ||
const leaflet_css = document.createElement("link"); | ||
leaflet_css.rel = "stylesheet"; | ||
leaflet_css.href = "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css"; | ||
leaflet_css.integrity = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="; | ||
leaflet_css.crossOrigin = "anonymous"; | ||
document.head.appendChild(leaflet_css); | ||
const leaflet_js = document.createElement("script"); | ||
leaflet_js.src = "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"; | ||
leaflet_js.integrity = "sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="; | ||
leaflet_js.crossOrigin = "anonymous"; | ||
leaflet_js.nonce = nonce; | ||
leaflet_js.onload = onLeafletLoad; | ||
document.head.appendChild(leaflet_js); | ||
is_leaflet_injected = true; | ||
} | ||
if (first_map && is_leaflet_loaded) { | ||
onLeafletLoad(); | ||
} | ||
function parseCoords(coords) { | ||
return coords && coords.split(",").map(c => parseFloat(c)); | ||
} | ||
function onLeafletLoad() { | ||
is_leaflet_loaded = true; | ||
const maps = document.querySelectorAll("[data-pre-init=map]"); | ||
for (const m of maps) { | ||
const tile_source = m.dataset.tile_source; | ||
const maxZoom = +m.dataset.max_zoom; | ||
const attribution = m.dataset.attribution; | ||
const map = L.map(m, { attributionControl: !!attribution }); | ||
const zoom = m.dataset.zoom; | ||
let center = parseCoords(m.dataset.center); | ||
if (tile_source) L.tileLayer(tile_source, { attribution, maxZoom }).addTo(map); | ||
map._sqlpage_markers = []; | ||
for (const marker_elem of m.getElementsByClassName("marker")) { | ||
setTimeout(addMarker, 0, marker_elem, map); | ||
} | ||
setTimeout(() => { | ||
if (center == null && map._sqlpage_markers.length) { | ||
map.fitBounds(map._sqlpage_markers.map(m => | ||
m.getLatLng ? m.getLatLng() : m.getBounds() | ||
)); | ||
if (zoom != null) map.setZoom(+zoom); | ||
} else map.setView(center, +zoom); | ||
}, 100); | ||
m.removeAttribute("data-pre-init"); | ||
m.getElementsByClassName("spinner-border")[0]?.remove(); | ||
const first_map = document.querySelector("[data-pre-init=map]"); | ||
if (first_map && !is_leaflet_injected) { | ||
// Add the leaflet js and css to the page | ||
const leaflet_css = document.createElement("link"); | ||
leaflet_css.rel = "stylesheet"; | ||
leaflet_css.href = "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.css"; | ||
leaflet_css.integrity = "sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="; | ||
leaflet_css.crossOrigin = "anonymous"; | ||
document.head.appendChild(leaflet_css); | ||
const leaflet_js = document.createElement("script"); | ||
leaflet_js.src = "https://cdn.jsdelivr.net/npm/leaflet@1.9.4/dist/leaflet.js"; | ||
leaflet_js.integrity = "sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="; | ||
leaflet_js.crossOrigin = "anonymous"; | ||
leaflet_js.nonce = nonce; | ||
leaflet_js.onload = onLeafletLoad; | ||
document.head.appendChild(leaflet_js); | ||
is_leaflet_injected = true; | ||
} | ||
if (first_map && is_leaflet_loaded) { | ||
onLeafletLoad(); | ||
} | ||
function parseCoords(coords) { | ||
return coords && coords.split(",").map(c => parseFloat(c)); | ||
} | ||
function onLeafletLoad() { | ||
is_leaflet_loaded = true; | ||
const maps = document.querySelectorAll("[data-pre-init=map]"); | ||
for (const m of maps) { | ||
const tile_source = m.dataset.tile_source; | ||
const maxZoom = +m.dataset.max_zoom; | ||
const attribution = m.dataset.attribution; | ||
const map = L.map(m, { attributionControl: !!attribution }); | ||
const zoom = m.dataset.zoom; | ||
let center = parseCoords(m.dataset.center); | ||
if (tile_source) L.tileLayer(tile_source, { attribution, maxZoom }).addTo(map); | ||
map._sqlpage_markers = []; | ||
for (const marker_elem of m.getElementsByClassName("marker")) { | ||
setTimeout(addMarker, 0, marker_elem, map); | ||
} | ||
setTimeout(() => { | ||
if (center == null && map._sqlpage_markers.length) { | ||
map.fitBounds(map._sqlpage_markers.map(m => | ||
m.getLatLng ? m.getLatLng() : m.getBounds() | ||
)); | ||
if (zoom != null) map.setZoom(+zoom); | ||
} else map.setView(center, +zoom); | ||
}, 100); | ||
m.removeAttribute("data-pre-init"); | ||
m.getElementsByClassName("spinner-border")[0]?.remove(); | ||
} | ||
} | ||
|
||
function addMarker(marker_elem, map) { | ||
const { dataset } = marker_elem; | ||
const options = { | ||
color: marker_elem.dataset.color, | ||
title: marker_elem.getElementsByTagName("h3")[0].textContent.trim(), | ||
}; | ||
const marker = | ||
dataset.coords ? createMarker(marker_elem, options) | ||
: createGeoJSONMarker(marker_elem, options); | ||
marker.addTo(map); | ||
map._sqlpage_markers.push(marker); | ||
if (options.title) marker.bindPopup(marker_elem); | ||
else if (marker_elem.dataset.link) marker.on('click', () => window.location = marker_elem.dataset.link); | ||
function addMarker(marker_elem, map) { | ||
const { dataset } = marker_elem; | ||
const options = { | ||
color: marker_elem.dataset.color, | ||
title: marker_elem.getElementsByTagName("h3")[0].textContent.trim(), | ||
}; | ||
const marker = | ||
dataset.coords ? createMarker(marker_elem, options) | ||
: createGeoJSONMarker(marker_elem, options); | ||
marker.addTo(map); | ||
map._sqlpage_markers.push(marker); | ||
if (options.title) marker.bindPopup(marker_elem); | ||
else if (marker_elem.dataset.link) marker.on('click', () => window.location = marker_elem.dataset.link); | ||
} | ||
function createMarker(marker_elem, options) { | ||
const coords = parseCoords(marker_elem.dataset.coords); | ||
const icon_obj = marker_elem.getElementsByClassName("mapicon")[0]; | ||
if (icon_obj) { | ||
const size = 1.5 * +(options.size || icon_obj.firstChild?.getAttribute('width') || 24); | ||
options.icon = L.divIcon({ | ||
html: icon_obj, | ||
className: `border-0 bg-${options.color || 'primary'} bg-gradient text-white rounded-circle shadow d-flex justify-content-center align-items-center`, | ||
iconSize: [size, size], | ||
iconAnchor: [size / 2, size / 2], | ||
}); | ||
} | ||
function createMarker(marker_elem, options) { | ||
const coords = parseCoords(marker_elem.dataset.coords); | ||
const icon_obj = marker_elem.getElementsByClassName("mapicon")[0]; | ||
if (icon_obj) { | ||
const size = 1.5 * +(options.size || icon_obj.firstChild?.getAttribute('width') || 24); | ||
options.icon = L.divIcon({ | ||
html: icon_obj, | ||
className: `border-0 bg-${options.color || 'primary'} bg-gradient text-white rounded-circle shadow d-flex justify-content-center align-items-center`, | ||
iconSize: [size, size], | ||
iconAnchor: [size/2, size/2], | ||
}); | ||
} | ||
return L.marker(coords, options); | ||
return L.marker(coords, options); | ||
} | ||
function createGeoJSONMarker(marker_elem, options) { | ||
let geojson = JSON.parse(marker_elem.dataset.geojson); | ||
if (options.color) { | ||
options.color = get_tabler_color(options.color) || options.color; | ||
} | ||
function createGeoJSONMarker(marker_elem, options) { | ||
let geojson = JSON.parse(marker_elem.dataset.geojson); | ||
if (options.color) { | ||
options.color = get_tabler_color(options.color) || options.color; | ||
} | ||
function style({ properties }) { | ||
if (typeof properties !== "object") return options; | ||
return {...options, ...properties}; | ||
} | ||
function pointToLayer(feature, latlng) { | ||
marker_elem.dataset.coords = latlng.lat + "," + latlng.lng; | ||
return createMarker(marker_elem, { ...options, ...feature.properties }); | ||
} | ||
return L.geoJSON(geojson, { style, pointToLayer }); | ||
function style({ properties }) { | ||
if (typeof properties !== "object") return options; | ||
return { ...options, ...properties }; | ||
} | ||
function pointToLayer(feature, latlng) { | ||
marker_elem.dataset.coords = latlng.lat + "," + latlng.lng; | ||
return createMarker(marker_elem, { ...options, ...feature.properties }); | ||
} | ||
return L.geoJSON(geojson, { style, pointToLayer }); | ||
} | ||
} | ||
|
||
function sqlpage_form() { | ||
const file_inputs = document.querySelectorAll("input[type=file][data-max-size]"); | ||
for (const input of file_inputs) { | ||
const max_size = +input.dataset.maxSize; | ||
input.addEventListener("change", function() { | ||
input.classList.remove("is-invalid"); | ||
input.setCustomValidity(""); | ||
for (const {size} of this.files) { | ||
if (size > max_size){ | ||
input.classList.add("is-invalid"); | ||
return input.setCustomValidity(`File size must be less than ${max_size/1000} kB.`); | ||
} | ||
const file_inputs = document.querySelectorAll("input[type=file][data-max-size]"); | ||
for (const input of file_inputs) { | ||
const max_size = +input.dataset.maxSize; | ||
input.addEventListener("change", function () { | ||
input.classList.remove("is-invalid"); | ||
input.setCustomValidity(""); | ||
for (const { size } of this.files) { | ||
if (size > max_size) { | ||
input.classList.add("is-invalid"); | ||
return input.setCustomValidity(`File size must be less than ${max_size / 1000} kB.`); | ||
} | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
function get_tabler_color(name) { | ||
return getComputedStyle(document.documentElement).getPropertyValue('--tblr-' + name); | ||
function create_tabler_color() { | ||
const style = getComputedStyle(document.documentElement); | ||
return function get_tabler_color(name) { | ||
return style.getPropertyValue('--tblr-' + name); | ||
} | ||
} | ||
|
||
const get_tabler_color = create_tabler_color(); | ||
|
||
function load_scripts() { | ||
let addjs = document.querySelectorAll("[data-sqlpage-js]"); | ||
for (const js of new Set([...addjs].map(({dataset}) => dataset.sqlpageJs))) { | ||
for (const js of new Set([...addjs].map(({ dataset }) => dataset.sqlpageJs))) { | ||
const script = document.createElement("script"); | ||
script.src = js; | ||
document.head.appendChild(script); | ||
|
@@ -198,6 +209,6 @@ function add_init_fn(f) { | |
|
||
add_init_fn(sqlpage_table); | ||
add_init_fn(sqlpage_map); | ||
add_init_fn(sqlpage_card); | ||
add_init_fn(sqlpage_embed); | ||
add_init_fn(sqlpage_form); | ||
add_init_fn(load_scripts); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should we keep
as embed
oras lazy
here ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think
as embed
is good