@linear_non/stellar-kit 1.1.6 → 1.1.7

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.
@@ -4,6 +4,7 @@ import { emitter, EVENTS } from "../events"
4
4
  export default class FontLoader {
5
5
  static type = "fonts"
6
6
  static events = { PROGRESS: EVENTS.APP_FONTS_PROGRESS, LOADED: EVENTS.APP_FONTS_LOADED }
7
+ static getCount = (opts = {}) => opts.fonts?.length ?? 0
7
8
  constructor({ fonts = [] }) {
8
9
  // fonts = [{ family: "Inter", url: "/fonts/Inter.woff2" }, ...]
9
10
 
@@ -1,4 +1,4 @@
1
- // ImageLoader.js
1
+ // classes/ImageLoader.js
2
2
  import { emitter, EVENTS } from "../events"
3
3
 
4
4
  export default class ImageLoader {
@@ -7,97 +7,158 @@ export default class ImageLoader {
7
7
  PROGRESS: EVENTS.APP_IMAGES_PROGRESS,
8
8
  LOADED: EVENTS.APP_IMAGES_LOADED,
9
9
  }
10
+ static getCount = (opts = {}) => opts.urls?.length ?? opts.total ?? 0
10
11
 
11
- constructor({ origin, fileName, extension, total, urls = [] }) {
12
+ constructor({ origin, fileName, extension, total, urls = [], useWorker = true, cache = null }) {
12
13
  this.origin = origin
13
14
  this.fileName = fileName
14
15
  this.extension = extension
15
16
  this.total = total
16
17
  this.urls = urls
18
+ this.useWorker = useWorker
19
+ this.cache = cache
17
20
 
18
21
  this.images = []
19
22
  this.loaded = 0
20
23
  this.isLoaded = false
24
+
25
+ this._results = new Map() // url -> HTMLImageElement
26
+ this._objectURLs = [] // for revoke
21
27
  }
22
28
 
23
29
  async load() {
24
- const urls = this.urls.length
30
+ const raw = this.urls.length
25
31
  ? this.urls
26
32
  : Array.from({ length: this.total }, (_, i) => `${this.origin}/${this.fileName}${i}.${this.extension}`)
27
33
 
34
+ const base =
35
+ typeof document !== "undefined"
36
+ ? document.baseURI
37
+ : typeof location !== "undefined"
38
+ ? location.href
39
+ : ""
40
+ const urls = raw.map(u => {
41
+ try {
42
+ return new URL(u, base).href
43
+ } catch {
44
+ return u
45
+ }
46
+ })
47
+
48
+ // cache-aware filter
49
+ const toLoad = this.cache ? urls.filter(u => !this.cache.has(u)) : urls
50
+
28
51
  this.total = urls.length
52
+ this.loaded = this.total - toLoad.length // count already-cached as loaded
29
53
 
30
- const promises = urls.map((url, i) => this.loadSingle(url, i))
31
- await Promise.all(promises)
54
+ // (optional) emit initial progress if cache pre-fills
55
+ if (this.loaded > 0) {
56
+ emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
57
+ loaded: this.loaded,
58
+ total: this.total,
59
+ percent: Math.round((this.loaded / this.total) * 100),
60
+ })
61
+ }
32
62
 
63
+ await Promise.all(toLoad.map(url => this._loadOne(url)))
64
+
65
+ // build ordered result (use cache first, then freshly loaded)
66
+ this.images = urls.map(u => this.cache?.get(u) || this._results.get(u) || null)
33
67
  this.isLoaded = true
34
68
  emitter.emit(EVENTS.APP_IMAGES_LOADED, this.images)
35
-
36
69
  return this.images
37
70
  }
38
71
 
39
- // --- inline worker (classic) ---
40
- createWorker() {
72
+ _createInlineWorker() {
41
73
  const code = `
42
74
  self.onmessage = async (e) => {
43
- const { url, index } = e.data;
75
+ const { url } = e.data;
44
76
  try {
45
77
  const res = await fetch(url);
46
78
  const blob = await res.blob();
47
- self.postMessage({ url, blob, index });
79
+ self.postMessage({ url, blob });
48
80
  } catch (err) {
49
- self.postMessage({ url, error: String(err), index });
81
+ self.postMessage({ url, error: String(err) });
50
82
  }
51
83
  };`
52
84
  const blob = new Blob([code], { type: "application/javascript" })
53
- const workerUrl = URL.createObjectURL(blob)
54
- const w = new Worker(workerUrl) // classic worker (no imports)
55
- // (optional) URL.revokeObjectURL(workerUrl) after creation; leaving as-is for safety
56
- return w
85
+ return new Worker(URL.createObjectURL(blob))
57
86
  }
58
87
 
59
- loadSingle(url, index) {
60
- return new Promise(resolve => {
61
- const worker = this.createWorker()
62
- worker.postMessage({ url, index })
63
-
64
- worker.addEventListener("message", e => {
65
- const { blob, index, error } = e.data
66
- this.loaded++
67
-
68
- if (!error && blob) {
69
- const img = new Image()
70
- img.src = URL.createObjectURL(blob)
71
- img.onload = () => {
72
- this.images[index] = img
73
- emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
74
- loaded: this.loaded,
75
- total: this.total,
76
- percent: Math.round((this.loaded / this.total) * 100),
77
- })
78
- resolve(img)
79
- worker.terminate()
88
+ async _loadOne(url) {
89
+ // worker path
90
+ if (this.useWorker && typeof Worker !== "undefined") {
91
+ return new Promise(resolve => {
92
+ const w = this._createInlineWorker()
93
+ w.onmessage = async e => {
94
+ const { blob, error } = e.data
95
+ if (error || !blob) {
96
+ this._bumpProgress()
97
+ w.terminate()
98
+ return resolve(null)
80
99
  }
81
- img.onerror = () => {
82
- emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
83
- loaded: this.loaded,
84
- total: this.total,
85
- percent: Math.round((this.loaded / this.total) * 100),
86
- })
87
- resolve(null)
88
- worker.terminate()
89
- }
90
- } else {
91
- // count progress even on error
92
- emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
93
- loaded: this.loaded,
94
- total: this.total,
95
- percent: Math.round((this.loaded / this.total) * 100),
96
- })
97
- resolve(null)
98
- worker.terminate()
100
+ const img = await this._blobToImage(blob)
101
+ this._store(url, img)
102
+ this._bumpProgress()
103
+ w.terminate()
104
+ resolve(img)
99
105
  }
106
+ w.postMessage({ url })
100
107
  })
108
+ }
109
+
110
+ // fallback path (no worker)
111
+ try {
112
+ const res = await fetch(url)
113
+ const blob = await res.blob()
114
+ const img = await this._blobToImage(blob)
115
+ this._store(url, img)
116
+ this._bumpProgress()
117
+ return img
118
+ } catch {
119
+ this._bumpProgress()
120
+ return null
121
+ }
122
+ }
123
+
124
+ async _blobToImage(blob) {
125
+ if (!blob || !blob.type?.startsWith?.("image/")) return null
126
+ return new Promise(resolve => {
127
+ const url = URL.createObjectURL(blob)
128
+ this._objectURLs.push(url)
129
+ const img = new Image()
130
+ img.src = url
131
+ img.onload = async () => {
132
+ if (img.decode) {
133
+ try {
134
+ await img.decode()
135
+ } catch {}
136
+ }
137
+ resolve(img)
138
+ }
139
+ img.onerror = () => resolve(null)
140
+ })
141
+ }
142
+
143
+ _store(url, img) {
144
+ if (!img) return
145
+ this._results.set(url, img)
146
+ if (this.cache) this.cache.set(url, img)
147
+ }
148
+
149
+ _bumpProgress() {
150
+ this.loaded++
151
+ emitter.emit(EVENTS.APP_IMAGES_PROGRESS, {
152
+ loaded: this.loaded,
153
+ total: this.total,
154
+ percent: Math.round((this.loaded / this.total) * 100),
101
155
  })
102
156
  }
157
+
158
+ destroy() {
159
+ this._objectURLs.forEach(u => URL.revokeObjectURL(u))
160
+ this._objectURLs.length = 0
161
+ this.images = []
162
+ this._results.clear()
163
+ }
103
164
  }
@@ -12,7 +12,7 @@ export default class MasterLoader {
12
12
  add(loaderClass, options) {
13
13
  const loader = new loaderClass(options)
14
14
  this.loaders.push({ loaderClass, loader })
15
- this.total += loader.total || 0
15
+ this.total += loaderClass.getCount?.(options) ?? loader.total ?? 0
16
16
  return loader
17
17
  }
18
18
 
package/kitStore.js CHANGED
@@ -7,6 +7,9 @@ export const sizes = {
7
7
  m: 390, // mobile width
8
8
  }
9
9
 
10
+ // in case you want to "cache" your assets
11
+ export const assets = {}
12
+
10
13
  export const flags = {
11
14
  isFocus: false,
12
15
  isSmooth: false,
@@ -34,4 +37,5 @@ export default {
34
37
  mouse,
35
38
  flags,
36
39
  pageContent: null, // This should be set to the main content element
40
+ assets,
37
41
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linear_non/stellar-kit",
3
- "version": "1.1.6",
3
+ "version": "1.1.7",
4
4
  "description": "Stellar frontend core for Non-Linear Studio projects.",
5
5
  "main": "index.js",
6
6
  "exports": {