@frybynite/image-cloud 0.2.8 → 0.2.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Image Cloud Library
4
4
 
5
- A TypeScript library for creating interactive image clouds with animated scattered layouts and zoom effects. Supports multiple image sources (Google Drive, static URLs) and layout algorithms.
5
+ A TypeScript library for creating interactive image clouds with animated scattered layouts and zoom effects. Supports multiple image sources (static URLs, JSON endpoints, Google Drive) and layout algorithms.
6
6
 
7
7
  > [!WARNING]
8
8
  > ⚠️ All minor versions of this library before 1.0 (e.g., 0.1, 0.2, ...) will include breaking changes during development. Please re-test every time before upgrading until we have published v1.0.
@@ -15,7 +15,7 @@ A TypeScript library for creating interactive image clouds with animated scatter
15
15
  - 🔍 Zoom/focus interactions with keyboard navigation
16
16
  - 🎨 State-based image styling (borders, shadows, filters for default/hover/focused)
17
17
  - 📱 Responsive design with adaptive sizing
18
- - 🖼️ Multiple image sources (Google Drive, static URLs, composite loaders)
18
+ - 🖼️ Multiple image sources (static URLs, JSON endpoints, Google Drive, composite loaders)
19
19
  - 🛠️ Interactive configurator for visual configuration
20
20
  - 📦 Zero runtime dependencies
21
21
  - 🔷 Full TypeScript support
@@ -121,7 +121,7 @@ await cloud.init();
121
121
 
122
122
  For detailed configuration, see the documentation in the `docs/` folder:
123
123
 
124
- 1. **[Loaders](docs/LOADERS.md)** — Configure image sources (static URLs, local paths, Google Drive folders)
124
+ 1. **[Loaders](docs/LOADERS.md)** — Configure image sources (static URLs, JSON endpoints, local paths, Google Drive folders)
125
125
  2. **[Layout Generators](docs/GENERATORS.md)** — Choose and customize layout algorithms (radial, grid, spiral, cluster, random)
126
126
  3. **[Image Sizing](docs/IMAGE_SIZING.md)** — Control base sizes, variance, and responsive/adaptive behavior
127
127
  4. **[Full Parameter Reference](docs/PARAMETERS.md)** — Complete configuration options for animation, interaction, styling, and more
@@ -169,6 +169,8 @@ Check out the `examples/` directory for various usage patterns:
169
169
  - `cdn-umd-example.html` - Traditional script tag / CDN usage
170
170
  - `auto-init-example.html` - HTML data attribute initialization
171
171
  - `static-loader-example.html` - Static image URLs
172
+ - `static-urls-shorthand-example.html` - Simplest static loader: direct URL array shorthand
173
+ - `static-json-source-example.html` - Load images from a JSON endpoint
172
174
  - `google-drive-loader-example.html` - Google Drive folder source
173
175
  - `layout-algorithms.html` - Compare all layout algorithms
174
176
  - `entry-animations.html` - Entry animation styles
@@ -95,7 +95,9 @@ const kt = ".fbn-ic-gallery{--fbn-ic-bg-primary: #05060F;--fbn-ic-bg-secondary:
95
95
  }),
96
96
  static: Object.freeze({
97
97
  sources: [],
98
- // Must be provided by user
98
+ // Must be provided by user (or use urls shorthand)
99
+ urls: void 0,
100
+ // Shorthand for sources: [{ type: 'urls', urls: [...] }]
99
101
  validateUrls: !0,
100
102
  validationTimeout: 5e3,
101
103
  validationMethod: "head",
@@ -299,7 +301,7 @@ function Bt(o) {
299
301
  }
300
302
  };
301
303
  }
302
- function Vt(o = {}) {
304
+ function Jt(o = {}) {
303
305
  var s;
304
306
  const t = Xt(o), e = Bt(o);
305
307
  let i = o.image;
@@ -333,6 +335,7 @@ function Vt(o = {}) {
333
335
  ...y.loader.static,
334
336
  ...o.loader.static,
335
337
  sources: o.loader.static.sources || y.loader.static.sources,
338
+ urls: o.loader.static.urls || void 0,
336
339
  allowedExtensions: o.loader.static.allowedExtensions || y.loader.static.allowedExtensions
337
340
  })), o.layout && (n.layout = {
338
341
  ...y.layout,
@@ -396,7 +399,7 @@ function Vt(o = {}) {
396
399
  ...o.rendering.performance
397
400
  })), o.debug !== void 0 && (n.debug = o.debug), n;
398
401
  }
