Files
fluxscape/packages/noodl-viewer-react/src/nodes/controls/slider.ts

510 lines
14 KiB
TypeScript

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.attrs = {};
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);
}
},
testId: {
index: 100009,
displayName: 'Test ID Attribute',
group: 'Advanced HTML',
type: 'string',
set(value) {
this.props.attrs["data-testid"] = value;
this.forceUpdate();
}
}
},
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);