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:
Michael Cartner
2024-01-26 11:52:55 +01:00
commit b9c60b07dc
2789 changed files with 868795 additions and 0 deletions

View 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);

View 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);

View 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);

View 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);

View File

@@ -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;

View 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);

View 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);

View 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
};