399
- function Jt(o, t) {
402
+ function Vt(o, t) {
400
403
  return { ...o ? zt[o] : zt.playful, ...t };
401
404
  }
402
405
  function Kt(o, t) {
@@ -675,7 +678,7 @@ function re(o) {
675
678
  let A;
676
679
  switch (p) {
677
680
  case "bounce": {
678
- const _ = Jt(
681
+ const _ = Vt(
679
682
  n.bouncePreset,
680
683
  n.bounce
681
684
  );
@@ -1244,24 +1247,24 @@ class fe {
1244
1247
  for (let F = 0; F < t; F++) {
1245
1248
  let $, N, G = 0;
1246
1249
  if (M && F >= z) {
1247
- const J = F - z, V = J % z;
1248
- G = Math.floor(J / z) + 1, _[V]++, a.fillDirection === "row" ? ($ = V % g, N = Math.floor(V / g)) : (N = V % f, $ = Math.floor(V / f));
1250
+ const V = F - z, J = V % z;
1251
+ G = Math.floor(V / z) + 1, _[J]++, a.fillDirection === "row" ? ($ = J % g, N = Math.floor(J / g)) : (N = J % f, $ = Math.floor(J / f));
1249
1252
  } else
1250
1253
  a.fillDirection === "row" ? ($ = F % g, N = Math.floor(F / g)) : (N = F % f, $ = Math.floor(F / f));
1251
1254
  let H = A + $ * (x + a.gap) + x / 2, it = O + N * (L + a.gap) + L / 2;
1252
1255
  if (a.stagger === "row" && N % 2 === 1 ? H += x / 2 : a.stagger === "column" && $ % 2 === 1 && (it += L / 2), G > 0) {
1253
- const J = (G - 1) % Lt.length, V = Lt[J];
1254
- H += V.x * K, it += V.y * K;
1256
+ const V = (G - 1) % Lt.length, J = Lt[V];
1257
+ H += J.x * K, it += J.y * K;
1255
1258
  }
1256
1259
  if (a.jitter > 0) {
1257
- const J = x / 2 * a.jitter, V = L / 2 * a.jitter;
1258
- H += this.random(-J, J), it += this.random(-V, V);
1260
+ const V = x / 2 * a.jitter, J = L / 2 * a.jitter;
1261
+ H += this.random(-V, V), it += this.random(-J, J);
1259
1262
  }
1260
1263
  let nt = H, ot = it;
1261
1264
  if (!M && a.fillDirection === "row") {
1262
- const J = t % g || g;
1263
- if (N === Math.floor((t - 1) / g) && J < g) {
1264
- const Mt = J * x + (J - 1) * a.gap;
1265
+ const V = t % g || g;
1266
+ if (N === Math.floor((t - 1) / g) && V < g) {
1267
+ const Mt = V * x + (V - 1) * a.gap;
1265
1268
  let St = 0;
1266
1269
  a.alignment === "center" ? St = (C - Mt) / 2 : a.alignment === "end" && (St = C - Mt), nt += St;
1267
1270
  }
@@ -1270,8 +1273,8 @@ class fe {
1270
1273
  nt = Math.max(yt, Math.min(nt, vt)), ot = Math.max(Ht, Math.min(ot, _t));
1271
1274
  let wt = 0;
1272
1275
  if (u === "random") {
1273
- const J = ((q = (tt = this.imageConfig.rotation) == null ? void 0 : tt.range) == null ? void 0 : q.min) ?? -15, V = ((Y = (et = this.imageConfig.rotation) == null ? void 0 : et.range) == null ? void 0 : Y.max) ?? 15;
1274
- a.jitter > 0 ? wt = this.random(J * a.jitter, V * a.jitter) : wt = this.random(J, V);
1276
+ const V = ((q = (tt = this.imageConfig.rotation) == null ? void 0 : tt.range) == null ? void 0 : q.min) ?? -15, J = ((Y = (et = this.imageConfig.rotation) == null ? void 0 : et.range) == null ? void 0 : Y.max) ?? 15;
1277
+ a.jitter > 0 ? wt = this.random(V * a.jitter, J * a.jitter) : wt = this.random(V, J);
1275
1278
  }
1276
1279
  let xt;
1277
1280
  M && G > 0 ? xt = 50 - G : xt = M ? 100 + F : F + 1, n.push({
@@ -2575,8 +2578,8 @@ class Le {
2575
2578
  }
2576
2579
  class Oe {
2577
2580
  constructor(t = {}) {
2578
- if (this._prepared = !1, this._discoveredUrls = [], this.validateUrls = t.validateUrls !== !1, this.validationTimeout = t.validationTimeout ?? 5e3, this.validationMethod = t.validationMethod ?? "head", this.sources = t.sources ?? [], this.debugLogging = t.debugLogging ?? !1, !this.sources || this.sources.length === 0)
2579
- throw new Error("StaticImageLoader requires at least one source to be configured");
2581
+ if (this._prepared = !1, this._discoveredUrls = [], this.validateUrls = t.validateUrls !== !1, this.validationTimeout = t.validationTimeout ?? 5e3, this.validationMethod = t.validationMethod ?? "head", this.debugLogging = t.debugLogging ?? !1, t.urls && Array.isArray(t.urls) && t.urls.length > 0 ? this.sources = [{ type: "urls", urls: t.urls }, ...t.sources ?? []] : this.sources = t.sources ?? [], !this.sources || this.sources.length === 0)
2582
+ throw new Error("StaticImageLoader requires at least one source (or urls shorthand) to be configured");
2580
2583
  this.log("StaticImageLoader initialized with config:", t);
2581
2584
  }
2582
2585
  /**
@@ -2625,7 +2628,7 @@ class Oe {
2625
2628
  * @returns Promise resolving to array of valid URLs from this source
2626
2629
  */
2627
2630
  async processSource(t, e) {
2628
- return !t || !t.type ? (console.warn("Invalid source object (missing type):", t), []) : t.type === "urls" ? await this.processUrls(t.urls || [], e) : t.type === "path" ? await this.processPath(t.basePath, t.files || [], e) : (console.warn(`Unknown source type: ${t.type}`), []);
2631
+ return !t || !t.type ? (console.warn("Invalid source object (missing type):", t), []) : t.type === "urls" ? await this.processUrls(t.urls || [], e) : t.type === "path" ? await this.processPath(t.basePath, t.files || [], e) : t.type === "json" ? await this.processJson(t.url, e) : (console.warn(`Unknown source type: ${t.type}`), []);
2629
2632
  }
2630
2633
  /**
2631
2634
  * Process a list of direct URLs
@@ -2670,6 +2673,30 @@ class Oe {
2670
2673
  }
2671
2674
  return n;
2672
2675
  }
2676
+ /**
2677
+ * Process a JSON endpoint source
2678
+ * Fetches a JSON endpoint that returns { images: string[] }
2679
+ * @param url - JSON endpoint URL
2680
+ * @param filter - Filter to apply to discovered images
2681
+ * @returns Promise resolving to array of validated URLs
2682
+ */
2683
+ async processJson(t, e) {
2684
+ if (!t)
2685
+ return console.warn("url is required for json-type sources"), [];
2686
+ this.log(`Fetching JSON endpoint: ${t}`);
2687
+ const i = new AbortController(), n = setTimeout(() => i.abort(), 1e4);
2688
+ try {
2689
+ const s = await fetch(t, { signal: i.signal });
2690
+ if (clearTimeout(n), !s.ok)
2691
+ throw new Error(`HTTP ${s.status} fetching ${t}`);
2692
+ const r = await s.json();
2693
+ if (!r || !Array.isArray(r.images))
2694
+ throw new Error('JSON source must return JSON with shape { "images": ["url1", "url2", ...] }');
2695
+ return this.log(`JSON endpoint returned ${r.images.length} image(s)`), await this.processUrls(r.images, e);
2696
+ } catch (s) {
2697
+ throw clearTimeout(n), s instanceof Error && s.name === "AbortError" ? new Error(`Timeout fetching JSON endpoint: ${t}`) : s;
2698
+ }
2699
+ }
2673
2700
  /**
2674
2701
  * Validate a single URL using HEAD request
2675
2702
  * @param url - URL to validate
@@ -2879,7 +2906,7 @@ function Ue() {
2879
2906
  class He {
2880
2907
  constructor(t = {}) {
2881
2908
  var i, n, s, r, a, h;
2882
- this.fullConfig = Vt(t), t.container instanceof HTMLElement ? (this.containerRef = t.container, this.containerId = null) : (this.containerRef = null, this.containerId = t.container || "imageCloud"), this.imagesLoaded = !1, this.imageElements = [], this.imageLayouts = [], this.currentImageHeight = 225, this.currentFocusIndex = null, this.hoveredImage = null, this.resizeTimeout = null, this.displayQueue = [], this.queueInterval = null, this.loadGeneration = 0, this.animationEngine = new Qt(this.fullConfig.animation), this.layoutEngine = new xe({
2909
+ this.fullConfig = Jt(t), t.container instanceof HTMLElement ? (this.containerRef = t.container, this.containerId = null) : (this.containerRef = null, this.containerId = t.container || "imageCloud"), this.imagesLoaded = !1, this.imageElements = [], this.imageLayouts = [], this.currentImageHeight = 225, this.currentFocusIndex = null, this.hoveredImage = null, this.resizeTimeout = null, this.displayQueue = [], this.queueInterval = null, this.loadGeneration = 0, this.animationEngine = new Qt(this.fullConfig.animation), this.layoutEngine = new xe({
2883
2910
  layout: this.fullConfig.layout,
2884
2911
  image: this.fullConfig.image
2885
2912
  }), this.zoomEngine = new Te(this.fullConfig.interaction.focus, this.animationEngine, this.fullConfig.styling), this.defaultStyles = ft((i = this.fullConfig.styling) == null ? void 0 : i.default), this.hoverStyles = ft((n = this.fullConfig.styling) == null ? void 0 : n.hover), this.defaultClassName = (r = (s = this.fullConfig.styling) == null ? void 0 : s.default) == null ? void 0 : r.className, this.hoverClassName = (h = (a = this.fullConfig.styling) == null ? void 0 : a.hover) == null ? void 0 : h.className;