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.
Files changed (97) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +129 -0
  3. package/dist/ConfigObject.d.ts +16 -0
  4. package/dist/UiConfigRendererBlueprint.d.ts +13 -0
  5. package/dist/allPaths-PklnL3F6.js +23 -0
  6. package/dist/allPaths-PklnL3F6.js.map +1 -0
  7. package/dist/allPathsLoader-2dOu0jQ3.js +27 -0
  8. package/dist/allPathsLoader-2dOu0jQ3.js.map +1 -0
  9. package/dist/bpComponents/BPButtonComponent.d.ts +8 -0
  10. package/dist/bpComponents/BPColorInputComponent.d.ts +25 -0
  11. package/dist/bpComponents/BPComponent.d.ts +25 -0
  12. package/dist/bpComponents/BPContainerComponent.d.ts +21 -0
  13. package/dist/bpComponents/BPDropdownInputComponent.d.ts +16 -0
  14. package/dist/bpComponents/BPFileComponent.d.ts +26 -0
  15. package/dist/bpComponents/BPFolderComponent.d.ts +16 -0
  16. package/dist/bpComponents/BPInputComponent.d.ts +11 -0
  17. package/dist/bpComponents/BPLabelledComponent.d.ts +8 -0
  18. package/dist/bpComponents/BPNumberInputComponent.d.ts +11 -0
  19. package/dist/bpComponents/BPPanelComponent.d.ts +17 -0
  20. package/dist/bpComponents/BPSliderInputComponent.d.ts +15 -0
  21. package/dist/bpComponents/BPTextInputComponent.d.ts +8 -0
  22. package/dist/bpComponents/BPToggleInputComponent.d.ts +9 -0
  23. package/dist/bpComponents/BPTreeComponent.d.ts +32 -0
  24. package/dist/bpComponents/BPTreeFolderComponent.d.ts +10 -0
  25. package/dist/bpComponents/BPValueComponent.d.ts +16 -0
  26. package/dist/bpComponents/BPVectorInputComponent.d.ts +20 -0
  27. package/dist/components/DraggableIcon.d.ts +17 -0
  28. package/dist/components/ExtendedNumericInput.d.ts +23 -0
  29. package/dist/components/FileComponent.d.ts +7 -0
  30. package/dist/components/FormGroupComponent.d.ts +6 -0
  31. package/dist/components/InspectorStackComponent.d.ts +5 -0
  32. package/dist/components/VisualStyleContext.d.ts +110 -0
  33. package/dist/components/bpIconWrapper.d.ts +2 -0
  34. package/dist/components/variableIcons.d.ts +41 -0
  35. package/dist/contexts.d.ts +1 -0
  36. package/dist/index-PTojlie-.js +29271 -0
  37. package/dist/index-PTojlie-.js.map +1 -0
  38. package/dist/index-iM9IChdK.js +604 -0
  39. package/dist/index-iM9IChdK.js.map +1 -0
  40. package/dist/index-tqa0R-I0.js +604 -0
  41. package/dist/index-tqa0R-I0.js.map +1 -0
  42. package/dist/index.d.ts +4 -0
  43. package/dist/index.js +292 -0
  44. package/dist/index.js.map +1 -0
  45. package/dist/index.mjs +12 -0
  46. package/dist/index.mjs.map +1 -0
  47. package/dist/splitPathsBySizeLoader-QNLjzk6A.js +36 -0
  48. package/dist/splitPathsBySizeLoader-QNLjzk6A.js.map +1 -0
  49. package/dist/threejs.d.ts +2 -0
  50. package/docs/.nojekyll +1 -0
  51. package/docs/assets/highlight.css +120 -0
  52. package/docs/assets/main.js +58 -0
  53. package/docs/assets/search.js +1 -0
  54. package/docs/assets/style.css +1279 -0
  55. package/docs/classes/UI.html +403 -0
  56. package/docs/classes/UiConfigRendererBlueprint.html +404 -0
  57. package/docs/index.html +134 -0
  58. package/docs/modules.html +50 -0
  59. package/package.json +127 -0
  60. package/src/ConfigObject.tsx +137 -0
  61. package/src/UiConfigRendererBlueprint.tsx +85 -0
  62. package/src/bpComponents/BPButtonComponent.scss +8 -0
  63. package/src/bpComponents/BPButtonComponent.tsx +27 -0
  64. package/src/bpComponents/BPColorInputComponent.tsx +138 -0
  65. package/src/bpComponents/BPComponent.tsx +93 -0
  66. package/src/bpComponents/BPContainerComponent.tsx +57 -0
  67. package/src/bpComponents/BPDropdownInputComponent.tsx +80 -0
  68. package/src/bpComponents/BPFileComponent.tsx +73 -0
  69. package/src/bpComponents/BPFolderComponent.scss +107 -0
  70. package/src/bpComponents/BPFolderComponent.tsx +91 -0
  71. package/src/bpComponents/BPInputComponent.tsx +33 -0
  72. package/src/bpComponents/BPLabelledComponent.tsx +19 -0
  73. package/src/bpComponents/BPNumberInputComponent.tsx +35 -0
  74. package/src/bpComponents/BPPanelComponent.tsx +50 -0
  75. package/src/bpComponents/BPSliderInputComponent.tsx +71 -0
  76. package/src/bpComponents/BPTextInputComponent.tsx +26 -0
  77. package/src/bpComponents/BPToggleInputComponent.tsx +31 -0
  78. package/src/bpComponents/BPTreeComponent.scss +3 -0
  79. package/src/bpComponents/BPTreeComponent.tsx +127 -0
  80. package/src/bpComponents/BPTreeFolderComponent.tsx +31 -0
  81. package/src/bpComponents/BPValueComponent.tsx +46 -0
  82. package/src/bpComponents/BPVectorInputComponent.tsx +163 -0
  83. package/src/bpComponents/index.scss +3 -0
  84. package/src/components/DraggableIcon.tsx +75 -0
  85. package/src/components/ExtendedNumericInput.tsx +219 -0
  86. package/src/components/FileComponent.tsx +79 -0
  87. package/src/components/FormGroupComponent.tsx +22 -0
  88. package/src/components/InspectorStackComponent.tsx +72 -0
  89. package/src/components/VisualStyleContext.tsx +69 -0
  90. package/src/components/bpIconWrapper.tsx +20 -0
  91. package/src/components/common.scss +21 -0
  92. package/src/components/variableIcons.tsx +177 -0
  93. package/src/contexts.ts +1 -0
  94. package/src/index.ts +8 -0
  95. package/src/renderer.scss +193 -0
  96. package/src/threejs.ts +3 -0
  97. 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
+ }