@smartnet360/svelte-components 0.0.21 → 0.0.23
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/apps/antenna-pattern/utils/msi-parser.js +18 -1
- package/dist/cellular/CellularChartsView.svelte +293 -0
- package/dist/cellular/CellularChartsView.svelte.d.ts +7 -0
- package/dist/cellular/HierarchicalTree.svelte +469 -0
- package/dist/cellular/HierarchicalTree.svelte.d.ts +9 -0
- package/dist/cellular/SiteTree.svelte +286 -0
- package/dist/cellular/SiteTree.svelte.d.ts +11 -0
- package/dist/cellular/cellular-transforms.d.ts +25 -0
- package/dist/cellular/cellular-transforms.js +129 -0
- package/dist/cellular/cellular.model.d.ts +63 -0
- package/dist/cellular/cellular.model.js +6 -0
- package/dist/cellular/index.d.ts +11 -0
- package/dist/cellular/index.js +11 -0
- package/dist/cellular/mock-cellular-data.d.ts +13 -0
- package/dist/cellular/mock-cellular-data.js +241 -0
- package/dist/core/Charts/ChartCard.svelte +65 -16
- package/dist/core/Charts/ChartCard.svelte.d.ts +2 -0
- package/dist/core/Charts/ChartComponent.svelte +166 -34
- package/dist/core/Charts/ChartComponent.svelte.d.ts +1 -0
- package/dist/core/Charts/GlobalControls.svelte +188 -0
- package/dist/core/Charts/GlobalControls.svelte.d.ts +8 -0
- package/dist/core/Charts/charts.model.d.ts +7 -0
- package/dist/core/TreeChartView/TreeChartView.svelte +208 -0
- package/dist/core/TreeChartView/TreeChartView.svelte.d.ts +42 -0
- package/dist/core/TreeChartView/index.d.ts +7 -0
- package/dist/core/TreeChartView/index.js +7 -0
- package/dist/core/TreeView/TreeNode.svelte +173 -0
- package/dist/core/TreeView/TreeNode.svelte.d.ts +10 -0
- package/dist/core/TreeView/TreeView.svelte +163 -0
- package/dist/core/TreeView/TreeView.svelte.d.ts +10 -0
- package/dist/core/TreeView/index.d.ts +48 -0
- package/dist/core/TreeView/index.js +50 -0
- package/dist/core/TreeView/tree-utils.d.ts +56 -0
- package/dist/core/TreeView/tree-utils.js +194 -0
- package/dist/core/TreeView/tree.model.d.ts +104 -0
- package/dist/core/TreeView/tree.model.js +5 -0
- package/dist/core/TreeView/tree.store.d.ts +10 -0
- package/dist/core/TreeView/tree.store.js +225 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +4 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +3 -1
- package/package.json +1 -1
@@ -0,0 +1,469 @@
|
|
1
|
+
<svelte:options runes={true} />
|
2
|
+
|
3
|
+
<script lang="ts">
|
4
|
+
import type { CellularSite } from './cellular.model.js';
|
5
|
+
import { getBandColor, getBandLabel } from './mock-cellular-data.js';
|
6
|
+
|
7
|
+
interface Props {
|
8
|
+
sites: CellularSite[];
|
9
|
+
visibility: Map<string, boolean>; // Single flat map with keys like "site:a", "site:a:sec-1", "site:a:sec-1:700"
|
10
|
+
onVisibilityChange: (updates: Map<string, boolean>) => void;
|
11
|
+
}
|
12
|
+
|
13
|
+
let { sites, visibility, onVisibilityChange }: Props = $props();
|
14
|
+
|
15
|
+
// Track expanded/collapsed state for UI only
|
16
|
+
let expandedSites = $state<Set<string>>(new Set(sites.map(s => s.siteId)));
|
17
|
+
let expandedSectors = $state<Set<string>>(new Set());
|
18
|
+
|
19
|
+
function toggleSiteExpand(siteId: string) {
|
20
|
+
if (expandedSites.has(siteId)) {
|
21
|
+
expandedSites.delete(siteId);
|
22
|
+
} else {
|
23
|
+
expandedSites.add(siteId);
|
24
|
+
}
|
25
|
+
expandedSites = new Set(expandedSites);
|
26
|
+
}
|
27
|
+
|
28
|
+
function toggleSectorExpand(siteId: string, sectorId: string) {
|
29
|
+
const key = `${siteId}:${sectorId}`;
|
30
|
+
if (expandedSectors.has(key)) {
|
31
|
+
expandedSectors.delete(key);
|
32
|
+
} else {
|
33
|
+
expandedSectors.add(key);
|
34
|
+
}
|
35
|
+
expandedSectors = new Set(expandedSectors);
|
36
|
+
}
|
37
|
+
|
38
|
+
// Get keys for hierarchy
|
39
|
+
function getSiteKey(siteId: string): string {
|
40
|
+
return siteId;
|
41
|
+
}
|
42
|
+
|
43
|
+
function getSectorKey(siteId: string, sectorId: string): string {
|
44
|
+
return `${siteId}:${sectorId}`;
|
45
|
+
}
|
46
|
+
|
47
|
+
function getCellKey(siteId: string, sectorId: string, band: number): string {
|
48
|
+
return `${siteId}:${sectorId}:${band}`;
|
49
|
+
}
|
50
|
+
|
51
|
+
// Toggle handlers with cascading
|
52
|
+
function handleToggleSite(siteId: string) {
|
53
|
+
const site = sites.find(s => s.siteId === siteId);
|
54
|
+
if (!site) return;
|
55
|
+
|
56
|
+
const siteKey = getSiteKey(siteId);
|
57
|
+
const currentValue = visibility.get(siteKey) ?? true;
|
58
|
+
const newValue = !currentValue;
|
59
|
+
|
60
|
+
const updates = new Map<string, boolean>();
|
61
|
+
updates.set(siteKey, newValue);
|
62
|
+
|
63
|
+
// Cascade to all sectors and cells
|
64
|
+
site.sectors.forEach(sector => {
|
65
|
+
const sectorKey = getSectorKey(siteId, sector.sectorId);
|
66
|
+
updates.set(sectorKey, newValue);
|
67
|
+
|
68
|
+
sector.cells.forEach(cell => {
|
69
|
+
const cellKey = getCellKey(siteId, sector.sectorId, cell.band);
|
70
|
+
updates.set(cellKey, newValue);
|
71
|
+
});
|
72
|
+
});
|
73
|
+
|
74
|
+
onVisibilityChange(updates);
|
75
|
+
}
|
76
|
+
|
77
|
+
function handleToggleSector(siteId: string, sectorId: string) {
|
78
|
+
const site = sites.find(s => s.siteId === siteId);
|
79
|
+
const sector = site?.sectors.find(s => s.sectorId === sectorId);
|
80
|
+
if (!sector) return;
|
81
|
+
|
82
|
+
const sectorKey = getSectorKey(siteId, sectorId);
|
83
|
+
const currentValue = visibility.get(sectorKey) ?? true;
|
84
|
+
const newValue = !currentValue;
|
85
|
+
|
86
|
+
const updates = new Map<string, boolean>();
|
87
|
+
updates.set(sectorKey, newValue);
|
88
|
+
|
89
|
+
// Cascade to all cells
|
90
|
+
sector.cells.forEach(cell => {
|
91
|
+
const cellKey = getCellKey(siteId, sectorId, cell.band);
|
92
|
+
updates.set(cellKey, newValue);
|
93
|
+
});
|
94
|
+
|
95
|
+
// Update parent site visibility (if all sectors are off, turn off site)
|
96
|
+
if (!newValue && site) {
|
97
|
+
const allSectorsOff = site.sectors.every(s => {
|
98
|
+
const key = getSectorKey(siteId, s.sectorId);
|
99
|
+
return s.sectorId === sectorId ? false : !(visibility.get(key) ?? true);
|
100
|
+
});
|
101
|
+
if (allSectorsOff) {
|
102
|
+
updates.set(getSiteKey(siteId), false);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
// If turning on, ensure parent site is on
|
107
|
+
if (newValue) {
|
108
|
+
updates.set(getSiteKey(siteId), true);
|
109
|
+
}
|
110
|
+
|
111
|
+
onVisibilityChange(updates);
|
112
|
+
}
|
113
|
+
|
114
|
+
function handleToggleCell(siteId: string, sectorId: string, band: number) {
|
115
|
+
const site = sites.find(s => s.siteId === siteId);
|
116
|
+
const sector = site?.sectors.find(s => s.sectorId === sectorId);
|
117
|
+
if (!sector) return;
|
118
|
+
|
119
|
+
const cellKey = getCellKey(siteId, sectorId, band);
|
120
|
+
const currentValue = visibility.get(cellKey) ?? true;
|
121
|
+
const newValue = !currentValue;
|
122
|
+
|
123
|
+
const updates = new Map<string, boolean>();
|
124
|
+
updates.set(cellKey, newValue);
|
125
|
+
|
126
|
+
// Update parent sector visibility (if all cells are off, turn off sector)
|
127
|
+
if (!newValue) {
|
128
|
+
const allCellsOff = sector.cells.every(c => {
|
129
|
+
const key = getCellKey(siteId, sectorId, c.band);
|
130
|
+
return c.band === band ? false : !(visibility.get(key) ?? true);
|
131
|
+
});
|
132
|
+
if (allCellsOff) {
|
133
|
+
updates.set(getSectorKey(siteId, sectorId), false);
|
134
|
+
|
135
|
+
// Check if all sectors are off
|
136
|
+
if (site) {
|
137
|
+
const allSectorsOff = site.sectors.every(s => {
|
138
|
+
if (s.sectorId === sectorId) return false;
|
139
|
+
const key = getSectorKey(siteId, s.sectorId);
|
140
|
+
return !(visibility.get(key) ?? true);
|
141
|
+
});
|
142
|
+
if (allSectorsOff) {
|
143
|
+
updates.set(getSiteKey(siteId), false);
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}
|
148
|
+
|
149
|
+
// If turning on, ensure parents are on
|
150
|
+
if (newValue) {
|
151
|
+
updates.set(getSectorKey(siteId, sectorId), true);
|
152
|
+
updates.set(getSiteKey(siteId), true);
|
153
|
+
}
|
154
|
+
|
155
|
+
onVisibilityChange(updates);
|
156
|
+
}
|
157
|
+
|
158
|
+
// Determine checkbox state (checked, indeterminate)
|
159
|
+
function getSiteCheckState(site: CellularSite): { checked: boolean; indeterminate: boolean } {
|
160
|
+
const siteKey = getSiteKey(site.siteId);
|
161
|
+
const siteVisible = visibility.get(siteKey) ?? true;
|
162
|
+
|
163
|
+
if (!siteVisible) {
|
164
|
+
return { checked: false, indeterminate: false };
|
165
|
+
}
|
166
|
+
|
167
|
+
let visibleCount = 0;
|
168
|
+
let totalCount = 0;
|
169
|
+
|
170
|
+
site.sectors.forEach(sector => {
|
171
|
+
sector.cells.forEach(cell => {
|
172
|
+
totalCount++;
|
173
|
+
const cellKey = getCellKey(site.siteId, sector.sectorId, cell.band);
|
174
|
+
if (visibility.get(cellKey) ?? true) {
|
175
|
+
visibleCount++;
|
176
|
+
}
|
177
|
+
});
|
178
|
+
});
|
179
|
+
|
180
|
+
if (visibleCount === 0) {
|
181
|
+
return { checked: false, indeterminate: false };
|
182
|
+
} else if (visibleCount === totalCount) {
|
183
|
+
return { checked: true, indeterminate: false };
|
184
|
+
} else {
|
185
|
+
return { checked: true, indeterminate: true };
|
186
|
+
}
|
187
|
+
}
|
188
|
+
|
189
|
+
function getSectorCheckState(site: CellularSite, sector: any): { checked: boolean; indeterminate: boolean } {
|
190
|
+
const sectorKey = getSectorKey(site.siteId, sector.sectorId);
|
191
|
+
const sectorVisible = visibility.get(sectorKey) ?? true;
|
192
|
+
|
193
|
+
if (!sectorVisible) {
|
194
|
+
return { checked: false, indeterminate: false };
|
195
|
+
}
|
196
|
+
|
197
|
+
let visibleCount = 0;
|
198
|
+
const totalCount = sector.cells.length;
|
199
|
+
|
200
|
+
sector.cells.forEach((cell: any) => {
|
201
|
+
const cellKey = getCellKey(site.siteId, sector.sectorId, cell.band);
|
202
|
+
if (visibility.get(cellKey) ?? true) {
|
203
|
+
visibleCount++;
|
204
|
+
}
|
205
|
+
});
|
206
|
+
|
207
|
+
if (visibleCount === 0) {
|
208
|
+
return { checked: false, indeterminate: false };
|
209
|
+
} else if (visibleCount === totalCount) {
|
210
|
+
return { checked: true, indeterminate: false };
|
211
|
+
} else {
|
212
|
+
return { checked: true, indeterminate: true };
|
213
|
+
}
|
214
|
+
}
|
215
|
+
</script>
|
216
|
+
|
217
|
+
<div class="site-tree">
|
218
|
+
<div class="tree-header">
|
219
|
+
<h6 class="mb-0">Site Selection</h6>
|
220
|
+
<small class="text-muted">Click checkboxes to show/hide</small>
|
221
|
+
</div>
|
222
|
+
|
223
|
+
<div class="tree-content">
|
224
|
+
{#each sites as site}
|
225
|
+
{@const isSiteExpanded = expandedSites.has(site.siteId)}
|
226
|
+
{@const siteCheckState = getSiteCheckState(site)}
|
227
|
+
|
228
|
+
<div class="tree-site">
|
229
|
+
<!-- Site Level -->
|
230
|
+
<div class="tree-item site-item">
|
231
|
+
<button
|
232
|
+
class="expand-btn"
|
233
|
+
onclick={() => toggleSiteExpand(site.siteId)}
|
234
|
+
aria-label={isSiteExpanded ? 'Collapse' : 'Expand'}
|
235
|
+
>
|
236
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
237
|
+
{#if isSiteExpanded}
|
238
|
+
<path d="M2 4l4 4 4-4z" />
|
239
|
+
{:else}
|
240
|
+
<path d="M4 2l4 4-4 4z" />
|
241
|
+
{/if}
|
242
|
+
</svg>
|
243
|
+
</button>
|
244
|
+
|
245
|
+
<input
|
246
|
+
type="checkbox"
|
247
|
+
class="form-check-input"
|
248
|
+
class:indeterminate={siteCheckState.indeterminate}
|
249
|
+
id="site-{site.siteId}"
|
250
|
+
checked={siteCheckState.checked}
|
251
|
+
indeterminate={siteCheckState.indeterminate}
|
252
|
+
onchange={() => handleToggleSite(site.siteId)}
|
253
|
+
/>
|
254
|
+
|
255
|
+
<label class="tree-label" for="site-{site.siteId}">
|
256
|
+
<strong>{site.siteName}</strong>
|
257
|
+
</label>
|
258
|
+
</div>
|
259
|
+
|
260
|
+
<!-- Sectors -->
|
261
|
+
{#if isSiteExpanded}
|
262
|
+
<div class="tree-children">
|
263
|
+
{#each site.sectors as sector}
|
264
|
+
{@const sectorKey = getSectorKey(site.siteId, sector.sectorId)}
|
265
|
+
{@const isSectorExpanded = expandedSectors.has(sectorKey)}
|
266
|
+
{@const sectorCheckState = getSectorCheckState(site, sector)}
|
267
|
+
|
268
|
+
<div class="tree-sector">
|
269
|
+
<!-- Sector Level -->
|
270
|
+
<div class="tree-item sector-item">
|
271
|
+
<button
|
272
|
+
class="expand-btn"
|
273
|
+
onclick={() => toggleSectorExpand(site.siteId, sector.sectorId)}
|
274
|
+
aria-label={isSectorExpanded ? 'Collapse' : 'Expand'}
|
275
|
+
>
|
276
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
277
|
+
{#if isSectorExpanded}
|
278
|
+
<path d="M2 4l4 4 4-4z" />
|
279
|
+
{:else}
|
280
|
+
<path d="M4 2l4 4-4 4z" />
|
281
|
+
{/if}
|
282
|
+
</svg>
|
283
|
+
</button>
|
284
|
+
|
285
|
+
<input
|
286
|
+
type="checkbox"
|
287
|
+
class="form-check-input"
|
288
|
+
class:indeterminate={sectorCheckState.indeterminate}
|
289
|
+
id="sector-{sectorKey}"
|
290
|
+
checked={sectorCheckState.checked}
|
291
|
+
indeterminate={sectorCheckState.indeterminate}
|
292
|
+
onchange={() => handleToggleSector(site.siteId, sector.sectorId)}
|
293
|
+
/>
|
294
|
+
|
295
|
+
<label class="tree-label" for="sector-{sectorKey}">
|
296
|
+
{sector.sectorName}
|
297
|
+
</label>
|
298
|
+
</div>
|
299
|
+
|
300
|
+
<!-- Cells (Frequency Bands) -->
|
301
|
+
{#if isSectorExpanded}
|
302
|
+
<div class="tree-children">
|
303
|
+
{#each sector.cells as cell}
|
304
|
+
{@const cellKey = getCellKey(site.siteId, sector.sectorId, cell.band)}
|
305
|
+
{@const isCellVisible = visibility.get(cellKey) ?? true}
|
306
|
+
|
307
|
+
<div class="tree-item cell-item">
|
308
|
+
<span class="expand-spacer"></span>
|
309
|
+
|
310
|
+
<input
|
311
|
+
type="checkbox"
|
312
|
+
class="form-check-input"
|
313
|
+
id="cell-{cellKey}"
|
314
|
+
checked={isCellVisible}
|
315
|
+
onchange={() => handleToggleCell(site.siteId, sector.sectorId, cell.band)}
|
316
|
+
/>
|
317
|
+
|
318
|
+
<label class="tree-label" for="cell-{cellKey}">
|
319
|
+
<span
|
320
|
+
class="band-indicator"
|
321
|
+
style:background-color={getBandColor(cell.band)}
|
322
|
+
></span>
|
323
|
+
{getBandLabel(cell.band)}
|
324
|
+
</label>
|
325
|
+
</div>
|
326
|
+
{/each}
|
327
|
+
</div>
|
328
|
+
{/if}
|
329
|
+
</div>
|
330
|
+
{/each}
|
331
|
+
</div>
|
332
|
+
{/if}
|
333
|
+
</div>
|
334
|
+
{/each}
|
335
|
+
</div>
|
336
|
+
</div>
|
337
|
+
|
338
|
+
<style>
|
339
|
+
.site-tree {
|
340
|
+
height: 100%;
|
341
|
+
display: flex;
|
342
|
+
flex-direction: column;
|
343
|
+
background-color: #f8f9fa;
|
344
|
+
border-right: 1px solid #dee2e6;
|
345
|
+
}
|
346
|
+
|
347
|
+
.tree-header {
|
348
|
+
padding: 0.75rem 1rem;
|
349
|
+
border-bottom: 1px solid #dee2e6;
|
350
|
+
background-color: #fff;
|
351
|
+
}
|
352
|
+
|
353
|
+
.tree-header small {
|
354
|
+
display: block;
|
355
|
+
margin-top: 0.25rem;
|
356
|
+
font-size: 0.75rem;
|
357
|
+
}
|
358
|
+
|
359
|
+
.tree-content {
|
360
|
+
flex: 1;
|
361
|
+
overflow-y: auto;
|
362
|
+
padding: 0.5rem;
|
363
|
+
}
|
364
|
+
|
365
|
+
.tree-site {
|
366
|
+
margin-bottom: 0.5rem;
|
367
|
+
}
|
368
|
+
|
369
|
+
.tree-item {
|
370
|
+
display: flex;
|
371
|
+
align-items: center;
|
372
|
+
gap: 0.5rem;
|
373
|
+
padding: 0.375rem 0.5rem;
|
374
|
+
border-radius: 0.25rem;
|
375
|
+
transition: background-color 0.15s ease;
|
376
|
+
}
|
377
|
+
|
378
|
+
.tree-item:hover {
|
379
|
+
background-color: rgba(0, 0, 0, 0.05);
|
380
|
+
}
|
381
|
+
|
382
|
+
.site-item {
|
383
|
+
font-size: 0.95rem;
|
384
|
+
}
|
385
|
+
|
386
|
+
.sector-item {
|
387
|
+
font-size: 0.9rem;
|
388
|
+
margin-left: 1rem;
|
389
|
+
}
|
390
|
+
|
391
|
+
.cell-item {
|
392
|
+
font-size: 0.85rem;
|
393
|
+
margin-left: 2rem;
|
394
|
+
}
|
395
|
+
|
396
|
+
.expand-btn {
|
397
|
+
background: none;
|
398
|
+
border: none;
|
399
|
+
padding: 0;
|
400
|
+
width: 1rem;
|
401
|
+
height: 1rem;
|
402
|
+
display: flex;
|
403
|
+
align-items: center;
|
404
|
+
justify-content: center;
|
405
|
+
cursor: pointer;
|
406
|
+
color: #6c757d;
|
407
|
+
flex-shrink: 0;
|
408
|
+
}
|
409
|
+
|
410
|
+
.expand-btn:hover {
|
411
|
+
color: #495057;
|
412
|
+
}
|
413
|
+
|
414
|
+
.expand-spacer {
|
415
|
+
width: 1rem;
|
416
|
+
flex-shrink: 0;
|
417
|
+
}
|
418
|
+
|
419
|
+
.form-check-input {
|
420
|
+
cursor: pointer;
|
421
|
+
flex-shrink: 0;
|
422
|
+
margin: 0;
|
423
|
+
}
|
424
|
+
|
425
|
+
.form-check-input.indeterminate {
|
426
|
+
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e");
|
427
|
+
}
|
428
|
+
|
429
|
+
.tree-label {
|
430
|
+
cursor: pointer;
|
431
|
+
margin: 0;
|
432
|
+
user-select: none;
|
433
|
+
flex: 1;
|
434
|
+
display: flex;
|
435
|
+
align-items: center;
|
436
|
+
gap: 0.5rem;
|
437
|
+
}
|
438
|
+
|
439
|
+
.tree-children {
|
440
|
+
margin-top: 0.25rem;
|
441
|
+
}
|
442
|
+
|
443
|
+
.band-indicator {
|
444
|
+
display: inline-block;
|
445
|
+
width: 0.75rem;
|
446
|
+
height: 0.75rem;
|
447
|
+
border-radius: 50%;
|
448
|
+
flex-shrink: 0;
|
449
|
+
border: 1px solid rgba(0, 0, 0, 0.1);
|
450
|
+
}
|
451
|
+
|
452
|
+
/* Scrollbar styling */
|
453
|
+
.tree-content::-webkit-scrollbar {
|
454
|
+
width: 0.5rem;
|
455
|
+
}
|
456
|
+
|
457
|
+
.tree-content::-webkit-scrollbar-track {
|
458
|
+
background: #f1f1f1;
|
459
|
+
}
|
460
|
+
|
461
|
+
.tree-content::-webkit-scrollbar-thumb {
|
462
|
+
background: #888;
|
463
|
+
border-radius: 0.25rem;
|
464
|
+
}
|
465
|
+
|
466
|
+
.tree-content::-webkit-scrollbar-thumb:hover {
|
467
|
+
background: #555;
|
468
|
+
}
|
469
|
+
</style>
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import type { CellularSite } from './cellular.model.js';
|
2
|
+
interface Props {
|
3
|
+
sites: CellularSite[];
|
4
|
+
visibility: Map<string, boolean>;
|
5
|
+
onVisibilityChange: (updates: Map<string, boolean>) => void;
|
6
|
+
}
|
7
|
+
declare const HierarchicalTree: import("svelte").Component<Props, {}, "">;
|
8
|
+
type HierarchicalTree = ReturnType<typeof HierarchicalTree>;
|
9
|
+
export default HierarchicalTree;
|