wunderbaum 0.2.0 → 0.3.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/README.md +1 -7
- package/dist/wunderbaum.css +3 -3
- package/dist/wunderbaum.d.ts +190 -101
- package/dist/wunderbaum.esm.js +422 -206
- package/dist/wunderbaum.esm.min.js +35 -35
- package/dist/wunderbaum.esm.min.js.map +1 -1
- package/dist/wunderbaum.umd.js +422 -206
- package/dist/wunderbaum.umd.min.js +44 -44
- package/dist/wunderbaum.umd.min.js.map +1 -1
- package/package.json +1 -1
- package/src/common.ts +9 -1
- package/src/deferred.ts +1 -1
- package/src/drag_observer.ts +1 -1
- package/src/types.ts +99 -70
- package/src/util.ts +61 -10
- package/src/wb_ext_dnd.ts +27 -7
- package/src/wb_ext_edit.ts +3 -3
- package/src/wb_ext_filter.ts +1 -1
- package/src/wb_ext_grid.ts +1 -1
- package/src/wb_ext_keynav.ts +1 -1
- package/src/wb_ext_logger.ts +1 -1
- package/src/wb_extension_base.ts +1 -1
- package/src/wb_node.ts +130 -30
- package/src/wb_options.ts +9 -3
- package/src/wunderbaum.scss +6 -3
- package/src/wunderbaum.ts +199 -143
package/src/wb_extension_base.ts
CHANGED
package/src/wb_node.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum - wunderbaum_node
|
|
3
|
-
* Copyright (c) 2021-
|
|
3
|
+
* Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
|
|
4
4
|
* @VERSION, @DATE (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -10,7 +10,7 @@ import * as util from "./util";
|
|
|
10
10
|
import { Wunderbaum } from "./wunderbaum";
|
|
11
11
|
import {
|
|
12
12
|
AddChildrenOptions,
|
|
13
|
-
|
|
13
|
+
InsertNodeType,
|
|
14
14
|
ApplyCommandOptions,
|
|
15
15
|
ApplyCommandType,
|
|
16
16
|
ChangeType,
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
SetExpandedOptions,
|
|
31
31
|
SetSelectedOptions,
|
|
32
32
|
SetStatusOptions,
|
|
33
|
+
SortCallback,
|
|
33
34
|
} from "./types";
|
|
34
35
|
import {
|
|
35
36
|
iconMap,
|
|
@@ -41,6 +42,7 @@ import {
|
|
|
41
42
|
ROW_HEIGHT,
|
|
42
43
|
TEST_IMG,
|
|
43
44
|
inflateSourceData,
|
|
45
|
+
nodeTitleSorter,
|
|
44
46
|
} from "./common";
|
|
45
47
|
import { Deferred } from "./deferred";
|
|
46
48
|
import { WbNodeData } from "./wb_options";
|
|
@@ -332,9 +334,12 @@ export class WunderbaumNode {
|
|
|
332
334
|
* @param [mode=child] 'before', 'after', 'firstChild', or 'child' ('over' is a synonym for 'child')
|
|
333
335
|
* @returns new node
|
|
334
336
|
*/
|
|
335
|
-
addNode(
|
|
336
|
-
|
|
337
|
-
|
|
337
|
+
addNode(
|
|
338
|
+
nodeData: WbNodeData,
|
|
339
|
+
mode: InsertNodeType = "appendChild"
|
|
340
|
+
): WunderbaumNode {
|
|
341
|
+
if (<string>mode === "over") {
|
|
342
|
+
mode = "appendChild"; // compatible with drop region
|
|
338
343
|
}
|
|
339
344
|
switch (mode) {
|
|
340
345
|
case "after":
|
|
@@ -343,11 +348,11 @@ export class WunderbaumNode {
|
|
|
343
348
|
});
|
|
344
349
|
case "before":
|
|
345
350
|
return this.parent.addChildren(nodeData, { before: this });
|
|
346
|
-
case "
|
|
351
|
+
case "prependChild":
|
|
347
352
|
// Insert before the first child if any
|
|
348
353
|
// let insertBefore = this.children ? this.children[0] : undefined;
|
|
349
354
|
return this.addChildren(nodeData, { before: 0 });
|
|
350
|
-
case "
|
|
355
|
+
case "appendChild":
|
|
351
356
|
return this.addChildren(nodeData);
|
|
352
357
|
}
|
|
353
358
|
util.assert(false, "Invalid mode: " + mode);
|
|
@@ -784,8 +789,17 @@ export class WunderbaumNode {
|
|
|
784
789
|
* an expand operation is currently possible.
|
|
785
790
|
*/
|
|
786
791
|
isExpandable(andCollapsed = false): boolean {
|
|
787
|
-
//
|
|
788
|
-
|
|
792
|
+
// `false` is never expandable (unoffical)
|
|
793
|
+
if ((andCollapsed && this.expanded) || <any>this.children === false) {
|
|
794
|
+
return false;
|
|
795
|
+
}
|
|
796
|
+
if (this.children == null) {
|
|
797
|
+
return this.lazy; // null or undefined can trigger lazy load
|
|
798
|
+
}
|
|
799
|
+
if (this.children.length === 0) {
|
|
800
|
+
return !!this.tree.options.emptyChildListExpandable;
|
|
801
|
+
}
|
|
802
|
+
return true;
|
|
789
803
|
}
|
|
790
804
|
|
|
791
805
|
/** Return true if this node is currently in edit-title mode. */
|
|
@@ -953,7 +967,7 @@ export class WunderbaumNode {
|
|
|
953
967
|
tree.logInfo("Redefine columns", source.columns);
|
|
954
968
|
tree.columns = source.columns;
|
|
955
969
|
delete source.columns;
|
|
956
|
-
tree.
|
|
970
|
+
tree.setModified(ChangeType.colStructure);
|
|
957
971
|
}
|
|
958
972
|
|
|
959
973
|
this.addChildren(source.children);
|
|
@@ -969,12 +983,50 @@ export class WunderbaumNode {
|
|
|
969
983
|
this._callEvent("load");
|
|
970
984
|
}
|
|
971
985
|
|
|
986
|
+
async _fetchWithOptions(source: any) {
|
|
987
|
+
// Either a URL string or an object with a `.url` property.
|
|
988
|
+
let url: string, params, body, options, rest;
|
|
989
|
+
let fetchOpts: any = {};
|
|
990
|
+
if (typeof source === "string") {
|
|
991
|
+
// source is a plain URL string: assume GET request
|
|
992
|
+
url = source;
|
|
993
|
+
fetchOpts.method = "GET";
|
|
994
|
+
} else if (util.isPlainObject(source)) {
|
|
995
|
+
// source is a plain object with `.url` property.
|
|
996
|
+
({ url, params, body, options, ...rest } = source);
|
|
997
|
+
util.assert(typeof url === "string", `expected source.url as string`);
|
|
998
|
+
if (util.isPlainObject(options)) {
|
|
999
|
+
fetchOpts = options;
|
|
1000
|
+
}
|
|
1001
|
+
if (util.isPlainObject(body)) {
|
|
1002
|
+
// we also accept 'body' as object...
|
|
1003
|
+
util.assert(
|
|
1004
|
+
!fetchOpts.body,
|
|
1005
|
+
"options.body should be passed as source.body"
|
|
1006
|
+
);
|
|
1007
|
+
fetchOpts.body = JSON.stringify(fetchOpts.body);
|
|
1008
|
+
fetchOpts.method ??= "POST"; // set default
|
|
1009
|
+
}
|
|
1010
|
+
if (util.isPlainObject(params)) {
|
|
1011
|
+
url += "?" + new URLSearchParams(params);
|
|
1012
|
+
fetchOpts.method ??= "GET"; // set default
|
|
1013
|
+
}
|
|
1014
|
+
} else {
|
|
1015
|
+
url = ""; // keep linter happy
|
|
1016
|
+
util.error(`Unsupported source format: ${source}`);
|
|
1017
|
+
}
|
|
1018
|
+
this.setStatus(NodeStatusType.loading);
|
|
1019
|
+
const response = await fetch(url, fetchOpts);
|
|
1020
|
+
if (!response.ok) {
|
|
1021
|
+
util.error(`GET ${url} returned ${response.status}, ${response}`);
|
|
1022
|
+
}
|
|
1023
|
+
return await response.json();
|
|
1024
|
+
}
|
|
972
1025
|
/** Download data from the cloud, then call `.update()`. */
|
|
973
1026
|
async load(source: any) {
|
|
974
1027
|
const tree = this.tree;
|
|
975
1028
|
const requestId = Date.now();
|
|
976
1029
|
const prevParent = this.parent;
|
|
977
|
-
const url = typeof source === "string" ? source : source.url;
|
|
978
1030
|
const start = Date.now();
|
|
979
1031
|
let elap = 0,
|
|
980
1032
|
elapLoad = 0,
|
|
@@ -992,16 +1044,16 @@ export class WunderbaumNode {
|
|
|
992
1044
|
// const timerLabel = tree.logTime(this + ".load()");
|
|
993
1045
|
|
|
994
1046
|
try {
|
|
1047
|
+
let url: string = typeof source === "string" ? source : source.url;
|
|
995
1048
|
if (!url) {
|
|
1049
|
+
// An array or a plain object (that does NOT contain a `.url` property)
|
|
1050
|
+
// will be treated as native Wunderbaum data
|
|
996
1051
|
this._loadSourceObject(source);
|
|
997
1052
|
elapProcess = Date.now() - start;
|
|
998
1053
|
} else {
|
|
999
|
-
|
|
1000
|
-
const
|
|
1001
|
-
|
|
1002
|
-
util.error(`GET ${url} returned ${response.status}, ${response}`);
|
|
1003
|
-
}
|
|
1004
|
-
const data = await response.json();
|
|
1054
|
+
// Either a URL string or an object with a `.url` property.
|
|
1055
|
+
const data = await this._fetchWithOptions(source);
|
|
1056
|
+
|
|
1005
1057
|
elapLoad = Date.now() - start;
|
|
1006
1058
|
|
|
1007
1059
|
if (this._requestId && this._requestId > requestId) {
|
|
@@ -1169,9 +1221,12 @@ export class WunderbaumNode {
|
|
|
1169
1221
|
/** Move this node to targetNode. */
|
|
1170
1222
|
moveTo(
|
|
1171
1223
|
targetNode: WunderbaumNode,
|
|
1172
|
-
mode:
|
|
1224
|
+
mode: InsertNodeType = "appendChild",
|
|
1173
1225
|
map?: NodeAnyCallback
|
|
1174
1226
|
) {
|
|
1227
|
+
if (<string>mode === "over") {
|
|
1228
|
+
mode = "appendChild"; // compatible with drop region
|
|
1229
|
+
}
|
|
1175
1230
|
if (mode === "prependChild") {
|
|
1176
1231
|
if (targetNode.children && targetNode.children.length) {
|
|
1177
1232
|
mode = "before";
|
|
@@ -1229,7 +1284,7 @@ export class WunderbaumNode {
|
|
|
1229
1284
|
targetParent.children!.splice(pos + 1, 0, this);
|
|
1230
1285
|
break;
|
|
1231
1286
|
default:
|
|
1232
|
-
util.error(
|
|
1287
|
+
util.error(`Invalid mode '${mode}'.`);
|
|
1233
1288
|
}
|
|
1234
1289
|
} else {
|
|
1235
1290
|
targetParent.children = [this];
|
|
@@ -1255,8 +1310,12 @@ export class WunderbaumNode {
|
|
|
1255
1310
|
n.tree = targetNode.tree;
|
|
1256
1311
|
}, true);
|
|
1257
1312
|
}
|
|
1258
|
-
|
|
1259
|
-
|
|
1313
|
+
// Make sure we update async, because discarding the markup would prevent
|
|
1314
|
+
// DragAndDrop to generate a dragend event on the source node
|
|
1315
|
+
setTimeout(() => {
|
|
1316
|
+
// Even indentation may have changed:
|
|
1317
|
+
tree.setModified(ChangeType.any);
|
|
1318
|
+
}, 0);
|
|
1260
1319
|
// TODO: fix selection state
|
|
1261
1320
|
// TODO: fix active state
|
|
1262
1321
|
}
|
|
@@ -1395,7 +1454,7 @@ export class WunderbaumNode {
|
|
|
1395
1454
|
icon = iconMap.loading;
|
|
1396
1455
|
}
|
|
1397
1456
|
if (icon === false) {
|
|
1398
|
-
return null;
|
|
1457
|
+
return null; // explicitly disabled: don't try default icons
|
|
1399
1458
|
}
|
|
1400
1459
|
if (typeof icon === "string") {
|
|
1401
1460
|
// Callback returned an icon definition
|
|
@@ -1413,7 +1472,10 @@ export class WunderbaumNode {
|
|
|
1413
1472
|
}
|
|
1414
1473
|
|
|
1415
1474
|
// this.log("_createIcon: " + icon);
|
|
1416
|
-
if (icon
|
|
1475
|
+
if (!icon) {
|
|
1476
|
+
iconSpan = document.createElement("i");
|
|
1477
|
+
iconSpan.className = "wb-icon";
|
|
1478
|
+
} else if (icon.indexOf("<") >= 0) {
|
|
1417
1479
|
// HTML
|
|
1418
1480
|
iconSpan = util.elemFromHtml(icon);
|
|
1419
1481
|
} else if (TEST_IMG.test(icon)) {
|
|
@@ -1765,9 +1827,12 @@ export class WunderbaumNode {
|
|
|
1765
1827
|
case "data":
|
|
1766
1828
|
this._render_data(opts);
|
|
1767
1829
|
break;
|
|
1768
|
-
|
|
1830
|
+
case "row":
|
|
1831
|
+
// _rowElem is not yet created (asserted in _render_markup)
|
|
1769
1832
|
this._render_markup(opts);
|
|
1770
1833
|
break;
|
|
1834
|
+
default:
|
|
1835
|
+
util.error(`Invalid change type '${opts.change}'.`);
|
|
1771
1836
|
}
|
|
1772
1837
|
}
|
|
1773
1838
|
|
|
@@ -2005,9 +2070,9 @@ export class WunderbaumNode {
|
|
|
2005
2070
|
}
|
|
2006
2071
|
|
|
2007
2072
|
/** Set a new icon path or class. */
|
|
2008
|
-
setIcon() {
|
|
2009
|
-
|
|
2010
|
-
|
|
2073
|
+
setIcon(icon: string) {
|
|
2074
|
+
this.icon = icon;
|
|
2075
|
+
this.setModified();
|
|
2011
2076
|
}
|
|
2012
2077
|
|
|
2013
2078
|
/** Change node's {@link key} and/or {@link refKey}. */
|
|
@@ -2016,11 +2081,15 @@ export class WunderbaumNode {
|
|
|
2016
2081
|
}
|
|
2017
2082
|
|
|
2018
2083
|
/**
|
|
2019
|
-
*
|
|
2084
|
+
* Trigger a repaint, typically after a status or data change.
|
|
2020
2085
|
*
|
|
2021
2086
|
* `change` defaults to 'data', which handles modifcations of title, icon,
|
|
2022
2087
|
* and column content. It can be reduced to 'ChangeType.status' if only
|
|
2023
2088
|
* active/focus/selected state has changed.
|
|
2089
|
+
*
|
|
2090
|
+
* This method will eventually call {@link WunderbaumNode.render()} with
|
|
2091
|
+
* default options, but may be more consistent with the tree's
|
|
2092
|
+
* {@link Wunderbaum.setModified()} API.
|
|
2024
2093
|
*/
|
|
2025
2094
|
setModified(change: ChangeType = ChangeType.data) {
|
|
2026
2095
|
util.assert(change === ChangeType.status || change === ChangeType.data);
|
|
@@ -2067,7 +2136,7 @@ export class WunderbaumNode {
|
|
|
2067
2136
|
util.assert(data.statusNodeType);
|
|
2068
2137
|
util.assert(!firstChild || !firstChild.isStatusNode());
|
|
2069
2138
|
|
|
2070
|
-
statusNode = this.addNode(data, "
|
|
2139
|
+
statusNode = this.addNode(data, "prependChild");
|
|
2071
2140
|
statusNode.match = true;
|
|
2072
2141
|
tree.setModified(ChangeType.structure);
|
|
2073
2142
|
|
|
@@ -2139,6 +2208,37 @@ export class WunderbaumNode {
|
|
|
2139
2208
|
// this.triggerModify("rename"); // TODO
|
|
2140
2209
|
}
|
|
2141
2210
|
|
|
2211
|
+
_sortChildren(cmp: SortCallback, deep: boolean): void {
|
|
2212
|
+
const cl = this.children;
|
|
2213
|
+
|
|
2214
|
+
if (!cl) {
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
cl.sort(cmp);
|
|
2218
|
+
if (deep) {
|
|
2219
|
+
for (let i = 0, l = cl.length; i < l; i++) {
|
|
2220
|
+
if (cl[i].children) {
|
|
2221
|
+
cl[i]._sortChildren(cmp, deep);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
/**
|
|
2228
|
+
* Sort child list by title or custom criteria.
|
|
2229
|
+
* @param {function} cmp custom compare function(a, b) that returns -1, 0, or 1
|
|
2230
|
+
* (defaults to sorting by title).
|
|
2231
|
+
* @param {boolean} deep pass true to sort all descendant nodes recursively
|
|
2232
|
+
*/
|
|
2233
|
+
sortChildren(
|
|
2234
|
+
cmp: SortCallback | null = nodeTitleSorter,
|
|
2235
|
+
deep: boolean = false
|
|
2236
|
+
): void {
|
|
2237
|
+
this._sortChildren(cmp || nodeTitleSorter, deep);
|
|
2238
|
+
this.tree.setModified(ChangeType.structure);
|
|
2239
|
+
// this.triggerModify("sort"); // TODO
|
|
2240
|
+
}
|
|
2241
|
+
|
|
2142
2242
|
/**
|
|
2143
2243
|
* Trigger `modifyChild` event on a parent to signal that a child was modified.
|
|
2144
2244
|
* @param {string} operation Type of change: 'add', 'remove', 'rename', 'move', 'data', ...
|
|
@@ -2148,8 +2248,8 @@ export class WunderbaumNode {
|
|
|
2148
2248
|
child: WunderbaumNode | null,
|
|
2149
2249
|
extra?: any
|
|
2150
2250
|
) {
|
|
2251
|
+
this.logDebug(`modifyChild(${operation})`, extra, child);
|
|
2151
2252
|
if (!this.tree.options.modifyChild) return;
|
|
2152
|
-
|
|
2153
2253
|
if (child && child.parent !== this) {
|
|
2154
2254
|
util.error("child " + child + " is not a child of " + this);
|
|
2155
2255
|
}
|
package/src/wb_options.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum - utils
|
|
3
|
-
* Copyright (c) 2021-
|
|
3
|
+
* Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
|
|
4
4
|
* @VERSION, @DATE (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -44,7 +44,7 @@ export interface WbNodeData {
|
|
|
44
44
|
* ```js
|
|
45
45
|
* const tree = new mar10.Wunderbaum({
|
|
46
46
|
* id: "demo",
|
|
47
|
-
* element: document.
|
|
47
|
+
* element: document.getElementById("demo-tree"),
|
|
48
48
|
* source: "url/of/data/request",
|
|
49
49
|
* ...
|
|
50
50
|
* });
|
|
@@ -107,7 +107,7 @@ export interface WunderbaumOptions {
|
|
|
107
107
|
* real data.
|
|
108
108
|
* Default: false.
|
|
109
109
|
*/
|
|
110
|
-
skeleton?:
|
|
110
|
+
skeleton?: boolean;
|
|
111
111
|
/**
|
|
112
112
|
* Translation map for some system messages.
|
|
113
113
|
*/
|
|
@@ -123,6 +123,12 @@ export interface WunderbaumOptions {
|
|
|
123
123
|
* Default: 0
|
|
124
124
|
*/
|
|
125
125
|
minExpandLevel?: number;
|
|
126
|
+
/**
|
|
127
|
+
* If true, allow to expand parent nodes, even if `node.children` conatains
|
|
128
|
+
* an empty array (`[]`). This is the the behavior of macOS Finder, for example.
|
|
129
|
+
* Default: false
|
|
130
|
+
*/
|
|
131
|
+
emptyChildListExpandable?: boolean;
|
|
126
132
|
// escapeTitles: boolean;
|
|
127
133
|
// /**
|
|
128
134
|
// * Height of the header row div.
|
package/src/wunderbaum.scss
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*!
|
|
2
2
|
* Wunderbaum style sheet (generated from wunderbaum.scss)
|
|
3
|
-
* Copyright (c) 2021-
|
|
3
|
+
* Copyright (c) 2021-2023, Martin Wendt. Released under the MIT license.
|
|
4
4
|
* @VERSION, @DATE (https://github.com/mar10/wunderbaum)
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -52,11 +52,14 @@ $icon-padding-x: ($icon-outer-width - $icon-width) / 2;
|
|
|
52
52
|
$header-height: $row-outer-height;
|
|
53
53
|
|
|
54
54
|
// PyCharm:
|
|
55
|
-
$level-rainbow: rgba(255, 255, 232, 1), rgba(240, 255, 240, 1),
|
|
56
|
-
|
|
55
|
+
// $level-rainbow: rgba(255, 255, 232, 1), rgba(240, 255, 240, 1),
|
|
56
|
+
// rgba(255, 240, 255, 1), rgba(234, 253, 253, 1);
|
|
57
57
|
// VS-Code_
|
|
58
58
|
// $level-rainbow: rgba(255, 255, 64, 0.07), rgba(127, 255, 127, 0.07),
|
|
59
59
|
// rgba(255, 127, 255, 0.07), rgba(79, 236, 236, 0.07);
|
|
60
|
+
// Slightly stronger*
|
|
61
|
+
$level-rainbow: rgb(255, 255, 201), rgb(218, 255, 218), rgb(255, 217, 254),
|
|
62
|
+
rgb(204, 250, 250);
|
|
60
63
|
|
|
61
64
|
div.wunderbaum {
|
|
62
65
|
box-sizing: border-box;
|