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,138 @@
|
|
|
1
|
+
import React, {createRef} from "react";
|
|
2
|
+
import {BPComponentProps, UiConfigRendererContextType} from "./BPComponent";
|
|
3
|
+
import {Button, Icon, InputGroup, Popover} from "@blueprintjs/core";
|
|
4
|
+
import {BPValueComponent, BPValueComponentState} from "./BPValueComponent";
|
|
5
|
+
import {HexColorPicker} from "react-colorful";
|
|
6
|
+
import {FormGroupComponent} from "../components/FormGroupComponent";
|
|
7
|
+
import {getOrCall} from 'ts-browser-helpers'
|
|
8
|
+
import type {Color, ColorRepresentation} from "three"
|
|
9
|
+
|
|
10
|
+
export type BPColorComponentState = BPValueComponentState<string> & {
|
|
11
|
+
mode: 'number' | 'string' | 'Color', lastValue?: ColorRepresentation,
|
|
12
|
+
srgbConvert: boolean
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
export class BPColorInputComponent extends BPValueComponent<ColorRepresentation, BPColorComponentState, string> {
|
|
17
|
+
private _inputRef = createRef<HTMLInputElement>()
|
|
18
|
+
private _tempColor: Color
|
|
19
|
+
|
|
20
|
+
constructor(props: BPComponentProps<ColorRepresentation>, context: UiConfigRendererContextType) {
|
|
21
|
+
super(props, context, {
|
|
22
|
+
label: 'Color',
|
|
23
|
+
value: "#000000",
|
|
24
|
+
mode: 'number',
|
|
25
|
+
srgbConvert: false,
|
|
26
|
+
});
|
|
27
|
+
if(!context.THREE?.Color) throw new Error('BPColorInputComponent requires THREE.Color to be available in context')
|
|
28
|
+
if(!this._tempColor) this._tempColor = new context.THREE.Color()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
convertValueToState(val: ColorRepresentation, state: BPColorComponentState): BPColorComponentState {
|
|
32
|
+
if(!this._tempColor) this._tempColor = new this.context.THREE!.Color()
|
|
33
|
+
let mode: BPColorComponentState['mode'] = state.mode
|
|
34
|
+
const srgbConvert = getOrCall(this.props.config.srgb) === true
|
|
35
|
+
if (typeof val === 'string') mode = 'string'
|
|
36
|
+
else if (typeof val === 'number') mode = 'number';
|
|
37
|
+
else if (val.isColor)
|
|
38
|
+
mode = 'Color'
|
|
39
|
+
else console.error('not supported color', val)
|
|
40
|
+
this._tempColor.set(val)
|
|
41
|
+
if (srgbConvert) this._tempColor.convertLinearToSRGB()
|
|
42
|
+
const value = '#' + this._tempColor.getHexString()
|
|
43
|
+
// console.log('color', mode, value, val)
|
|
44
|
+
return {...state, mode, value, lastValue: val, srgbConvert} // default config.srgb = false
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async convertStateToValue(state: BPColorComponentState): Promise<ColorRepresentation> {
|
|
48
|
+
if(!this._tempColor) this._tempColor = new this.context.THREE!.Color()
|
|
49
|
+
let val: ColorRepresentation = state.lastValue || 0
|
|
50
|
+
this._tempColor.set(state.value)
|
|
51
|
+
if (state.srgbConvert) this._tempColor.convertSRGBToLinear()
|
|
52
|
+
if (state.mode === 'number') {
|
|
53
|
+
val = this._tempColor.getHex()
|
|
54
|
+
} else if (state.mode === 'string') {
|
|
55
|
+
val = '#' + this._tempColor.getHexString();
|
|
56
|
+
} else if (state.mode === 'Color') {
|
|
57
|
+
val = ((state.lastValue as Color) ?? new this.context.THREE!.Color()).copy(this._tempColor)
|
|
58
|
+
}
|
|
59
|
+
return val;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
forceOnChange: boolean = true
|
|
63
|
+
|
|
64
|
+
doesNeedRefresh(state: BPColorComponentState, _?: boolean): boolean {
|
|
65
|
+
const eq = !super.doesNeedRefresh(state);
|
|
66
|
+
return ((state.value as any as Color)?.isColor && eq) ? true : !eq
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private _mouseButtons = 0
|
|
70
|
+
|
|
71
|
+
componentDidMount() {
|
|
72
|
+
super.componentDidMount();
|
|
73
|
+
document.addEventListener('mousemove', this._mouseMove)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private _mouseMove = (ev: MouseEvent) => {
|
|
77
|
+
this._mouseButtons = ev.buttons
|
|
78
|
+
if (!this._mouseButtons && this._hasMouseUp) this._mouseUp()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
componentWillUnmount() {
|
|
82
|
+
document.removeEventListener('mousemove', this._mouseMove)
|
|
83
|
+
this._mouseButtons = 0
|
|
84
|
+
super.componentWillUnmount();
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private _hasMouseUp = false
|
|
88
|
+
private _mouseUp = () => {
|
|
89
|
+
document.removeEventListener('mouseup', this._mouseMove)
|
|
90
|
+
if (!this._hasMouseUp) return
|
|
91
|
+
this._hasMouseUp = false
|
|
92
|
+
this.setValue(this._inputRef.current!.value, true)
|
|
93
|
+
|
|
94
|
+
if (this._hasMouseUp) return
|
|
95
|
+
document.addEventListener('mouseup', this._mouseUp)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
render() {
|
|
99
|
+
return !this.state.hidden ? (
|
|
100
|
+
<FormGroupComponent label={this.state.label}>
|
|
101
|
+
<InputGroup inputRef={this._inputRef}
|
|
102
|
+
disabled={this.state.disabled} readOnly={this.state.readOnly}
|
|
103
|
+
leftElement={(
|
|
104
|
+
<Popover
|
|
105
|
+
popoverClassName={'color-picker-popover'}
|
|
106
|
+
usePortal={false}
|
|
107
|
+
minimal={false}
|
|
108
|
+
content={
|
|
109
|
+
<HexColorPicker color={this.state.value}
|
|
110
|
+
onChange={(c) => {
|
|
111
|
+
if(this.state.readOnly || this.state.disabled) return
|
|
112
|
+
this._tempColor.set(c)
|
|
113
|
+
const v = '#' + this._tempColor.getHexString().toUpperCase()
|
|
114
|
+
this.setValue(v, false)
|
|
115
|
+
this._inputRef.current!.value = v
|
|
116
|
+
|
|
117
|
+
if (this._hasMouseUp) return
|
|
118
|
+
this._hasMouseUp = true
|
|
119
|
+
document.addEventListener('mouseup', this._mouseUp)
|
|
120
|
+
}
|
|
121
|
+
}/>
|
|
122
|
+
}
|
|
123
|
+
>
|
|
124
|
+
<Button
|
|
125
|
+
disabled={this.state.disabled || this.state.readOnly}
|
|
126
|
+
icon={<Icon icon="full-circle" color={this.state.value}/>}
|
|
127
|
+
text="" minimal
|
|
128
|
+
/>
|
|
129
|
+
</Popover>
|
|
130
|
+
)} key={this.props.config.uuid} defaultValue={this.state.value} onChange={(e) => {
|
|
131
|
+
const isHex = /^([A-Fa-f\d]+)$/.exec(e.target.value) // if someone pastes a color hex without #
|
|
132
|
+
this._tempColor.set((isHex ? '#' : '') + e.target.value)
|
|
133
|
+
this.setValue('#' + this._tempColor.getHexString().toUpperCase())
|
|
134
|
+
}}/>
|
|
135
|
+
</FormGroupComponent>
|
|
136
|
+
) : null
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import React, {createContext} from "react";
|
|
2
|
+
import type {UiConfigRendererBlueprint} from '../UiConfigRendererBlueprint'
|
|
3
|
+
import {UiObjectConfig} from 'uiconfig.js'
|
|
4
|
+
import {getOrCall} from 'ts-browser-helpers'
|
|
5
|
+
|
|
6
|
+
export type BPComponentProps<T> = {
|
|
7
|
+
config: UiObjectConfig<T>,
|
|
8
|
+
level?: number
|
|
9
|
+
}
|
|
10
|
+
export type BPComponentState = {
|
|
11
|
+
hidden?: boolean
|
|
12
|
+
disabled?: boolean
|
|
13
|
+
readOnly?: boolean // same as disabled(for most inputs)
|
|
14
|
+
}
|
|
15
|
+
export const UiConfigRendererContext = createContext<UiConfigRendererBlueprint>(null as any)
|
|
16
|
+
export type UiConfigRendererContextType = React.ContextType<typeof UiConfigRendererContext>
|
|
17
|
+
export abstract class BPComponent<TValue, TState, TProps extends BPComponentProps<TValue> = BPComponentProps<TValue>> extends React.Component<TProps, TState> {
|
|
18
|
+
static contextType = UiConfigRendererContext
|
|
19
|
+
context!: UiConfigRendererContextType
|
|
20
|
+
|
|
21
|
+
protected constructor(props: TProps, context: UiConfigRendererContextType, state: TState) {
|
|
22
|
+
super(props, context);
|
|
23
|
+
this.state = this.getUpdatedState(state)
|
|
24
|
+
// this.state = state
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Set the state(and rerender), then return a promise that resolves when the state is set
|
|
29
|
+
* @param state
|
|
30
|
+
*/
|
|
31
|
+
setStatePromise(state: TState) {
|
|
32
|
+
return new Promise<void>(resolve => this.setState(state, resolve))
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* This is called to get the updated state from the config, copy any values/properties from the config to the state here
|
|
37
|
+
* @param state - the current state
|
|
38
|
+
*/
|
|
39
|
+
getUpdatedState(state: TState): TState{
|
|
40
|
+
return {
|
|
41
|
+
...state,
|
|
42
|
+
hidden: getOrCall(this.props.config.hidden) ?? false,
|
|
43
|
+
disabled: getOrCall(this.props.config.disabled) ?? false,
|
|
44
|
+
readOnly: getOrCall(this.props.config.readOnly) ?? false,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
private _refreshing = false;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Refreshes the state from the config
|
|
52
|
+
*
|
|
53
|
+
* This is called by the ui component when the value is changed and from uiRefresh from the config
|
|
54
|
+
* @param state - optional state to refresh from, if not provided, the current state is used
|
|
55
|
+
*/
|
|
56
|
+
async refreshConfigState(state?: TState) {
|
|
57
|
+
if (!this.props.config) return;
|
|
58
|
+
if (this._refreshing) return;
|
|
59
|
+
this._refreshing = true
|
|
60
|
+
const s = this.getUpdatedState(state || this.state)
|
|
61
|
+
// console.log('set state', s)
|
|
62
|
+
await this.setStatePromise(s)
|
|
63
|
+
// todo debug refresh frequency etc
|
|
64
|
+
// console.log('refreshing', this.props.config.label, this.props.config.type, this.state)
|
|
65
|
+
this._refreshing = false
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Add reference to this to uiRef and set uiRefresh to the config to refresh this component
|
|
70
|
+
*
|
|
71
|
+
* also refreshes the config once which re-renders (todo: check if this is required)
|
|
72
|
+
*/
|
|
73
|
+
componentDidMount() {
|
|
74
|
+
super.componentDidMount?.();
|
|
75
|
+
this.props.config.uiRef = this
|
|
76
|
+
this.props.config.uiRefresh = (deep = false, mode = 'postFrame', delay = 0) => {
|
|
77
|
+
// console.log('refresh', this.props.config.label, this.props.config.type)
|
|
78
|
+
this.context.addToRefreshQueue(mode, this.props.config, deep, delay)
|
|
79
|
+
}
|
|
80
|
+
// console.log('mount', this.props.config.label, this.props.config.type, this.props.config)
|
|
81
|
+
this.props.config.uiRefresh()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
componentWillUnmount() {
|
|
85
|
+
// console.log('unmount', this.props.config.label, this.props.config.type, this.props.config)
|
|
86
|
+
this.props.config.uiRef = undefined
|
|
87
|
+
this.props.config.uiRefresh = undefined
|
|
88
|
+
super.componentWillUnmount?.();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// abstract updateStateValue(state: TState): Promise<void>;
|
|
92
|
+
|
|
93
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {BPLabelledComponent, BPLabelledComponentState} from './BPLabelledComponent'
|
|
2
|
+
import {BPComponentProps, UiConfigRendererContextType} from './BPComponent'
|
|
3
|
+
import {PanelActions} from '@blueprintjs/core/lib/esm/components/panel-stack2/panelTypes'
|
|
4
|
+
import {UiObjectConfig} from 'uiconfig.js'
|
|
5
|
+
import {getOrCall} from 'ts-browser-helpers'
|
|
6
|
+
|
|
7
|
+
export type BPContainerComponentState = BPLabelledComponentState & {
|
|
8
|
+
children: UiObjectConfig[]
|
|
9
|
+
expanded: boolean
|
|
10
|
+
}
|
|
11
|
+
export class BPContainerComponent<TState extends BPContainerComponentState=BPContainerComponentState> extends BPLabelledComponent<void, TState, BPComponentProps<void> & PanelActions> {
|
|
12
|
+
|
|
13
|
+
protected _childParentOnChange: UiObjectConfig['parentOnChange'] = (ev, ...args) => {
|
|
14
|
+
console.warn('child change', ev, this.props.config)
|
|
15
|
+
this.context.methods.dispatchOnChangeSync(this.props.config, {...ev}, ...args)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
protected _registerChild(child: UiObjectConfig) {
|
|
19
|
+
child.parentOnChange = this._childParentOnChange
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
protected _unregisterChild(child: UiObjectConfig) {
|
|
23
|
+
if (child.parentOnChange !== this._childParentOnChange)
|
|
24
|
+
child.parentOnChange = undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
constructor(props: BPComponentProps<void> & PanelActions, context: UiConfigRendererContextType, state: TState) {
|
|
28
|
+
super(props, context, state);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getUpdatedState(state: TState) {
|
|
32
|
+
const oldChildren = state.children
|
|
33
|
+
const newChildren = this.context.methods.getChildren(this.props.config)
|
|
34
|
+
const removed = oldChildren.filter(c => !newChildren.includes(c))
|
|
35
|
+
const added = newChildren.filter(c => !oldChildren.includes(c))
|
|
36
|
+
// console.warn(this, this._registerChild, this._childParentOnChange, added, removed)
|
|
37
|
+
removed.forEach(c => this._unregisterChild(c))
|
|
38
|
+
added.forEach(c => this._registerChild(c))
|
|
39
|
+
return {
|
|
40
|
+
...super.getUpdatedState(state),
|
|
41
|
+
// children: (this.props.config.children || []).map(c => getOrCall(c) || {}).flat(2),
|
|
42
|
+
children: newChildren,
|
|
43
|
+
expanded: getOrCall(this.props.config.expanded) ?? false,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
componentDidMount() {
|
|
48
|
+
super.componentDidMount?.();
|
|
49
|
+
this.state.children.forEach(c => this._registerChild(c))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
componentWillUnmount() {
|
|
53
|
+
super.componentWillUnmount?.();
|
|
54
|
+
this.state.children.forEach(c => this._unregisterChild(c))
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {BPComponentProps, UiConfigRendererContextType} from "./BPComponent";
|
|
3
|
+
import {Button, MenuItem} from "@blueprintjs/core";
|
|
4
|
+
import {BPInputComponent} from "./BPInputComponent";
|
|
5
|
+
import {BPValueComponentState} from "./BPValueComponent";
|
|
6
|
+
import {ItemRenderer, Select} from "@blueprintjs/select";
|
|
7
|
+
|
|
8
|
+
export interface DropdownItem{
|
|
9
|
+
label: string,
|
|
10
|
+
value: string|number,
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type BPDropdownComponentState = BPValueComponentState<string|number> & {
|
|
14
|
+
options: DropdownItem[]
|
|
15
|
+
}
|
|
16
|
+
const renderDropdownItem: ItemRenderer<DropdownItem> = (item, { handleClick, handleFocus, modifiers }) => {
|
|
17
|
+
if (!modifiers.matchesPredicate) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return (
|
|
21
|
+
<MenuItem
|
|
22
|
+
text={item.label}
|
|
23
|
+
label={''}
|
|
24
|
+
roleStructure="listoption"
|
|
25
|
+
active={modifiers.active}
|
|
26
|
+
key={item.value}
|
|
27
|
+
onClick={handleClick}
|
|
28
|
+
onFocus={handleFocus}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class BPDropdownInputComponent extends BPInputComponent<string|number, BPDropdownComponentState> {
|
|
34
|
+
constructor(props: BPComponentProps<string|number>, context: UiConfigRendererContextType) {
|
|
35
|
+
super(props, context, {
|
|
36
|
+
value: '0',
|
|
37
|
+
label: 'Dropdown',
|
|
38
|
+
options: [{label: 'none', value: ''}]
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getUpdatedState(state: BPDropdownComponentState): BPDropdownComponentState {
|
|
43
|
+
const children = this.context.methods.getChildren(this.props.config)
|
|
44
|
+
const options: DropdownItem[] = children.map(value => {
|
|
45
|
+
const label = this.context.methods.getLabel(value)
|
|
46
|
+
return {label, value: value!.value ?? label}
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
// const selected = options.find(o=>o.value===val)||options[0]
|
|
50
|
+
return super.getUpdatedState({
|
|
51
|
+
...state, options
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
renderInput() {
|
|
56
|
+
const item = this.state.options.find(o=>o.value===this.state.value)||this.state.options[0]
|
|
57
|
+
return (
|
|
58
|
+
<Select<DropdownItem>
|
|
59
|
+
filterable={false}
|
|
60
|
+
activeItem={item}
|
|
61
|
+
key={this.props.config.uuid}
|
|
62
|
+
disabled={this.state.disabled}
|
|
63
|
+
popoverProps={{
|
|
64
|
+
minimal: true
|
|
65
|
+
}}
|
|
66
|
+
noResults={<MenuItem disabled={true} text="No results." roleStructure="listoption" />}
|
|
67
|
+
items={this.state.options}
|
|
68
|
+
onItemSelect={(item)=> {
|
|
69
|
+
if(this.state.readOnly) return
|
|
70
|
+
this.setValue(item.value)
|
|
71
|
+
}}
|
|
72
|
+
itemRenderer={renderDropdownItem}
|
|
73
|
+
>
|
|
74
|
+
{/* children become the popover target; render value here */}
|
|
75
|
+
<Button rightIcon="double-caret-vertical" small={true}
|
|
76
|
+
style={{whiteSpace: 'nowrap', textOverflow: "ellipsis"}}>{item.label}</Button>
|
|
77
|
+
</Select>
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import {FileComponent} from "../components/FileComponent";
|
|
3
|
+
import {BPComponentProps, UiConfigRendererContextType} from "./BPComponent";
|
|
4
|
+
import {BPValueComponent, BPValueComponentState} from "./BPValueComponent";
|
|
5
|
+
import {FormGroupComponent} from "../components/FormGroupComponent";
|
|
6
|
+
|
|
7
|
+
type StateValue = string | File | null
|
|
8
|
+
type FileImportType = any
|
|
9
|
+
export type BPFileComponentState = BPValueComponentState<StateValue> & {
|
|
10
|
+
mode: 'url'|'file',
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
type BPFileComponentExtras = {fileLoader?: {load: (v: string|File|{path: string, file: File|Blob})=>Promise<FileImportType>}}
|
|
14
|
+
type BPFileComponentContextType = UiConfigRendererContextType & BPFileComponentExtras
|
|
15
|
+
|
|
16
|
+
export class BPFileComponent extends BPValueComponent<FileImportType | null, BPFileComponentState, StateValue> {
|
|
17
|
+
context!: BPFileComponentContextType
|
|
18
|
+
props!: BPComponentProps<FileImportType | null> & BPFileComponentExtras
|
|
19
|
+
constructor(props: BPComponentProps<FileImportType | null> & BPFileComponentExtras, context: BPFileComponentContextType) {
|
|
20
|
+
super(props, context, {
|
|
21
|
+
mode: 'file',
|
|
22
|
+
value: null,
|
|
23
|
+
label: 'File'
|
|
24
|
+
});
|
|
25
|
+
if(!context.fileLoader && !props.fileLoader) throw new Error('BPFileComponent requires fileLoader to be available in context')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
convertValueToState(val: FileImportType | null, state: BPFileComponentState): BPFileComponentState {
|
|
29
|
+
let mode: BPFileComponentState['mode'] = state.mode
|
|
30
|
+
let value = state.value
|
|
31
|
+
if (val) { // todo: move to subclass in threepipe
|
|
32
|
+
if (val.userData) {
|
|
33
|
+
if (val.userData.__sourceBlob) {
|
|
34
|
+
value = val.userData.__sourceBlob
|
|
35
|
+
mode = 'file'
|
|
36
|
+
} else if (val.userData.rootPath?.length) {
|
|
37
|
+
value = val.userData.rootPath
|
|
38
|
+
mode = 'url'
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {...state, mode, value}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async convertStateToValue(state: BPFileComponentState): Promise<FileImportType|null> {
|
|
46
|
+
const value = state.value
|
|
47
|
+
let val: any
|
|
48
|
+
if (value === null) {
|
|
49
|
+
val = null
|
|
50
|
+
} else if (typeof value === 'string') {
|
|
51
|
+
val = await (this.context.fileLoader ?? this.props.fileLoader)!.load(value);
|
|
52
|
+
} else {
|
|
53
|
+
// todo handle blob with no name
|
|
54
|
+
val = await (this.context.fileLoader ?? this.props.fileLoader)!.load({path: value.name || 'file', file: value});
|
|
55
|
+
}
|
|
56
|
+
// console.log(val)
|
|
57
|
+
return val;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// doesNeedRefresh(state: BPFileComponentState): boolean {
|
|
61
|
+
// return super.doesNeedRefresh(state);
|
|
62
|
+
// }
|
|
63
|
+
|
|
64
|
+
render() {
|
|
65
|
+
return !this.state.hidden ? (
|
|
66
|
+
<FormGroupComponent label={this.state.label} flexBasis={"100%"} disabled={this.state.disabled}>
|
|
67
|
+
<FileComponent state={this.state} onChange={(s) => {
|
|
68
|
+
this.updateStateValue({...this.state, ...s}); // todo: set loading while this promise is happening.
|
|
69
|
+
}} key={this.props.config.uuid}/>
|
|
70
|
+
</FormGroupComponent>
|
|
71
|
+
) : null
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
@import "~@blueprintjs/core/lib/scss/variables.scss";
|
|
2
|
+
|
|
3
|
+
// todo: style for bp5-disabled
|
|
4
|
+
|
|
5
|
+
.folder-children {
|
|
6
|
+
gap: 0;
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: row;
|
|
9
|
+
flex-wrap: wrap;
|
|
10
|
+
padding-top: 2px;
|
|
11
|
+
padding-bottom: 2px;
|
|
12
|
+
align-items: flex-start;
|
|
13
|
+
|
|
14
|
+
> div {
|
|
15
|
+
display: contents;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.folder-card {
|
|
20
|
+
user-select: none;
|
|
21
|
+
width: 100%;
|
|
22
|
+
//padding-top: $pt-grid-size !important;
|
|
23
|
+
//padding-bottom: $pt-grid-size !important;
|
|
24
|
+
padding: 0 !important;
|
|
25
|
+
//border-top: 1px solid #444 !important;
|
|
26
|
+
//border-bottom: 1px solid #444 !important;
|
|
27
|
+
display: flex;
|
|
28
|
+
flex-direction: column;
|
|
29
|
+
background-color: $light-gray4 !important;
|
|
30
|
+
}
|
|
31
|
+
.bp5-dark .folder-card {
|
|
32
|
+
background-color: $dark-gray3 !important;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
//.folder-head-label{
|
|
37
|
+
// margin-bottom: 0;
|
|
38
|
+
// user-select: none;
|
|
39
|
+
// //line-height: $pt-input-height !important;
|
|
40
|
+
// padding: $pt-grid-size !important;
|
|
41
|
+
// width: auto;
|
|
42
|
+
// cursor: pointer;
|
|
43
|
+
//}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
.folder-trigger-button {
|
|
47
|
+
margin-top: 1px;
|
|
48
|
+
display: flex;
|
|
49
|
+
align-items: center;
|
|
50
|
+
justify-content: space-between;
|
|
51
|
+
transition: background-color 0.1s ease-in-out;
|
|
52
|
+
background-color: $light-gray5 !important;
|
|
53
|
+
|
|
54
|
+
&:hover {
|
|
55
|
+
background-color: $light-gray3 !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&:active {
|
|
59
|
+
background-color: $gray4 !important;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
.bp5-dark .folder-trigger-button {
|
|
63
|
+
background-color: $dark-gray1 !important;
|
|
64
|
+
|
|
65
|
+
&:hover {
|
|
66
|
+
background-color: $dark-gray3 !important;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
&:active {
|
|
70
|
+
background-color: $dark-gray5 !important;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//.folder-trigger-button:focus{
|
|
75
|
+
// outline: none !important;
|
|
76
|
+
//}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
.folder-head-card-minimal {
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: flex-end;
|
|
83
|
+
flex-direction: row;
|
|
84
|
+
|
|
85
|
+
> .folder-trigger-button {
|
|
86
|
+
margin-right: $pt-grid-size;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.folder-head-card {
|
|
91
|
+
display: flex;
|
|
92
|
+
align-items: center;
|
|
93
|
+
justify-content: space-between;
|
|
94
|
+
flex-direction: row;
|
|
95
|
+
box-shadow: 0 1px $pt-divider-black;
|
|
96
|
+
z-index: 1;
|
|
97
|
+
|
|
98
|
+
> button {
|
|
99
|
+
border-radius: 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.bp5-dark & {
|
|
103
|
+
box-shadow: 0 1px $pt-dark-divider-white;
|
|
104
|
+
//background-color: $dark-gray1;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React, {DOMAttributes} from "react";
|
|
2
|
+
import {BPComponentProps, UiConfigRendererContextType} from "./BPComponent";
|
|
3
|
+
import {Button, Collapse, Icon, Intent} from "@blueprintjs/core";
|
|
4
|
+
import {ConfigObject} from "../ConfigObject";
|
|
5
|
+
import {PanelActions} from "@blueprintjs/core/lib/esm/components/panel-stack2/panelTypes";
|
|
6
|
+
import {safeSetProperty} from 'ts-browser-helpers'
|
|
7
|
+
import {BPContainerComponent, BPContainerComponentState} from './BPContainerComponent'
|
|
8
|
+
|
|
9
|
+
export type BPFolderComponentState = BPContainerComponentState & {
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export class BPFolderComponent extends BPContainerComponent<BPFolderComponentState> {
|
|
13
|
+
constructor(props: BPComponentProps<void>&PanelActions, context: UiConfigRendererContextType) {
|
|
14
|
+
super(props, context, {children: [], expanded: false, label: 'Folder'});
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
render() {
|
|
18
|
+
const setExpanded = (e: boolean) => {
|
|
19
|
+
if (e === this.state.expanded) return
|
|
20
|
+
this.setState({...this.state, expanded: e})
|
|
21
|
+
safeSetProperty(this.props.config, "expanded", e, true)
|
|
22
|
+
// if (e) this.state.children.forEach(c => Array.isArray(c) ? null : c.uiRefresh?.("postFrame", true, 1)) // todo: handle array and functions
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return !this.state.hidden ? (
|
|
26
|
+
<FolderHeadCard
|
|
27
|
+
key={this.props.config.uuid}
|
|
28
|
+
open={this.state.expanded}
|
|
29
|
+
disabled={this.state.disabled}
|
|
30
|
+
minimal={(this.props.level ?? 0) > 0}
|
|
31
|
+
onClick={() => {
|
|
32
|
+
if(this.state.readOnly) return
|
|
33
|
+
setExpanded(!this.state.expanded)
|
|
34
|
+
}}
|
|
35
|
+
label={this.state.label}
|
|
36
|
+
>
|
|
37
|
+
<Collapse isOpen={this.state.expanded} keepChildrenMounted={false}>
|
|
38
|
+
<div className="folder-children" style={{listStyleType: "none", paddingLeft: "0"}}>
|
|
39
|
+
{this.state.children.map((c, i) => <ConfigObject key={'c' + i} {...this.props} config={c}
|
|
40
|
+
level={(this.props.level ?? 0) + 1}/>)}
|
|
41
|
+
</div>
|
|
42
|
+
</Collapse>
|
|
43
|
+
</FolderHeadCard>
|
|
44
|
+
) : null
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export const FolderHeadCard: React.FC<React.PropsWithChildren<{ open: boolean, label: string, minimal: boolean, disabled?: boolean, onClick: DOMAttributes<HTMLElement>['onClick'] }>> = (props) => {
|
|
49
|
+
return (
|
|
50
|
+
<div
|
|
51
|
+
// interactive={!props.open}
|
|
52
|
+
className="folder-card"
|
|
53
|
+
// elevation={props.open ? 3 : undefined}
|
|
54
|
+
>
|
|
55
|
+
<div className={props.minimal ? 'folder-head-card-minimal' : 'folder-head-card'}>
|
|
56
|
+
{/*<H6 className="folder-head-label"*/}
|
|
57
|
+
{/* onClick={props.onClick}>{props.label}</H6>*/}
|
|
58
|
+
{/*<Icon icon="chevron-right" style={{*/}
|
|
59
|
+
{/* rotate: props.open ? "90deg" : "0deg",*/}
|
|
60
|
+
{/* transition: "rotate 0.25s ease-in-out"*/}
|
|
61
|
+
{/*}} className="folder-head-label-icon"/>*/}
|
|
62
|
+
|
|
63
|
+
{/*<Button className="folder-trigger-button" onClick={props.onClick}*/}
|
|
64
|
+
{/* fill={true} minimal={true}*/}
|
|
65
|
+
{/* rightIcon={(*/}
|
|
66
|
+
{/* <Icon icon="chevron-right" style={{*/}
|
|
67
|
+
{/* rotate: props.open ? "90deg" : "0deg",*/}
|
|
68
|
+
{/* transition: "rotate 0.25s ease-in-out"*/}
|
|
69
|
+
{/* }} className="folder-head-label-icon"/>*/}
|
|
70
|
+
{/* )}>{props.label}*/}
|
|
71
|
+
{/*</Button>*/}
|
|
72
|
+
|
|
73
|
+
<Button
|
|
74
|
+
className="folder-trigger-button" fill={!props.minimal} onClick={props.onClick}
|
|
75
|
+
minimal={true}
|
|
76
|
+
disabled={props.disabled}
|
|
77
|
+
small={props.minimal}
|
|
78
|
+
style={props.minimal ? {} : {fontSize: "0.95rem", paddingTop: "8px", paddingBottom: "8px"}}
|
|
79
|
+
intent={props.open ? Intent.PRIMARY : Intent.NONE}
|
|
80
|
+
rightIcon={(
|
|
81
|
+
<Icon icon="chevron-right" style={{
|
|
82
|
+
rotate: props.open ? "90deg" : "0deg",
|
|
83
|
+
transition: "rotate 0.25s ease-in-out"
|
|
84
|
+
}}/>
|
|
85
|
+
)}>{props.label}
|
|
86
|
+
</Button>
|
|
87
|
+
</div>
|
|
88
|
+
{props.children}
|
|
89
|
+
</div>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {BPValueComponent, BPValueComponentState} from "./BPValueComponent";
|
|
2
|
+
import {BPComponentProps, UiConfigRendererContextType} from "./BPComponent";
|
|
3
|
+
import React, {ReactNode} from "react";
|
|
4
|
+
import {FormGroupComponent} from "../components/FormGroupComponent";
|
|
5
|
+
|
|
6
|
+
export abstract class BPInputComponent<
|
|
7
|
+
TStateValue, TState extends BPValueComponentState<TStateValue> = BPValueComponentState<TStateValue>>
|
|
8
|
+
extends BPValueComponent<TStateValue, TState, TStateValue> {
|
|
9
|
+
|
|
10
|
+
protected constructor(props: BPComponentProps<TStateValue>, context: UiConfigRendererContextType, state: TState) {
|
|
11
|
+
super(props, context, state);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
convertValueToState(value: TStateValue, state: TState): TState {
|
|
15
|
+
return {...state, value}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async convertStateToValue(state: TState): Promise<TStateValue> {
|
|
19
|
+
return state.value;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
abstract renderInput(): ReactNode;
|
|
23
|
+
|
|
24
|
+
protected flexBasis = "100%"
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
return !this.state.hidden ? (
|
|
28
|
+
<FormGroupComponent disabled={this.state.disabled} label={this.state.label} flexBasis={this.flexBasis}>
|
|
29
|
+
{this.renderInput()}
|
|
30
|
+
</FormGroupComponent>
|
|
31
|
+
) : null
|
|
32
|
+
}
|
|
33
|
+
}
|