wunderbaum 0.12.1 → 0.14.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/LICENSE +1 -1
- package/dist/wunderbaum.css +40 -11
- package/dist/wunderbaum.css.map +1 -1
- package/dist/wunderbaum.d.ts +648 -306
- package/dist/wunderbaum.esm.js +920 -356
- package/dist/wunderbaum.esm.min.js +27 -27
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +920 -356
- package/dist/wunderbaum.umd.min.js +31 -31
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +3 -2
- package/src/common.ts +49 -6
- package/src/types.ts +182 -33
- package/src/util.ts +75 -15
- package/src/wb_ext_dnd.ts +21 -20
- package/src/wb_ext_edit.ts +2 -2
- package/src/wb_ext_filter.ts +122 -43
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_extension_base.ts +19 -3
- package/src/wb_node.ts +239 -195
- package/src/wb_options.ts +172 -117
- package/src/wunderbaum.scss +9 -1
- package/src/wunderbaum.ts +574 -126
package/src/wb_node.ts
CHANGED
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
ApplyCommandType,
|
|
14
14
|
ChangeType,
|
|
15
15
|
CheckboxOption,
|
|
16
|
-
ColumnDefinition,
|
|
17
16
|
ColumnEventInfoMap,
|
|
18
17
|
ExpandAllOptions,
|
|
19
18
|
IconOption,
|
|
@@ -21,6 +20,7 @@ import {
|
|
|
21
20
|
MakeVisibleOptions,
|
|
22
21
|
MatcherCallback,
|
|
23
22
|
NavigateOptions,
|
|
23
|
+
NavigationType,
|
|
24
24
|
NodeAnyCallback,
|
|
25
25
|
NodeStatusType,
|
|
26
26
|
NodeStringCallback,
|
|
@@ -36,7 +36,7 @@ import {
|
|
|
36
36
|
SetStatusOptions,
|
|
37
37
|
SortByPropertyOptions,
|
|
38
38
|
SortCallback,
|
|
39
|
-
|
|
39
|
+
SortOptions,
|
|
40
40
|
SourceType,
|
|
41
41
|
TooltipOption,
|
|
42
42
|
TristateType,
|
|
@@ -46,11 +46,13 @@ import {
|
|
|
46
46
|
import {
|
|
47
47
|
decompressSourceData,
|
|
48
48
|
ICON_WIDTH,
|
|
49
|
-
|
|
49
|
+
KEY_TO_NAVIGATION_MAP,
|
|
50
50
|
makeNodeTitleMatcher,
|
|
51
|
+
NODE_TYPE_FOLDER,
|
|
51
52
|
nodeTitleSorter,
|
|
52
53
|
RESERVED_TREE_SOURCE_KEYS,
|
|
53
|
-
|
|
54
|
+
TEST_FILE_PATH,
|
|
55
|
+
TEST_HTML,
|
|
54
56
|
TITLE_SPAN_PAD_Y,
|
|
55
57
|
} from "./common";
|
|
56
58
|
import { Deferred } from "./deferred";
|
|
@@ -171,7 +173,11 @@ export class WunderbaumNode {
|
|
|
171
173
|
_partsel = false;
|
|
172
174
|
_partload = false;
|
|
173
175
|
// --- FILTER ---
|
|
174
|
-
|
|
176
|
+
/**
|
|
177
|
+
* > 0 if matched (-1 to keep system nodes visible);
|
|
178
|
+
* Added and removed by filter code.
|
|
179
|
+
*/
|
|
180
|
+
public match?: number;
|
|
175
181
|
public subMatchCount?: number = 0;
|
|
176
182
|
// public subMatchBadge?: HTMLElement;
|
|
177
183
|
/** @internal */
|
|
@@ -181,13 +187,13 @@ export class WunderbaumNode {
|
|
|
181
187
|
_rowIdx: number | undefined = 0;
|
|
182
188
|
_rowElem: HTMLDivElement | undefined = undefined;
|
|
183
189
|
|
|
184
|
-
constructor(tree: Wunderbaum, parent: WunderbaumNode, data:
|
|
190
|
+
constructor(tree: Wunderbaum, parent: WunderbaumNode, data: WbNodeData) {
|
|
185
191
|
util.assert(!parent || parent.tree === tree, `Invalid parent: ${parent}`);
|
|
186
192
|
util.assert(!data.children, "'children' not allowed here");
|
|
187
193
|
|
|
188
194
|
this.tree = tree;
|
|
189
195
|
this.parent = parent;
|
|
190
|
-
this.key =
|
|
196
|
+
this.key = tree._calculateKey(data, parent);
|
|
191
197
|
this.title = "" + (data.title ?? "<" + this.key + ">");
|
|
192
198
|
this.expanded = !!data.expanded;
|
|
193
199
|
this.lazy = !!data.lazy;
|
|
@@ -327,12 +333,19 @@ export class WunderbaumNode {
|
|
|
327
333
|
nodeData = [<WbNodeData>nodeData];
|
|
328
334
|
}
|
|
329
335
|
const forceExpand =
|
|
330
|
-
applyMinExpanLevel && _level < tree.options.minExpandLevel
|
|
336
|
+
applyMinExpanLevel && _level < tree.options.minExpandLevel;
|
|
331
337
|
for (const child of <WbNodeData[]>nodeData) {
|
|
332
338
|
const subChildren = child.children;
|
|
339
|
+
// Remove children property from source data because it should not be
|
|
340
|
+
// passed to the constructor of WunderbaumNode:
|
|
333
341
|
delete child.children;
|
|
334
342
|
|
|
335
343
|
const n = new WunderbaumNode(tree, this, child);
|
|
344
|
+
|
|
345
|
+
// Set `children` property again, so it can be used in `reload()`
|
|
346
|
+
if (subChildren != null) {
|
|
347
|
+
child.children = subChildren;
|
|
348
|
+
}
|
|
336
349
|
if (forceExpand && !n.isUnloaded()) {
|
|
337
350
|
n.expanded = true;
|
|
338
351
|
}
|
|
@@ -656,7 +669,7 @@ export class WunderbaumNode {
|
|
|
656
669
|
*
|
|
657
670
|
* @see {@link Wunderbaum.findRelatedNode|tree.findRelatedNode()}
|
|
658
671
|
*/
|
|
659
|
-
findRelatedNode(where:
|
|
672
|
+
findRelatedNode(where: NavigationType, includeHidden = false) {
|
|
660
673
|
return this.tree.findRelatedNode(this, where, includeHidden);
|
|
661
674
|
}
|
|
662
675
|
|
|
@@ -799,7 +812,7 @@ export class WunderbaumNode {
|
|
|
799
812
|
}
|
|
800
813
|
return l;
|
|
801
814
|
}
|
|
802
|
-
/** Return a string representing the
|
|
815
|
+
/** Return a string representing the hierarchical node path, e.g. "a/b/c".
|
|
803
816
|
* @param includeSelf
|
|
804
817
|
* @param part property name or callback
|
|
805
818
|
* @param separator
|
|
@@ -809,10 +822,6 @@ export class WunderbaumNode {
|
|
|
809
822
|
part: keyof WunderbaumNode | NodeAnyCallback = "title",
|
|
810
823
|
separator: string = "/"
|
|
811
824
|
) {
|
|
812
|
-
// includeSelf = includeSelf !== false;
|
|
813
|
-
// part = part || "title";
|
|
814
|
-
// separator = separator || "/";
|
|
815
|
-
|
|
816
825
|
let val;
|
|
817
826
|
const path: string[] = [];
|
|
818
827
|
const isFunc = typeof part === "function";
|
|
@@ -829,7 +838,7 @@ export class WunderbaumNode {
|
|
|
829
838
|
return path.join(separator);
|
|
830
839
|
}
|
|
831
840
|
|
|
832
|
-
/** Return the
|
|
841
|
+
/** Return the preceding node (under the same parent) or null. */
|
|
833
842
|
getPrevSibling(): WunderbaumNode | null {
|
|
834
843
|
const ac = this.parent.children!;
|
|
835
844
|
const idx = ac.indexOf(this);
|
|
@@ -861,7 +870,7 @@ export class WunderbaumNode {
|
|
|
861
870
|
return this.classes ? this.classes.has(className) : false;
|
|
862
871
|
}
|
|
863
872
|
|
|
864
|
-
/** Return true if node
|
|
873
|
+
/** Return true if node is the currently focused node. @since 0.9.0 */
|
|
865
874
|
hasFocus(): boolean {
|
|
866
875
|
return this.tree.focusNode === this;
|
|
867
876
|
}
|
|
@@ -922,7 +931,7 @@ export class WunderbaumNode {
|
|
|
922
931
|
* an expand operation is currently possible.
|
|
923
932
|
*/
|
|
924
933
|
isExpandable(andCollapsed = false): boolean {
|
|
925
|
-
// `false` is never expandable (
|
|
934
|
+
// `false` is never expandable (unofficial)
|
|
926
935
|
if ((andCollapsed && this.expanded) || <any>this.children === false) {
|
|
927
936
|
return false;
|
|
928
937
|
}
|
|
@@ -987,7 +996,7 @@ export class WunderbaumNode {
|
|
|
987
996
|
return other && other.parent === this;
|
|
988
997
|
}
|
|
989
998
|
|
|
990
|
-
/**
|
|
999
|
+
/** Return true if this node is partially loaded. @experimental */
|
|
991
1000
|
isPartload(): boolean {
|
|
992
1001
|
return !!this._partload;
|
|
993
1002
|
}
|
|
@@ -997,12 +1006,12 @@ export class WunderbaumNode {
|
|
|
997
1006
|
return !this.selected && !!this._partsel;
|
|
998
1007
|
}
|
|
999
1008
|
|
|
1000
|
-
/** Return true if this node has DOM
|
|
1009
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
1001
1010
|
isRadio(): boolean {
|
|
1002
1011
|
return !!this.parent.radiogroup || this.getOption("checkbox") === "radio";
|
|
1003
1012
|
}
|
|
1004
1013
|
|
|
1005
|
-
/** Return true if this node has DOM
|
|
1014
|
+
/** Return true if this node has DOM representation, i.e. is displayed in the viewport. */
|
|
1006
1015
|
isRendered(): boolean {
|
|
1007
1016
|
return !!this._rowElem;
|
|
1008
1017
|
}
|
|
@@ -1509,12 +1518,12 @@ export class WunderbaumNode {
|
|
|
1509
1518
|
* e.g. `ArrowLeft` = 'left'.
|
|
1510
1519
|
* @param options
|
|
1511
1520
|
*/
|
|
1512
|
-
async navigate(where: string, options?: NavigateOptions) {
|
|
1521
|
+
async navigate(where: NavigationType | string, options?: NavigateOptions) {
|
|
1513
1522
|
// Allow to pass 'ArrowLeft' instead of 'left'
|
|
1514
|
-
|
|
1523
|
+
const navType = (KEY_TO_NAVIGATION_MAP[where] ?? where) as NavigationType;
|
|
1515
1524
|
|
|
1516
1525
|
// Otherwise activate or focus the related node
|
|
1517
|
-
const node = this.findRelatedNode(
|
|
1526
|
+
const node = this.findRelatedNode(navType);
|
|
1518
1527
|
if (!node) {
|
|
1519
1528
|
this.logWarn(`Could not find related node '${where}'.`);
|
|
1520
1529
|
return Promise.resolve(this);
|
|
@@ -1618,91 +1627,19 @@ export class WunderbaumNode {
|
|
|
1618
1627
|
}
|
|
1619
1628
|
|
|
1620
1629
|
protected _createIcon(
|
|
1621
|
-
iconMap: any,
|
|
1622
1630
|
parentElem: HTMLElement,
|
|
1623
1631
|
replaceChild: HTMLElement | null,
|
|
1624
1632
|
showLoading: boolean
|
|
1625
1633
|
): HTMLElement | null {
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
} else if (this._isLoading && showLoading) {
|
|
1631
|
-
// Status nodes, or nodes without expander (< minExpandLevel) should
|
|
1632
|
-
// display the 'loading' status with the i.wb-icon span
|
|
1633
|
-
icon = iconMap.loading;
|
|
1634
|
-
}
|
|
1635
|
-
if (icon === false) {
|
|
1636
|
-
return null; // explicitly disabled: don't try default icons
|
|
1637
|
-
}
|
|
1638
|
-
if (typeof icon === "string") {
|
|
1639
|
-
// Callback returned an icon definition
|
|
1640
|
-
// icon = icon.trim()
|
|
1641
|
-
} else if (this.statusNodeType) {
|
|
1642
|
-
icon = (<any>iconMap)[this.statusNodeType];
|
|
1643
|
-
} else if (this.expanded) {
|
|
1644
|
-
icon = iconMap.folderOpen;
|
|
1645
|
-
} else if (this.children) {
|
|
1646
|
-
icon = iconMap.folder;
|
|
1647
|
-
} else if (this.lazy) {
|
|
1648
|
-
icon = iconMap.folderLazy;
|
|
1649
|
-
} else {
|
|
1650
|
-
icon = iconMap.doc;
|
|
1651
|
-
}
|
|
1652
|
-
|
|
1653
|
-
// this.log("_createIcon: " + icon);
|
|
1654
|
-
if (!icon) {
|
|
1655
|
-
iconSpan = document.createElement("i");
|
|
1656
|
-
iconSpan.className = "wb-icon";
|
|
1657
|
-
} else if (icon.indexOf("<") >= 0) {
|
|
1658
|
-
// HTML
|
|
1659
|
-
iconSpan = util.elemFromHtml(icon);
|
|
1660
|
-
} else if (TEST_IMG.test(icon)) {
|
|
1661
|
-
// Image URL
|
|
1662
|
-
iconSpan = util.elemFromHtml(
|
|
1663
|
-
`<i class="wb-icon" style="background-image: url('${icon}');">`
|
|
1664
|
-
);
|
|
1665
|
-
} else {
|
|
1666
|
-
// Class name
|
|
1667
|
-
iconSpan = document.createElement("i");
|
|
1668
|
-
iconSpan.className = "wb-icon " + icon;
|
|
1669
|
-
}
|
|
1670
|
-
if (replaceChild) {
|
|
1671
|
-
parentElem.replaceChild(iconSpan, replaceChild);
|
|
1672
|
-
} else {
|
|
1673
|
-
parentElem.appendChild(iconSpan);
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
// Event handler `tree.iconBadge` can return a badge text or HTMLSpanElement
|
|
1677
|
-
|
|
1678
|
-
const cbRes = this._callEvent("iconBadge", { iconSpan: iconSpan });
|
|
1679
|
-
let badge = null;
|
|
1680
|
-
if (cbRes != null && cbRes !== false) {
|
|
1681
|
-
let classes = "";
|
|
1682
|
-
let tooltip = "";
|
|
1683
|
-
if (util.isPlainObject(cbRes)) {
|
|
1684
|
-
badge = "" + cbRes.badge;
|
|
1685
|
-
classes = cbRes.badgeClass ? " " + cbRes.badgeClass : "";
|
|
1686
|
-
tooltip = cbRes.badgeTooltip ? ` title="${cbRes.badgeTooltip}"` : "";
|
|
1687
|
-
} else if (typeof cbRes === "number") {
|
|
1688
|
-
badge = "" + cbRes;
|
|
1634
|
+
const iconElem = this.tree._createNodeIcon(this, showLoading, true);
|
|
1635
|
+
if (iconElem) {
|
|
1636
|
+
if (replaceChild) {
|
|
1637
|
+
parentElem.replaceChild(iconElem, replaceChild);
|
|
1689
1638
|
} else {
|
|
1690
|
-
|
|
1691
|
-
}
|
|
1692
|
-
if (typeof badge === "string") {
|
|
1693
|
-
badge = util.elemFromHtml(
|
|
1694
|
-
`<span class="wb-badge${classes}"${tooltip}>${util.escapeHtml(
|
|
1695
|
-
badge
|
|
1696
|
-
)}</span>`
|
|
1697
|
-
);
|
|
1698
|
-
}
|
|
1699
|
-
if (badge) {
|
|
1700
|
-
iconSpan.append(<HTMLSpanElement>badge);
|
|
1639
|
+
parentElem.appendChild(iconElem);
|
|
1701
1640
|
}
|
|
1702
1641
|
}
|
|
1703
|
-
|
|
1704
|
-
// this.log("_createIcon: ", iconSpan);
|
|
1705
|
-
return iconSpan;
|
|
1642
|
+
return iconElem;
|
|
1706
1643
|
}
|
|
1707
1644
|
|
|
1708
1645
|
/**
|
|
@@ -1712,7 +1649,7 @@ export class WunderbaumNode {
|
|
|
1712
1649
|
protected _render_markup(opts: RenderOptions) {
|
|
1713
1650
|
const tree = this.tree;
|
|
1714
1651
|
const treeOptions = tree.options;
|
|
1715
|
-
const rowHeight = treeOptions.rowHeightPx
|
|
1652
|
+
const rowHeight = treeOptions.rowHeightPx;
|
|
1716
1653
|
const checkbox = this.getOption("checkbox");
|
|
1717
1654
|
const columns = tree.columns;
|
|
1718
1655
|
const level = this.getLevel();
|
|
@@ -1773,12 +1710,7 @@ export class WunderbaumNode {
|
|
|
1773
1710
|
|
|
1774
1711
|
// Render the icon (show a 'loading' icon if we do not have an expander that
|
|
1775
1712
|
// we would prefer).
|
|
1776
|
-
const iconSpan = this._createIcon(
|
|
1777
|
-
tree.iconMap,
|
|
1778
|
-
nodeElem,
|
|
1779
|
-
null,
|
|
1780
|
-
!expanderSpan
|
|
1781
|
-
);
|
|
1713
|
+
const iconSpan = this._createIcon(nodeElem, null, !expanderSpan);
|
|
1782
1714
|
if (iconSpan) {
|
|
1783
1715
|
ofsTitlePx += ICON_WIDTH;
|
|
1784
1716
|
}
|
|
@@ -1938,11 +1870,11 @@ export class WunderbaumNode {
|
|
|
1938
1870
|
const rowDiv = this._rowElem!;
|
|
1939
1871
|
|
|
1940
1872
|
// Row markup already exists
|
|
1941
|
-
const
|
|
1942
|
-
const
|
|
1873
|
+
const nodeSpan = rowDiv.querySelector("span.wb-node") as HTMLSpanElement;
|
|
1874
|
+
const expanderElem = nodeSpan.querySelector(
|
|
1943
1875
|
"i.wb-expander"
|
|
1944
1876
|
) as HTMLLIElement;
|
|
1945
|
-
const
|
|
1877
|
+
const checkboxElem = nodeSpan.querySelector(
|
|
1946
1878
|
"i.wb-checkbox"
|
|
1947
1879
|
) as HTMLLIElement;
|
|
1948
1880
|
|
|
@@ -1975,7 +1907,7 @@ export class WunderbaumNode {
|
|
|
1975
1907
|
rowDiv.classList.add(...typeInfo.classes);
|
|
1976
1908
|
}
|
|
1977
1909
|
|
|
1978
|
-
if (
|
|
1910
|
+
if (expanderElem) {
|
|
1979
1911
|
let image = null;
|
|
1980
1912
|
if (this._isLoading) {
|
|
1981
1913
|
image = iconMap.loading;
|
|
@@ -1990,14 +1922,17 @@ export class WunderbaumNode {
|
|
|
1990
1922
|
}
|
|
1991
1923
|
|
|
1992
1924
|
if (image == null) {
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1925
|
+
expanderElem.className = "wb-expander";
|
|
1926
|
+
expanderElem.classList.add("wb-indent");
|
|
1927
|
+
} else if (TEST_HTML.test(image)) {
|
|
1928
|
+
expanderElem.replaceWith(util.elemFromHtml(image));
|
|
1929
|
+
} else if (TEST_FILE_PATH.test(image)) {
|
|
1930
|
+
expanderElem.style.backgroundImage = `url('${image}')`;
|
|
1996
1931
|
} else {
|
|
1997
|
-
|
|
1932
|
+
expanderElem.className = "wb-expander " + image;
|
|
1998
1933
|
}
|
|
1999
1934
|
}
|
|
2000
|
-
if (
|
|
1935
|
+
if (checkboxElem) {
|
|
2001
1936
|
let cbclass = "wb-checkbox ";
|
|
2002
1937
|
if (this.isRadio()) {
|
|
2003
1938
|
cbclass += "wb-radio ";
|
|
@@ -2017,7 +1952,7 @@ export class WunderbaumNode {
|
|
|
2017
1952
|
cbclass += iconMap.checkUnchecked;
|
|
2018
1953
|
}
|
|
2019
1954
|
}
|
|
2020
|
-
|
|
1955
|
+
checkboxElem.className = cbclass;
|
|
2021
1956
|
}
|
|
2022
1957
|
// Fix active cell in cell-nav mode
|
|
2023
1958
|
if (!opts.isNew) {
|
|
@@ -2027,9 +1962,9 @@ export class WunderbaumNode {
|
|
|
2027
1962
|
colSpan.classList.remove("wb-error", "wb-invalid");
|
|
2028
1963
|
}
|
|
2029
1964
|
// Update icon (if not opts.isNew, which would rebuild markup anyway)
|
|
2030
|
-
const iconSpan =
|
|
1965
|
+
const iconSpan = nodeSpan.querySelector("i.wb-icon") as HTMLElement;
|
|
2031
1966
|
if (iconSpan) {
|
|
2032
|
-
this._createIcon(
|
|
1967
|
+
this._createIcon(nodeSpan, iconSpan, !expanderElem);
|
|
2033
1968
|
}
|
|
2034
1969
|
}
|
|
2035
1970
|
// Adjust column width
|
|
@@ -2289,6 +2224,7 @@ export class WunderbaumNode {
|
|
|
2289
2224
|
async setExpanded(flag: boolean = true, options?: SetExpandedOptions) {
|
|
2290
2225
|
const { force, scrollIntoView, immediate, resetLazy } = options ?? {};
|
|
2291
2226
|
const sendEvents = !options?.noEvents; // Default: send events
|
|
2227
|
+
|
|
2292
2228
|
if (
|
|
2293
2229
|
!flag &&
|
|
2294
2230
|
this.isExpanded() &&
|
|
@@ -2355,6 +2291,32 @@ export class WunderbaumNode {
|
|
|
2355
2291
|
setKey(key: string | null, refKey: string | null) {
|
|
2356
2292
|
throw new Error("Not yet implemented");
|
|
2357
2293
|
}
|
|
2294
|
+
// /**
|
|
2295
|
+
// * Calculate a *stable*, unique key for this node from its refKey (or title).
|
|
2296
|
+
// * We also add information from the parent, because a refKey may occur multiple
|
|
2297
|
+
// * times in a tree.
|
|
2298
|
+
// */
|
|
2299
|
+
// calcUniqueKey() {
|
|
2300
|
+
// // Assuming that the parent's key was calculated the same way, we implicitly
|
|
2301
|
+
// // involve the whole refKey-path:
|
|
2302
|
+
// const s = this.key + (this.refKey || this.title);
|
|
2303
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
2304
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
2305
|
+
// const h1 = util.murmurHash3(s, true);
|
|
2306
|
+
// return "id_" + h1 + util.murmurHash3(h1 + s, true);
|
|
2307
|
+
// // const l = [];
|
|
2308
|
+
// // // eslint-disable-next-line @typescript-eslint/no-this-alias
|
|
2309
|
+
// // let node: WunderbaumNode = this;
|
|
2310
|
+
// // while (node.parent) {
|
|
2311
|
+
// // l.unshift(node.refKey || node.key);
|
|
2312
|
+
// // node = node.parent;
|
|
2313
|
+
// // }
|
|
2314
|
+
// // const path = l.join("/");
|
|
2315
|
+
// // 32-bit has a high probability of collisions, so we pump up to 64-bit
|
|
2316
|
+
// // https://security.stackexchange.com/q/209882/207588
|
|
2317
|
+
// // const h1 = util.murmurHash3(path, true);
|
|
2318
|
+
// // return "id_" + h1 + util.murmurHash3(h1 + path, true);
|
|
2319
|
+
// }
|
|
2358
2320
|
|
|
2359
2321
|
/**
|
|
2360
2322
|
* Trigger a repaint, typically after a status or data change.
|
|
@@ -2392,6 +2354,24 @@ export class WunderbaumNode {
|
|
|
2392
2354
|
return nodeList;
|
|
2393
2355
|
}
|
|
2394
2356
|
|
|
2357
|
+
/**
|
|
2358
|
+
* Return an array of refKey values.
|
|
2359
|
+
*
|
|
2360
|
+
* RefKeys are unique identifiers for a node data, and are used to identify
|
|
2361
|
+
* clones.
|
|
2362
|
+
* If more than one node has the same refKey, it is only returned once.
|
|
2363
|
+
* @param selected if true, only return refKeys of selected nodes.
|
|
2364
|
+
*/
|
|
2365
|
+
getRefKeys(selected = false): string[] {
|
|
2366
|
+
const refKeys = new Set<string>();
|
|
2367
|
+
this.visit((node) => {
|
|
2368
|
+
if (node.refKey != null && (!selected || node.selected)) {
|
|
2369
|
+
refKeys.add(node.refKey);
|
|
2370
|
+
}
|
|
2371
|
+
});
|
|
2372
|
+
return Array.from(refKeys);
|
|
2373
|
+
}
|
|
2374
|
+
|
|
2395
2375
|
/** Toggle the check/uncheck state. */
|
|
2396
2376
|
toggleSelected(options?: SetSelectedOptions): TristateType {
|
|
2397
2377
|
let flag = this.isSelected();
|
|
@@ -2593,9 +2573,11 @@ export class WunderbaumNode {
|
|
|
2593
2573
|
this.selected = flag;
|
|
2594
2574
|
if (selectMode === "hier") {
|
|
2595
2575
|
this.fixSelection3AfterClick();
|
|
2596
|
-
} else if (selectMode === "single") {
|
|
2576
|
+
} else if (selectMode === "single" && flag) {
|
|
2597
2577
|
tree.visit((n) => {
|
|
2598
|
-
n
|
|
2578
|
+
if (n !== this) {
|
|
2579
|
+
n.selected = false;
|
|
2580
|
+
}
|
|
2599
2581
|
});
|
|
2600
2582
|
}
|
|
2601
2583
|
}
|
|
@@ -2641,7 +2623,7 @@ export class WunderbaumNode {
|
|
|
2641
2623
|
);
|
|
2642
2624
|
|
|
2643
2625
|
statusNode = this.addNode(data, "prependChild");
|
|
2644
|
-
statusNode.match =
|
|
2626
|
+
statusNode.match = -1; // Mark as 'match' to avoid hiding
|
|
2645
2627
|
tree.update(ChangeType.structure);
|
|
2646
2628
|
|
|
2647
2629
|
return statusNode;
|
|
@@ -2718,35 +2700,19 @@ export class WunderbaumNode {
|
|
|
2718
2700
|
this.update();
|
|
2719
2701
|
}
|
|
2720
2702
|
|
|
2721
|
-
_sortChildren(cmp: SortCallback, deep: boolean): void {
|
|
2722
|
-
const cl = this.children;
|
|
2723
|
-
|
|
2724
|
-
if (!cl) {
|
|
2725
|
-
return;
|
|
2726
|
-
}
|
|
2727
|
-
cl.sort(cmp);
|
|
2728
|
-
if (deep) {
|
|
2729
|
-
for (let i = 0, l = cl.length; i < l; i++) {
|
|
2730
|
-
if (cl[i].children) {
|
|
2731
|
-
cl[i]._sortChildren(cmp, deep);
|
|
2732
|
-
}
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
}
|
|
2736
|
-
|
|
2737
2703
|
/**
|
|
2738
2704
|
* Sort child list by title or custom criteria.
|
|
2739
2705
|
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
2740
2706
|
* (defaults to sorting by title).
|
|
2741
2707
|
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
2708
|
+
* @deprecated use {@link sort}
|
|
2742
2709
|
*/
|
|
2743
2710
|
sortChildren(
|
|
2744
2711
|
cmp: SortCallback | null = nodeTitleSorter,
|
|
2745
2712
|
deep: boolean = false
|
|
2746
2713
|
): void {
|
|
2747
|
-
this.
|
|
2748
|
-
this.
|
|
2749
|
-
// this.triggerModify("sort"); // TODO
|
|
2714
|
+
this.tree.logDeprecate("node.sortChildren()", { since: "0.14.0" });
|
|
2715
|
+
return this.sort({ cmp: cmp ? cmp : undefined, deep: deep });
|
|
2750
2716
|
}
|
|
2751
2717
|
|
|
2752
2718
|
/**
|
|
@@ -2771,82 +2737,159 @@ export class WunderbaumNode {
|
|
|
2771
2737
|
/**
|
|
2772
2738
|
* Convenience method to implement column sorting.
|
|
2773
2739
|
* @since 0.11.0
|
|
2740
|
+
* @deprecated use {@link sort}
|
|
2774
2741
|
*/
|
|
2775
2742
|
sortByProperty(options: SortByPropertyOptions) {
|
|
2776
|
-
|
|
2777
|
-
|
|
2743
|
+
this.tree.logDeprecate("node.sortByProperty()", { since: "0.14.0" });
|
|
2744
|
+
return this.sort(options);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2747
|
+
/**
|
|
2748
|
+
* Implement column sorting.
|
|
2749
|
+
* @since 0.14.0
|
|
2750
|
+
*/
|
|
2751
|
+
sort(options: SortOptions) {
|
|
2752
|
+
const tree = this.tree;
|
|
2753
|
+
let {
|
|
2754
|
+
propName = undefined,
|
|
2778
2755
|
deep = true,
|
|
2779
|
-
|
|
2756
|
+
key = undefined,
|
|
2757
|
+
order = undefined,
|
|
2758
|
+
caseInsensitive = true,
|
|
2759
|
+
cmp = undefined,
|
|
2760
|
+
// Support click on column sort header:
|
|
2780
2761
|
updateColInfo = false,
|
|
2762
|
+
nativeOrderPropName = "_nativeIndex",
|
|
2763
|
+
colId = undefined,
|
|
2781
2764
|
} = options;
|
|
2782
2765
|
|
|
2783
|
-
|
|
2784
|
-
|
|
2766
|
+
propName ??= colId;
|
|
2767
|
+
if (propName === "*") {
|
|
2768
|
+
propName = "title";
|
|
2769
|
+
}
|
|
2770
|
+
|
|
2771
|
+
const isFolder =
|
|
2772
|
+
tree.options.sortFoldersFirst === true
|
|
2773
|
+
? (node: WunderbaumNode) =>
|
|
2774
|
+
node.hasChildren() !== false || node.type === NODE_TYPE_FOLDER
|
|
2775
|
+
: tree.options.sortFoldersFirst;
|
|
2785
2776
|
|
|
2786
2777
|
if (updateColInfo) {
|
|
2787
|
-
colDef = this.tree["_columnsById"][options.colId!];
|
|
2778
|
+
const colDef = this.tree["_columnsById"][options.colId!];
|
|
2788
2779
|
util.assert(colDef, `Invalid colId specified: ${options.colId}`);
|
|
2789
|
-
order
|
|
2790
|
-
options.order ??
|
|
2791
|
-
util.rotate(colDef!.sortOrder, ["asc", "desc", undefined]);
|
|
2780
|
+
order ??= util.rotate(colDef.sortOrder, ["asc", "desc", undefined]);
|
|
2792
2781
|
|
|
2793
2782
|
for (const col of this.tree.columns) {
|
|
2794
2783
|
col.sortOrder = col === colDef ? order : undefined;
|
|
2795
2784
|
}
|
|
2796
|
-
|
|
2785
|
+
if (order === undefined) {
|
|
2786
|
+
propName = nativeOrderPropName;
|
|
2787
|
+
order = "asc";
|
|
2788
|
+
}
|
|
2797
2789
|
this.tree.update(ChangeType.colStructure);
|
|
2798
2790
|
} else {
|
|
2799
|
-
|
|
2791
|
+
propName ??= "title";
|
|
2792
|
+
order ??= "asc";
|
|
2800
2793
|
}
|
|
2801
2794
|
|
|
2802
|
-
|
|
2803
|
-
|
|
2804
|
-
propName = "title";
|
|
2805
|
-
}
|
|
2806
|
-
if (order == null) {
|
|
2807
|
-
propName = nativeOrderPropName;
|
|
2808
|
-
order = "asc";
|
|
2809
|
-
}
|
|
2810
|
-
this.logDebug(`sortByProperty(), propName=${propName}, ${order}`, options);
|
|
2811
|
-
util.assert(propName, "No property name specified");
|
|
2795
|
+
this.logDebug(`sort(), propName=${propName}, ${order}`, options);
|
|
2796
|
+
util.assert(propName || cmp || key, "No `propName` or `key` specified");
|
|
2812
2797
|
|
|
2813
|
-
|
|
2814
|
-
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
2818
|
-
|
|
2819
|
-
|
|
2820
|
-
|
|
2821
|
-
|
|
2822
|
-
|
|
2823
|
-
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
|
|
2829
|
-
|
|
2830
|
-
|
|
2831
|
-
|
|
2832
|
-
|
|
2833
|
-
|
|
2798
|
+
// Define a key callback from the parameters we have
|
|
2799
|
+
if (key == null && cmp == null) {
|
|
2800
|
+
key = (node) => {
|
|
2801
|
+
let val;
|
|
2802
|
+
if (NODE_DICT_PROPS.has(<string>propName)) {
|
|
2803
|
+
val = node[propName as keyof WunderbaumNode];
|
|
2804
|
+
} else {
|
|
2805
|
+
val = node.data[propName!];
|
|
2806
|
+
}
|
|
2807
|
+
if (caseInsensitive && typeof val === "string") {
|
|
2808
|
+
val = val.toLowerCase();
|
|
2809
|
+
}
|
|
2810
|
+
return val;
|
|
2811
|
+
};
|
|
2812
|
+
}
|
|
2813
|
+
// Define a compare callback that uses the key callback
|
|
2814
|
+
if (cmp) {
|
|
2815
|
+
util.assert(!key, "`key` and `cmp` are mutually exclusive");
|
|
2816
|
+
tree.logDeprecate("SortOptions.cmp", {
|
|
2817
|
+
since: "0.14.0",
|
|
2818
|
+
hint: "use the `key` callback instead",
|
|
2819
|
+
});
|
|
2820
|
+
} else {
|
|
2821
|
+
if (options.propName || options.caseInsensitive) {
|
|
2822
|
+
tree.logWarn("sort(): ignoring propName, caseInsensitive");
|
|
2834
2823
|
}
|
|
2835
|
-
|
|
2836
|
-
|
|
2837
|
-
|
|
2824
|
+
|
|
2825
|
+
cmp = (a, b) => {
|
|
2826
|
+
if (isFolder) {
|
|
2827
|
+
const isFolderA = isFolder(a);
|
|
2828
|
+
if (isFolderA !== isFolder(b)) {
|
|
2829
|
+
return isFolderA ? -1 : 1;
|
|
2830
|
+
}
|
|
2831
|
+
}
|
|
2832
|
+
let x = key!(a);
|
|
2833
|
+
let y = key!(b);
|
|
2834
|
+
// Assure we have reasonable comparisons with null values:
|
|
2835
|
+
if (x == null) {
|
|
2836
|
+
x = typeof y === "string" ? "" : 0;
|
|
2837
|
+
} else if (typeof x === "boolean") {
|
|
2838
|
+
x = x ? 1 : 0;
|
|
2839
|
+
}
|
|
2840
|
+
if (y == null) {
|
|
2841
|
+
y = typeof x === "string" ? "" : 0;
|
|
2842
|
+
} else if (typeof y === "boolean") {
|
|
2843
|
+
y = y ? 1 : 0;
|
|
2838
2844
|
}
|
|
2839
|
-
|
|
2840
|
-
|
|
2845
|
+
|
|
2846
|
+
if (order === "desc") {
|
|
2847
|
+
return x === y ? 0 : x > y ? -1 : 1;
|
|
2841
2848
|
}
|
|
2849
|
+
return x === y ? 0 : x > y ? 1 : -1;
|
|
2850
|
+
};
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
function _sortChildren(cl: WunderbaumNode[]): void {
|
|
2854
|
+
if (!cl) {
|
|
2855
|
+
return;
|
|
2842
2856
|
}
|
|
2843
|
-
|
|
2844
|
-
|
|
2857
|
+
cl.sort(cmp);
|
|
2858
|
+
if (deep) {
|
|
2859
|
+
for (let i = 0, l = cl.length; i < l; i++) {
|
|
2860
|
+
if (cl[i].children) {
|
|
2861
|
+
_sortChildren(cl[i].children!);
|
|
2862
|
+
}
|
|
2863
|
+
}
|
|
2845
2864
|
}
|
|
2846
|
-
|
|
2847
|
-
|
|
2865
|
+
}
|
|
2866
|
+
if (this.children) {
|
|
2867
|
+
_sortChildren(this.children);
|
|
2868
|
+
}
|
|
2869
|
+
this.tree.update(ChangeType.structure);
|
|
2870
|
+
// this.triggerModify("sort"); // TODO
|
|
2871
|
+
}
|
|
2848
2872
|
|
|
2849
|
-
|
|
2873
|
+
/**
|
|
2874
|
+
* Re-apply current sorting if any (use after lazy load).
|
|
2875
|
+
* Example:
|
|
2876
|
+
* ```js
|
|
2877
|
+
* load: function (e) {
|
|
2878
|
+
* // Whe loading a lazy branch, apply current sort order if any
|
|
2879
|
+
* e.node.resort();
|
|
2880
|
+
* },
|
|
2881
|
+
* ```
|
|
2882
|
+
* @since 0.14.0
|
|
2883
|
+
*/
|
|
2884
|
+
resort(options: SortOptions = {}): void {
|
|
2885
|
+
for (const colDef of this.tree.columns) {
|
|
2886
|
+
if (colDef.sortOrder) {
|
|
2887
|
+
options.colId = colDef.id;
|
|
2888
|
+
options.order = colDef.sortOrder;
|
|
2889
|
+
this.sort(options);
|
|
2890
|
+
break;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2850
2893
|
}
|
|
2851
2894
|
|
|
2852
2895
|
/**
|
|
@@ -2893,7 +2936,8 @@ export class WunderbaumNode {
|
|
|
2893
2936
|
* @param {function} callback the callback function.
|
|
2894
2937
|
* Return false to stop iteration, return "skip" to skip this node and
|
|
2895
2938
|
* its children only.
|
|
2896
|
-
* @see
|
|
2939
|
+
* @see `wb_node.WunderbaumNode.IterableIterator<WunderbaumNode>`
|
|
2940
|
+
* @see {@link Wunderbaum.visit}.
|
|
2897
2941
|
*/
|
|
2898
2942
|
visit(
|
|
2899
2943
|
callback: NodeVisitCallback,
|