@esphome/compose 0.0.1 → 0.1.3
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/dist/index.d.mts +520 -5
- package/dist/index.d.ts +520 -5
- package/dist/index.js +113 -27
- package/dist/index.mjs +113 -27
- package/package.json +13 -7
package/dist/index.js
CHANGED
|
@@ -174,10 +174,16 @@ function useScript(metadata) {
|
|
|
174
174
|
return [{ "script.execute": metadata.id }];
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
-
// src/
|
|
177
|
+
// src/serialize.ts
|
|
178
178
|
function camelToSnake(key) {
|
|
179
179
|
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
180
180
|
}
|
|
181
|
+
function toYamlKey(type) {
|
|
182
|
+
if (type.startsWith("lvgl-")) {
|
|
183
|
+
return type.slice(5).replace(/-/g, "_");
|
|
184
|
+
}
|
|
185
|
+
return type;
|
|
186
|
+
}
|
|
181
187
|
function keysToSnakeCase(obj) {
|
|
182
188
|
const out = {};
|
|
183
189
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -193,6 +199,95 @@ function serializeValue(v) {
|
|
|
193
199
|
}
|
|
194
200
|
return v;
|
|
195
201
|
}
|
|
202
|
+
function stripUndefined(obj) {
|
|
203
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
204
|
+
}
|
|
205
|
+
function extractElementProps(el) {
|
|
206
|
+
const { children, ref, "x:custom": xCustom, ...ownProps } = el.props;
|
|
207
|
+
const propsWithId = ref != null ? { id: isRef(ref) ? ref.toString() : String(ref), ...ownProps } : ownProps;
|
|
208
|
+
const allProps = xCustom != null ? { ...propsWithId, ...xCustom } : propsWithId;
|
|
209
|
+
return { allProps, children };
|
|
210
|
+
}
|
|
211
|
+
function Fragment(props) {
|
|
212
|
+
const { children } = props;
|
|
213
|
+
if (children == null) return null;
|
|
214
|
+
return Array.isArray(children) ? children : [children];
|
|
215
|
+
}
|
|
216
|
+
function flattenFragments(elements) {
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const el of elements) {
|
|
219
|
+
if (el.type === Fragment) {
|
|
220
|
+
const result = Fragment(el.props);
|
|
221
|
+
if (result != null) {
|
|
222
|
+
const nested = Array.isArray(result) ? result : [result];
|
|
223
|
+
out.push(...flattenFragments(nested));
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
out.push(el);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// src/lvgl.ts
|
|
233
|
+
function isLvglElement(type) {
|
|
234
|
+
return typeof type === "string" && type.startsWith("lvgl-");
|
|
235
|
+
}
|
|
236
|
+
function resolveLvglChildren(children) {
|
|
237
|
+
if (!children) return [];
|
|
238
|
+
const arr = Array.isArray(children) ? children : [children];
|
|
239
|
+
const flat = flattenFragments(arr);
|
|
240
|
+
const resolved = [];
|
|
241
|
+
for (const el of flat) {
|
|
242
|
+
if (typeof el.type === "function") {
|
|
243
|
+
const result = el.type(el.props);
|
|
244
|
+
if (result == null) continue;
|
|
245
|
+
const rendered = Array.isArray(result) ? result : [result];
|
|
246
|
+
resolved.push(...resolveLvglChildren(rendered));
|
|
247
|
+
} else {
|
|
248
|
+
resolved.push(el);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return resolved;
|
|
252
|
+
}
|
|
253
|
+
function lvglWidgetToPlain(el) {
|
|
254
|
+
const { allProps, children } = extractElementProps(el);
|
|
255
|
+
const widgetChildren = resolveLvglChildren(children);
|
|
256
|
+
const lvglChildren = widgetChildren.filter((c) => isLvglElement(c.type));
|
|
257
|
+
const nestedWidgets = lvglChildren.map(lvglWidgetToPlain);
|
|
258
|
+
const data = { ...allProps };
|
|
259
|
+
if (nestedWidgets.length > 0) {
|
|
260
|
+
data.widgets = nestedWidgets;
|
|
261
|
+
}
|
|
262
|
+
const yamlKey = toYamlKey(el.type);
|
|
263
|
+
return { [yamlKey]: stripUndefined(keysToSnakeCase(data)) };
|
|
264
|
+
}
|
|
265
|
+
function buildLvglSection(el) {
|
|
266
|
+
const { allProps, children } = extractElementProps(el);
|
|
267
|
+
const resolved = resolveLvglChildren(children);
|
|
268
|
+
const pages = [];
|
|
269
|
+
const topWidgets = [];
|
|
270
|
+
for (const child of resolved) {
|
|
271
|
+
if (child.type === "lvgl-page") {
|
|
272
|
+
const { allProps: pageProps, children: pageChildren } = extractElementProps(child);
|
|
273
|
+
const pageResolved = resolveLvglChildren(pageChildren);
|
|
274
|
+
const pageWidgets = pageResolved.filter((c) => isLvglElement(c.type)).map(lvglWidgetToPlain);
|
|
275
|
+
const pageData = { ...pageProps };
|
|
276
|
+
if (pageWidgets.length > 0) {
|
|
277
|
+
pageData.widgets = pageWidgets;
|
|
278
|
+
}
|
|
279
|
+
pages.push(stripUndefined(keysToSnakeCase(pageData)));
|
|
280
|
+
} else if (isLvglElement(child.type)) {
|
|
281
|
+
topWidgets.push(lvglWidgetToPlain(child));
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const data = { ...allProps };
|
|
285
|
+
if (pages.length > 0) data.pages = pages;
|
|
286
|
+
if (topWidgets.length > 0) data.widgets = topWidgets;
|
|
287
|
+
return stripUndefined(keysToSnakeCase(data));
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// src/runtime.ts
|
|
196
291
|
function createElement(type, props, ...children) {
|
|
197
292
|
const flatChildren = children.flat().filter((c) => c != null);
|
|
198
293
|
return {
|
|
@@ -203,11 +298,6 @@ function createElement(type, props, ...children) {
|
|
|
203
298
|
}
|
|
204
299
|
};
|
|
205
300
|
}
|
|
206
|
-
function Fragment(props) {
|
|
207
|
-
const { children } = props;
|
|
208
|
-
if (children == null) return null;
|
|
209
|
-
return Array.isArray(children) ? children : [children];
|
|
210
|
-
}
|
|
211
301
|
function toPlainObject(el) {
|
|
212
302
|
if (el == null) return void 0;
|
|
213
303
|
if (Array.isArray(el)) {
|
|
@@ -231,11 +321,18 @@ function toPlainObject(el) {
|
|
|
231
321
|
...childSections
|
|
232
322
|
};
|
|
233
323
|
}
|
|
324
|
+
if (el.type === "lvgl") {
|
|
325
|
+
const lvglData = buildLvglSection(el);
|
|
326
|
+
return { lvgl: Object.keys(lvglData).length > 0 ? lvglData : null };
|
|
327
|
+
}
|
|
328
|
+
if (isLvglElement(el.type)) {
|
|
329
|
+
return lvglWidgetToPlain(el);
|
|
330
|
+
}
|
|
234
331
|
const childData = buildChildData(
|
|
235
332
|
children
|
|
236
333
|
);
|
|
237
334
|
const data = stripUndefined(keysToSnakeCase({ ...allProps, ...childData }));
|
|
238
|
-
return { [el.type]: Object.keys(data).length > 0 ? data : null };
|
|
335
|
+
return { [toYamlKey(el.type)]: Object.keys(data).length > 0 ? data : null };
|
|
239
336
|
}
|
|
240
337
|
function childrenToTopLevelSections(children) {
|
|
241
338
|
if (!children) return {};
|
|
@@ -261,6 +358,12 @@ function childrenToTopLevelSections(children) {
|
|
|
261
358
|
return out;
|
|
262
359
|
}
|
|
263
360
|
function mergeSection(sections, child) {
|
|
361
|
+
if (child.type === "lvgl") {
|
|
362
|
+
const lvglData = buildLvglSection(child);
|
|
363
|
+
if (!sections["lvgl"]) sections["lvgl"] = [];
|
|
364
|
+
sections["lvgl"].push(Object.keys(lvglData).length > 0 ? lvglData : null);
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
264
367
|
const { children: grandchildren, ref, "x:custom": xCustom, ...ownProps } = child.props;
|
|
265
368
|
const propsWithId = ref != null ? { id: isRef(ref) ? ref.toString() : String(ref), ...ownProps } : ownProps;
|
|
266
369
|
const allProps = xCustom != null ? { ...propsWithId, ...xCustom } : propsWithId;
|
|
@@ -268,8 +371,9 @@ function mergeSection(sections, child) {
|
|
|
268
371
|
grandchildren
|
|
269
372
|
);
|
|
270
373
|
const data = stripUndefined(keysToSnakeCase({ ...allProps, ...childData }));
|
|
271
|
-
|
|
272
|
-
sections[
|
|
374
|
+
const yamlKey = toYamlKey(child.type);
|
|
375
|
+
if (!sections[yamlKey]) sections[yamlKey] = [];
|
|
376
|
+
sections[yamlKey].push(Object.keys(data).length > 0 ? data : null);
|
|
273
377
|
}
|
|
274
378
|
function buildChildData(children) {
|
|
275
379
|
if (!children) return {};
|
|
@@ -283,24 +387,6 @@ function buildChildData(children) {
|
|
|
283
387
|
}
|
|
284
388
|
return out;
|
|
285
389
|
}
|
|
286
|
-
function flattenFragments(elements) {
|
|
287
|
-
const out = [];
|
|
288
|
-
for (const el of elements) {
|
|
289
|
-
if (el.type === Fragment) {
|
|
290
|
-
const result = Fragment(el.props);
|
|
291
|
-
if (result != null) {
|
|
292
|
-
const nested = Array.isArray(result) ? result : [result];
|
|
293
|
-
out.push(...flattenFragments(nested));
|
|
294
|
-
}
|
|
295
|
-
} else {
|
|
296
|
-
out.push(el);
|
|
297
|
-
}
|
|
298
|
-
}
|
|
299
|
-
return out;
|
|
300
|
-
}
|
|
301
|
-
function stripUndefined(obj) {
|
|
302
|
-
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
303
|
-
}
|
|
304
390
|
function render(element) {
|
|
305
391
|
return toPlainObject(Array.isArray(element) ? element : element);
|
|
306
392
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -114,10 +114,16 @@ function useScript(metadata) {
|
|
|
114
114
|
return [{ "script.execute": metadata.id }];
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
// src/
|
|
117
|
+
// src/serialize.ts
|
|
118
118
|
function camelToSnake(key) {
|
|
119
119
|
return key.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
120
120
|
}
|
|
121
|
+
function toYamlKey(type) {
|
|
122
|
+
if (type.startsWith("lvgl-")) {
|
|
123
|
+
return type.slice(5).replace(/-/g, "_");
|
|
124
|
+
}
|
|
125
|
+
return type;
|
|
126
|
+
}
|
|
121
127
|
function keysToSnakeCase(obj) {
|
|
122
128
|
const out = {};
|
|
123
129
|
for (const [k, v] of Object.entries(obj)) {
|
|
@@ -133,6 +139,95 @@ function serializeValue(v) {
|
|
|
133
139
|
}
|
|
134
140
|
return v;
|
|
135
141
|
}
|
|
142
|
+
function stripUndefined(obj) {
|
|
143
|
+
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
144
|
+
}
|
|
145
|
+
function extractElementProps(el) {
|
|
146
|
+
const { children, ref, "x:custom": xCustom, ...ownProps } = el.props;
|
|
147
|
+
const propsWithId = ref != null ? { id: isRef(ref) ? ref.toString() : String(ref), ...ownProps } : ownProps;
|
|
148
|
+
const allProps = xCustom != null ? { ...propsWithId, ...xCustom } : propsWithId;
|
|
149
|
+
return { allProps, children };
|
|
150
|
+
}
|
|
151
|
+
function Fragment(props) {
|
|
152
|
+
const { children } = props;
|
|
153
|
+
if (children == null) return null;
|
|
154
|
+
return Array.isArray(children) ? children : [children];
|
|
155
|
+
}
|
|
156
|
+
function flattenFragments(elements) {
|
|
157
|
+
const out = [];
|
|
158
|
+
for (const el of elements) {
|
|
159
|
+
if (el.type === Fragment) {
|
|
160
|
+
const result = Fragment(el.props);
|
|
161
|
+
if (result != null) {
|
|
162
|
+
const nested = Array.isArray(result) ? result : [result];
|
|
163
|
+
out.push(...flattenFragments(nested));
|
|
164
|
+
}
|
|
165
|
+
} else {
|
|
166
|
+
out.push(el);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return out;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/lvgl.ts
|
|
173
|
+
function isLvglElement(type) {
|
|
174
|
+
return typeof type === "string" && type.startsWith("lvgl-");
|
|
175
|
+
}
|
|
176
|
+
function resolveLvglChildren(children) {
|
|
177
|
+
if (!children) return [];
|
|
178
|
+
const arr = Array.isArray(children) ? children : [children];
|
|
179
|
+
const flat = flattenFragments(arr);
|
|
180
|
+
const resolved = [];
|
|
181
|
+
for (const el of flat) {
|
|
182
|
+
if (typeof el.type === "function") {
|
|
183
|
+
const result = el.type(el.props);
|
|
184
|
+
if (result == null) continue;
|
|
185
|
+
const rendered = Array.isArray(result) ? result : [result];
|
|
186
|
+
resolved.push(...resolveLvglChildren(rendered));
|
|
187
|
+
} else {
|
|
188
|
+
resolved.push(el);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return resolved;
|
|
192
|
+
}
|
|
193
|
+
function lvglWidgetToPlain(el) {
|
|
194
|
+
const { allProps, children } = extractElementProps(el);
|
|
195
|
+
const widgetChildren = resolveLvglChildren(children);
|
|
196
|
+
const lvglChildren = widgetChildren.filter((c) => isLvglElement(c.type));
|
|
197
|
+
const nestedWidgets = lvglChildren.map(lvglWidgetToPlain);
|
|
198
|
+
const data = { ...allProps };
|
|
199
|
+
if (nestedWidgets.length > 0) {
|
|
200
|
+
data.widgets = nestedWidgets;
|
|
201
|
+
}
|
|
202
|
+
const yamlKey = toYamlKey(el.type);
|
|
203
|
+
return { [yamlKey]: stripUndefined(keysToSnakeCase(data)) };
|
|
204
|
+
}
|
|
205
|
+
function buildLvglSection(el) {
|
|
206
|
+
const { allProps, children } = extractElementProps(el);
|
|
207
|
+
const resolved = resolveLvglChildren(children);
|
|
208
|
+
const pages = [];
|
|
209
|
+
const topWidgets = [];
|
|
210
|
+
for (const child of resolved) {
|
|
211
|
+
if (child.type === "lvgl-page") {
|
|
212
|
+
const { allProps: pageProps, children: pageChildren } = extractElementProps(child);
|
|
213
|
+
const pageResolved = resolveLvglChildren(pageChildren);
|
|
214
|
+
const pageWidgets = pageResolved.filter((c) => isLvglElement(c.type)).map(lvglWidgetToPlain);
|
|
215
|
+
const pageData = { ...pageProps };
|
|
216
|
+
if (pageWidgets.length > 0) {
|
|
217
|
+
pageData.widgets = pageWidgets;
|
|
218
|
+
}
|
|
219
|
+
pages.push(stripUndefined(keysToSnakeCase(pageData)));
|
|
220
|
+
} else if (isLvglElement(child.type)) {
|
|
221
|
+
topWidgets.push(lvglWidgetToPlain(child));
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
const data = { ...allProps };
|
|
225
|
+
if (pages.length > 0) data.pages = pages;
|
|
226
|
+
if (topWidgets.length > 0) data.widgets = topWidgets;
|
|
227
|
+
return stripUndefined(keysToSnakeCase(data));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/runtime.ts
|
|
136
231
|
function createElement(type, props, ...children) {
|
|
137
232
|
const flatChildren = children.flat().filter((c) => c != null);
|
|
138
233
|
return {
|
|
@@ -143,11 +238,6 @@ function createElement(type, props, ...children) {
|
|
|
143
238
|
}
|
|
144
239
|
};
|
|
145
240
|
}
|
|
146
|
-
function Fragment(props) {
|
|
147
|
-
const { children } = props;
|
|
148
|
-
if (children == null) return null;
|
|
149
|
-
return Array.isArray(children) ? children : [children];
|
|
150
|
-
}
|
|
151
241
|
function toPlainObject(el) {
|
|
152
242
|
if (el == null) return void 0;
|
|
153
243
|
if (Array.isArray(el)) {
|
|
@@ -171,11 +261,18 @@ function toPlainObject(el) {
|
|
|
171
261
|
...childSections
|
|
172
262
|
};
|
|
173
263
|
}
|
|
264
|
+
if (el.type === "lvgl") {
|
|
265
|
+
const lvglData = buildLvglSection(el);
|
|
266
|
+
return { lvgl: Object.keys(lvglData).length > 0 ? lvglData : null };
|
|
267
|
+
}
|
|
268
|
+
if (isLvglElement(el.type)) {
|
|
269
|
+
return lvglWidgetToPlain(el);
|
|
270
|
+
}
|
|
174
271
|
const childData = buildChildData(
|
|
175
272
|
children
|
|
176
273
|
);
|
|
177
274
|
const data = stripUndefined(keysToSnakeCase({ ...allProps, ...childData }));
|
|
178
|
-
return { [el.type]: Object.keys(data).length > 0 ? data : null };
|
|
275
|
+
return { [toYamlKey(el.type)]: Object.keys(data).length > 0 ? data : null };
|
|
179
276
|
}
|
|
180
277
|
function childrenToTopLevelSections(children) {
|
|
181
278
|
if (!children) return {};
|
|
@@ -201,6 +298,12 @@ function childrenToTopLevelSections(children) {
|
|
|
201
298
|
return out;
|
|
202
299
|
}
|
|
203
300
|
function mergeSection(sections, child) {
|
|
301
|
+
if (child.type === "lvgl") {
|
|
302
|
+
const lvglData = buildLvglSection(child);
|
|
303
|
+
if (!sections["lvgl"]) sections["lvgl"] = [];
|
|
304
|
+
sections["lvgl"].push(Object.keys(lvglData).length > 0 ? lvglData : null);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
204
307
|
const { children: grandchildren, ref, "x:custom": xCustom, ...ownProps } = child.props;
|
|
205
308
|
const propsWithId = ref != null ? { id: isRef(ref) ? ref.toString() : String(ref), ...ownProps } : ownProps;
|
|
206
309
|
const allProps = xCustom != null ? { ...propsWithId, ...xCustom } : propsWithId;
|
|
@@ -208,8 +311,9 @@ function mergeSection(sections, child) {
|
|
|
208
311
|
grandchildren
|
|
209
312
|
);
|
|
210
313
|
const data = stripUndefined(keysToSnakeCase({ ...allProps, ...childData }));
|
|
211
|
-
|
|
212
|
-
sections[
|
|
314
|
+
const yamlKey = toYamlKey(child.type);
|
|
315
|
+
if (!sections[yamlKey]) sections[yamlKey] = [];
|
|
316
|
+
sections[yamlKey].push(Object.keys(data).length > 0 ? data : null);
|
|
213
317
|
}
|
|
214
318
|
function buildChildData(children) {
|
|
215
319
|
if (!children) return {};
|
|
@@ -223,24 +327,6 @@ function buildChildData(children) {
|
|
|
223
327
|
}
|
|
224
328
|
return out;
|
|
225
329
|
}
|
|
226
|
-
function flattenFragments(elements) {
|
|
227
|
-
const out = [];
|
|
228
|
-
for (const el of elements) {
|
|
229
|
-
if (el.type === Fragment) {
|
|
230
|
-
const result = Fragment(el.props);
|
|
231
|
-
if (result != null) {
|
|
232
|
-
const nested = Array.isArray(result) ? result : [result];
|
|
233
|
-
out.push(...flattenFragments(nested));
|
|
234
|
-
}
|
|
235
|
-
} else {
|
|
236
|
-
out.push(el);
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
return out;
|
|
240
|
-
}
|
|
241
|
-
function stripUndefined(obj) {
|
|
242
|
-
return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== void 0));
|
|
243
|
-
}
|
|
244
330
|
function render(element) {
|
|
245
331
|
return toPlainObject(Array.isArray(element) ? element : element);
|
|
246
332
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esphome/compose",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "ESPHome Compose SDK - TypeScript framework for generating ESPHome YAML",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,6 +25,14 @@
|
|
|
25
25
|
],
|
|
26
26
|
"author": "",
|
|
27
27
|
"license": "MIT",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/xmlguy74/espcompose.git",
|
|
31
|
+
"directory": "packages/sdk"
|
|
32
|
+
},
|
|
33
|
+
"engines": {
|
|
34
|
+
"node": ">=22"
|
|
35
|
+
},
|
|
28
36
|
"publishConfig": {
|
|
29
37
|
"access": "public"
|
|
30
38
|
},
|
|
@@ -32,10 +40,9 @@
|
|
|
32
40
|
"yaml": "^2.4.0"
|
|
33
41
|
},
|
|
34
42
|
"devDependencies": {
|
|
35
|
-
"
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
38
|
-
"eslint": "^8.0.0",
|
|
43
|
+
"typescript-eslint": "^8.0.0",
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"eslint": "^9.0.0",
|
|
39
46
|
"tsup": "^8.0.0",
|
|
40
47
|
"tsx": "^4.0.0",
|
|
41
48
|
"typescript": "^5.4.0"
|
|
@@ -43,8 +50,7 @@
|
|
|
43
50
|
"scripts": {
|
|
44
51
|
"build": "tsup src/index.ts --format cjs,esm --dts --tsconfig tsconfig.json",
|
|
45
52
|
"clean": "rimraf dist",
|
|
46
|
-
"
|
|
47
|
-
"lint": "eslint --ext .ts,.tsx src",
|
|
53
|
+
"lint": "eslint src",
|
|
48
54
|
"test": "echo \"No tests yet\""
|
|
49
55
|
}
|
|
50
56
|
}
|