@lightningjs/blits 1.1.0 → 1.2.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/CHANGELOG.md +10 -0
- package/docs/_sidebar.md +1 -0
- package/docs/plugins/language.md +174 -0
- package/docs/sidebar.json +4 -0
- package/index.d.ts +2 -0
- package/index.js +2 -0
- package/package.json +3 -2
- package/src/component/base/index.js +4 -1
- package/src/component/base/log.js +1 -1
- package/src/component.d.ts +6 -0
- package/src/component.js +26 -1
- package/src/components/index.js +0 -2
- package/src/components/index.orig.js +0 -2
- package/src/engines/L3/element.js +1 -1
- package/src/helpers/fetchJson.js +34 -0
- package/src/lib/codegenerator/generator.js +13 -4
- package/src/lib/codegenerator/generator.test.js +52 -0
- package/src/lib/symbols.js +1 -0
- package/src/plugin.d.ts +71 -0
- package/src/plugin.js +23 -0
- package/src/{components/Image.orig.js → plugins/index.js} +2 -17
- package/src/plugins/language.js +122 -0
- package/src/components/Image.js +0 -52
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v1.2.0
|
|
4
|
+
|
|
5
|
+
_5 aug 2024_
|
|
6
|
+
|
|
7
|
+
- Added plugin system for Blits
|
|
8
|
+
- Added Language plugin, modeled after L2-SDK Language plugin
|
|
9
|
+
- Removed unused built-in Image component
|
|
10
|
+
- Marked `this.trigger()`-method as deprecated in definition file
|
|
11
|
+
- Removed double assignment of `this.node` during element creation
|
|
12
|
+
|
|
3
13
|
## v1.1.0
|
|
4
14
|
|
|
5
15
|
_25 july 2024_
|
package/docs/_sidebar.md
CHANGED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Blits - Lightning 3 App Development Framework
|
|
2
|
+
|
|
3
|
+
## Language plugin
|
|
4
|
+
|
|
5
|
+
Blits comes with a built-in Language plugin, that can be used to add internationalization (i18n) to your App.
|
|
6
|
+
|
|
7
|
+
The Blits Language plugin is a simple and lightweight implementation, closely modeled after the tried and tested Language plugin in the Lightning-SDK (for Lightning 2). It is designed to provide a flexible and easy way of translating texts to multiple languages, in a performant way.
|
|
8
|
+
|
|
9
|
+
### Registering the plugin
|
|
10
|
+
|
|
11
|
+
While the Language plugin is provided in the core Blits package, it's an _optional_ plugin that needs to be registered before you're able to use it. As such the Language plugin is _tree-shakable_, i.e. for Apps that don't have a need for translation functionality, the Language plugin will not be part of the final App bundle.
|
|
12
|
+
|
|
13
|
+
The Language plugin should be imported from Blits and registered in the App's `index.js`, as demonstrated in the example below.
|
|
14
|
+
|
|
15
|
+
Make sure to place the `Blits.Plugin()` method _before_ calling the `Blits.Launch()` method
|
|
16
|
+
|
|
17
|
+
```js
|
|
18
|
+
// index.js
|
|
19
|
+
|
|
20
|
+
import Blits from '@lightningjs/blits'
|
|
21
|
+
// import the language plugin
|
|
22
|
+
import { language } from '@lightningjs/blits/plugins'
|
|
23
|
+
|
|
24
|
+
import App from './App.js'
|
|
25
|
+
|
|
26
|
+
// Use the Blits Language plugin and
|
|
27
|
+
// optionally pass in a settings object
|
|
28
|
+
Blits.Plugin(language, {
|
|
29
|
+
file: '/assets/translations.json', // file with translations
|
|
30
|
+
language: 'EN-us', // default language
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
Blits.Launch(App, 'app', {
|
|
35
|
+
// launch settings
|
|
36
|
+
})
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The Language plugin accepts an optional configuration object with 2 keys:
|
|
40
|
+
|
|
41
|
+
- `file` - the JSON file with translations to be loaded at initialization
|
|
42
|
+
- `language` - the default language to use
|
|
43
|
+
|
|
44
|
+
After registration of the Language plugin, it will be availabe in each Blits Component as `this.$language`.
|
|
45
|
+
|
|
46
|
+
### Translations file
|
|
47
|
+
|
|
48
|
+
The most common way of defining a set of translations, is to use a dedicated JSON file, modeled after
|
|
49
|
+
the following format:
|
|
50
|
+
|
|
51
|
+
```json
|
|
52
|
+
{
|
|
53
|
+
"nl-NL": {
|
|
54
|
+
"hello": "Hallo, alles goed?",
|
|
55
|
+
"bye": "Doei!",
|
|
56
|
+
"replacement": "Mijn naam is {first} {last}, ik ben {age} jaar"
|
|
57
|
+
},
|
|
58
|
+
"en-US": {
|
|
59
|
+
"hello": "Hello, how are you doing?",
|
|
60
|
+
"bye": "Goodbye!",
|
|
61
|
+
"replacement": "My name is {first} {last}, I'm {age} years old"
|
|
62
|
+
},
|
|
63
|
+
"fr-FR": {
|
|
64
|
+
"hello": "Bonjour, ça va?",
|
|
65
|
+
"bye": "Au revoir!",
|
|
66
|
+
"replacement": "Je m'appelle {first} {last}, j'ai {age} ans"
|
|
67
|
+
},
|
|
68
|
+
"de-DE": {
|
|
69
|
+
"hello": "Gutentag, wie geht's?",
|
|
70
|
+
"bye": "Tschüss!",
|
|
71
|
+
"replacement": "Mein Name ist {first} {last}, ich bin {age} Jahre alt"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Each top level key represents a language, which contains an object with translation key-value pairs. The language key
|
|
77
|
+
doesn't require to be in a specific format, but it's recommended to use ISO-codes.
|
|
78
|
+
|
|
79
|
+
When the JSON file is specified in the Plugin registration method, the language file is loaded automatically upon instantiation.
|
|
80
|
+
|
|
81
|
+
In case you want to load the file with translations manually, you can use the `this.$language.load()`-method anywhere in a Component and pass in the path to the JSON file as the first argument.
|
|
82
|
+
|
|
83
|
+
### Defining translations manualy
|
|
84
|
+
|
|
85
|
+
As an alternative to a JSON file with translations, you can also define translations directly via an object, using the `translations()`-method.
|
|
86
|
+
|
|
87
|
+
The `this.$language.translations()`-methods accepts a translations object as it's first argument, following the same format as the JSON file. The method is typically used in an `init` or `ready` hook
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
Blits.Component('MyComponent', {
|
|
91
|
+
// ...
|
|
92
|
+
hooks: {
|
|
93
|
+
init() {
|
|
94
|
+
this.$language.translations({
|
|
95
|
+
english: {
|
|
96
|
+
hello: "Hello",
|
|
97
|
+
world: "World"
|
|
98
|
+
},
|
|
99
|
+
italian: {
|
|
100
|
+
hello: "Ciao",
|
|
101
|
+
world: "Mondo"
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Setting the language
|
|
110
|
+
|
|
111
|
+
In order to set (or change) the current language, the `this.$language.set()`-method can be used, passing in the new language code (matching one of the languages defined in the translations).
|
|
112
|
+
|
|
113
|
+
```js
|
|
114
|
+
Blits.Component('MyComponent', {
|
|
115
|
+
// ...
|
|
116
|
+
input: {
|
|
117
|
+
enter() {
|
|
118
|
+
this.$language.set('pt-BR')
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Getting the current language
|
|
125
|
+
|
|
126
|
+
The current language can be retrieved via the `this.$language.get()` method. It will return the currently set language code.
|
|
127
|
+
|
|
128
|
+
### Translating
|
|
129
|
+
|
|
130
|
+
The most important functionality in the Language plugin is translating. The Language plugin exposes a `translate()` method that takes as string to translate as it's first argument. It will return the translated value in the currently set language. If either the language or the string to translate can't be found, it will return the inputed string.
|
|
131
|
+
|
|
132
|
+
The translate method can be used both inside the javascript business logic, but also directly in the template.
|
|
133
|
+
|
|
134
|
+
```js
|
|
135
|
+
Blits.Component('MyComponent', {
|
|
136
|
+
template: `
|
|
137
|
+
<Element>
|
|
138
|
+
<Text :content="$$language.translate('hello')" />
|
|
139
|
+
<Text y="100" :content="$$language.get()" />
|
|
140
|
+
</Element>
|
|
141
|
+
`,
|
|
142
|
+
hooks: {
|
|
143
|
+
ready() {
|
|
144
|
+
console.log(this.$language.translate('world'))
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
Note that in the template definition 2 consecutive dollars signs are used (`$$language`). The first `$`-sign is used
|
|
151
|
+
to refer to the Component scope, as with any other state variable or computed property refereced in the template.
|
|
152
|
+
|
|
153
|
+
The second `$`-sign is needed, since the Language plugin itself is prefixed with a dollar sign on the Component scope as well (i.e `this.$language`).
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
#### Dynamic replacements
|
|
157
|
+
|
|
158
|
+
The `tranlate()`-method also supports dynamic replacements. This allows you to specify variables inside your translation string, and replace them with dynamic values passed into the `translate()`-method.
|
|
159
|
+
|
|
160
|
+
Replacements are marked in the translation value using _handlebars_, like so:
|
|
161
|
+
|
|
162
|
+
```js
|
|
163
|
+
{
|
|
164
|
+
replacements = "My name is {first} {last}, I'm {age} years old"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Now when you call the `translate()`-method and pass in an object with dynamic values, those will be replaced
|
|
169
|
+
in the final string accordingly:
|
|
170
|
+
|
|
171
|
+
```js
|
|
172
|
+
this.$language.translate('replacements', {name: 'John', last: 'Doe', age: 18})
|
|
173
|
+
// result: My name is John Doe, I'm 18 years old
|
|
174
|
+
```
|
package/docs/sidebar.json
CHANGED
package/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
import Component from './src/component.js'
|
|
19
19
|
import Application from './src/application.js'
|
|
20
20
|
import Launch from './src/launch.js'
|
|
21
|
+
import Plugin from './src/plugin.js'
|
|
21
22
|
|
|
22
23
|
/**
|
|
23
24
|
* Blits - The Lightning 3 App Development Framework
|
|
@@ -26,6 +27,7 @@ declare module Blits {
|
|
|
26
27
|
export { Component }
|
|
27
28
|
export { Application }
|
|
28
29
|
export { Launch }
|
|
30
|
+
export { Plugin }
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
export default Blits
|
package/index.js
CHANGED
|
@@ -18,9 +18,11 @@
|
|
|
18
18
|
import Component from './src/component.js'
|
|
19
19
|
import Application from './src/application.js'
|
|
20
20
|
import Launch from './src/launch.js'
|
|
21
|
+
import Plugin from './src/plugin.js'
|
|
21
22
|
|
|
22
23
|
export default {
|
|
23
24
|
Component,
|
|
24
25
|
Application,
|
|
25
26
|
Launch,
|
|
27
|
+
Plugin,
|
|
26
28
|
}
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lightningjs/blits",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Blits: The Lightning 3 App Development Framework",
|
|
5
5
|
"bin": "bin/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./index.js",
|
|
8
8
|
"./vite": "./vite/index.js",
|
|
9
9
|
"./transitions": "./src/router/transitions/index.js",
|
|
10
|
-
"./precompiler": "./src/lib/precompiler/precompiler.js"
|
|
10
|
+
"./precompiler": "./src/lib/precompiler/precompiler.js",
|
|
11
|
+
"./plugins": "./src/plugins/index.js"
|
|
11
12
|
},
|
|
12
13
|
"scripts": {
|
|
13
14
|
"test": "c8 npm run test:run",
|
|
@@ -22,8 +22,11 @@ import log from './log.js'
|
|
|
22
22
|
import router from './router.js'
|
|
23
23
|
import announcer from './announcer.js'
|
|
24
24
|
import utils from './utils.js'
|
|
25
|
+
import symbols from '../../lib/symbols.js'
|
|
25
26
|
|
|
26
27
|
export default Object.defineProperties(
|
|
27
|
-
{
|
|
28
|
+
{
|
|
29
|
+
[symbols['pluginsRegistered']]: false,
|
|
30
|
+
},
|
|
28
31
|
{ ...methods, ...scheduling, ...events, ...log, ...router, ...announcer, ...utils }
|
|
29
32
|
)
|
package/src/component.d.ts
CHANGED
|
@@ -328,6 +328,12 @@ declare namespace Component {
|
|
|
328
328
|
* Triggers a forced update on state variables.
|
|
329
329
|
*/
|
|
330
330
|
$trigger: (key: string) => void
|
|
331
|
+
/**
|
|
332
|
+
* @deprecated
|
|
333
|
+
*
|
|
334
|
+
* Triggers a forced update on state variables.
|
|
335
|
+
* Deprecated: use `this.$trigger()` instead
|
|
336
|
+
*/
|
|
331
337
|
trigger: (key: string) => void
|
|
332
338
|
|
|
333
339
|
/**
|
package/src/component.js
CHANGED
|
@@ -34,6 +34,8 @@ import Settings from './settings.js'
|
|
|
34
34
|
import setupComponent from './component/setup/index.js'
|
|
35
35
|
import components from './components/index.js'
|
|
36
36
|
|
|
37
|
+
import { plugins } from './plugin.js'
|
|
38
|
+
|
|
37
39
|
// object to store global components
|
|
38
40
|
let globalComponents
|
|
39
41
|
|
|
@@ -202,7 +204,30 @@ const Component = (name = required('name'), config = required('config')) => {
|
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
const factory = (options = {}, parentEl, parentComponent, rootComponent) => {
|
|
205
|
-
//
|
|
207
|
+
// Register user defined plugins once on the Base object
|
|
208
|
+
if (Base[symbols['pluginsRegistered']] === false) {
|
|
209
|
+
Object.keys(plugins).forEach((pluginName) => {
|
|
210
|
+
const prefixedPluginName = `$${pluginName}`
|
|
211
|
+
if (prefixedPluginName in Base) {
|
|
212
|
+
Log.warn(
|
|
213
|
+
`"${pluginName}" (this.${prefixedPluginName}) already exists as a property or plugin on the Base Component. You may be overwriting built-in functionality. Proceed with care!`
|
|
214
|
+
)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const plugin = plugins[pluginName]
|
|
218
|
+
|
|
219
|
+
Object.defineProperty(Base, prefixedPluginName, {
|
|
220
|
+
// instantiate the plugin, passing in provided options
|
|
221
|
+
value: plugin.plugin(plugin.options),
|
|
222
|
+
writable: false,
|
|
223
|
+
enumerable: true,
|
|
224
|
+
configurable: false,
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
Base[symbols['pluginsRegistered']] = true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// setup the component once per component type, using Base as the prototype
|
|
206
231
|
if (!base) {
|
|
207
232
|
Log.debug(`Setting up ${name} component`)
|
|
208
233
|
base = setupComponent(Object.create(Base), config)
|
package/src/components/index.js
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import Image from './Image.js'
|
|
19
18
|
import Circle from './Circle.js'
|
|
20
19
|
import RouterView from './RouterView.js'
|
|
21
20
|
import Sprite from './Sprite.js'
|
|
@@ -23,7 +22,6 @@ import FPScounter from './FPScounter.js'
|
|
|
23
22
|
import Layout from './Layout.js'
|
|
24
23
|
|
|
25
24
|
export default () => ({
|
|
26
|
-
Image: Image(),
|
|
27
25
|
Circle: Circle(),
|
|
28
26
|
RouterView: RouterView(),
|
|
29
27
|
Sprite: Sprite(),
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
import Image from './Image.js'
|
|
19
18
|
import Circle from './Circle.js'
|
|
20
19
|
import RouterView from './RouterView.js'
|
|
21
20
|
import Sprite from './Sprite.js'
|
|
@@ -23,7 +22,6 @@ import FPScounter from './FPScounter.js'
|
|
|
23
22
|
import Layout from './Layout.js'
|
|
24
23
|
|
|
25
24
|
export default () => ({
|
|
26
|
-
Image: Image(),
|
|
27
25
|
Circle: Circle(),
|
|
28
26
|
RouterView: RouterView(),
|
|
29
27
|
Sprite: Sprite(),
|
|
@@ -272,7 +272,7 @@ const Element = {
|
|
|
272
272
|
|
|
273
273
|
this.node = props.__textnode
|
|
274
274
|
? renderer.createTextNode({ ...textDefaults, ...this.props.props })
|
|
275
|
-
:
|
|
275
|
+
: renderer.createNode(this.props.props)
|
|
276
276
|
|
|
277
277
|
if (props['@loaded']) {
|
|
278
278
|
this.node.on('loaded', (el, { type, dimensions }) => {
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* If not stated otherwise in this file or this component's LICENSE file the
|
|
3
|
+
* following copyright and licenses apply:
|
|
4
|
+
*
|
|
5
|
+
* Copyright 2020 Metrological
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the License);
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export default (file) => {
|
|
21
|
+
return new Promise((resolve, reject) => {
|
|
22
|
+
const xhr = new XMLHttpRequest()
|
|
23
|
+
xhr.onreadystatechange = function () {
|
|
24
|
+
if (xhr.readyState == XMLHttpRequest.DONE) {
|
|
25
|
+
// file protocol returns 0
|
|
26
|
+
// http(s) protocol returns 200
|
|
27
|
+
if (xhr.status === 0 || xhr.status === 200) resolve(JSON.parse(xhr.responseText))
|
|
28
|
+
else reject(xhr.statusText)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
xhr.open('GET', file)
|
|
32
|
+
xhr.send(null)
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -458,7 +458,7 @@ const generateCode = function (templateObject, parent = false, options = {}) {
|
|
|
458
458
|
const interpolate = (val, component = 'component.') => {
|
|
459
459
|
if (val === undefined) return val
|
|
460
460
|
const replaceString = /('.*?')+/gi
|
|
461
|
-
const replaceDollar =
|
|
461
|
+
const replaceDollar = /\$(\$(?=\$)|\$?)/g
|
|
462
462
|
const matches = val.matchAll(replaceString)
|
|
463
463
|
const restore = []
|
|
464
464
|
|
|
@@ -471,10 +471,19 @@ const interpolate = (val, component = 'component.') => {
|
|
|
471
471
|
i++
|
|
472
472
|
}
|
|
473
473
|
|
|
474
|
-
// replace
|
|
475
|
-
val = val.replace(replaceDollar,
|
|
474
|
+
// replace dollar signs using the regex and function
|
|
475
|
+
val = val.replace(replaceDollar, (match, group1) => {
|
|
476
|
+
// single dollar sign
|
|
477
|
+
if (group1 === '') {
|
|
478
|
+
return component
|
|
479
|
+
}
|
|
480
|
+
// consecutive dollar sign, replace only the first $
|
|
481
|
+
else if (group1 === '$') {
|
|
482
|
+
return component + '$'
|
|
483
|
+
}
|
|
484
|
+
})
|
|
476
485
|
|
|
477
|
-
// restore string
|
|
486
|
+
// restore string replacement
|
|
478
487
|
restore.forEach((el, idx) => {
|
|
479
488
|
val = val.replace(`[@@REPLACEMENT${idx}@@]`, el)
|
|
480
489
|
})
|
|
@@ -2290,3 +2290,55 @@ test('Generate code for a template with a simple for-loop on an Element with an
|
|
|
2290
2290
|
|
|
2291
2291
|
assert.end()
|
|
2292
2292
|
})
|
|
2293
|
+
|
|
2294
|
+
test('Generate code for a template with double $$ (i.e. referencing a Blits plugin)', (assert) => {
|
|
2295
|
+
const templateObject = {
|
|
2296
|
+
children: [
|
|
2297
|
+
{
|
|
2298
|
+
[Symbol.for('componentType')]: 'Text',
|
|
2299
|
+
':content': "$$language.translate('hello')",
|
|
2300
|
+
},
|
|
2301
|
+
],
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
const expectedRender = `
|
|
2305
|
+
function anonymous(parent, component, context, components, effect, getRaw) {
|
|
2306
|
+
const elms = []
|
|
2307
|
+
let componentType
|
|
2308
|
+
const rootComponent = component
|
|
2309
|
+
const elementConfig0 = {}
|
|
2310
|
+
|
|
2311
|
+
elms[0] = this.element({ parent: parent || 'root' }, component)
|
|
2312
|
+
elementConfig0['content'] = component.$language.translate('hello')
|
|
2313
|
+
elementConfig0['__textnode'] = true
|
|
2314
|
+
elms[0].populate(elementConfig0)
|
|
2315
|
+
|
|
2316
|
+
return elms
|
|
2317
|
+
}
|
|
2318
|
+
`
|
|
2319
|
+
|
|
2320
|
+
const expectedEffect1 = `
|
|
2321
|
+
function anonymous(component, elms, context, components, rootComponent, effect) {
|
|
2322
|
+
elms[0].set('content', component.$language.translate('hello'))
|
|
2323
|
+
}
|
|
2324
|
+
`
|
|
2325
|
+
|
|
2326
|
+
const actual = generator.call(scope, templateObject)
|
|
2327
|
+
|
|
2328
|
+
assert.equal(
|
|
2329
|
+
normalize(actual.render.toString()),
|
|
2330
|
+
normalize(expectedRender),
|
|
2331
|
+
'Generator should return a render function with the correct code'
|
|
2332
|
+
)
|
|
2333
|
+
assert.ok(
|
|
2334
|
+
Array.isArray(actual.effects) && actual.effects.length === 1,
|
|
2335
|
+
'Generator should return an effects array with 1 item'
|
|
2336
|
+
)
|
|
2337
|
+
assert.equal(
|
|
2338
|
+
normalize(actual.effects[0].toString()),
|
|
2339
|
+
normalize(expectedEffect1),
|
|
2340
|
+
'Generator should return first render function with the correct code'
|
|
2341
|
+
)
|
|
2342
|
+
|
|
2343
|
+
assert.end()
|
|
2344
|
+
})
|
package/src/lib/symbols.js
CHANGED
package/src/plugin.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
/*
|
|
3
|
+
* Copyright 2024 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
|
+
declare namespace Plugin {
|
|
20
|
+
|
|
21
|
+
interface Plugin {
|
|
22
|
+
/**
|
|
23
|
+
* Name of the plugin. The plugin will be accessible on each Component's
|
|
24
|
+
* `this` scope under this name, prefixed with a `$` (i.e. myplugin => `this.$myplugin`)
|
|
25
|
+
*/
|
|
26
|
+
name: string,
|
|
27
|
+
/**
|
|
28
|
+
* Singleton function that will be used to instantiate the plugin.
|
|
29
|
+
* Should do all necessary setup and ideally return an object with
|
|
30
|
+
* properties or methods that can be used in the App
|
|
31
|
+
*/
|
|
32
|
+
plugin: () => any
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Blits.Plugin()
|
|
40
|
+
*
|
|
41
|
+
* Method to register a plugin
|
|
42
|
+
*/
|
|
43
|
+
declare function Plugin(
|
|
44
|
+
/**
|
|
45
|
+
* Plugin object or singleton function that will be used to instantiate the
|
|
46
|
+
* plugin. Should do all necessary setup and ideally return an object with
|
|
47
|
+
* properties or methods that can be used in the App
|
|
48
|
+
*/
|
|
49
|
+
plugin: Plugin.Plugin | (() => any),
|
|
50
|
+
/**
|
|
51
|
+
* Name of the plugin or options object, to be passed into the plugin instantiation.
|
|
52
|
+
*
|
|
53
|
+
* When passing a string, the value will be used as **name** of the plugin. The plugin
|
|
54
|
+
* will be accessible on each Component's `this` scope under this name, prefixed with a `$`
|
|
55
|
+
* (i.e. myplugin => `this.$myplugin`)
|
|
56
|
+
*
|
|
57
|
+
* Passing a name is **required** when `plugin` argument is a singleton function
|
|
58
|
+
*
|
|
59
|
+
* When passing an object, the value will be considered to be an `options` object.
|
|
60
|
+
* The options object will be passed into the plugin instantiation method. Can
|
|
61
|
+
* be used to set default values
|
|
62
|
+
*/
|
|
63
|
+
nameOrOptions?: string | object,
|
|
64
|
+
/**
|
|
65
|
+
* Object with options to be passed into the plugin instantiation method.
|
|
66
|
+
* Can be used to set default values
|
|
67
|
+
*/
|
|
68
|
+
options?: object
|
|
69
|
+
) : void
|
|
70
|
+
|
|
71
|
+
export default Plugin;
|
package/src/plugin.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const plugins = {}
|
|
2
|
+
|
|
3
|
+
const registerPlugin = (plugin, nameOrOptions = '', options = {}) => {
|
|
4
|
+
// map name and options depending on the arguments provided
|
|
5
|
+
let name
|
|
6
|
+
if (typeof nameOrOptions === 'object') {
|
|
7
|
+
options = nameOrOptions
|
|
8
|
+
name = ''
|
|
9
|
+
} else {
|
|
10
|
+
name = nameOrOptions
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (typeof plugin === 'function') {
|
|
14
|
+
if (name === '') {
|
|
15
|
+
throw Error('Error registering plugin: name is required for plugin')
|
|
16
|
+
}
|
|
17
|
+
plugins[name] = { plugin, options }
|
|
18
|
+
} else if (plugin.plugin) {
|
|
19
|
+
registerPlugin(plugin.plugin, plugin.name, options)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default registerPlugin
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2024 Comcast Cable Communications Management, LLC
|
|
3
3
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License.
|
|
5
5
|
* You may obtain a copy of the License at
|
|
@@ -15,19 +15,4 @@
|
|
|
15
15
|
* SPDX-License-Identifier: Apache-2.0
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export default () =>
|
|
21
|
-
Component('Image', {
|
|
22
|
-
template: `
|
|
23
|
-
<Element :imageSource="$imageSource" />`,
|
|
24
|
-
props: ['src'],
|
|
25
|
-
computed: {
|
|
26
|
-
imageSource() {
|
|
27
|
-
if (/^(?:https?:)?\/\//i.test(this.src)) {
|
|
28
|
-
return this.src
|
|
29
|
-
}
|
|
30
|
-
return `${window.location.protocol}//${window.location.host}/${this.src}`
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
})
|
|
18
|
+
export { default as language } from './language.js'
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2024 Comcast Cable Communications Management, LLC
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*
|
|
15
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import fetchJson from '../helpers/fetchJson'
|
|
19
|
+
import { Log } from '../lib/log'
|
|
20
|
+
import { reactive } from '../lib/reactivity/reactive'
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
name: 'language',
|
|
24
|
+
plugin(options = {}) {
|
|
25
|
+
let translations = {}
|
|
26
|
+
let dictionary = {}
|
|
27
|
+
|
|
28
|
+
const state = reactive({
|
|
29
|
+
language: '',
|
|
30
|
+
loaded: 0,
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const setLanguage = (language) => {
|
|
34
|
+
// define the dictionary
|
|
35
|
+
dictionary = translations[language] || {}
|
|
36
|
+
// set the current language in the reactive state
|
|
37
|
+
state.language = language
|
|
38
|
+
|
|
39
|
+
// warnings
|
|
40
|
+
if (Object.keys(translations).length === 0) {
|
|
41
|
+
Log.warn(
|
|
42
|
+
'No translations loaded. Please load a file with translations or specify a translations object manually'
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
if (language in translations === false) {
|
|
46
|
+
Log.warn(`Language ${language} not available in the loaded translations`)
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const loadLanguageFile = (filePath) => {
|
|
51
|
+
return fetchJson(filePath)
|
|
52
|
+
.then((result) => {
|
|
53
|
+
setTranslations(result)
|
|
54
|
+
})
|
|
55
|
+
.catch((e) => {
|
|
56
|
+
Log.error(e)
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const setTranslations = (translationsObject) => {
|
|
61
|
+
translations = translationsObject
|
|
62
|
+
dictionary = translations[state.language] || {}
|
|
63
|
+
state.loaded++
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if ('file' in options) {
|
|
67
|
+
loadLanguageFile(options.file).then(() => {
|
|
68
|
+
if ('language' in options) {
|
|
69
|
+
setLanguage(options.language)
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
} else if ('language' in options) {
|
|
73
|
+
setLanguage(options.language)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Public API language plugin
|
|
77
|
+
return {
|
|
78
|
+
translate(key) {
|
|
79
|
+
// reference state.language, to trigger reactive effects for
|
|
80
|
+
// translate functions when language is modified
|
|
81
|
+
state.language
|
|
82
|
+
|
|
83
|
+
// reference state.loaded, to trigger reactive effects for
|
|
84
|
+
// translate functions when language is modified
|
|
85
|
+
state.loaded
|
|
86
|
+
|
|
87
|
+
let replacements = [...arguments].slice(1)
|
|
88
|
+
|
|
89
|
+
// no replacements so just translated string
|
|
90
|
+
if (replacements.length === 0) {
|
|
91
|
+
return (dictionary && dictionary[key]) || key
|
|
92
|
+
} else {
|
|
93
|
+
if (replacements.length === 1 && typeof replacements[0] === 'object') {
|
|
94
|
+
replacements = replacements.pop()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Object.keys(
|
|
98
|
+
// maps array input to an object {0: 'item1', 1: 'item2'}
|
|
99
|
+
Array.isArray(replacements) ? Object.assign({}, replacements) : replacements
|
|
100
|
+
).reduce((text, replacementKey) => {
|
|
101
|
+
return text.replace(
|
|
102
|
+
new RegExp('{\\s?' + replacementKey + '\\s?}', 'g'),
|
|
103
|
+
replacements[replacementKey]
|
|
104
|
+
)
|
|
105
|
+
}, (dictionary && dictionary[key]) || key)
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
get language() {
|
|
109
|
+
return state.language
|
|
110
|
+
},
|
|
111
|
+
set(language) {
|
|
112
|
+
setLanguage(language)
|
|
113
|
+
},
|
|
114
|
+
translations(translationsObject) {
|
|
115
|
+
setTranslations(translationsObject)
|
|
116
|
+
},
|
|
117
|
+
load(file) {
|
|
118
|
+
return loadLanguageFile(file)
|
|
119
|
+
},
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
}
|
package/src/components/Image.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright 2023 Comcast Cable Communications Management, LLC
|
|
3
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
-
* you may not use this file except in compliance with the License.
|
|
5
|
-
* You may obtain a copy of the License at
|
|
6
|
-
*
|
|
7
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
-
*
|
|
9
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
-
* See the License for the specific language governing permissions and
|
|
13
|
-
* limitations under the License.
|
|
14
|
-
*
|
|
15
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import Component from '../component.js'
|
|
19
|
-
|
|
20
|
-
export default () =>
|
|
21
|
-
Component('Image', {
|
|
22
|
-
/* eslint-disable no-unused-vars */
|
|
23
|
-
code: {
|
|
24
|
-
render: function anonymous(parent, component, context, components, effect, getRaw) {
|
|
25
|
-
const elms = []
|
|
26
|
-
let componentType
|
|
27
|
-
const rootComponent = component
|
|
28
|
-
const elementConfig0 = {}
|
|
29
|
-
|
|
30
|
-
elms[0] = this.element({ parent: parent || 'root' }, component)
|
|
31
|
-
|
|
32
|
-
elementConfig0['imageSource'] = component.imageSource
|
|
33
|
-
elms[0].populate(elementConfig0)
|
|
34
|
-
return elms
|
|
35
|
-
},
|
|
36
|
-
effects: [
|
|
37
|
-
function anonymous(component, elms, context, components, rootComponent, effect) {
|
|
38
|
-
elms[0].set('imageSource', component.imageSource)
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
context: {},
|
|
42
|
-
},
|
|
43
|
-
props: ['src'],
|
|
44
|
-
computed: {
|
|
45
|
-
imageSource() {
|
|
46
|
-
if (/^(?:https?:)?\/\//i.test(this.src)) {
|
|
47
|
-
return this.src
|
|
48
|
-
}
|
|
49
|
-
return `${window.location.protocol}//${window.location.host}/${this.src}`
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
})
|