@lightningjs/blits 1.7.0 → 1.8.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lightningjs/blits",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "Blits: The Lightning 3 App Development Framework",
5
5
  "bin": "bin/index.js",
6
6
  "exports": {
@@ -19,6 +19,7 @@
19
19
  "prepublishOnly": "node scripts/prepublishOnly.js",
20
20
  "postpublish": "node scripts/postpublish.js"
21
21
  },
22
+ "types": "./index.d.ts",
22
23
  "lint-staged": {
23
24
  "*.js": [
24
25
  "eslint --fix"
@@ -47,8 +48,8 @@
47
48
  "tape": "^5.5.0"
48
49
  },
49
50
  "dependencies": {
50
- "@lightningjs/msdf-generator": "^1.0.2",
51
- "@lightningjs/renderer": "^2.3.2"
51
+ "@lightningjs/msdf-generator": "^1.1.0",
52
+ "@lightningjs/renderer": "^2.4.0"
52
53
  },
53
54
  "repository": {
54
55
  "type": "git",
@@ -386,13 +386,21 @@ const Element = {
386
386
  f,
387
387
  }
388
388
 
389
- if (transition.start && typeof transition.start === 'function') {
389
+ if (transition.start !== undefined && typeof transition.start === 'function') {
390
390
  // fire transition start callback when animation really starts (depending on specified delay)
391
391
  f.once('animating', () => {
392
392
  transition.start.call(this.component, this, prop, startValue)
393
393
  })
394
394
  }
395
395
 
396
+ if (transition.progress !== undefined && typeof transition.progress === 'function') {
397
+ let prevProgress = 0
398
+ f.on('tick', (_node, { progress }) => {
399
+ transition.progress.call(this.component, this, prop, progress, prevProgress)
400
+ prevProgress = progress
401
+ })
402
+ }
403
+
396
404
  f.once('stopped', () => {
397
405
  // remove the prop from scheduled transitions
398
406
  this.scheduledTransitions[prop] = undefined
@@ -1,5 +1,6 @@
1
1
  import path from 'path'
2
2
  import * as fs from 'fs'
3
+ import { createHash } from 'crypto'
3
4
  import { genFont, setGeneratePaths } from '@lightningjs/msdf-generator'
4
5
  import { adjustFont } from '@lightningjs/msdf-generator/adjustFont'
5
6
 
@@ -21,6 +22,8 @@ class TaskQueue {
21
22
 
22
23
  const fontGenerationQueue = new TaskQueue()
23
24
  let config
25
+ let checksumPaths = []
26
+ let checksumData = []
24
27
 
25
28
  export default function () {
26
29
  let msdfOutputDir = ''
@@ -49,9 +52,21 @@ export default function () {
49
52
  if (!fs.existsSync(path.resolve(fontDir, file))) {
50
53
  const fontName = match[1]
51
54
  const ext = match[2]
52
- const fontFile = path.join(fontDir, `${fontName}.ttf`)
53
55
 
54
- if (fs.existsSync(fontFile)) {
56
+ // Attempt to find the font file with supported extensions
57
+ const supportedExtensions = ['ttf', 'otf', 'woff']
58
+ let fontFile = null
59
+ let fontType = null
60
+ for (const fontExt of supportedExtensions) {
61
+ const potentialPath = path.join(fontDir, `${fontName}.${fontExt}`)
62
+ if (fs.existsSync(potentialPath)) {
63
+ fontFile = potentialPath
64
+ fontType = '.' + fontExt
65
+ break
66
+ }
67
+ }
68
+
69
+ if (fontFile) {
55
70
  const generatedFontFile = path.join(
56
71
  msdfOutputDir,
57
72
  fontPath,
@@ -60,10 +75,13 @@ export default function () {
60
75
  const mimeType = ext === 'png' ? 'image/png' : 'application/json'
61
76
 
62
77
  await fontGenerationQueue.enqueue(async () => {
63
- if (!fs.existsSync(generatedFontFile)) {
64
- // Check if generation is needed
78
+ if (isGenerationRequired(fontDir, targetDir, fontName, fontType)) {
79
+ const configFilePath = path.join(fontDir, `${fontName}.config.json`)
80
+
65
81
  console.log(`\nGenerating ${targetDir}/${fontName}.msdf.${ext}`)
66
- await generateSDF(fontFile, path.join(targetDir))
82
+ await generateSDF(fontFile, path.join(targetDir), configFilePath)
83
+
84
+ saveChecksum()
67
85
  }
68
86
  })
69
87
 
@@ -83,7 +101,7 @@ export default function () {
83
101
  next() // Handle case where generation might have failed
84
102
  }
85
103
  } else {
86
- next() // ttf file does not exist
104
+ next() // No supported font file found
87
105
  }
88
106
  } else {
89
107
  next() // file exists
@@ -95,18 +113,21 @@ export default function () {
95
113
  },
96
114
  // after build ends, the first hook is renderStart where we can modify the output
97
115
  async renderStart() {
98
- const ttfFiles = findAllTtfFiles(publicDir)
99
-
100
- for (const ttfFile of ttfFiles) {
101
- const relativePath = path.relative(publicDir, path.dirname(ttfFile))
102
- const baseName = path.basename(ttfFile).replace(/\.ttf$/i, '')
103
- const msdfJsonPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.json`)
104
- const msdfPngPath = path.join(msdfOutputDir, relativePath, `${baseName}.msdf.png`)
105
-
106
- // Check if MSDF files are generated, if not, generate them
107
- if (!fs.existsSync(msdfJsonPath) || !fs.existsSync(msdfPngPath)) {
108
- console.log(`Generating missing MSDF files for ${ttfFile}`)
109
- await generateSDF(ttfFile, path.join(msdfOutputDir, relativePath))
116
+ const fontFiles = findAllFontFiles(publicDir)
117
+
118
+ for (const fontFile of fontFiles) {
119
+ const relativePath = path.relative(publicDir, path.dirname(fontFile))
120
+ const fontExt = path.extname(fontFile)
121
+ const baseName = path.basename(fontFile).replace(/\.(ttf|otf|woff)$/i, '')
122
+ const targetDir = path.join(msdfOutputDir, relativePath)
123
+
124
+ // Check MSDF generation is required
125
+ if (isGenerationRequired(path.dirname(fontFile), targetDir, baseName, fontExt)) {
126
+ const configFilePath = path.join(path.dirname(fontFile), `${baseName}.config.json`)
127
+ console.log(`Generating missing MSDF files for ${fontFile}`)
128
+ await generateSDF(fontFile, path.join(msdfOutputDir, relativePath), configFilePath)
129
+
130
+ saveChecksum()
110
131
  }
111
132
  }
112
133
 
@@ -117,11 +138,11 @@ export default function () {
117
138
  }
118
139
  }
119
140
 
120
- const generateSDF = async (inputFilePath, outputDirPath) => {
141
+ const generateSDF = async (inputFilePath, outputDirPath, configFilePath) => {
121
142
  // Ensure the destination directory exists
122
143
  fs.mkdirSync(outputDirPath, { recursive: true })
123
144
 
124
- setGeneratePaths(path.dirname(inputFilePath), outputDirPath)
145
+ setGeneratePaths(path.dirname(inputFilePath), outputDirPath, configFilePath)
125
146
 
126
147
  let font = await genFont(path.basename(inputFilePath), 'msdf')
127
148
 
@@ -129,17 +150,17 @@ const generateSDF = async (inputFilePath, outputDirPath) => {
129
150
  else console.error('Failed to generate MSDF file')
130
151
  }
131
152
 
132
- // finds all TTF files in a directory
133
- const findAllTtfFiles = (dir, filesList = []) => {
153
+ // Finds all font files (.ttf, .otf, .woff) in a directory
154
+ const findAllFontFiles = (dir, filesList = []) => {
134
155
  const files = fs.readdirSync(dir, { withFileTypes: true })
135
156
 
136
157
  files.forEach((file) => {
137
158
  if (file.isDirectory()) {
138
159
  const dirPath = path.join(dir, file.name)
139
- findAllTtfFiles(dirPath, filesList)
160
+ findAllFontFiles(dirPath, filesList)
140
161
  } else {
141
- // Check for all case variations of TTF extension
142
- if (file.name.match(/\.ttf$/i)) {
162
+ // Check for supported font extensions
163
+ if (file.name.match(/\.(ttf|otf|woff)$/i)) {
143
164
  filesList.push(path.join(dir, file.name))
144
165
  }
145
166
  }
@@ -158,6 +179,74 @@ const copyDir = (src, dest) => {
158
179
  const srcPath = path.join(src, entry.name)
159
180
  const destPath = path.join(dest, entry.name)
160
181
 
182
+ // Should not copy .checksum files to dist directory
183
+ if (srcPath.indexOf('.checksum') !== -1) continue
184
+
161
185
  entry.isDirectory() ? copyDir(srcPath, destPath) : fs.copyFileSync(srcPath, destPath) // Copy files
162
186
  }
163
187
  }
188
+
189
+ // Check font config.json or font file modified since last generation
190
+ const isGenerationRequired = (fontDir, targetDir, fontName, fontExt) => {
191
+ const fontFilePath = path.resolve(fontDir, fontName + fontExt)
192
+ const fontChecksumPath = path.resolve(targetDir, fontName + fontExt + '.checksum')
193
+
194
+ const configJsonPath = path.resolve(fontDir, fontName + '.config.json')
195
+ const configChecksumPath = path.resolve(targetDir, fontName + '.config.checksum')
196
+
197
+ // If font file checksum not exists, should generate msdf
198
+ if (!fs.existsSync(fontChecksumPath)) {
199
+ checksumPaths.push(fontChecksumPath)
200
+ checksumData.push(generateHash(fontFilePath))
201
+
202
+ if (fs.existsSync(configJsonPath)) {
203
+ checksumPaths.push(configChecksumPath)
204
+ checksumData.push(generateHash(configJsonPath))
205
+ }
206
+ return true
207
+ }
208
+
209
+ // If config.json exists, can be newly added or modified since last generation of msdf
210
+ if (fs.existsSync(configJsonPath)) {
211
+ // If config.json checksum not exists, should generate msdf
212
+ if (!fs.existsSync(configChecksumPath)) {
213
+ checksumPaths.push(configChecksumPath)
214
+ checksumData.push(generateHash(configJsonPath))
215
+ return true
216
+ }
217
+ const isConfigModified = isInputFileModified(configJsonPath, configChecksumPath)
218
+
219
+ // If config.json is modified, should generate msdf
220
+ if (isConfigModified) return true
221
+ }
222
+
223
+ // Check font file modified, if modified, should generate msdf
224
+ return isInputFileModified(fontFilePath, fontChecksumPath)
225
+ }
226
+
227
+ const isInputFileModified = (inputFilePath, targetFilePath) => {
228
+ const inputChecksum = generateHash(inputFilePath)
229
+ const targetChecksum = fs.readFileSync(targetFilePath).toString()
230
+ if (inputChecksum !== targetChecksum) {
231
+ checksumPaths.push(targetFilePath)
232
+ checksumData.push(inputChecksum)
233
+ return true
234
+ }
235
+ return false
236
+ }
237
+
238
+ const generateHash = (filePath) => {
239
+ const buffer = fs.readFileSync(filePath)
240
+ return createHash('md5').update(buffer).digest('hex')
241
+ }
242
+
243
+ const saveChecksum = () => {
244
+ if (checksumPaths.length > 0 && checksumData.length > 0) {
245
+ for (let i = 0; i < checksumPaths.length; i++) {
246
+ // Save checksum
247
+ fs.writeFileSync(checksumPaths[i], checksumData[i])
248
+ }
249
+ checksumData.length = 0
250
+ checksumPaths.length = 0
251
+ }
252
+ }
@@ -1,174 +0,0 @@
1
-
2
- /*
3
- * Copyright 2023 Comcast Cable Communications Management, LLC
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- *
16
- * SPDX-License-Identifier: Apache-2.0
17
- */
18
-
19
- import {default as Component} from './component'
20
-
21
- declare namespace Application {
22
-
23
- interface Transition {
24
- /**
25
- * Name of the prop to transition (i.e. 'x', 'y', 'alpha', 'color')
26
- */
27
- prop: string,
28
- /**
29
- * Value the prop should transition to (i.e. 0, 100, '#223388')
30
- */
31
- value: any,
32
- /**
33
- * Duration of the transition in milliseconds, defaults to 300
34
- */
35
- duration?: number,
36
- /**
37
- * Easing function to apply to the transition
38
- */
39
- easing?: string,
40
- /**
41
- * Delay before the transition starts in milliseconds
42
- */
43
- delay?: number
44
- }
45
-
46
- interface Before {
47
- /**
48
- * Name of the prop to set before the transition starts
49
- */
50
- prop: string,
51
- /**
52
- * Value the prop to set before the transition starts
53
- */
54
- value: any,
55
- }
56
-
57
- interface RouteTransition {
58
- /**
59
- * Setting or Array of Settings before new view enters into the router view
60
- */
61
- before: Before | Before[],
62
- /**
63
- * Transition or Array of Transitions for new view to enters into the router view
64
- */
65
- in: Transition | Transition[],
66
- /**
67
- * Transition or Array of Transitions for old view to leave the router view
68
- */
69
- out: Transition | Transition[],
70
- }
71
-
72
- type RouteTransitionFunction = (previousRoute: Route, currentRoute: Route) => RequireAtLeastOne<RouteTransition>
73
-
74
- interface RouteAnnounce {
75
- /**
76
- * Message to be announced
77
- */
78
- message: String,
79
- /**
80
- * Politeness level
81
- *
82
- * Defaults to 'off'
83
- */
84
- politeness?: 'off' | 'polite' | 'assertive'
85
- }
86
-
87
- type RequireAtLeastOne<T> = {
88
- [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>
89
- }[keyof T]
90
-
91
- interface RouteHooks {
92
- before?: (to: Route, from: Route) => string | Route;
93
- }
94
-
95
- type Route = {
96
- /**
97
- * URI path for the route
98
- */
99
- path: string,
100
- /**
101
- * Component to load when activating the route
102
- */
103
- component: Component,
104
- /**
105
- * Transition configuration for the route
106
- */
107
- transition?: RequireAtLeastOne<RouteTransition> | RouteTransitionFunction,
108
- /**
109
- * Extra route options
110
- */
111
- options?: object // todo: specify which options are available,
112
- /**
113
- * Message to be announced when visiting the route (often used for accessibility purposes)
114
- *
115
- * Can be either a `String` with the message or an object that defines the message and the
116
- * politeness level
117
- */
118
- announce?: String | RouteAnnounce
119
- /**
120
- * Register hooks for the route
121
- */
122
- hooks?: RouteHooks
123
- /**
124
- * Route path parameters
125
- */
126
- readonly params?: {
127
- [key: string]: string | number
128
- }
129
- /**
130
- * Allows for attaching custom data to a route, either hardcoded in the
131
- * route definition or asigned to the route object in a before hook
132
- *
133
- * Will be merged with the route params and navigation data and passed as
134
- * props into the route component
135
- */
136
- data?: {
137
- [key: string]: any
138
- }
139
- }
140
-
141
- export interface ApplicationInstance extends Component.ComponentInstance {}
142
-
143
- export interface ApplicationConfig<Props extends string, S, M> extends Component.ComponentConfig<Props, S, M> {
144
- /**
145
- * Routes definition
146
- *
147
- * @example
148
- *
149
- * ```js
150
- * routes: [
151
- * { path: '/', component: Home },
152
- * { path: '/details', component: Details },
153
- * { path: '/account', component: Account },
154
- * ]
155
- * ```
156
- */
157
- routes?: Route[]
158
- }
159
- }
160
-
161
- /**
162
- * Blits.Application()
163
- *
164
- * Root App component
165
- */
166
- declare function Application<
167
- Props extends string,
168
- S extends Component.State,
169
- M extends Component.Methods
170
- >(
171
- config: Application.ApplicationConfig<Props, S, M>
172
- ) : Application.ApplicationInstance
173
-
174
- export default Application;