uiconfig-blueprint 0.0.1
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 +190 -0
- package/README.md +129 -0
- package/dist/ConfigObject.d.ts +16 -0
- package/dist/UiConfigRendererBlueprint.d.ts +13 -0
- package/dist/allPaths-PklnL3F6.js +23 -0
- package/dist/allPaths-PklnL3F6.js.map +1 -0
- package/dist/allPathsLoader-2dOu0jQ3.js +27 -0
- package/dist/allPathsLoader-2dOu0jQ3.js.map +1 -0
- package/dist/bpComponents/BPButtonComponent.d.ts +8 -0
- package/dist/bpComponents/BPColorInputComponent.d.ts +25 -0
- package/dist/bpComponents/BPComponent.d.ts +25 -0
- package/dist/bpComponents/BPContainerComponent.d.ts +21 -0
- package/dist/bpComponents/BPDropdownInputComponent.d.ts +16 -0
- package/dist/bpComponents/BPFileComponent.d.ts +26 -0
- package/dist/bpComponents/BPFolderComponent.d.ts +16 -0
- package/dist/bpComponents/BPInputComponent.d.ts +11 -0
- package/dist/bpComponents/BPLabelledComponent.d.ts +8 -0
- package/dist/bpComponents/BPNumberInputComponent.d.ts +11 -0
- package/dist/bpComponents/BPPanelComponent.d.ts +17 -0
- package/dist/bpComponents/BPSliderInputComponent.d.ts +15 -0
- package/dist/bpComponents/BPTextInputComponent.d.ts +8 -0
- package/dist/bpComponents/BPToggleInputComponent.d.ts +9 -0
- package/dist/bpComponents/BPTreeComponent.d.ts +32 -0
- package/dist/bpComponents/BPTreeFolderComponent.d.ts +10 -0
- package/dist/bpComponents/BPValueComponent.d.ts +16 -0
- package/dist/bpComponents/BPVectorInputComponent.d.ts +20 -0
- package/dist/components/DraggableIcon.d.ts +17 -0
- package/dist/components/ExtendedNumericInput.d.ts +23 -0
- package/dist/components/FileComponent.d.ts +7 -0
- package/dist/components/FormGroupComponent.d.ts +6 -0
- package/dist/components/InspectorStackComponent.d.ts +5 -0
- package/dist/components/VisualStyleContext.d.ts +110 -0
- package/dist/components/bpIconWrapper.d.ts +2 -0
- package/dist/components/variableIcons.d.ts +41 -0
- package/dist/contexts.d.ts +1 -0
- package/dist/index-PTojlie-.js +29271 -0
- package/dist/index-PTojlie-.js.map +1 -0
- package/dist/index-iM9IChdK.js +604 -0
- package/dist/index-iM9IChdK.js.map +1 -0
- package/dist/index-tqa0R-I0.js +604 -0
- package/dist/index-tqa0R-I0.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +292 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +12 -0
- package/dist/index.mjs.map +1 -0
- package/dist/splitPathsBySizeLoader-QNLjzk6A.js +36 -0
- package/dist/splitPathsBySizeLoader-QNLjzk6A.js.map +1 -0
- package/dist/threejs.d.ts +2 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/highlight.css +120 -0
- package/docs/assets/main.js +58 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1279 -0
- package/docs/classes/UI.html +403 -0
- package/docs/classes/UiConfigRendererBlueprint.html +404 -0
- package/docs/index.html +134 -0
- package/docs/modules.html +50 -0
- package/package.json +127 -0
- package/src/ConfigObject.tsx +137 -0
- package/src/UiConfigRendererBlueprint.tsx +85 -0
- package/src/bpComponents/BPButtonComponent.scss +8 -0
- package/src/bpComponents/BPButtonComponent.tsx +27 -0
- package/src/bpComponents/BPColorInputComponent.tsx +138 -0
- package/src/bpComponents/BPComponent.tsx +93 -0
- package/src/bpComponents/BPContainerComponent.tsx +57 -0
- package/src/bpComponents/BPDropdownInputComponent.tsx +80 -0
- package/src/bpComponents/BPFileComponent.tsx +73 -0
- package/src/bpComponents/BPFolderComponent.scss +107 -0
- package/src/bpComponents/BPFolderComponent.tsx +91 -0
- package/src/bpComponents/BPInputComponent.tsx +33 -0
- package/src/bpComponents/BPLabelledComponent.tsx +19 -0
- package/src/bpComponents/BPNumberInputComponent.tsx +35 -0
- package/src/bpComponents/BPPanelComponent.tsx +50 -0
- package/src/bpComponents/BPSliderInputComponent.tsx +71 -0
- package/src/bpComponents/BPTextInputComponent.tsx +26 -0
- package/src/bpComponents/BPToggleInputComponent.tsx +31 -0
- package/src/bpComponents/BPTreeComponent.scss +3 -0
- package/src/bpComponents/BPTreeComponent.tsx +127 -0
- package/src/bpComponents/BPTreeFolderComponent.tsx +31 -0
- package/src/bpComponents/BPValueComponent.tsx +46 -0
- package/src/bpComponents/BPVectorInputComponent.tsx +163 -0
- package/src/bpComponents/index.scss +3 -0
- package/src/components/DraggableIcon.tsx +75 -0
- package/src/components/ExtendedNumericInput.tsx +219 -0
- package/src/components/FileComponent.tsx +79 -0
- package/src/components/FormGroupComponent.tsx +22 -0
- package/src/components/InspectorStackComponent.tsx +72 -0
- package/src/components/VisualStyleContext.tsx +69 -0
- package/src/components/bpIconWrapper.tsx +20 -0
- package/src/components/common.scss +21 -0
- package/src/components/variableIcons.tsx +177 -0
- package/src/contexts.ts +1 -0
- package/src/index.ts +8 -0
- package/src/renderer.scss +193 -0
- package/src/threejs.ts +3 -0
- package/tsconfig.json +40 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import {Icon, IconName, MaybeElement} from "@blueprintjs/core";
|
|
3
|
+
|
|
4
|
+
export class DraggableIcon extends React.Component<{ icon: IconName | MaybeElement, value: number, stepSize?: number, onChange: (v: number, last?: boolean) => void }> {
|
|
5
|
+
private cursor = 'auto'
|
|
6
|
+
private dragStartOffset = -Infinity
|
|
7
|
+
private dragStartValue = -Infinity
|
|
8
|
+
private lastValue = -Infinity
|
|
9
|
+
private removeEvents = () => {
|
|
10
|
+
if (isFinite(this.lastValue)) this.props.onChange(this.lastValue)
|
|
11
|
+
this.lastValue = -Infinity
|
|
12
|
+
this.dragStartOffset = -Infinity
|
|
13
|
+
this.dragStartValue = -Infinity
|
|
14
|
+
document.removeEventListener('mousemove', this.mouseMoveEvent)
|
|
15
|
+
document.removeEventListener('mouseup', this.mouseUpEvent)
|
|
16
|
+
document.documentElement.style.cursor = this.cursor
|
|
17
|
+
// setDragging(false)
|
|
18
|
+
};
|
|
19
|
+
private mouseUpEvent = (ev: MouseEvent) => {
|
|
20
|
+
ev.stopPropagation()
|
|
21
|
+
ev.preventDefault()
|
|
22
|
+
this.removeEvents()
|
|
23
|
+
};
|
|
24
|
+
private mouseMoveEvent = (ev: MouseEvent) => {
|
|
25
|
+
ev.stopPropagation()
|
|
26
|
+
ev.preventDefault()
|
|
27
|
+
if (ev.buttons > 0) {
|
|
28
|
+
if (isFinite(this.dragStartValue) && isFinite(this.dragStartOffset)) {
|
|
29
|
+
let stepSize = (this.props.stepSize ?? 1) / 10
|
|
30
|
+
let curr = ev.clientX;
|
|
31
|
+
let mm = 100;
|
|
32
|
+
let off = this.dragStartOffset - curr;
|
|
33
|
+
// off = Math.min(mm, Math.max(-mm, off));
|
|
34
|
+
off = off / mm; //-1, 1
|
|
35
|
+
// off = (off + 1)/2;
|
|
36
|
+
// off *= (props.max - props.min) / 2
|
|
37
|
+
off *= ((stepSize) * 100) / 2
|
|
38
|
+
if (ev.shiftKey) off *= 10
|
|
39
|
+
if (ev.altKey) off *= 0.1
|
|
40
|
+
|
|
41
|
+
let val = this.dragStartValue - off;
|
|
42
|
+
// val = Math.min(props.max, Math.max(props.min, val)); // todo
|
|
43
|
+
val = Number(val.toFixed(Math.max(1, -Math.log10(stepSize / 10)))); // clip decimal places depending on step size
|
|
44
|
+
this.props.onChange(val, false)
|
|
45
|
+
this.lastValue = val;
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
this.removeEvents()
|
|
49
|
+
// setDragging(false)
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
render() {
|
|
54
|
+
// const [dragging, setDragging] = React.useState(false)
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div style={{cursor: 'ew-resize', margin: "6px"}}
|
|
58
|
+
onMouseDown={(ev) => {
|
|
59
|
+
// if(dragging) return
|
|
60
|
+
this.dragStartOffset = ev.clientX
|
|
61
|
+
this.dragStartValue = this.props.value
|
|
62
|
+
ev.stopPropagation()
|
|
63
|
+
ev.preventDefault()
|
|
64
|
+
this.cursor = document.documentElement.style.cursor
|
|
65
|
+
document.documentElement.style.cursor = "ew-resize"
|
|
66
|
+
document.addEventListener('mousemove', this.mouseMoveEvent)
|
|
67
|
+
document.addEventListener('mouseup', this.mouseUpEvent)
|
|
68
|
+
// setDragging(true)
|
|
69
|
+
}}
|
|
70
|
+
>
|
|
71
|
+
<Icon icon={this.props.icon}/>
|
|
72
|
+
</div>
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2017 Palantir Technologies, Inc. All rights reserved.
|
|
3
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
* you may not use this file except in compliance with the License.
|
|
5
|
+
* You may obtain a copy of the License at
|
|
6
|
+
*
|
|
7
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
*
|
|
9
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
* See the License for the specific language governing permissions and
|
|
13
|
+
* limitations under the License.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
//https://github.com/palantir/blueprint/blob/develop/packages/docs-app/src/examples/core-examples/numericInputExtendedExample.tsx
|
|
17
|
+
|
|
18
|
+
import React from "react";
|
|
19
|
+
|
|
20
|
+
import type {HTMLInputProps, NumericInputProps} from "@blueprintjs/core";
|
|
21
|
+
import {NumericInput} from "@blueprintjs/core";
|
|
22
|
+
import {DraggableIcon} from "./DraggableIcon";
|
|
23
|
+
|
|
24
|
+
export interface IExtendedNumericInputState {
|
|
25
|
+
value?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const NumberAbbreviation = {
|
|
29
|
+
BILLION: "b",
|
|
30
|
+
MILLION: "m",
|
|
31
|
+
THOUSAND: "k",
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const NUMBER_ABBREVIATION_REGEX = /((\.\d+)|(\d+(\.\d+)?))(k|m|b)\b/gi;
|
|
35
|
+
const SCIENTIFIC_NOTATION_REGEX = /((\.\d+)|(\d+(\.\d+)?))(e\d+)\b/gi;
|
|
36
|
+
|
|
37
|
+
export class ExtendedNumericInput extends React.PureComponent<HTMLInputProps & NumericInputProps & { onChange2: (v: number, last?: boolean) => void }, IExtendedNumericInputState> {
|
|
38
|
+
public state: IExtendedNumericInputState = {
|
|
39
|
+
value: (this.props.value ?? this.props.defaultValue ?? '').toString(),
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
async setValue(val?: number) {
|
|
43
|
+
return new Promise<void>((resolve) => {
|
|
44
|
+
this.setState({value: (val ?? this.props.value ?? this.props.defaultValue ?? '').toString()}, resolve)
|
|
45
|
+
})
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
public render() {
|
|
49
|
+
const {value} = this.state;
|
|
50
|
+
const props2: HTMLInputProps & NumericInputProps & { onChange2?: (v: number, last?: boolean) => void } = {...this.props}
|
|
51
|
+
if (props2.onChange2) delete props2.onChange2
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<NumericInput
|
|
55
|
+
{...props2}
|
|
56
|
+
leftElement={(
|
|
57
|
+
<DraggableIcon icon={this.props.leftIcon ?? "variable"}
|
|
58
|
+
value={parseFloat(value ?? '0')}
|
|
59
|
+
stepSize={this.props.stepSize}
|
|
60
|
+
onChange={(v, last) => {
|
|
61
|
+
this.handleValueChange(v, v.toString(), null, last, true)
|
|
62
|
+
}}/>
|
|
63
|
+
)}
|
|
64
|
+
// leftIcon={"variable"}
|
|
65
|
+
allowNumericCharactersOnly={false}
|
|
66
|
+
onBlur={this.handleBlur}
|
|
67
|
+
onKeyDown={this.handleKeyDown}
|
|
68
|
+
onValueChange={this.handleValueChange}
|
|
69
|
+
placeholder="Enter a number or expression..."
|
|
70
|
+
value={value}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private handleBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
|
76
|
+
this.handleConfirm((e.target as HTMLInputElement).value);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
private handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
80
|
+
if (e.code === 'Enter') {
|
|
81
|
+
this.handleConfirm((e.target as HTMLInputElement).value);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
private handleValueChange = (_valueAsNumber: number, valueAsString: string, inputElement: HTMLInputElement | null, last?: boolean, dispatchOnChange2 = false) => {
|
|
86
|
+
this.setState({value: valueAsString}, () => {
|
|
87
|
+
this.props.onValueChange?.(_valueAsNumber, valueAsString, inputElement)
|
|
88
|
+
if(dispatchOnChange2)
|
|
89
|
+
this.props.onChange2?.(_valueAsNumber, last)
|
|
90
|
+
});
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
private handleConfirm = (value: string) => {
|
|
94
|
+
let result = value;
|
|
95
|
+
result = this.expandScientificNotationTerms(result);
|
|
96
|
+
result = this.expandNumberAbbreviationTerms(result);
|
|
97
|
+
result = this.evaluateSimpleMathExpression(result);
|
|
98
|
+
result = this.nanStringToEmptyString(result);
|
|
99
|
+
this.setState({value: result}, () => {
|
|
100
|
+
this.props.onValueChange?.(parseFloat(result) || 0, result, null)
|
|
101
|
+
this.props.onChange2?.(parseFloat(result) || 0)
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// the user could have typed a different expression that evaluates to
|
|
105
|
+
// the same value. force the update to ensure a render triggers even if
|
|
106
|
+
// this is the case.
|
|
107
|
+
this.forceUpdate();
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
private expandScientificNotationTerms = (value: string) => {
|
|
111
|
+
// leave empty strings empty
|
|
112
|
+
if (!value) {
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
return value.replace(SCIENTIFIC_NOTATION_REGEX, this.expandScientificNotationNumber);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
private expandNumberAbbreviationTerms = (value: string) => {
|
|
119
|
+
// leave empty strings empty
|
|
120
|
+
if (!value) {
|
|
121
|
+
return value;
|
|
122
|
+
}
|
|
123
|
+
return value.replace(NUMBER_ABBREVIATION_REGEX, this.expandAbbreviatedNumber);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Adapted from http://stackoverflow.com/questions/2276021/evaluating-a-string-as-a-mathematical-expression-in-javascript
|
|
127
|
+
// maybe try a better solution from above, right now it only works left to right
|
|
128
|
+
private evaluateSimpleMathExpression = (value: string) => {
|
|
129
|
+
// leave empty strings empty
|
|
130
|
+
if (!value) {
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// parse all terms from the expression. split on the +, -, *, /, ^(pow) characters and then
|
|
135
|
+
// validate that each term is a number.
|
|
136
|
+
const terms = value.split(/[+\-*\/^]/);
|
|
137
|
+
|
|
138
|
+
// ex. "1 + 2 - 3 # 4" will parse on the + and - signs into
|
|
139
|
+
// ["1 ", " 2 ", " 3 # 4"]. after trimming whitespace from each term
|
|
140
|
+
// and coercing them to numbers, the third term will become NaN,
|
|
141
|
+
// indicating that there was some illegal character present in it.
|
|
142
|
+
const trimmedTerms = terms.map((term: string) => term.trim());
|
|
143
|
+
const numericTerms = trimmedTerms.map((term: string) => +term);
|
|
144
|
+
const illegalTerms = numericTerms.filter(isNaN);
|
|
145
|
+
|
|
146
|
+
if (illegalTerms.length > 0) {
|
|
147
|
+
return "";
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// evaluate the expression now that we know it's valid
|
|
151
|
+
let total = 0;
|
|
152
|
+
|
|
153
|
+
// the regex below will match decimal numbers--optionally preceded by
|
|
154
|
+
// +-*/^ followed by any number of spaces—-including each of the
|
|
155
|
+
// following:
|
|
156
|
+
// ".1"
|
|
157
|
+
// " 1"
|
|
158
|
+
// "1.1"
|
|
159
|
+
// "+ 1"
|
|
160
|
+
// "- 1.1"
|
|
161
|
+
const matches = value.match(/[+\-*\/^]*\s*(\.\d+|\d+(\.\d+)?)/gi) || [];
|
|
162
|
+
for (const match of matches) {
|
|
163
|
+
const compactedMatch = match.replace(/\s/g, "");
|
|
164
|
+
const compactedMatchNum = compactedMatch.replace(/[*\/^]/g, "");
|
|
165
|
+
const num = parseFloat(compactedMatchNum);
|
|
166
|
+
if(compactedMatch.startsWith("*")) total *= num;
|
|
167
|
+
else if(compactedMatch.startsWith("/")) total /= num;
|
|
168
|
+
else if(compactedMatch.startsWith("^")) total = Math.pow(total, num);
|
|
169
|
+
else total += num;
|
|
170
|
+
}
|
|
171
|
+
const roundedTotal = this.roundValue(total);
|
|
172
|
+
return roundedTotal.toString();
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
private nanStringToEmptyString = (value: string) => {
|
|
176
|
+
// our evaluation logic isn't perfect, so use this as a final
|
|
177
|
+
// sanitization step if the result was not a number.
|
|
178
|
+
return value === "NaN" ? "" : value;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
private expandAbbreviatedNumber = (value: string) => {
|
|
182
|
+
if (!value) {
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const num = +value.substring(0, value.length - 1);
|
|
187
|
+
const lastChar = value.charAt(value.length - 1).toLowerCase();
|
|
188
|
+
|
|
189
|
+
let result: number | null = null;
|
|
190
|
+
|
|
191
|
+
if (lastChar === NumberAbbreviation.THOUSAND) {
|
|
192
|
+
result = num * 1e3;
|
|
193
|
+
} else if (lastChar === NumberAbbreviation.MILLION) {
|
|
194
|
+
result = num * 1e6;
|
|
195
|
+
} else if (lastChar === NumberAbbreviation.BILLION) {
|
|
196
|
+
result = num * 1e9;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const isValid = result != null && !isNaN(result);
|
|
200
|
+
|
|
201
|
+
if (isValid) {
|
|
202
|
+
result = this.roundValue(result!);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return isValid ? result!.toString() : "";
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
private expandScientificNotationNumber = (value: string) => {
|
|
209
|
+
if (!value) {
|
|
210
|
+
return value;
|
|
211
|
+
}
|
|
212
|
+
return (+value).toString();
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
private roundValue = (value: number, precision: number = 1) => {
|
|
216
|
+
// round to at most two decimal places
|
|
217
|
+
return Math.round(value * 10 ** precision) / 10 ** precision;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import React, {useEffect, useRef} from "react";
|
|
2
|
+
import {Button, ControlGroup, FileInput, HTMLSelect, InputGroup} from "@blueprintjs/core";
|
|
3
|
+
import {BPFileComponentState} from "../bpComponents/BPFileComponent";
|
|
4
|
+
|
|
5
|
+
export interface FileComponentProps {
|
|
6
|
+
state: BPFileComponentState
|
|
7
|
+
onChange: (state: Partial<BPFileComponentState>)=>void
|
|
8
|
+
}
|
|
9
|
+
export const FileComponent: React.FC<FileComponentProps> = ({state, onChange})=>{
|
|
10
|
+
const fileInputRef = useRef<HTMLInputElement>()
|
|
11
|
+
const textInputRef = useRef<HTMLInputElement>()
|
|
12
|
+
// console.log('render file component', state)
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const fileElement = fileInputRef.current;
|
|
15
|
+
const textElement = textInputRef.current;
|
|
16
|
+
const val = state.value
|
|
17
|
+
if(!val) {
|
|
18
|
+
fileElement && (fileElement.value = '')
|
|
19
|
+
textElement && (textElement.value = '')
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
if(typeof val === 'string') {
|
|
23
|
+
textElement && (textElement.value = val)
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
if(!fileElement) return;
|
|
27
|
+
// console.log(val)
|
|
28
|
+
const dataTransfer = new DataTransfer();
|
|
29
|
+
dataTransfer.items.add(val instanceof File ? val : new File([val], (val as any).name||('file'+((val as any).ext?'.'+(val as any).ext:'')))); // it can be Blob also because of GLTF loaded...
|
|
30
|
+
fileElement!.files = dataTransfer.files
|
|
31
|
+
}, [state.value]);
|
|
32
|
+
// useEffect(() => {
|
|
33
|
+
// fileInputRef.current && (fileInputRef.current.placeholder = 'https://example.com/file.png')
|
|
34
|
+
// }, []);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div style={{flexGrow: "1", display: "flex", height: "100%"}}>
|
|
38
|
+
<ControlGroup fill={false} vertical={false} style={{width: "100%"}}>
|
|
39
|
+
{!state.readOnly ? (<HTMLSelect options={['url', 'file']} minimal={true} fill={false}
|
|
40
|
+
disabled={state.disabled}
|
|
41
|
+
value={state.mode} onChange={(e)=>{
|
|
42
|
+
onChange({mode: e.target.value as 'url'|'file'})
|
|
43
|
+
}} /> ) : null}
|
|
44
|
+
{state.mode === 'url' ? (
|
|
45
|
+
<InputGroup style={{flexGrow: "1", flexShrink: "1"}} inputRef={textInputRef as any} type="url" defaultValue={typeof state.value === 'string' ? state.value : ''}
|
|
46
|
+
rightElement={(
|
|
47
|
+
<Button disabled={state.readOnly} minimal={true} icon="arrow-right" onClick={()=>{
|
|
48
|
+
onChange({mode:'url', value: textInputRef.current?.value??''})
|
|
49
|
+
}} ></Button>
|
|
50
|
+
)}
|
|
51
|
+
disabled={state.disabled} readOnly={state.readOnly}
|
|
52
|
+
onChange={(_)=>{
|
|
53
|
+
}} placeholder="https://example.com/file.png"/>
|
|
54
|
+
) : (
|
|
55
|
+
<FileInput inputProps={{ref: fileInputRef as any}} style={{flexGrow: "1", flexShrink: "1", minWidth: "50px"}}
|
|
56
|
+
text={(state.value as File)?.name || 'No file'}
|
|
57
|
+
disabled={state.disabled}
|
|
58
|
+
onClick={(e)=>{
|
|
59
|
+
if(state.readOnly) {
|
|
60
|
+
e.preventDefault()
|
|
61
|
+
e.stopPropagation()
|
|
62
|
+
}
|
|
63
|
+
}}
|
|
64
|
+
buttonText={state.readOnly ? '-' : state.value ? 'Change' : 'Choose'}
|
|
65
|
+
onChange={()=>{
|
|
66
|
+
onChange({mode: 'file', value: fileInputRef.current?.files?.[0]??null})
|
|
67
|
+
}} className="inputGroupFile" />
|
|
68
|
+
)}
|
|
69
|
+
{!state.readOnly && state.value && (
|
|
70
|
+
<Button minimal={true} icon="small-cross"
|
|
71
|
+
disabled={state.disabled}
|
|
72
|
+
onClick={()=>{
|
|
73
|
+
onChange({value: null})
|
|
74
|
+
}}></Button>
|
|
75
|
+
)}
|
|
76
|
+
</ControlGroup>
|
|
77
|
+
</div>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {FormGroup} from "@blueprintjs/core";
|
|
2
|
+
import React, {PropsWithChildren} from "react";
|
|
3
|
+
|
|
4
|
+
export function FormGroupComponent(props: PropsWithChildren<{ label: string, flexBasis?: string, disabled?: boolean }>) {
|
|
5
|
+
return (
|
|
6
|
+
<FormGroup className="xPaddedContent folderContent" contentClassName="formGroupContent"
|
|
7
|
+
style={{
|
|
8
|
+
justifyContent: "space-between",
|
|
9
|
+
marginTop: "2px",
|
|
10
|
+
marginBottom: "2px",
|
|
11
|
+
flexBasis: props.flexBasis ?? "50%"
|
|
12
|
+
}}
|
|
13
|
+
// helperText="Helper text with details..."
|
|
14
|
+
label={(<span title={props.label}>{props.label}</span>)}
|
|
15
|
+
inline={true}
|
|
16
|
+
disabled={props.disabled}
|
|
17
|
+
// labelInfo="(required)"
|
|
18
|
+
>
|
|
19
|
+
{props.children}
|
|
20
|
+
</FormGroup>
|
|
21
|
+
)
|
|
22
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {Button, Card, Panel, PanelStack2, Popover} from "@blueprintjs/core";
|
|
3
|
+
import {ConfigObject, ConfigProps} from "../ConfigObject";
|
|
4
|
+
import {UiObjectConfig} from 'uiconfig.js'
|
|
5
|
+
import {UiConfigRendererContext} from '../bpComponents/BPComponent'
|
|
6
|
+
import {Classes} from '@blueprintjs/core/src/common'
|
|
7
|
+
import {defaultVisualStyle, ThemeSettingsMenuComponent, VisualStyleContext} from './VisualStyleContext'
|
|
8
|
+
|
|
9
|
+
// import {ViewerAppContext} from "./ViewerAppComponent";
|
|
10
|
+
|
|
11
|
+
export function InspectorStackComponent({config}: { config: UiObjectConfig<any, 'panel'> }) {
|
|
12
|
+
const renderer = React.useContext(UiConfigRendererContext)
|
|
13
|
+
const stackItem = React.useCallback(() => {
|
|
14
|
+
return {
|
|
15
|
+
props: {config: config},
|
|
16
|
+
renderPanel(props: ConfigProps) {
|
|
17
|
+
// console.log(props.config === config)
|
|
18
|
+
return (
|
|
19
|
+
<ul style={{listStyleType: "none", paddingLeft: "0", margin: "0"}}>
|
|
20
|
+
{/*{v && (<ConfigObject {...props}/>)}*/}
|
|
21
|
+
{config && (<ConfigObject {...props} isPanel={true}/>)}
|
|
22
|
+
</ul>
|
|
23
|
+
)
|
|
24
|
+
},
|
|
25
|
+
title: renderer.methods.getLabel(config)
|
|
26
|
+
} as Panel<{ config: UiObjectConfig }>
|
|
27
|
+
}, [config])
|
|
28
|
+
const [currentPanelStack, setCurrentPanelStack] = React.useState<Array<Panel<{ config: UiObjectConfig }>>>([stackItem()]);
|
|
29
|
+
React.useEffect(() => {
|
|
30
|
+
// console.warn('change', config)
|
|
31
|
+
setCurrentPanelStack([stackItem()])
|
|
32
|
+
}, [config])
|
|
33
|
+
|
|
34
|
+
const localStoragePrefix = 'bpUi'
|
|
35
|
+
const localStorageIds = {
|
|
36
|
+
'darkMode': localStoragePrefix + 'DarkMode',
|
|
37
|
+
'theme': localStoragePrefix + 'Theme',
|
|
38
|
+
}
|
|
39
|
+
const [darkMode, setDarkMode] = React.useState(localStorage.getItem(localStorageIds.darkMode) !== (defaultVisualStyle.darkMode ? 'false' : 'true'))
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
localStorage.setItem('bpUiDarkMode', darkMode ? 'true' : 'false')
|
|
42
|
+
}, [darkMode])
|
|
43
|
+
const [theme, setTheme] = React.useState(localStorage.getItem(localStorageIds.theme) || defaultVisualStyle.theme)
|
|
44
|
+
React.useEffect(() => {
|
|
45
|
+
localStorage.setItem('bpUiTheme', theme)
|
|
46
|
+
document.documentElement.classList.remove(...Object.keys(defaultVisualStyle.themes).map(k => k))
|
|
47
|
+
document.documentElement.classList.add(theme)
|
|
48
|
+
}, [theme])
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<VisualStyleContext.Provider value={{...defaultVisualStyle, darkMode, setDarkMode, theme, setTheme}}>
|
|
52
|
+
<Card id="bpInspectorCard" className={darkMode?Classes.DARK:""} style={{height: "100%", flexGrow: "1", position: "relative"}}>
|
|
53
|
+
<PanelStack2 className="inspectorPanelStack"
|
|
54
|
+
showPanelHeader={true}
|
|
55
|
+
renderActivePanelOnly={true}
|
|
56
|
+
onOpen={(p) => setCurrentPanelStack([...currentPanelStack, p] as any)}
|
|
57
|
+
onClose={() => setCurrentPanelStack(currentPanelStack.slice(0, -1))}
|
|
58
|
+
stack={currentPanelStack}/>
|
|
59
|
+
<div className={"bpPanelStackHeaderOverlay " + Classes.PANEL_STACK2_HEADER}>
|
|
60
|
+
<Popover targetProps={{style: {position: "absolute", right: "5px"}}}
|
|
61
|
+
minimal={true}
|
|
62
|
+
targetTagName={"div"}
|
|
63
|
+
content={
|
|
64
|
+
<ThemeSettingsMenuComponent/>
|
|
65
|
+
} placement="bottom">
|
|
66
|
+
<Button icon="cog" small minimal text="" />
|
|
67
|
+
</Popover>
|
|
68
|
+
</div>
|
|
69
|
+
</Card>
|
|
70
|
+
</VisualStyleContext.Provider>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react'
|
|
2
|
+
import {Menu, MenuItem} from '@blueprintjs/core'
|
|
3
|
+
import {Classes} from '@blueprintjs/core/src/common'
|
|
4
|
+
|
|
5
|
+
export const defaultVisualStyle = {
|
|
6
|
+
darkMode: true,
|
|
7
|
+
setDarkMode: (_: boolean) => {},
|
|
8
|
+
themes: {
|
|
9
|
+
// https://github.com/pnnl/blueprint-styler/blob/9a2f5461f6e8682f829bd9e1722c5c4c051f7c0f/src/styles/style-manifest.ts#L12
|
|
10
|
+
'bpx-default': { label: 'Blueprint Default', link: 'https://blueprintjs.com/docs/#blueprint' },
|
|
11
|
+
'bpx-bpv3': { label: 'Blueprint v3', link: 'https://blueprintjs.com/docs/versions/3/' },
|
|
12
|
+
'bpx-flat': { label: 'Flat', link: 'https://pnnl.github.io/blueprint-styler/?style=bpx-flat' },
|
|
13
|
+
'bpx-carbon': { label: 'IBM Carbon', link: 'https://carbondesignsystem.com/components/overview/' },
|
|
14
|
+
'bpx-antd': { label: 'Ant Design', link: 'https://ant.design/components/overview/' },
|
|
15
|
+
'bpx-fluent': { label: 'Microsoft Fluent', link: 'https://fluentuipr.z22.web.core.windows.net/heads/master/theming-designer/index.html' },
|
|
16
|
+
'bpx-pnnl': { label: 'PNNL v3', link: 'https://forgedev.pnnl.gov/prc3/' },
|
|
17
|
+
// 'bpx-static': { label: 'None', link: 'https://blueprintjs.com/docs/#blueprint' },
|
|
18
|
+
},
|
|
19
|
+
theme: 'bpx-default',
|
|
20
|
+
setTheme: (_: string) => {},
|
|
21
|
+
}
|
|
22
|
+
export const VisualStyleContext = React.createContext<typeof defaultVisualStyle>(defaultVisualStyle)
|
|
23
|
+
export const useVisualStyle = () => React.useContext(VisualStyleContext)
|
|
24
|
+
|
|
25
|
+
export function ThemeSettingsMenuComponent() {
|
|
26
|
+
const visualStyle = useVisualStyle()
|
|
27
|
+
return <Menu className={Classes.ELEVATION_0} >
|
|
28
|
+
<MenuItem
|
|
29
|
+
icon="contrast"
|
|
30
|
+
text="Color"
|
|
31
|
+
// intent={intent}
|
|
32
|
+
// labelElement={"⌘,"}
|
|
33
|
+
roleStructure="menuitem"
|
|
34
|
+
children={
|
|
35
|
+
<>
|
|
36
|
+
<MenuItem icon="flash" text="Light Mode"
|
|
37
|
+
shouldDismissPopover={false}
|
|
38
|
+
selected={!visualStyle.darkMode}
|
|
39
|
+
onClick={() => visualStyle.setDarkMode(false)}
|
|
40
|
+
roleStructure="listoption"/>
|
|
41
|
+
<MenuItem icon="moon" text="Dark Mode"
|
|
42
|
+
shouldDismissPopover={false}
|
|
43
|
+
selected={visualStyle.darkMode}
|
|
44
|
+
onClick={() => visualStyle.setDarkMode(true)}
|
|
45
|
+
roleStructure="listoption"/>
|
|
46
|
+
</>
|
|
47
|
+
}
|
|
48
|
+
/>
|
|
49
|
+
<MenuItem
|
|
50
|
+
icon="style"
|
|
51
|
+
text="Theme"
|
|
52
|
+
// intent={intent}
|
|
53
|
+
// labelElement={"⌘,"}
|
|
54
|
+
roleStructure="menuitem"
|
|
55
|
+
children={
|
|
56
|
+
<>
|
|
57
|
+
{Object.entries(visualStyle.themes).map(([key, value]) => (
|
|
58
|
+
<MenuItem
|
|
59
|
+
shouldDismissPopover={false}
|
|
60
|
+
text={value.label}
|
|
61
|
+
selected={visualStyle.theme === key}
|
|
62
|
+
onClick={() => visualStyle.setTheme(key)}
|
|
63
|
+
roleStructure="listoption"/>
|
|
64
|
+
))}
|
|
65
|
+
</>
|
|
66
|
+
}
|
|
67
|
+
/>
|
|
68
|
+
</Menu>
|
|
69
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// https://github.com/palantir/blueprint/issues/2348#issuecomment-389322440
|
|
2
|
+
import React, {ReactNode} from "react";
|
|
3
|
+
|
|
4
|
+
export const bpIconWrapper = (path: ReactNode, viewbox = 16) => {
|
|
5
|
+
return (
|
|
6
|
+
<span className="bp5-icon">
|
|
7
|
+
<svg
|
|
8
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
9
|
+
x="0"
|
|
10
|
+
y="0"
|
|
11
|
+
width="16"
|
|
12
|
+
height="16"
|
|
13
|
+
role="img"
|
|
14
|
+
viewBox={`0 0 ${viewbox} ${viewbox}`}
|
|
15
|
+
>
|
|
16
|
+
{path}
|
|
17
|
+
</svg>
|
|
18
|
+
</span>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
|
|
2
|
+
.bpButton {
|
|
3
|
+
display: flex;
|
|
4
|
+
justify-content: space-between;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.bpIconButton {
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.bpPanelStackHeaderOverlay{
|
|
11
|
+
position: absolute;
|
|
12
|
+
top: 0;
|
|
13
|
+
left: 0;
|
|
14
|
+
width: 100%;
|
|
15
|
+
background: transparent;
|
|
16
|
+
box-shadow: none;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.hidden{
|
|
20
|
+
display: none;
|
|
21
|
+
}
|