@saltcorn/builder 0.9.4-beta.2 → 0.9.4-beta.21
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/builder_bundle.js +23 -11
- package/package.json +3 -1
- package/src/components/Builder.js +92 -66
- package/src/components/Library.js +46 -22
- package/src/components/RenderNode.js +5 -0
- package/src/components/Toolbox.js +173 -165
- package/src/components/elements/Action.js +12 -0
- package/src/components/elements/Aggregation.js +193 -104
- package/src/components/elements/BoxModelEditor.js +8 -8
- package/src/components/elements/Column.js +16 -2
- package/src/components/elements/Columns.js +4 -4
- package/src/components/elements/Container.js +26 -2
- package/src/components/elements/DropMenu.js +31 -2
- package/src/components/elements/Field.js +22 -20
- package/src/components/elements/HTMLCode.js +1 -1
- package/src/components/elements/JoinField.js +25 -1
- package/src/components/elements/Link.js +1 -0
- package/src/components/elements/ListColumn.js +177 -0
- package/src/components/elements/ListColumns.js +62 -0
- package/src/components/elements/RelationBadges.js +53 -44
- package/src/components/elements/Tabs.js +118 -40
- package/src/components/elements/Text.js +4 -2
- package/src/components/elements/View.js +125 -68
- package/src/components/elements/ViewLink.js +100 -48
- package/src/components/elements/utils.js +198 -98
- package/src/components/storage.js +67 -5
- package/tests/relations_finder.test.js +58 -92
- package/tests/test_data.js +0 -163
|
@@ -9,6 +9,7 @@ import { useNode } from "@craftjs/core";
|
|
|
9
9
|
import optionsCtx from "../context";
|
|
10
10
|
import previewCtx from "../preview_context";
|
|
11
11
|
import relationsCtx from "../relations_context";
|
|
12
|
+
import Select from "react-select";
|
|
12
13
|
|
|
13
14
|
import {
|
|
14
15
|
fetchViewPreview,
|
|
@@ -17,13 +18,18 @@ import {
|
|
|
17
18
|
buildOptions,
|
|
18
19
|
HelpTopicLink,
|
|
19
20
|
initialRelation,
|
|
20
|
-
|
|
21
|
-
updateRelationsCache,
|
|
21
|
+
buildLayers,
|
|
22
22
|
} from "./utils";
|
|
23
23
|
|
|
24
24
|
import { RelationBadges } from "./RelationBadges";
|
|
25
25
|
import { RelationOnDemandPicker } from "./RelationOnDemandPicker";
|
|
26
26
|
|
|
27
|
+
import {
|
|
28
|
+
RelationsFinder,
|
|
29
|
+
Relation,
|
|
30
|
+
buildTableCaches,
|
|
31
|
+
} from "@saltcorn/common-code";
|
|
32
|
+
|
|
27
33
|
export /**
|
|
28
34
|
* @param {object} props
|
|
29
35
|
* @param {*} props.name
|
|
@@ -48,7 +54,6 @@ const View = ({ name, view, configuration, state }) => {
|
|
|
48
54
|
if (rest.startsWith(".")) viewname = prefix;
|
|
49
55
|
else viewname = rest;
|
|
50
56
|
}
|
|
51
|
-
|
|
52
57
|
const theview = options.views.find((v) => v.name === viewname);
|
|
53
58
|
const label = theview ? theview.label : view;
|
|
54
59
|
const { previews, setPreviews } = React.useContext(previewCtx);
|
|
@@ -109,10 +114,23 @@ const ViewSettings = () => {
|
|
|
109
114
|
extra_state_fml,
|
|
110
115
|
} = node;
|
|
111
116
|
const options = React.useContext(optionsCtx);
|
|
112
|
-
const {
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
117
|
+
const {
|
|
118
|
+
tables,
|
|
119
|
+
views,
|
|
120
|
+
max_relations_layer_depth,
|
|
121
|
+
tableName,
|
|
122
|
+
excluded_subview_templates,
|
|
123
|
+
} = options;
|
|
124
|
+
// not needed in page editor
|
|
125
|
+
let finder = null;
|
|
126
|
+
let tableCaches = null;
|
|
127
|
+
if (tables && views) {
|
|
128
|
+
finder = useMemo(
|
|
129
|
+
() => new RelationsFinder(tables, views, max_relations_layer_depth),
|
|
130
|
+
[undefined]
|
|
131
|
+
);
|
|
132
|
+
tableCaches = useMemo(() => buildTableCaches(tables), [undefined]);
|
|
133
|
+
}
|
|
116
134
|
const fixed_state_fields =
|
|
117
135
|
options.fixed_state_fields && options.fixed_state_fields[view];
|
|
118
136
|
const { setPreviews } = React.useContext(previewCtx);
|
|
@@ -134,87 +152,118 @@ const ViewSettings = () => {
|
|
|
134
152
|
if (rest.startsWith(".")) viewname = prefix;
|
|
135
153
|
else viewname = rest;
|
|
136
154
|
}
|
|
137
|
-
if (viewname.includes(".")) viewname = viewname.split(".")[0];
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
155
|
+
if (viewname && viewname.includes(".")) viewname = viewname.split(".")[0];
|
|
156
|
+
|
|
157
|
+
if (
|
|
158
|
+
finder &&
|
|
159
|
+
!(relationsCache[tableName] && relationsCache[tableName][viewname])
|
|
160
|
+
) {
|
|
161
|
+
const relations = finder.findRelations(
|
|
162
|
+
tableName,
|
|
163
|
+
viewname,
|
|
164
|
+
excluded_subview_templates
|
|
145
165
|
);
|
|
146
|
-
|
|
147
|
-
|
|
166
|
+
const layers = buildLayers(
|
|
167
|
+
relations,
|
|
168
|
+
tableName,
|
|
169
|
+
tableCaches.tableNameCache
|
|
170
|
+
);
|
|
171
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
172
|
+
relationsCache[tableName][viewname] = { relations, layers };
|
|
173
|
+
setRelationsCache({ ...relationsCache });
|
|
174
|
+
}
|
|
175
|
+
const [relationsData, setRelationsData] = finder
|
|
176
|
+
? React.useState(relationsCache[tableName][viewname])
|
|
148
177
|
: [undefined, undefined];
|
|
149
|
-
let safeRelation =
|
|
178
|
+
let safeRelation = null;
|
|
179
|
+
const subView = views.find((view) => view.name === viewname);
|
|
180
|
+
if (relation && subView) {
|
|
181
|
+
const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
|
|
182
|
+
safeRelation = new Relation(
|
|
183
|
+
relation,
|
|
184
|
+
subTbl ? subTbl.name : "",
|
|
185
|
+
subView.display_type
|
|
186
|
+
);
|
|
187
|
+
}
|
|
150
188
|
if (
|
|
151
189
|
options.mode !== "filter" &&
|
|
190
|
+
subView?.table_id &&
|
|
152
191
|
!safeRelation &&
|
|
153
192
|
!hasLegacyRelation &&
|
|
154
|
-
relations
|
|
193
|
+
relationsData?.relations.length > 0
|
|
155
194
|
) {
|
|
156
|
-
safeRelation = initialRelation(relations
|
|
195
|
+
safeRelation = initialRelation(relationsData.relations);
|
|
157
196
|
setProp((prop) => {
|
|
158
|
-
prop.relation = safeRelation;
|
|
197
|
+
prop.relation = safeRelation.relationString;
|
|
159
198
|
});
|
|
160
199
|
}
|
|
161
200
|
const helpContext = { view_name: viewname };
|
|
162
201
|
if (options.tableName) helpContext.srcTable = options.tableName;
|
|
163
202
|
const set_view_name = (e) => {
|
|
164
|
-
if (e
|
|
165
|
-
const target_value = e.target.value;
|
|
203
|
+
if (e?.target?.value || e?.value) {
|
|
204
|
+
const target_value = e.target?.value || e.value;
|
|
166
205
|
if (target_value !== viewname) {
|
|
167
206
|
if (options.mode === "filter") {
|
|
168
207
|
setProp((prop) => {
|
|
169
208
|
prop.view = target_value;
|
|
170
209
|
});
|
|
171
210
|
} else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
211
|
+
const newRelations = finder.findRelations(
|
|
212
|
+
tableName,
|
|
213
|
+
target_value,
|
|
214
|
+
excluded_subview_templates
|
|
215
|
+
);
|
|
216
|
+
const layers = buildLayers(
|
|
217
|
+
newRelations,
|
|
218
|
+
tableName,
|
|
219
|
+
tableCaches.tableNameCache
|
|
178
220
|
);
|
|
179
|
-
|
|
180
|
-
|
|
221
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
222
|
+
relationsCache[tableName][target_value] = {
|
|
223
|
+
relations: newRelations,
|
|
224
|
+
layers,
|
|
225
|
+
};
|
|
226
|
+
if (newRelations.length > 0) {
|
|
181
227
|
setProp((prop) => {
|
|
182
228
|
prop.view = target_value;
|
|
183
|
-
prop.relation = initialRelation(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
229
|
+
prop.relation = initialRelation(newRelations).relationString;
|
|
230
|
+
});
|
|
231
|
+
setRelationsData({ relations: newRelations, layers });
|
|
232
|
+
} else
|
|
233
|
+
window.notifyAlert({
|
|
234
|
+
type: "warning",
|
|
235
|
+
text: `${target_value} has no relations`,
|
|
187
236
|
});
|
|
188
|
-
setRelations(newRelations);
|
|
189
|
-
}
|
|
190
237
|
}
|
|
191
238
|
}
|
|
192
239
|
}
|
|
193
240
|
};
|
|
194
|
-
|
|
241
|
+
const viewOptions = options.views.map(({ name, label }) => ({
|
|
242
|
+
label,
|
|
243
|
+
value: name,
|
|
244
|
+
}));
|
|
245
|
+
const selectedView = viewOptions.find((v) => v.value === viewname);
|
|
195
246
|
return (
|
|
196
247
|
<div>
|
|
197
|
-
{
|
|
248
|
+
{relationsData ? (
|
|
198
249
|
<Fragment>
|
|
199
250
|
<div>
|
|
200
251
|
<label>View to {options.mode === "show" ? "embed" : "show"}</label>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
))}
|
|
212
|
-
</select>
|
|
252
|
+
{options.inJestTestingMode ? null : (
|
|
253
|
+
<Select
|
|
254
|
+
options={viewOptions}
|
|
255
|
+
value={selectedView}
|
|
256
|
+
onChange={set_view_name}
|
|
257
|
+
onBlur={set_view_name}
|
|
258
|
+
menuPortalTarget={document.body}
|
|
259
|
+
styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
|
|
260
|
+
></Select>
|
|
261
|
+
)}
|
|
213
262
|
</div>
|
|
214
263
|
{options.mode !== "filter" && (
|
|
215
264
|
<div>
|
|
216
265
|
<RelationOnDemandPicker
|
|
217
|
-
relations={
|
|
266
|
+
relations={relationsData.layers}
|
|
218
267
|
update={(relPath) => {
|
|
219
268
|
if (relPath.startsWith(".")) {
|
|
220
269
|
setProp((prop) => {
|
|
@@ -232,8 +281,8 @@ const ViewSettings = () => {
|
|
|
232
281
|
<RelationBadges
|
|
233
282
|
view={view}
|
|
234
283
|
relation={safeRelation}
|
|
235
|
-
parentTbl={
|
|
236
|
-
|
|
284
|
+
parentTbl={tableName}
|
|
285
|
+
caches={tableCaches}
|
|
237
286
|
/>
|
|
238
287
|
</div>
|
|
239
288
|
)}
|
|
@@ -241,18 +290,26 @@ const ViewSettings = () => {
|
|
|
241
290
|
) : (
|
|
242
291
|
<div>
|
|
243
292
|
<label>View to {options.mode === "show" ? "embed" : "show"}</label>
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
293
|
+
{options.inJestTestingMode ? null : (
|
|
294
|
+
<Select
|
|
295
|
+
options={viewOptions}
|
|
296
|
+
value={selectedView}
|
|
297
|
+
onChange={(e) => {
|
|
298
|
+
const target_value = e?.target?.value || e?.value;
|
|
299
|
+
setProp((prop) => {
|
|
300
|
+
prop.view = target_value;
|
|
301
|
+
});
|
|
302
|
+
}}
|
|
303
|
+
onBlur={(e) => {
|
|
304
|
+
const target_value = e?.target?.value || e?.value;
|
|
305
|
+
setProp((prop) => {
|
|
306
|
+
prop.view = target_value;
|
|
307
|
+
});
|
|
308
|
+
}}
|
|
309
|
+
menuPortalTarget={document.body}
|
|
310
|
+
styles={{ menuPortal: (base) => ({ ...base, zIndex: 19999 }) }}
|
|
311
|
+
></Select>
|
|
312
|
+
)}
|
|
256
313
|
</div>
|
|
257
314
|
)}
|
|
258
315
|
{options.mode !== "edit" && (
|
|
@@ -293,7 +350,7 @@ const ViewSettings = () => {
|
|
|
293
350
|
)}
|
|
294
351
|
</Fragment>
|
|
295
352
|
)}
|
|
296
|
-
{
|
|
353
|
+
{
|
|
297
354
|
<Fragment>
|
|
298
355
|
<label>
|
|
299
356
|
Extra state Formula
|
|
@@ -311,7 +368,7 @@ const ViewSettings = () => {
|
|
|
311
368
|
</small>
|
|
312
369
|
) : null}
|
|
313
370
|
</Fragment>
|
|
314
|
-
|
|
371
|
+
}
|
|
315
372
|
{view ? (
|
|
316
373
|
<a
|
|
317
374
|
className="d-block mt-2"
|
|
@@ -18,13 +18,19 @@ import {
|
|
|
18
18
|
setAPropGen,
|
|
19
19
|
FormulaTooltip,
|
|
20
20
|
HelpTopicLink,
|
|
21
|
-
prepCacheAndFinder,
|
|
22
21
|
initialRelation,
|
|
23
|
-
|
|
22
|
+
buildLayers,
|
|
24
23
|
} from "./utils";
|
|
25
24
|
|
|
26
25
|
import { RelationBadges } from "./RelationBadges";
|
|
27
26
|
import { RelationOnDemandPicker } from "./RelationOnDemandPicker";
|
|
27
|
+
import Select from "react-select";
|
|
28
|
+
|
|
29
|
+
import {
|
|
30
|
+
RelationsFinder,
|
|
31
|
+
Relation,
|
|
32
|
+
buildTableCaches,
|
|
33
|
+
} from "@saltcorn/common-code";
|
|
28
34
|
|
|
29
35
|
export /**
|
|
30
36
|
* @param {object} props
|
|
@@ -127,10 +133,18 @@ const ViewLinkSettings = () => {
|
|
|
127
133
|
link_target_blank,
|
|
128
134
|
} = node;
|
|
129
135
|
const options = React.useContext(optionsCtx);
|
|
130
|
-
const {
|
|
131
|
-
|
|
136
|
+
const {
|
|
137
|
+
tables,
|
|
138
|
+
views,
|
|
139
|
+
tableName,
|
|
140
|
+
excluded_subview_templates,
|
|
141
|
+
max_relations_layer_depth,
|
|
142
|
+
} = options;
|
|
143
|
+
const finder = useMemo(
|
|
144
|
+
() => new RelationsFinder(tables, views, max_relations_layer_depth),
|
|
132
145
|
[undefined]
|
|
133
146
|
);
|
|
147
|
+
const tableCaches = useMemo(() => buildTableCaches(tables), [undefined]);
|
|
134
148
|
const { relationsCache, setRelationsCache } = React.useContext(relationsCtx);
|
|
135
149
|
let errorString = false;
|
|
136
150
|
try {
|
|
@@ -147,50 +161,87 @@ const ViewLinkSettings = () => {
|
|
|
147
161
|
const safeViewName = use_view_name?.includes(".")
|
|
148
162
|
? use_view_name.split(".")[0]
|
|
149
163
|
: use_view_name;
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
const subView = views.find((view) => view.name === safeViewName);
|
|
165
|
+
const hasTableId = subView?.table_id !== undefined;
|
|
166
|
+
if (!(relationsCache[tableName] && relationsCache[tableName][safeViewName])) {
|
|
167
|
+
const relations = finder.findRelations(
|
|
168
|
+
tableName,
|
|
169
|
+
safeViewName,
|
|
170
|
+
excluded_subview_templates
|
|
171
|
+
);
|
|
172
|
+
const layers = buildLayers(
|
|
173
|
+
relations,
|
|
174
|
+
tableName,
|
|
175
|
+
tableCaches.tableNameCache
|
|
176
|
+
);
|
|
177
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
178
|
+
relationsCache[tableName][safeViewName] = { relations, layers };
|
|
179
|
+
setRelationsCache({ ...relationsCache });
|
|
180
|
+
}
|
|
181
|
+
const [relationsData, setRelationsData] = React.useState(
|
|
158
182
|
relationsCache[options.tableName][safeViewName]
|
|
159
183
|
);
|
|
160
|
-
let safeRelation =
|
|
161
|
-
if (
|
|
162
|
-
|
|
184
|
+
let safeRelation = null;
|
|
185
|
+
if (relation) {
|
|
186
|
+
const subView = views.find((view) => view.name === safeViewName);
|
|
187
|
+
const subTbl = tables.find((tbl) => tbl.id === subView.table_id);
|
|
188
|
+
safeRelation = new Relation(
|
|
189
|
+
relation,
|
|
190
|
+
subTbl ? subTbl.name : "",
|
|
191
|
+
subView.display_type
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
!safeRelation &&
|
|
196
|
+
!hasLegacyRelation &&
|
|
197
|
+
relationsData?.relations.length > 0
|
|
198
|
+
) {
|
|
199
|
+
safeRelation = initialRelation(relationsData.relations);
|
|
163
200
|
setProp((prop) => {
|
|
164
|
-
prop.relation = safeRelation;
|
|
201
|
+
prop.relation = safeRelation.relationString;
|
|
165
202
|
});
|
|
166
203
|
}
|
|
167
204
|
const set_view_name = (e) => {
|
|
168
|
-
if (e
|
|
169
|
-
const target_value = e.target.value;
|
|
205
|
+
if (e?.target?.value || e?.value) {
|
|
206
|
+
const target_value = e.target?.value || e.value;
|
|
170
207
|
if (target_value !== use_view_name) {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
finder,
|
|
176
|
-
target_value
|
|
208
|
+
const newRelations = finder.findRelations(
|
|
209
|
+
tableName,
|
|
210
|
+
target_value,
|
|
211
|
+
excluded_subview_templates
|
|
177
212
|
);
|
|
178
|
-
const
|
|
179
|
-
|
|
213
|
+
const layers = buildLayers(
|
|
214
|
+
newRelations,
|
|
215
|
+
tableName,
|
|
216
|
+
tableCaches.tableNameCache
|
|
217
|
+
);
|
|
218
|
+
|
|
219
|
+
relationsCache[tableName] = relationsCache[tableName] || {};
|
|
220
|
+
relationsCache[tableName][target_value] = {
|
|
221
|
+
relations: newRelations,
|
|
222
|
+
layers,
|
|
223
|
+
};
|
|
224
|
+
if (newRelations.length > 0) {
|
|
180
225
|
setProp((prop) => {
|
|
181
226
|
prop.name = target_value;
|
|
182
|
-
prop.relation = initialRelation(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
227
|
+
prop.relation = initialRelation(newRelations).relationString;
|
|
228
|
+
});
|
|
229
|
+
setRelationsData({ relations: newRelations, layers });
|
|
230
|
+
} else
|
|
231
|
+
window.notifyAlert({
|
|
232
|
+
type: "warning",
|
|
233
|
+
text: `${target_value} has no relations`,
|
|
186
234
|
});
|
|
187
|
-
setRelations(newRelations);
|
|
188
|
-
}
|
|
189
235
|
}
|
|
190
236
|
}
|
|
191
237
|
};
|
|
192
238
|
const helpContext = { view_name: use_view_name };
|
|
193
|
-
if (
|
|
239
|
+
if (tableName) helpContext.srcTable = tableName;
|
|
240
|
+
const viewOptions = options.views.map(({ name, label }) => ({
|
|
241
|
+
label,
|
|
242
|
+
value: name,
|
|
243
|
+
}));
|
|
244
|
+
const selectedView = viewOptions.find((v) => v.value === use_view_name);
|
|
194
245
|
return (
|
|
195
246
|
<div>
|
|
196
247
|
<table className="w-100">
|
|
@@ -198,24 +249,24 @@ const ViewLinkSettings = () => {
|
|
|
198
249
|
<tr>
|
|
199
250
|
<td colSpan="2">
|
|
200
251
|
<label>View to link to</label>
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
{
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
252
|
+
{options.inJestTestingMode ? null : (
|
|
253
|
+
<Select
|
|
254
|
+
options={viewOptions}
|
|
255
|
+
value={selectedView}
|
|
256
|
+
onChange={set_view_name}
|
|
257
|
+
onBlur={set_view_name}
|
|
258
|
+
menuPortalTarget={document.body}
|
|
259
|
+
styles={{
|
|
260
|
+
menuPortal: (base) => ({ ...base, zIndex: 19999 }),
|
|
261
|
+
}}
|
|
262
|
+
></Select>
|
|
263
|
+
)}
|
|
213
264
|
</td>
|
|
214
265
|
</tr>
|
|
215
266
|
<tr>
|
|
216
267
|
<td colSpan="2">
|
|
217
268
|
<RelationOnDemandPicker
|
|
218
|
-
relations={
|
|
269
|
+
relations={relationsData.layers}
|
|
219
270
|
update={(relPath) => {
|
|
220
271
|
if (relPath.startsWith(".")) {
|
|
221
272
|
setProp((prop) => {
|
|
@@ -233,8 +284,8 @@ const ViewLinkSettings = () => {
|
|
|
233
284
|
<RelationBadges
|
|
234
285
|
view={name}
|
|
235
286
|
relation={safeRelation}
|
|
236
|
-
parentTbl={
|
|
237
|
-
|
|
287
|
+
parentTbl={tableName}
|
|
288
|
+
caches={tableCaches}
|
|
238
289
|
/>
|
|
239
290
|
</td>
|
|
240
291
|
</tr>
|
|
@@ -278,6 +329,7 @@ const ViewLinkSettings = () => {
|
|
|
278
329
|
values={node}
|
|
279
330
|
linkFirst={true}
|
|
280
331
|
linkIsBlank={true}
|
|
332
|
+
allowRunOnLoad={false}
|
|
281
333
|
/>
|
|
282
334
|
</tbody>
|
|
283
335
|
</table>
|