mirror of
https://github.com/fluxscape/fluxscape.git
synced 2026-01-12 23:32:55 +01:00
Initial commit
Co-Authored-By: Eric Tuvesson <eric.tuvesson@gmail.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: Anders Larsson <64838990+anders-topp@users.noreply.github.com> Co-Authored-By: Johan <4934465+joolsus@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: victoratndl <99176179+victoratndl@users.noreply.github.com>
This commit is contained in:
86
packages/noodl-viewer-react/src/nodes/controls/button.ts
Normal file
86
packages/noodl-viewer-react/src/nodes/controls/button.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { Button } from '../../components/controls/Button';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
import Utils from './utils';
|
||||
|
||||
const ButtonNode = {
|
||||
name: 'net.noodl.controls.button',
|
||||
displayName: 'Button',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/button',
|
||||
allowChildren: true,
|
||||
noodlNodeAsProp: true,
|
||||
usePortAsLabel: 'label',
|
||||
portLabelTruncationMode: 'length',
|
||||
nodeDoubleClickAction: {
|
||||
focusPort: 'label'
|
||||
},
|
||||
connectionPanel: {
|
||||
groupPriority: [
|
||||
'General',
|
||||
'Style',
|
||||
'Actions',
|
||||
'Events',
|
||||
'States',
|
||||
'Mounted',
|
||||
'Label',
|
||||
'Label Text Style',
|
||||
'Hover Events',
|
||||
'Pointer Events',
|
||||
'Focus Events'
|
||||
]
|
||||
},
|
||||
initialize() {
|
||||
this.props.layout = 'row'; //Used to tell child nodes what layout to expect
|
||||
},
|
||||
getReactComponent() {
|
||||
return Button;
|
||||
},
|
||||
inputCss: {
|
||||
backgroundColor: {
|
||||
index: 100,
|
||||
displayName: 'Background Color',
|
||||
group: 'Style',
|
||||
type: 'color',
|
||||
default: '#000000',
|
||||
allowVisualStates: true
|
||||
}
|
||||
},
|
||||
outputProps: {
|
||||
onClick: {
|
||||
displayName: 'Click',
|
||||
group: 'Events',
|
||||
type: 'signal'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeSharedPortDefinitions.addDimensions(ButtonNode, {
|
||||
defaultSizeMode: 'contentSize',
|
||||
contentLabel: 'Content'
|
||||
});
|
||||
NodeSharedPortDefinitions.addTextStyleInputs(ButtonNode);
|
||||
NodeSharedPortDefinitions.addAlignInputs(ButtonNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(ButtonNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(ButtonNode, {
|
||||
defaults: {
|
||||
paddingTop: 5,
|
||||
paddingRight: 20,
|
||||
paddingBottom: 5,
|
||||
paddingLeft: 20
|
||||
}
|
||||
});
|
||||
NodeSharedPortDefinitions.addMarginInputs(ButtonNode);
|
||||
NodeSharedPortDefinitions.addLabelInputs(ButtonNode, {
|
||||
defaults: { useLabel: true }
|
||||
});
|
||||
NodeSharedPortDefinitions.addIconInputs(ButtonNode, {
|
||||
enableIconPlacement: true,
|
||||
defaults: { useIcon: false }
|
||||
});
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(ButtonNode);
|
||||
NodeSharedPortDefinitions.addBorderInputs(ButtonNode);
|
||||
NodeSharedPortDefinitions.addShadowInputs(ButtonNode);
|
||||
|
||||
Utils.addControlEventsAndStates(ButtonNode);
|
||||
|
||||
export default createNodeFromReactComponent(ButtonNode);
|
||||
178
packages/noodl-viewer-react/src/nodes/controls/checkbox.ts
Normal file
178
packages/noodl-viewer-react/src/nodes/controls/checkbox.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { Checkbox } from '../../components/controls/Checkbox';
|
||||
import guid from '../../guid';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
import Utils from './utils';
|
||||
|
||||
const CheckBoxNode = {
|
||||
name: 'net.noodl.controls.checkbox',
|
||||
displayName: 'Checkbox',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/checkbox',
|
||||
allowChildren: false,
|
||||
noodlNodeAsProp: true,
|
||||
nodeDoubleClickAction: {
|
||||
focusPort: 'label'
|
||||
},
|
||||
usePortAsLabel: 'label',
|
||||
portLabelTruncationMode: 'length',
|
||||
connectionPanel: {
|
||||
groupPriority: [
|
||||
'General',
|
||||
'Style',
|
||||
'Actions',
|
||||
'Events',
|
||||
'States',
|
||||
'Mounted',
|
||||
'Label',
|
||||
'Label Text Style',
|
||||
'Hover Events',
|
||||
'Pointer Events',
|
||||
'Focus Events'
|
||||
]
|
||||
},
|
||||
initialize() {
|
||||
this.props.sizeMode = 'explicit';
|
||||
this.props.id = 'input-' + guid();
|
||||
this.props.checked = this._internal.checked = false;
|
||||
|
||||
this.props.checkedChanged = (checked) => {
|
||||
const changed = this._internal.checked !== checked;
|
||||
this._internal.checked = checked;
|
||||
if (changed) {
|
||||
this.flagOutputDirty('checked');
|
||||
this.sendSignalOnOutput('onChange');
|
||||
this._updateVisualState();
|
||||
}
|
||||
};
|
||||
},
|
||||
getReactComponent() {
|
||||
return Checkbox;
|
||||
},
|
||||
inputs: {
|
||||
checked: {
|
||||
type: 'boolean',
|
||||
displayName: 'Checked',
|
||||
group: 'General',
|
||||
default: false,
|
||||
index: 100,
|
||||
set: function (value) {
|
||||
value = !!value;
|
||||
const changed = value !== this._internal.checked;
|
||||
this.props.checked = this._internal.checked = value;
|
||||
|
||||
if (changed) {
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('checked');
|
||||
this._updateVisualState();
|
||||
}
|
||||
}
|
||||
},
|
||||
check: {
|
||||
type: 'signal',
|
||||
displayName: 'Check',
|
||||
group: 'Actions',
|
||||
valueChangedToTrue() {
|
||||
if (this._internal.checked === true) return;
|
||||
|
||||
this.props.checked = this._internal.checked = true;
|
||||
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('checked');
|
||||
this._updateVisualState();
|
||||
}
|
||||
},
|
||||
uncheck: {
|
||||
type: 'signal',
|
||||
displayName: 'Uncheck',
|
||||
group: 'Actions',
|
||||
valueChangedToTrue() {
|
||||
if (this._internal.checked === false) return;
|
||||
|
||||
this.props.checked = this._internal.checked = false;
|
||||
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('checked');
|
||||
this._updateVisualState();
|
||||
}
|
||||
}
|
||||
},
|
||||
inputCss: {
|
||||
backgroundColor: {
|
||||
index: 201,
|
||||
displayName: 'Background Color',
|
||||
group: 'Style',
|
||||
type: 'color',
|
||||
default: 'transparent',
|
||||
applyDefault: false,
|
||||
allowVisualStates: true,
|
||||
styleTag: 'checkbox'
|
||||
},
|
||||
width: {
|
||||
index: 11,
|
||||
group: 'Dimensions',
|
||||
displayName: 'Width',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', 'vw', 'vh'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
default: 32,
|
||||
allowVisualStates: true,
|
||||
styleTag: 'checkbox'
|
||||
},
|
||||
height: {
|
||||
index: 12,
|
||||
group: 'Dimensions',
|
||||
displayName: 'Height',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', 'vw', 'vh'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
default: 32,
|
||||
allowVisualStates: true,
|
||||
styleTag: 'checkbox'
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
checked: {
|
||||
type: 'boolean',
|
||||
displayName: 'Checked',
|
||||
group: 'States',
|
||||
getter: function () {
|
||||
return this._internal.checked;
|
||||
}
|
||||
},
|
||||
onChange: {
|
||||
displayName: 'Changed',
|
||||
group: 'Events',
|
||||
type: 'signal'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeSharedPortDefinitions.addAlignInputs(CheckBoxNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(CheckBoxNode);
|
||||
NodeSharedPortDefinitions.addMarginInputs(CheckBoxNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(CheckBoxNode);
|
||||
NodeSharedPortDefinitions.addIconInputs(CheckBoxNode);
|
||||
NodeSharedPortDefinitions.addLabelInputs(CheckBoxNode, {
|
||||
enableSpacing: true,
|
||||
styleTag: 'label'
|
||||
});
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(CheckBoxNode);
|
||||
NodeSharedPortDefinitions.addBorderInputs(CheckBoxNode, {
|
||||
defaults: {
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 2,
|
||||
borderColor: '#000000',
|
||||
borderRadius: 3
|
||||
},
|
||||
styleTag: 'checkbox'
|
||||
});
|
||||
NodeSharedPortDefinitions.addShadowInputs(CheckBoxNode, {
|
||||
styleTag: 'checkbox'
|
||||
});
|
||||
Utils.addControlEventsAndStates(CheckBoxNode, { checked: true });
|
||||
|
||||
export default createNodeFromReactComponent(CheckBoxNode);
|
||||
171
packages/noodl-viewer-react/src/nodes/controls/options.ts
Normal file
171
packages/noodl-viewer-react/src/nodes/controls/options.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Select } from '../../components/controls/Select';
|
||||
import guid from '../../guid';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
import Utils from './utils';
|
||||
|
||||
const OptionsNode = {
|
||||
name: 'net.noodl.controls.options',
|
||||
displayName: 'Dropdown',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/dropdown',
|
||||
allowChildren: false,
|
||||
noodlNodeAsProp: true,
|
||||
usePortAsLabel: 'label',
|
||||
nodeDoubleClickAction: {
|
||||
focusPort: 'label'
|
||||
},
|
||||
connectionPanel: {
|
||||
groupPriority: [
|
||||
'General',
|
||||
'Style',
|
||||
'Actions',
|
||||
'Events',
|
||||
'States',
|
||||
'Mounted',
|
||||
'Text Style',
|
||||
'Label',
|
||||
'Label Text Style',
|
||||
'Hover Events',
|
||||
'Pointer Events',
|
||||
'Focus Events'
|
||||
]
|
||||
},
|
||||
initialize: function () {
|
||||
this._itemsChanged = () => {
|
||||
this.forceUpdate();
|
||||
};
|
||||
|
||||
this.props.id = 'input-' + guid();
|
||||
|
||||
this.props.valueChanged = (value) => {
|
||||
const changed = this._internal.value !== value;
|
||||
this._internal.value = value;
|
||||
if (changed) {
|
||||
this.flagOutputDirty('value');
|
||||
this.sendSignalOnOutput('onChange');
|
||||
}
|
||||
};
|
||||
},
|
||||
getReactComponent() {
|
||||
return Select;
|
||||
},
|
||||
inputs: {
|
||||
items: {
|
||||
type: 'array',
|
||||
displayName: 'Items',
|
||||
group: 'General',
|
||||
set: function (newValue) {
|
||||
if (this._internal.items !== newValue && this._internal.items !== undefined) {
|
||||
this._internal.items.off('change', this._itemsChanged);
|
||||
}
|
||||
this._internal.items = newValue;
|
||||
this._internal.items.on('change', this._itemsChanged);
|
||||
|
||||
this.props.items = this._internal.items;
|
||||
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
value: {
|
||||
type: 'string',
|
||||
displayName: 'Value',
|
||||
group: 'General',
|
||||
set: function (value) {
|
||||
if (value !== undefined && typeof value !== 'string') {
|
||||
if (value?.toString !== undefined) value = value.toString();
|
||||
else return;
|
||||
}
|
||||
|
||||
// // if value is passed in before the items, then items is undefined
|
||||
// if (this._internal.items) {
|
||||
// //make sure this is a valid value that exists in the dropdown. If it doesn't, deselect all options
|
||||
// value = this._internal.items.find((i) => i.Value === value) ? value : undefined;
|
||||
// }
|
||||
|
||||
const changed = value !== this._internal.value;
|
||||
this.props.value = this._internal.value = value;
|
||||
|
||||
if (changed) {
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('value');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
inputProps: {
|
||||
placeholder: {
|
||||
displayName: 'Placeholder',
|
||||
type: 'string',
|
||||
group: 'Placeholder'
|
||||
},
|
||||
placeholderOpacity: {
|
||||
group: 'Placeholder',
|
||||
displayName: 'Placeholder opacity',
|
||||
type: 'number',
|
||||
default: 0.5
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
value: {
|
||||
type: 'string',
|
||||
displayName: 'Value',
|
||||
group: 'States',
|
||||
getter: function () {
|
||||
return this._internal.value;
|
||||
}
|
||||
},
|
||||
onChange: {
|
||||
type: 'signal',
|
||||
displayName: 'Changed',
|
||||
group: 'Events'
|
||||
}
|
||||
},
|
||||
inputCss: {
|
||||
backgroundColor: {
|
||||
index: 100,
|
||||
displayName: 'Background Color',
|
||||
group: 'Style',
|
||||
type: 'color',
|
||||
default: 'transparent',
|
||||
styleTag: 'inputWrapper',
|
||||
allowVisualStates: true
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeSharedPortDefinitions.addDimensions(OptionsNode, {
|
||||
defaultSizeMode: 'contentSize',
|
||||
contentLabel: 'Content'
|
||||
});
|
||||
NodeSharedPortDefinitions.addAlignInputs(OptionsNode);
|
||||
NodeSharedPortDefinitions.addTextStyleInputs(OptionsNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(OptionsNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(OptionsNode, {
|
||||
styleTag: 'inputWrapper'
|
||||
});
|
||||
NodeSharedPortDefinitions.addMarginInputs(OptionsNode);
|
||||
NodeSharedPortDefinitions.addIconInputs(OptionsNode, {
|
||||
enableIconPlacement: true,
|
||||
defaults: { useIcon: false, iconColor: '#000000' }
|
||||
});
|
||||
NodeSharedPortDefinitions.addLabelInputs(OptionsNode, {
|
||||
enableSpacing: true,
|
||||
styleTag: 'label'
|
||||
});
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(OptionsNode);
|
||||
NodeSharedPortDefinitions.addBorderInputs(OptionsNode, {
|
||||
defaults: {
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 2,
|
||||
borderColor: '#000000',
|
||||
borderRadius: 5
|
||||
},
|
||||
styleTag: 'inputWrapper'
|
||||
});
|
||||
|
||||
NodeSharedPortDefinitions.addShadowInputs(OptionsNode, {
|
||||
styleTag: 'inputWrapper'
|
||||
});
|
||||
Utils.addControlEventsAndStates(OptionsNode);
|
||||
|
||||
export default createNodeFromReactComponent(OptionsNode);
|
||||
164
packages/noodl-viewer-react/src/nodes/controls/radiobutton.ts
Normal file
164
packages/noodl-viewer-react/src/nodes/controls/radiobutton.ts
Normal file
@@ -0,0 +1,164 @@
|
||||
import { RadioButton } from '../../components/controls/RadioButton';
|
||||
import guid from '../../guid';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
import Utils from './utils';
|
||||
|
||||
const RadioButtonNode = {
|
||||
name: 'net.noodl.controls.radiobutton',
|
||||
displayName: 'Radio Button',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/radio-button',
|
||||
allowChildren: false,
|
||||
noodlNodeAsProp: true,
|
||||
usePortAsLabel: 'label',
|
||||
nodeDoubleClickAction: {
|
||||
focusPort: 'label'
|
||||
},
|
||||
connectionPanel: {
|
||||
groupPriority: [
|
||||
'General',
|
||||
'Style',
|
||||
'Fill Style',
|
||||
'Actions',
|
||||
'Events',
|
||||
'States',
|
||||
'Mounted',
|
||||
'Label',
|
||||
'Label Text Style',
|
||||
'Hover Events',
|
||||
'Pointer Events',
|
||||
'Focus Events'
|
||||
]
|
||||
},
|
||||
initialize() {
|
||||
this.props.sizeMode = 'explicit';
|
||||
this.props.id = 'input-' + guid();
|
||||
|
||||
this._internal.checked = false;
|
||||
|
||||
this.props.checkedChanged = (checked) => {
|
||||
const changed = this._internal.checked !== checked;
|
||||
this._internal.checked = checked;
|
||||
if (changed) {
|
||||
this.flagOutputDirty('checked');
|
||||
this._updateVisualState();
|
||||
}
|
||||
};
|
||||
|
||||
this.props.styles.fill = {};
|
||||
},
|
||||
getReactComponent() {
|
||||
return RadioButton;
|
||||
},
|
||||
inputs: {
|
||||
fillColor: {
|
||||
index: 19,
|
||||
displayName: 'Fill Color',
|
||||
group: 'Fill Style',
|
||||
type: 'color',
|
||||
allowVisualStates: true,
|
||||
styleTag: 'fill',
|
||||
set(value) {
|
||||
this.setStyle({ backgroundColor: value }, 'fill');
|
||||
}
|
||||
}
|
||||
},
|
||||
inputProps: {
|
||||
value: {
|
||||
type: 'string',
|
||||
displayName: 'Value',
|
||||
group: 'General',
|
||||
index: 100
|
||||
},
|
||||
fillSpacing: {
|
||||
displayName: 'Fill Spacing',
|
||||
group: 'Fill Style',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', 'vw', 'vh'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
allowVisualStates: true,
|
||||
default: 2
|
||||
}
|
||||
},
|
||||
inputCss: {
|
||||
width: {
|
||||
index: 11,
|
||||
group: 'Dimensions',
|
||||
displayName: 'Width',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', '%', 'vw', 'vh'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
default: 32,
|
||||
allowVisualStates: true,
|
||||
styleTag: 'radio',
|
||||
onChange() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
height: {
|
||||
index: 12,
|
||||
group: 'Dimensions',
|
||||
displayName: 'Height',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', '%'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
default: 32,
|
||||
allowVisualStates: true,
|
||||
styleTag: 'radio',
|
||||
onChange() {
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
backgroundColor: {
|
||||
index: 201,
|
||||
displayName: 'Background Color',
|
||||
group: 'Style',
|
||||
type: 'color',
|
||||
allowVisualStates: true,
|
||||
styleTag: 'radio',
|
||||
default: 'transparent',
|
||||
applyDefault: false
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
checked: {
|
||||
type: 'boolean',
|
||||
displayName: 'Checked',
|
||||
group: 'States',
|
||||
get() {
|
||||
return this._internal.checked;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeSharedPortDefinitions.addAlignInputs(RadioButtonNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(RadioButtonNode);
|
||||
NodeSharedPortDefinitions.addMarginInputs(RadioButtonNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(RadioButtonNode);
|
||||
NodeSharedPortDefinitions.addIconInputs(RadioButtonNode);
|
||||
NodeSharedPortDefinitions.addLabelInputs(RadioButtonNode, {
|
||||
enableSpacing: true,
|
||||
styleTag: 'label'
|
||||
});
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(RadioButtonNode);
|
||||
NodeSharedPortDefinitions.addBorderInputs(RadioButtonNode, {
|
||||
defaults: {
|
||||
borderStyle: 'solid',
|
||||
borderWidth: 2,
|
||||
borderRadius: 16
|
||||
},
|
||||
styleTag: 'radio'
|
||||
});
|
||||
NodeSharedPortDefinitions.addShadowInputs(RadioButtonNode, {
|
||||
styleTag: 'radio'
|
||||
});
|
||||
Utils.addControlEventsAndStates(RadioButtonNode, { checked: true });
|
||||
|
||||
export default createNodeFromReactComponent(RadioButtonNode);
|
||||
@@ -0,0 +1,125 @@
|
||||
import { RadioButtonGroup } from '../../components/controls/RadioButtonGroup';
|
||||
import { flexDirectionValues } from '../../constants/flex';
|
||||
import guid from '../../guid';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
|
||||
let RadioButtonGroupNode = {
|
||||
name: 'Radio Button Group',
|
||||
displayName: 'Radio Button Group',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/radio-button-group',
|
||||
allowChildren: true,
|
||||
noodlNodeAsProp: true,
|
||||
useVariants: false,
|
||||
connectionPanel: {
|
||||
groupPriority: ['General', 'Style', 'Actions', 'Events', 'Mounted', 'States']
|
||||
},
|
||||
initialize() {
|
||||
this.props.name = 'radio-' + guid();
|
||||
|
||||
this.props.valueChanged = (value) => {
|
||||
const changed = this._internal.value !== value;
|
||||
this._internal.value = value;
|
||||
this.props.value = value;
|
||||
if (changed) {
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('value');
|
||||
this.sendSignalOnOutput('onChange');
|
||||
}
|
||||
};
|
||||
},
|
||||
getReactComponent() {
|
||||
return RadioButtonGroup;
|
||||
},
|
||||
defaultCss: {
|
||||
display: 'flex',
|
||||
position: 'relative',
|
||||
flexDirection: 'column'
|
||||
},
|
||||
inputs: {
|
||||
flexDirection: {
|
||||
//don't rename for backwards compat
|
||||
index: 11,
|
||||
displayName: 'Layout',
|
||||
group: 'Layout',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ label: 'Vertical', value: 'column' },
|
||||
{ label: 'Horizontal', value: 'row' }
|
||||
]
|
||||
},
|
||||
default: 'column',
|
||||
set(value) {
|
||||
this.props.layout = value;
|
||||
|
||||
if (value !== 'none') {
|
||||
this.setStyle({ flexDirection: value });
|
||||
} else {
|
||||
this.removeStyle(['flexDirection']);
|
||||
}
|
||||
|
||||
if (this.context.editorConnection) {
|
||||
// Send warning if the value is wrong
|
||||
if (value !== 'none' && !flexDirectionValues.includes(value)) {
|
||||
this.context.editorConnection.sendWarning(this.nodeScope.componentOwner.name, this.id, 'layout-warning', {
|
||||
message: 'Invalid Layout value has to be a valid flex-direction value.'
|
||||
});
|
||||
} else {
|
||||
this.context.editorConnection.clearWarning(this.nodeScope.componentOwner.name, this.id, 'layout-warning');
|
||||
}
|
||||
}
|
||||
|
||||
this.forceUpdate();
|
||||
}
|
||||
},
|
||||
value: {
|
||||
index: 20,
|
||||
type: 'string',
|
||||
displayName: 'Value',
|
||||
group: 'General',
|
||||
set: function (value) {
|
||||
if (typeof value !== 'string' && value.toString !== undefined) value = value.toString();
|
||||
if (typeof value !== 'string') return;
|
||||
|
||||
const changed = value !== this._internal.value;
|
||||
this.props.value = this._internal.value = value;
|
||||
|
||||
if (changed) {
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('value');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
value: {
|
||||
type: 'string',
|
||||
displayName: 'Value',
|
||||
group: 'States',
|
||||
getter: function () {
|
||||
return this._internal.value;
|
||||
}
|
||||
},
|
||||
onChange: {
|
||||
type: 'signal',
|
||||
displayName: 'Changed',
|
||||
group: 'Events'
|
||||
}
|
||||
},
|
||||
inputProps: {},
|
||||
outputProps: {}
|
||||
};
|
||||
|
||||
NodeSharedPortDefinitions.addDimensions(RadioButtonGroupNode, {
|
||||
defaultSizeMode: 'contentSize',
|
||||
contentLabel: 'Content'
|
||||
});
|
||||
NodeSharedPortDefinitions.addAlignInputs(RadioButtonGroupNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(RadioButtonGroupNode);
|
||||
NodeSharedPortDefinitions.addMarginInputs(RadioButtonGroupNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(RadioButtonGroupNode);
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(RadioButtonGroupNode);
|
||||
|
||||
RadioButtonGroupNode = createNodeFromReactComponent(RadioButtonGroupNode);
|
||||
export default RadioButtonGroupNode;
|
||||
498
packages/noodl-viewer-react/src/nodes/controls/slider.ts
Normal file
498
packages/noodl-viewer-react/src/nodes/controls/slider.ts
Normal file
@@ -0,0 +1,498 @@
|
||||
import { Slider } from '../../components/controls/Slider';
|
||||
import guid from '../../guid';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
import Utils from './utils';
|
||||
|
||||
const thumbPopout = { group: 'thumb-styles', label: 'Thumb Styles' };
|
||||
const trackPopout = { group: 'track-styles', label: 'Track Styles' };
|
||||
|
||||
const RangeNode = {
|
||||
name: 'net.noodl.controls.range',
|
||||
displayNodeName: 'Slider',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/slider',
|
||||
allowChildren: false,
|
||||
noodlNodeAsProp: true,
|
||||
connectionPanel: {
|
||||
groupPriority: [
|
||||
'General',
|
||||
'Style',
|
||||
'Actions',
|
||||
'Events',
|
||||
'States',
|
||||
'Mounted',
|
||||
'Hover Events',
|
||||
'Pointer Events',
|
||||
'Focus Events'
|
||||
]
|
||||
},
|
||||
initialize() {
|
||||
this.props.sizeMode = 'contentHeight';
|
||||
this.props.id = 'input-' + guid();
|
||||
|
||||
this.props.value = this.props.min;
|
||||
this._internal.outputValue = 0;
|
||||
|
||||
this.props._nodeId = this.id;
|
||||
|
||||
//this is used by the range to communicate with the node whenever the range changes value
|
||||
this.props.updateOutputValue = (value: string | number) => {
|
||||
value = typeof value === 'string' ? parseFloat(value) : value;
|
||||
|
||||
const valueChanged = this._internal.outputValue !== value;
|
||||
if (valueChanged) {
|
||||
this._internal.outputValue = value;
|
||||
this.flagOutputDirty('value');
|
||||
this._updateOutputValuePercent(value);
|
||||
this.sendSignalOnOutput('onChange');
|
||||
}
|
||||
|
||||
// On first mount, output the initial percentage.
|
||||
if (!this._internal.valuePercent) {
|
||||
this._updateOutputValuePercent(value);
|
||||
}
|
||||
};
|
||||
|
||||
this.props.updateOutputValue(this.props.value); // Set the value output to an initial value
|
||||
},
|
||||
getReactComponent() {
|
||||
return Slider;
|
||||
},
|
||||
inputs: {
|
||||
value: {
|
||||
type: 'string',
|
||||
displayName: 'Value',
|
||||
group: 'General',
|
||||
index: 100,
|
||||
set(value) {
|
||||
this._setInputValue(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
value: {
|
||||
type: 'number',
|
||||
displayName: 'Value',
|
||||
group: 'States',
|
||||
get() {
|
||||
return this._internal.outputValue;
|
||||
}
|
||||
},
|
||||
valuePercent: {
|
||||
type: 'number',
|
||||
displayName: 'Value Percent',
|
||||
group: 'States',
|
||||
get() {
|
||||
return this._internal.valuePercent;
|
||||
}
|
||||
},
|
||||
onChange: {
|
||||
type: 'signal',
|
||||
displayName: 'Changed',
|
||||
group: 'Events'
|
||||
}
|
||||
},
|
||||
inputProps: {
|
||||
min: {
|
||||
type: 'number',
|
||||
displayName: 'Min',
|
||||
group: 'General',
|
||||
default: 0,
|
||||
index: 100,
|
||||
onChange() {
|
||||
this._setInputValue(this.props.value);
|
||||
}
|
||||
},
|
||||
max: {
|
||||
type: 'number',
|
||||
displayName: 'Max',
|
||||
group: 'General',
|
||||
default: 100,
|
||||
index: 100,
|
||||
onChange() {
|
||||
this._setInputValue(this.props.value);
|
||||
}
|
||||
},
|
||||
step: {
|
||||
type: 'number',
|
||||
displayName: 'Step',
|
||||
group: 'General',
|
||||
default: 1,
|
||||
index: 100
|
||||
},
|
||||
width: {
|
||||
index: 11,
|
||||
group: 'Dimensions',
|
||||
displayName: 'Width',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['%', 'px', 'vw'],
|
||||
defaultUnit: '%'
|
||||
},
|
||||
default: 100,
|
||||
allowVisualStates: true
|
||||
},
|
||||
// Styles
|
||||
thumbWidth: {
|
||||
group: 'Thumb Style',
|
||||
displayName: 'Width',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', 'vw', '%'],
|
||||
defaultUnit: 'px',
|
||||
allowEditOnly: true
|
||||
},
|
||||
default: 16,
|
||||
popout: thumbPopout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
thumbHeight: {
|
||||
group: 'Thumb Style',
|
||||
displayName: 'Height',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', 'vh', '%'],
|
||||
defaultUnit: 'px',
|
||||
allowEditOnly: true
|
||||
},
|
||||
default: 16,
|
||||
popout: thumbPopout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
thumbColor: {
|
||||
group: 'Thumb Style',
|
||||
displayName: 'Color',
|
||||
type: { name: 'color', allowEditOnly: true },
|
||||
default: '#000000',
|
||||
popout: thumbPopout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
trackHeight: {
|
||||
group: 'Track Style',
|
||||
displayName: 'Height',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', 'vh', '%'],
|
||||
defaultUnit: 'px',
|
||||
allowEditOnly: true
|
||||
},
|
||||
default: 6,
|
||||
popout: trackPopout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
trackColor: {
|
||||
group: 'Track Style',
|
||||
displayName: 'Inactive Color',
|
||||
type: { name: 'color', allowEditOnly: true },
|
||||
default: '#f0f0f0',
|
||||
popout: trackPopout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
trackActiveColor: {
|
||||
group: 'Track Style',
|
||||
displayName: 'Active Color',
|
||||
type: { name: 'color', allowEditOnly: true },
|
||||
default: '#f0f0f0',
|
||||
popout: trackPopout,
|
||||
allowVisualStates: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_updateOutputValuePercent(value: number) {
|
||||
const min = this.props.min;
|
||||
const max = this.props.max;
|
||||
const valuePercent = Math.floor(((value - min) / (max - min)) * 100);
|
||||
const valuePercentChanged = this._internal.valuePercentChanged !== valuePercent;
|
||||
|
||||
this._internal.valuePercent = valuePercent;
|
||||
valuePercentChanged && this.flagOutputDirty('valuePercent');
|
||||
},
|
||||
_setInputValue(newValue) {
|
||||
//make sure value never goes out of range
|
||||
const value = Math.max(this.props.min, Math.min(this.props.max, newValue || 0));
|
||||
|
||||
const changed = value !== this.props.value;
|
||||
|
||||
if (changed) {
|
||||
this.props.value = value;
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//Add borders
|
||||
function addBorderInputs(definition, opts) {
|
||||
opts = opts || {};
|
||||
const defaults = opts.defaults || {};
|
||||
const popout = opts.popout;
|
||||
|
||||
defaults.borderStyle = 'none';
|
||||
defaults.borderWidth = 0;
|
||||
defaults.borderColor = '#000000';
|
||||
|
||||
const prefixLabel = opts.propPrefix[0].toUpperCase() + opts.propPrefix.slice(1);
|
||||
|
||||
function defineBorderTab(definition, suffix, tabName, indexOffset) {
|
||||
const styleName = opts.propPrefix + `Border${suffix}Style`;
|
||||
const widthName = opts.propPrefix + `Border${suffix}Width`;
|
||||
const colorName = opts.propPrefix + `Border${suffix}Color`;
|
||||
|
||||
let orNotSet = defaults.borderStyle !== 'none' ? 'OR borderStyle NOT SET' : '';
|
||||
|
||||
if (suffix) {
|
||||
if (defaults[styleName] && defaults[styleName] !== 'none') orNotSet += `OR ${styleName} NOT SET`;
|
||||
NodeSharedPortDefinitions.addDynamicInputPorts(
|
||||
definition,
|
||||
`${styleName} = solid OR ${styleName} = dashed OR ${styleName} = dotted OR borderStyle = solid OR borderStyle = dashed OR borderStyle = dotted ${orNotSet}`,
|
||||
[`${widthName}`, `${colorName}`]
|
||||
);
|
||||
} else {
|
||||
NodeSharedPortDefinitions.addDynamicInputPorts(
|
||||
definition,
|
||||
`${styleName} = solid OR ${styleName} = dashed OR ${styleName} = dotted ${orNotSet}`,
|
||||
[`${widthName}`, `${colorName}`]
|
||||
);
|
||||
}
|
||||
|
||||
const tab = {
|
||||
group: opts.propPrefix + '-border-styles',
|
||||
tab: tabName,
|
||||
label: suffix
|
||||
};
|
||||
|
||||
const editorName = (name) => `${prefixLabel} ${name} ${suffix ? '(' + suffix + ')' : ''}`;
|
||||
const index = 202 + indexOffset * 4;
|
||||
|
||||
const groupName = prefixLabel + ' Border Style';
|
||||
|
||||
NodeSharedPortDefinitions.addInputProps(definition, {
|
||||
[styleName]: {
|
||||
index: index + 1,
|
||||
displayName: 'Border Style',
|
||||
editorName: editorName('Border Style'),
|
||||
group: groupName,
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ label: 'None', value: 'none' },
|
||||
{ label: 'Solid', value: 'solid' },
|
||||
{ label: 'Dotted', value: 'dotted' },
|
||||
{ label: 'Dashed', value: 'dashed' }
|
||||
]
|
||||
},
|
||||
default: defaults[`border${suffix}Style`],
|
||||
tab,
|
||||
popout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
[widthName]: {
|
||||
index: index + 2,
|
||||
displayName: 'Border Width',
|
||||
editorName: editorName('Border Width'),
|
||||
group: groupName,
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
default: defaults[`border${suffix}Width`],
|
||||
tab,
|
||||
popout,
|
||||
allowVisualStates: true
|
||||
},
|
||||
[colorName]: {
|
||||
index: index + 3,
|
||||
displayName: 'Border Color',
|
||||
editorName: editorName('Border Color'),
|
||||
group: groupName,
|
||||
type: 'color',
|
||||
default: defaults[`border${suffix}Color`],
|
||||
tab,
|
||||
popout,
|
||||
allowVisualStates: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
defineBorderTab(definition, '', 'borders-all', 0);
|
||||
defineBorderTab(definition, 'Left', 'borders-left', 1);
|
||||
defineBorderTab(definition, 'Top', 'borders-top', 2);
|
||||
defineBorderTab(definition, 'Right', 'borders-right', 3);
|
||||
defineBorderTab(definition, 'Bottom', 'borders-bottom', 4);
|
||||
}
|
||||
|
||||
function addBorderRadius(definition, opts) {
|
||||
opts = opts || {};
|
||||
const defaults = opts.defaults || {};
|
||||
const popout = opts.popout;
|
||||
|
||||
if (!defaults.borderRadius) defaults.borderRadius = 0;
|
||||
|
||||
const prefixLabel = opts.propPrefix[0].toUpperCase() + opts.propPrefix.slice(1);
|
||||
|
||||
function defineCornerTab(definition, suffix, tabName, indexOffset) {
|
||||
const editorName = (name) => `${prefixLabel} ${name} ${suffix ? '(' + suffix + ')' : ''}`;
|
||||
|
||||
const tab = {
|
||||
group: opts.propPrefix + '-corners',
|
||||
tab: tabName,
|
||||
label: suffix
|
||||
};
|
||||
const radiusName = `Border${suffix}Radius`;
|
||||
|
||||
NodeSharedPortDefinitions.addInputProps(definition, {
|
||||
[opts.propPrefix + radiusName]: {
|
||||
index: 240 + indexOffset,
|
||||
displayName: 'Corner Radius',
|
||||
editorName: editorName('Corner Radius'),
|
||||
group: prefixLabel + ' Corner Radius',
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px', '%'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
default: defaults[`border${suffix}Radius`],
|
||||
tab,
|
||||
popout
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
defineCornerTab(definition, '', 'corners-all', 0);
|
||||
defineCornerTab(definition, 'TopLeft', 'corners-top-left', 1);
|
||||
defineCornerTab(definition, 'TopRight', 'corners-top-right', 2);
|
||||
defineCornerTab(definition, 'BottomRight', 'corners-bottom-right', 3);
|
||||
defineCornerTab(definition, 'BottomLeft', 'corners-bottom-left', 4);
|
||||
}
|
||||
|
||||
function addShadowInputs(definition, opts) {
|
||||
opts = opts || {};
|
||||
const popout = opts.popout;
|
||||
const prefix = opts.propPrefix;
|
||||
|
||||
NodeSharedPortDefinitions.addDynamicInputPorts(definition, `${prefix}BoxShadowEnabled = true`, [
|
||||
`${prefix}BoxShadowOffsetX`,
|
||||
`${prefix}BoxShadowOffsetY`,
|
||||
`${prefix}BoxShadowInset`,
|
||||
`${prefix}BoxShadowBlurRadius`,
|
||||
`${prefix}BoxShadowSpreadRadius`,
|
||||
`${prefix}BoxShadowColor`
|
||||
]);
|
||||
|
||||
const prefixLabel = opts.propPrefix[0].toUpperCase() + opts.propPrefix.slice(1);
|
||||
const editorName = (name) => `${prefixLabel} ${name}`;
|
||||
|
||||
NodeSharedPortDefinitions.addInputProps(definition, {
|
||||
[`${prefix}BoxShadowEnabled`]: {
|
||||
index: 250,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Shadow Enabled',
|
||||
editorName: editorName('Shadow Enabled'),
|
||||
type: 'boolean',
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
},
|
||||
[`${prefix}BoxShadowOffsetX`]: {
|
||||
index: 251,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Offset X',
|
||||
editorName: editorName('Offset X'),
|
||||
default: 0,
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
},
|
||||
[`${prefix}BoxShadowOffsetY`]: {
|
||||
index: 252,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Offset Y',
|
||||
editorName: editorName('Offset Y'),
|
||||
default: 0,
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
},
|
||||
[`${prefix}BoxShadowBlurRadius`]: {
|
||||
index: 253,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Blur Radius',
|
||||
editorName: editorName('Blur Radius'),
|
||||
default: 5,
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
},
|
||||
[`${prefix}BoxShadowSpreadRadius`]: {
|
||||
index: 254,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Spread Radius',
|
||||
editorName: editorName('Spread Radius'),
|
||||
default: 2,
|
||||
type: {
|
||||
name: 'number',
|
||||
units: ['px'],
|
||||
defaultUnit: 'px'
|
||||
},
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
},
|
||||
[`${prefix}BoxShadowInset`]: {
|
||||
index: 255,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Inset',
|
||||
editorName: editorName('Inset'),
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
},
|
||||
[`${prefix}BoxShadowColor`]: {
|
||||
index: 256,
|
||||
group: opts.group || 'Box Shadow',
|
||||
displayName: 'Shadow Color',
|
||||
editorName: editorName('Shadow Color'),
|
||||
type: 'color',
|
||||
default: '#00000033',
|
||||
allowVisualStates: true,
|
||||
popout
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NodeSharedPortDefinitions.addAlignInputs(RangeNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(RangeNode);
|
||||
NodeSharedPortDefinitions.addMarginInputs(RangeNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(RangeNode);
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(RangeNode);
|
||||
addBorderInputs(RangeNode, { propPrefix: 'track', popout: trackPopout });
|
||||
addBorderRadius(RangeNode, { propPrefix: 'track', popout: trackPopout });
|
||||
addShadowInputs(RangeNode, {
|
||||
propPrefix: 'track',
|
||||
popout: trackPopout,
|
||||
group: 'Track Box Shadow'
|
||||
});
|
||||
|
||||
addBorderInputs(RangeNode, { propPrefix: 'thumb', popout: thumbPopout });
|
||||
addBorderRadius(RangeNode, { propPrefix: 'thumb', popout: thumbPopout });
|
||||
addShadowInputs(RangeNode, {
|
||||
propPrefix: 'thumb',
|
||||
popout: thumbPopout,
|
||||
group: 'Thumb Box Shadow'
|
||||
});
|
||||
|
||||
Utils.addControlEventsAndStates(RangeNode);
|
||||
|
||||
export default createNodeFromReactComponent(RangeNode);
|
||||
281
packages/noodl-viewer-react/src/nodes/controls/text-input.ts
Normal file
281
packages/noodl-viewer-react/src/nodes/controls/text-input.ts
Normal file
@@ -0,0 +1,281 @@
|
||||
import { TextInput } from '../../components/controls/TextInput';
|
||||
import guid from '../../guid';
|
||||
import NodeSharedPortDefinitions from '../../node-shared-port-definitions';
|
||||
import { createNodeFromReactComponent } from '../../react-component-node';
|
||||
import Utils from './utils';
|
||||
|
||||
function _styleTemplate(className, props) {
|
||||
return `
|
||||
.${className}::placeholder {
|
||||
opacity: ${props.placeholderOpacity};
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
const TextInputNode = {
|
||||
name: 'net.noodl.controls.textinput',
|
||||
displayName: 'Text Input',
|
||||
docs: 'https://docs.noodl.net/nodes/ui-controls/text-input',
|
||||
allowChildren: false,
|
||||
noodlNodeAsProp: true,
|
||||
usePortAsLabel: 'label',
|
||||
nodeDoubleClickAction: {
|
||||
focusPort: 'label'
|
||||
},
|
||||
connectionPanel: {
|
||||
groupPriority: [
|
||||
'General',
|
||||
'Text',
|
||||
'Style',
|
||||
'Actions',
|
||||
'Events',
|
||||
'States',
|
||||
'Mounted',
|
||||
'Text Style',
|
||||
'Label',
|
||||
'Label Text Style',
|
||||
'Hover Events',
|
||||
'Pointer Events',
|
||||
'Focus Events'
|
||||
]
|
||||
},
|
||||
getReactComponent() {
|
||||
return TextInput;
|
||||
},
|
||||
initialize() {
|
||||
this.props.startValue = '';
|
||||
this.props.id = this._internal.controlId = 'input-' + guid();
|
||||
},
|
||||
inputProps: {
|
||||
type: {
|
||||
displayName: 'Type',
|
||||
group: 'Text',
|
||||
index: 19,
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ label: 'Text', value: 'text' },
|
||||
{ label: 'Text Area', value: 'textArea' },
|
||||
{ label: 'Email', value: 'email' },
|
||||
{ label: 'Number', value: 'number' },
|
||||
{ label: 'Password', value: 'password' },
|
||||
{ label: 'URL', value: 'url' }
|
||||
]
|
||||
},
|
||||
default: 'text'
|
||||
},
|
||||
placeholder: {
|
||||
index: 22,
|
||||
group: 'Text',
|
||||
displayName: 'Placeholder',
|
||||
default: 'Type here...',
|
||||
type: {
|
||||
name: 'string'
|
||||
}
|
||||
},
|
||||
maxLength: {
|
||||
group: 'Text',
|
||||
displayName: 'Max length',
|
||||
type: 'number',
|
||||
index: 24
|
||||
}
|
||||
},
|
||||
inputs: {
|
||||
placeHolderOpacity: {
|
||||
index: 23,
|
||||
group: 'Text',
|
||||
displayName: 'Placeholder opacity',
|
||||
type: 'number',
|
||||
default: 0.5,
|
||||
set(value) {
|
||||
const className = this._internal.controlId;
|
||||
Utils.updateStylesForClass(className, { placeholderOpacity: value }, _styleTemplate);
|
||||
}
|
||||
},
|
||||
set: {
|
||||
group: 'Actions',
|
||||
displayName: 'Set',
|
||||
type: 'signal',
|
||||
valueChangedToTrue() {
|
||||
this.scheduleAfterInputsHaveUpdated(() => {
|
||||
this.setText(this._internal.text);
|
||||
});
|
||||
}
|
||||
},
|
||||
startValue: {
|
||||
index: 18,
|
||||
displayName: 'Text',
|
||||
type: 'string',
|
||||
group: 'Text',
|
||||
set(value) {
|
||||
if (this._internal.text === value) return;
|
||||
|
||||
this._internal.text = value;
|
||||
if (this.isInputConnected('set') === false) {
|
||||
this.setText(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
clear: {
|
||||
type: 'signal',
|
||||
group: 'Actions',
|
||||
displayName: 'Clear',
|
||||
valueChangedToTrue() {
|
||||
this.clear();
|
||||
}
|
||||
},
|
||||
focus: {
|
||||
type: 'signal',
|
||||
group: 'Actions',
|
||||
displayName: 'Focus',
|
||||
valueChangedToTrue() {
|
||||
this.context.setNodeFocused(this, true);
|
||||
}
|
||||
},
|
||||
blur: {
|
||||
type: 'signal',
|
||||
group: 'Actions',
|
||||
displayName: 'Blur',
|
||||
valueChangedToTrue() {
|
||||
this.context.setNodeFocused(this, false);
|
||||
}
|
||||
},
|
||||
textAlignX: {
|
||||
group: 'Text Alignment',
|
||||
index: 13,
|
||||
displayName: 'Text Horizontal Align',
|
||||
type: {
|
||||
name: 'enum',
|
||||
enums: [
|
||||
{ label: 'left', value: 'left' },
|
||||
{ label: 'center', value: 'center' },
|
||||
{ label: 'right', value: 'right' }
|
||||
],
|
||||
alignComp: 'justify'
|
||||
},
|
||||
default: 'left',
|
||||
set(value) {
|
||||
switch (value) {
|
||||
case 'left':
|
||||
this.setStyle({ textAlign: 'left' }, 'input');
|
||||
break;
|
||||
case 'center':
|
||||
this.setStyle({ textAlign: 'center' }, 'input');
|
||||
break;
|
||||
case 'right':
|
||||
this.setStyle({ textAlign: 'right' }, 'input');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
inputCss: {
|
||||
backgroundColor: {
|
||||
index: 100,
|
||||
displayName: 'Background Color',
|
||||
group: 'Style',
|
||||
type: 'color',
|
||||
default: 'transparent',
|
||||
allowVisualStates: true,
|
||||
styleTag: 'inputWrapper'
|
||||
}
|
||||
},
|
||||
outputProps: {
|
||||
// Value
|
||||
onTextChanged: {
|
||||
group: 'General',
|
||||
displayName: 'Text',
|
||||
type: 'string',
|
||||
index: 1,
|
||||
onChange() {
|
||||
this.sendSignalOnOutput('textChanged');
|
||||
}
|
||||
},
|
||||
|
||||
// Events
|
||||
onEnter: {
|
||||
group: 'Events',
|
||||
displayName: 'On Enter',
|
||||
type: 'signal'
|
||||
}
|
||||
},
|
||||
outputs: {
|
||||
textChanged: {
|
||||
displayName: 'Text Changed',
|
||||
type: 'signal',
|
||||
group: 'General',
|
||||
index: 2
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
_focus() {
|
||||
if (!this.innerReactComponentRef) return;
|
||||
this.innerReactComponentRef.focus();
|
||||
},
|
||||
_blur() {
|
||||
if (!this.innerReactComponentRef) return;
|
||||
this.innerReactComponentRef.blur();
|
||||
},
|
||||
clear() {
|
||||
this.props.startValue = '';
|
||||
if (this.innerReactComponentRef) {
|
||||
this.innerReactComponentRef.setText('');
|
||||
}
|
||||
},
|
||||
setText(text) {
|
||||
this.props.startValue = text;
|
||||
if (this.innerReactComponentRef) {
|
||||
//the text component is currently mounted, and will signal the onTextChanged output
|
||||
if (this.innerReactComponentRef.hasFocus() === false) {
|
||||
this.innerReactComponentRef.setText(text);
|
||||
}
|
||||
} else if (this.outputPropValues['onTextChanged'] !== text) {
|
||||
//text component isn't mounted yet, set the output manually
|
||||
this.outputPropValues['onTextChanged'] = text;
|
||||
this.flagOutputDirty('onTextChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NodeSharedPortDefinitions.addDimensions(TextInputNode, {
|
||||
defaultSizeMode: 'contentSize',
|
||||
contentLabel: 'Text'
|
||||
});
|
||||
NodeSharedPortDefinitions.addIconInputs(TextInputNode, {
|
||||
enableIconPlacement: true,
|
||||
defaults: { useIcon: false, iconColor: '#000000' }
|
||||
});
|
||||
NodeSharedPortDefinitions.addLabelInputs(TextInputNode, {
|
||||
enableSpacing: true,
|
||||
styleTag: 'label',
|
||||
displayName: 'Label'
|
||||
});
|
||||
|
||||
NodeSharedPortDefinitions.addTextStyleInputs(TextInputNode, {
|
||||
styleTag: 'input',
|
||||
portPrefix: '',
|
||||
portIndex: 18,
|
||||
popout: {
|
||||
group: 'input-text-style',
|
||||
label: 'Text Style',
|
||||
parentGroup: 'Text'
|
||||
}
|
||||
});
|
||||
|
||||
NodeSharedPortDefinitions.addAlignInputs(TextInputNode);
|
||||
NodeSharedPortDefinitions.addTransformInputs(TextInputNode);
|
||||
NodeSharedPortDefinitions.addPaddingInputs(TextInputNode, {
|
||||
styleTag: 'inputWrapper'
|
||||
});
|
||||
NodeSharedPortDefinitions.addMarginInputs(TextInputNode);
|
||||
NodeSharedPortDefinitions.addSharedVisualInputs(TextInputNode);
|
||||
NodeSharedPortDefinitions.addBorderInputs(TextInputNode, {
|
||||
styleTag: 'inputWrapper'
|
||||
});
|
||||
NodeSharedPortDefinitions.addShadowInputs(TextInputNode, {
|
||||
styleTag: 'inputWrapper'
|
||||
});
|
||||
Utils.addControlEventsAndStates(TextInputNode);
|
||||
|
||||
export default createNodeFromReactComponent(TextInputNode);
|
||||
309
packages/noodl-viewer-react/src/nodes/controls/utils.ts
Normal file
309
packages/noodl-viewer-react/src/nodes/controls/utils.ts
Normal file
@@ -0,0 +1,309 @@
|
||||
import PointerListeners from '../../pointerlisteners';
|
||||
|
||||
function _shallowCompare(o1, o2) {
|
||||
let p;
|
||||
for (p in o1) {
|
||||
if (o1.hasOwnProperty(p)) {
|
||||
if (o1[p] !== o2[p]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (p in o2) {
|
||||
if (o2.hasOwnProperty(p)) {
|
||||
if (o1[p] !== o2[p]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const _styleSheets = {};
|
||||
|
||||
function updateStylesForClass(_class, props, _styleTemplate) {
|
||||
if (_styleSheets[_class]) {
|
||||
// Check if props have changed
|
||||
if (!_shallowCompare(props, _styleSheets[_class].props)) {
|
||||
_styleSheets[_class].style.innerHTML = _styleTemplate(_class, props);
|
||||
_styleSheets[_class].props = Object.assign({}, props);
|
||||
}
|
||||
} else {
|
||||
// Create a new style sheet if none exists
|
||||
const style = document.createElement('style');
|
||||
style.innerHTML = _styleTemplate(_class, props);
|
||||
document.head.appendChild(style);
|
||||
|
||||
_styleSheets[_class] = { style, props: Object.assign({}, props) };
|
||||
}
|
||||
}
|
||||
|
||||
function mergeAttribute(definition, attribute, values) {
|
||||
if (!definition[attribute]) {
|
||||
definition[attribute] = {};
|
||||
}
|
||||
|
||||
for (const name in values) {
|
||||
definition[attribute][name] = values[name];
|
||||
}
|
||||
}
|
||||
|
||||
function addInputs(definition, values) {
|
||||
mergeAttribute(definition, 'inputs', values);
|
||||
}
|
||||
|
||||
function addInputProps(definition, values) {
|
||||
mergeAttribute(definition, 'inputProps', values);
|
||||
}
|
||||
|
||||
function addOutputProps(definition, values) {
|
||||
mergeAttribute(definition, 'outputProps', values);
|
||||
}
|
||||
|
||||
function addOutputs(definition, values) {
|
||||
mergeAttribute(definition, 'outputs', values);
|
||||
}
|
||||
|
||||
function addControlEventsAndStates(definition, args?) {
|
||||
args = args || {};
|
||||
|
||||
definition.visualStates = [
|
||||
{ name: 'neutral', label: 'Neutral' },
|
||||
{ name: 'hover', label: 'Hover' },
|
||||
{ name: 'pressed', label: 'Pressed' },
|
||||
{ name: 'focused', label: 'Focused' },
|
||||
{ name: 'disabled', label: 'Disabled' }
|
||||
];
|
||||
|
||||
if (args.checked) {
|
||||
definition.visualStates.splice(3, 0, { name: 'checked', label: 'Checked' });
|
||||
}
|
||||
|
||||
addInputs(definition, {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
displayName: 'Enabled',
|
||||
group: 'General',
|
||||
default: true,
|
||||
set: function (value) {
|
||||
value = !!value;
|
||||
const changed = value !== this._internal.enabled;
|
||||
this.props.enabled = this._internal.enabled = value;
|
||||
|
||||
if (changed) {
|
||||
this._updateVisualState();
|
||||
this.forceUpdate();
|
||||
this.flagOutputDirty('enabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addInputProps(definition, {
|
||||
blockTouch: {
|
||||
index: 450,
|
||||
displayName: 'Block Pointer Events',
|
||||
type: 'boolean',
|
||||
group: 'Pointer Events'
|
||||
}
|
||||
});
|
||||
|
||||
definition.methods._updateVisualState = function () {
|
||||
const states = [];
|
||||
|
||||
//make sure they are in the order they should be applied
|
||||
if (this._internal.enabled) {
|
||||
if (this.outputPropValues.hoverState) states.push('hover');
|
||||
if (this.outputPropValues.pressedState) states.push('pressed');
|
||||
if (this.outputPropValues.focusState) states.push('focused');
|
||||
}
|
||||
|
||||
if (args.checked && this._internal.checked) states.push('checked');
|
||||
if (!this._internal.enabled) states.push('disabled');
|
||||
|
||||
this.setVisualStates(states);
|
||||
};
|
||||
|
||||
addOutputProps(definition, {
|
||||
// Focus
|
||||
focusState: {
|
||||
displayName: 'Focused',
|
||||
group: 'States',
|
||||
type: 'boolean',
|
||||
props: {
|
||||
onFocus() {
|
||||
this.outputPropValues.focusState = true;
|
||||
this.flagOutputDirty('focusState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onBlur() {
|
||||
this.outputPropValues.focusState = false;
|
||||
this.flagOutputDirty('focusState');
|
||||
this._updateVisualState();
|
||||
}
|
||||
}
|
||||
},
|
||||
onFocus: {
|
||||
displayName: 'Focused',
|
||||
group: 'Focus Events',
|
||||
type: 'signal',
|
||||
props: {
|
||||
onFocus() {
|
||||
this.sendSignalOnOutput('onFocus');
|
||||
}
|
||||
}
|
||||
},
|
||||
onBlur: {
|
||||
displayName: 'Blurred',
|
||||
group: 'Focus Events',
|
||||
type: 'signal',
|
||||
props: {
|
||||
onBlur() {
|
||||
this.sendSignalOnOutput('onBlur');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Hover
|
||||
hoverState: {
|
||||
displayName: 'Hover',
|
||||
group: 'States',
|
||||
type: 'boolean',
|
||||
props: {
|
||||
onMouseOver() {
|
||||
this.outputPropValues.hoverState = true;
|
||||
this.flagOutputDirty('hoverState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onMouseLeave() {
|
||||
this.outputPropValues.hoverState = false;
|
||||
this.flagOutputDirty('hoverState');
|
||||
this._updateVisualState();
|
||||
}
|
||||
}
|
||||
},
|
||||
hoverStart: {
|
||||
displayName: 'Hover Start',
|
||||
group: 'Pointer Events',
|
||||
type: 'signal',
|
||||
props: {
|
||||
onMouseOver() {
|
||||
this.sendSignalOnOutput('hoverStart');
|
||||
}
|
||||
}
|
||||
},
|
||||
hoverEnd: {
|
||||
displayName: 'Hover End',
|
||||
group: 'Pointer Events',
|
||||
type: 'signal',
|
||||
props: {
|
||||
onMouseLeave() {
|
||||
this.sendSignalOnOutput('hoverEnd');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Pressed
|
||||
pressedState: {
|
||||
displayName: 'Pressed',
|
||||
group: 'States',
|
||||
type: 'boolean',
|
||||
props: {
|
||||
onMouseDown() {
|
||||
this.outputPropValues.pressedState = true;
|
||||
this.flagOutputDirty('pressedState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onTouchStart() {
|
||||
this.outputPropValues.pressedState = true;
|
||||
this.flagOutputDirty('pressedState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onMouseUp() {
|
||||
this.outputPropValues.pressedState = false;
|
||||
this.flagOutputDirty('pressedState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onTouchEnd() {
|
||||
this.outputPropValues.pressedState = false;
|
||||
this.flagOutputDirty('pressedState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onTouchCancel() {
|
||||
this.outputPropValues.pressedState = false;
|
||||
this.flagOutputDirty('pressedState');
|
||||
this._updateVisualState();
|
||||
},
|
||||
onMouseLeave() {
|
||||
this.outputPropValues.pressedState = false;
|
||||
this.flagOutputDirty('pressedState');
|
||||
this._updateVisualState();
|
||||
}
|
||||
}
|
||||
},
|
||||
pointerDown: {
|
||||
displayName: 'Pointer Down',
|
||||
group: 'Pointer Events',
|
||||
type: 'signal',
|
||||
props: {
|
||||
onMouseDown() {
|
||||
this.sendSignalOnOutput('pointerDown');
|
||||
},
|
||||
onTouchStart() {
|
||||
this.sendSignalOnOutput('pointerDown');
|
||||
}
|
||||
}
|
||||
},
|
||||
pointerUp: {
|
||||
displayName: 'Pointer Up',
|
||||
group: 'Pointer Events',
|
||||
type: 'signal',
|
||||
props: {
|
||||
onMouseUp() {
|
||||
this.sendSignalOnOutput('pointerUp');
|
||||
},
|
||||
onTouchEnd() {
|
||||
this.sendSignalOnOutput('pointerUp');
|
||||
},
|
||||
onTouchCancel() {
|
||||
this.sendSignalOnOutput('pointerUp');
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
addOutputs(definition, {
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
displayName: 'Enabled',
|
||||
group: 'States',
|
||||
getter: function () {
|
||||
return this._internal.enabled;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const oldInit = definition.initialize;
|
||||
definition.initialize = function () {
|
||||
oldInit && oldInit.call(this);
|
||||
this.props.enabled = this._internal.enabled = true;
|
||||
this.outputPropValues.hoverState = this.outputPropValues.focusState = this.outputPropValues.pressedState = false;
|
||||
};
|
||||
}
|
||||
|
||||
function controlEvents(props) {
|
||||
return Object.assign(
|
||||
{},
|
||||
{
|
||||
onFocus: props.onFocus,
|
||||
onBlur: props.onBlur
|
||||
},
|
||||
PointerListeners(props)
|
||||
);
|
||||
}
|
||||
|
||||
export default {
|
||||
updateStylesForClass,
|
||||
addControlEventsAndStates,
|
||||
controlEvents
|
||||
};
|
||||
Reference in New Issue
Block a user