1
0
Fork 0

🚧 [components] Work on draft/pattern settings/options

This commit is contained in:
Joost De Cock 2019-04-23 18:34:18 +02:00
parent 3ca3742953
commit c0690d5496
30 changed files with 2195 additions and 128 deletions

View file

@ -1,6 +1,9 @@
import React from "react";
import { configure } from "@storybook/react";
import { addParameters } from "@storybook/react";
import "../../../dist/css-theme/theme.css";
import { addParameters, addDecorator } from "@storybook/react";
import { IntlProvider } from "react-intl";
import { strings } from "@freesewing/i18n";
function loadStories() {
// Load all 'stories.js' files under src
@ -8,6 +11,12 @@ function loadStories() {
req.keys().forEach(filename => req(filename));
}
addDecorator(story => (
<IntlProvider locale="en" messages={strings.en}>
{story()}
</IntlProvider>
));
//addParameters({
// backgrounds: [
// { name: 'Light', value: '#f8f9fa', default: true },

1193
packages/components/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -26,7 +26,10 @@
},
"peerDependencies": {
"react": "^16.4.1",
"prop-types": "15.7.2"
"prop-types": "15.7.2",
"@freesewing/patterns": "^0.33.0",
"@freesewing/i18n": "^0.33.0",
"react-intl": "^2.8.0"
},
"files": [
"../../dist/packages/components/*",
@ -39,5 +42,9 @@
"engines": {
"node": ">=8.0.0",
"npm": ">=5"
},
"dependencies": {
"@freesewing/i18n": "0.11.3",
"@freesewing/patterns": "0.18.6"
}
}

View file

@ -1,4 +1,4 @@
import React from "react";
import DraftSettingBool from "../DraftSettingBool";
import PatternOptionBool from "../PatternOptionBool";
export default props => <DraftSettingBool {...props} name="complete" />;
export default props => <PatternOptionBool {...props} name="complete" />;

View file

@ -0,0 +1,62 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import FormFieldList from "../FormFieldList";
import OptionPreamble from "../OptionPreamble";
const DraftSettingLanguage = props => {
const [value, setValue] = useState(props.dflt);
const update = (name, newValue, evt) => {
props.updateValue(props.name, newValue);
setValue(newValue);
};
const reset = () => {
setValue(props.dflt);
props.updateValue(props.name, props.dflt);
};
return (
<div className={"pattern-option list"}>
<OptionPreamble
dflt={props.dflt}
value={value}
desc={props.desc}
title={props.title}
id={"po-list-" + props.name}
displayValue={props.languages[value]}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
type: "draftSetting",
value: props.name
})
}
/>
<FormFieldList
name={props.name}
value={value}
dflt={props.dflt}
onChange={update}
label={"po-list-" + props.name}
updateValue={update}
list={props.languages}
/>
</div>
);
};
DraftSettingLanguage.propTypes = {
triggerAction: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
dflt: PropTypes.oneOfType([
PropTypes.number.isRequired,
PropTypes.string.isRequired
]),
title: PropTypes.node.isRequired,
desc: PropTypes.node.isRequired,
list: PropTypes.object.isRequired
};
export default DraftSettingLanguage;

View file

@ -0,0 +1,27 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Lang from ".";
const props = {
triggerAction: (type, data) =>
console.log(`Action of type ${type} triggered, data passed is`, data),
updateValue: (name, value) =>
console.log(`Updated setting ${name}, value is now: ${value}`),
name: "exampleDraftSettingLanguage",
dflt: "en",
title:
"I am a language draft setting. This is my title. I'm wrapped in an h4 tag",
desc:
"This is the description. I'm wrapped in a p tag. This component only sets the CSS class on a non-default value. It's up to you to supply the CSS to style it.",
languages: {
de: "German",
en: "English",
es: "Spanish",
fr: "French",
nl: "Dutch"
}
};
storiesOf("DraftSettingLanguage", module).add("Basic", () => (
<Lang {...props} />
));

View file

@ -0,0 +1,74 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import FormFieldList from "../FormFieldList";
import FormFieldSlider from "../FormFieldSlider";
import { formatMm, roundMm, defaultSa, sliderStep } from "../utils";
import OptionPreamble from "../OptionPreamble";
const DraftSettingMargin = props => {
const [value, setValue] = useState(props.dflt);
const update = (name, newValue, evt) => {
newValue = roundMm(newValue);
// Sometimes, when sliding, the rapid succession of updates
// causes a weird timing issue to result in a value that is NaN.
// If that's the case, just ignore this update and keep the
// previous one instead
if (!isNaN(newValue)) {
setValue(newValue);
if (evt.type !== "mousemove") props.updateValue("margin", newValue);
} else {
if (evt.type !== "mousemove") props.updateValue("margin", newValue);
}
};
const reset = () => {
setValue(props.dflt);
props.updateValue("margin", props.dflt);
};
return (
<div className={"pattern-option list"}>
<OptionPreamble
dflt={props.dflt}
value={value}
desc={props.desc}
title={props.title}
id="po-slider-margin"
displayValue={formatMm(value, props.units)}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
type: "draftSetting",
value: "margin"
})
}
/>
<FormFieldSlider
name="customSa"
value={value}
dflt={props.dflt}
label="po-slider-margin"
updateValue={update}
min={0}
max={25.4}
step={sliderStep[props.units]}
/>
</div>
);
};
DraftSettingMargin.propTypes = {
triggerAction: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
units: PropTypes.oneOf(["metric", "imperial"]).isRequired,
labels: PropTypes.array
};
DraftSettingMargin.defaultProps = {
// FIXME
};
export default DraftSettingMargin;

View file

@ -0,0 +1,19 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Margin from ".";
const props = {
triggerAction: (type, data) =>
console.log(`Action of type ${type} triggered, data passed is`, data),
updateValue: (name, value) =>
console.log(`Updated setting ${name}, value is now: ${value}`),
name: "margin",
dflt: 2,
title: "Margin",
desc:
"This is the margin description. I'm wrapped in a p tag. This component only sets the CSS class on a non-default value. It's up to you to supply the CSS to style it."
};
storiesOf("DraftSettingMargin", module)
.add("Metric", () => <Margin {...props} units="metric" />)
.add("Imperial", () => <Margin {...props} units="imperial" />);

View file

@ -0,0 +1,78 @@
import React, { useState } from "react";
import FormFieldChecks from "../FormFieldChecks";
import FormFieldList from "../FormFieldList";
import FormFieldSlider from "../FormFieldSlider";
import { formatMm, roundMm, defaultSa, sliderStep } from "../utils";
import OptionPreamble from "../OptionPreamble";
const DraftSettingOnly = props => {
const [value, setValue] = useState(props.dflt);
const [parts, setParts] = useState([]);
const update = (name, newValue, evt) => {
setValue(newValue);
if (newValue === "dflt") props.updateValue("only", false);
else props.updateValue("only", parts);
};
const reset = () => {
setValue("dflt");
setParts([]);
props.updateValue("only", false);
};
const updateCustom = (name, newValue, evt) => {
props.updateValue("only", newValue);
setParts(newValue);
};
const list = {
dflt: props.labels.dflt,
custom: props.labels.custom
};
return (
<div className={"pattern-option list"}>
<OptionPreamble
dflt="dflt"
value={value}
desc={props.desc}
title={props.title}
id="po-list-only"
displayValue={props.labels[value]}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
type: "draftSetting",
value: "only"
})
}
/>
<FormFieldList
name="only"
value={value}
dflt={props.dflt}
onChange={update}
label="po-list-only"
updateValue={update}
list={list}
/>
{value === "custom" ? (
<FormFieldChecks
checks={props.parts}
name="parts"
value={value}
dflt={props.customDflt}
onChange={updateCustom}
label="po-list-only"
updateValue={updateCustom}
list={list}
/>
) : (
""
)}
</div>
);
};
export default DraftSettingOnly;

View file

@ -0,0 +1,38 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Sa from ".";
const props = {
triggerAction: (type, data) =>
console.log(`Action of type ${type} triggered, data passed is`, data),
updateValue: (name, value) =>
console.log(`Updated setting ${name}, value is now: ${value}`),
title: "Only (known as contents on the website) title",
desc:
"This is the only description. I'm wrapped in a p tag. This component only sets the CSS class on a non-default value. It's up to you to supply the CSS to style it.",
labels: {
dflt: "Default",
custom: "Custom"
},
dflt: "dflt",
customDflt: [],
parts: {
front: "Front",
back: "Back",
sleeve: "Sleeve",
pocket: "Pocket"
}
};
storiesOf("DraftSettingOnly", module)
.add("Default", () => <Sa {...props} />)
.add("Default, all parts preselected", () => (
<Sa {...props} customDflt={Object.keys(props.parts)} />
))
.add("Custom, some parts preselected", () => (
<Sa
{...props}
dflt="custom"
customDflt={Object.keys(props.parts).slice(2)}
/>
));

View file

@ -1,4 +1,4 @@
import React from "react";
import DraftSettingBool from "../DraftSettingBool";
import PatternOptionBool from "../PatternOptionBool";
export default props => <DraftSettingBool {...props} name="paperless" />;
export default props => <PatternOptionBool {...props} name="paperless" />;

View file

@ -0,0 +1,116 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import FormFieldList from "../FormFieldList";
import FormFieldSlider from "../FormFieldSlider";
import { formatMm, roundMm, defaultSa, sliderStep } from "../utils";
import OptionPreamble from "../OptionPreamble";
const DraftSettingSa = props => {
const [value, setValue] = useState("dflt");
const [saValue, setSaValue] = useState(defaultSa[props.units]);
const [customValue, setCustomValue] = useState(10);
const update = (name, newValue, evt) => {
switch (newValue) {
case "none":
props.updateValue("sa", 0);
setValue(newValue);
setSaValue(0);
break;
case "dflt":
props.updateValue("sa", defaultSa[props.units]);
setValue(newValue);
setSaValue(defaultSa[props.units]);
break;
default:
props.updateValue("sa", customValue);
setValue(newValue);
setSaValue(customValue);
break;
}
};
const reset = () => {
setValue("dflt");
setSaValue(defaultSa[props.units]);
props.updateValue("sa", defaultSa[props.units]);
};
const updateCustom = (name, newValue, evt) => {
newValue = roundMm(newValue);
// Sometimes, when sliding, the rapid succession of updates
// causes a weird timing issue to result in a value that is NaN.
// If that's the case, just ignore this update and keep the
// previous one instead
if (!isNaN(newValue)) {
setSaValue(newValue);
if (evt.type !== "mousemove") props.updateValue("sa", newValue);
} else {
if (evt.type !== "mousemove") props.updateValue("sa", newValue);
}
};
const list = {
none: props.labels.none,
dflt: props.labels.dflt,
custom: props.labels.custom
};
return (
<div className={"pattern-option list"}>
<OptionPreamble
dflt={"dflt"}
value={value}
desc={props.desc}
title={props.title}
id="po-list-sa"
displayValue={formatMm(saValue, props.units)}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
type: "draftSetting",
value: "sa"
})
}
/>
<FormFieldList
name="sa"
value={value}
dflt={"dflt"}
onChange={update}
label="po-bool-sa"
updateValue={update}
list={list}
/>
{value === "custom" ? (
<FormFieldSlider
name="customSa"
value={saValue}
dflt={defaultSa[props.units]}
label="po-bool-sa"
updateValue={updateCustom}
min={0}
max={25.4}
step={sliderStep[props.units]}
/>
) : (
""
)}
</div>
);
};
DraftSettingSa.propTypes = {
triggerAction: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
units: PropTypes.oneOf(["metric", "imperial"]).isRequired,
labels: PropTypes.array
};
DraftSettingSa.defaultProps = {
// FIXME
};
export default DraftSettingSa;

View file

@ -0,0 +1,24 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Sa from ".";
const props = {
triggerAction: (type, data) =>
console.log(`Action of type ${type} triggered, data passed is`, data),
updateValue: (name, value) =>
console.log(`Updated setting ${name}, value is now: ${value}`),
name: "sa",
dflt: "dflt",
title: "Seam allowance",
desc:
"This is the seam allowance description. I'm wrapped in a p tag. This component only sets the CSS class on a non-default value. It's up to you to supply the CSS to style it.",
labels: {
none: "No seam allowance",
dflt: "Standard seam allowance",
custom: "Custom seam allowance"
}
};
storiesOf("DraftSettingSa", module)
.add("Metric", () => <Sa {...props} units="metric" />)
.add("Imperial", () => <Sa {...props} units="imperial" />);

View file

@ -0,0 +1,48 @@
import React, { useState } from "react";
import FormFieldList from "../FormFieldList";
import OptionPreamble from "../OptionPreamble";
const DraftSettingUnits = props => {
const [value, setValue] = useState(props.dflt);
const update = (name, newValue, evt) => {
props.updateValue(props.name, newValue);
setValue(newValue);
};
const reset = () => {
setValue(props.dflt);
props.updateValue(props.name, props.dflt);
};
return (
<div className={"pattern-option list"}>
<OptionPreamble
dflt={props.dflt}
value={value}
desc={props.desc}
title={props.title}
id="po-list-units"
displayValue={props.list[value]}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
type: "draftSetting",
value: "units"
})
}
/>
<FormFieldList
name="units"
value={value}
dflt={props.dflt}
onChange={update}
label="po-bool-units"
updateValue={update}
list={props.list}
/>
</div>
);
};
export default DraftSettingUnits;

View file

@ -0,0 +1,21 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import Units from ".";
const props = {
triggerAction: (type, data) =>
console.log(`Action of type ${type} triggered, data passed is`, data),
updateValue: (name, value) =>
console.log(`Updated setting ${name}, value is now: ${value}`),
name: "paperless",
dflt: "metric",
title: "Units title",
desc:
"This is the units description. I'm wrapped in a p tag. This component only sets the CSS class on a non-default value. It's up to you to supply the CSS to style it.",
list: {
metric: "Metric",
imperial: "Imperial"
}
};
storiesOf("DraftSettingUnits", module).add("Basic", () => <Units {...props} />);

View file

@ -0,0 +1,54 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import FormControl from "@material-ui/core/FormControl";
import FormGroup from "@material-ui/core/FormGroup";
import FormControlLabel from "@material-ui/core/FormControlLabel";
import FormHelperText from "@material-ui/core/FormHelperText";
import Checkbox from "@material-ui/core/Checkbox";
const FormFieldChecks = props => {
const [value, setValue] = useState(props.dflt);
const toggle = part => {
let parts = value.slice(0);
let index = parts.indexOf(part);
if (index === -1) parts.push(part);
else parts.splice(index, 1);
setValue(parts);
props.updateValue(props.name, parts);
};
// Force state update when rerendering due to props change
// if (props.value !== value) setValue(props.value);
return (
<FormGroup>
{Object.keys(props.checks).map(i => {
let check = props.checks[i];
return (
<FormControlLabel
control={
<Checkbox
checked={value.indexOf(i) === -1 ? false : true}
onChange={() => toggle(i)}
value={i}
/>
}
label={props.checks[i]}
key={i}
className="po-list-item"
/>
);
})}
</FormGroup>
);
};
FormFieldChecks.propTypes = {
dflt: PropTypes.array,
checks: PropTypes.object,
updateValue: PropTypes.func.isRequired,
name: PropTypes.string.isRequired
};
export default FormFieldChecks;

View file

@ -0,0 +1,21 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import FormFieldChecks from ".";
const props = {
updateValue: (name, value) =>
console.log(`Updated option ${name}, value is now: ${value}`),
name: "exampleChecksOption",
checks: {
apple: "Apple",
banana: "Banana",
cherry: "Cherry"
},
dflt: []
};
storiesOf("FormFieldChecks", module)
.add("Basic", () => <FormFieldChecks {...props} />)
.add("Apple", () => <FormFieldChecks {...props} dflt={["apple"]} />)
.add("Banana", () => <FormFieldChecks {...props} dflt={["banana"]} />)
.add("Cherry", () => <FormFieldChecks {...props} dflt={["cherry"]} />);

View file

@ -4,7 +4,10 @@ import Slider from "@material-ui/lab/Slider";
import { withStyles } from "@material-ui/core/styles";
const PaddedSlider = withStyles({
container: { padding: "25px 0" },
container: {
padding: "25px 0",
overflowX: "hidden" // See: https://github.com/mui-org/material-ui/issues/14234
},
track: { height: "4px" },
thumb: { width: "16px", height: "16px" }
})(Slider);

View file

@ -0,0 +1,59 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import Pct from "../PatternOptionPercentage";
import Deg from "../PatternOptionDegree";
import Mm from "../PatternOptionMillimeter";
import Bool from "../PatternOptionBool";
import OptionGroup from "../OptionGroup";
import { optionType, defaultGist, gistDefaults } from "../utils";
import { patternInfo, patternList } from "@freesewing/patterns";
import { FormattedMessage } from "react-intl";
const GistConfigurator = props => {
const [gist, setGist] = useState(props.gist || defaultGist);
const update = (type, name, value) => {
console.log("updating", type, name, value);
};
let pattern = patternInfo[props.pattern];
let dflts = gistDefaults(pattern.config, gist);
return (
<div className="gist-config">
<div className="gist-options">
<h2>
<FormattedMessage id="app.patternOptions" />
</h2>
{Object.keys(pattern.optionGroups).map(group => (
<React.Fragment key={group}>
<h3 key={group + "-title"}>
<FormattedMessage id={"optiongroups." + group} />
</h3>
<OptionGroup
key={group + "-group"}
units={props.units}
pattern={pattern}
dflts={dflts}
options={pattern.optionGroups[group]}
updateValue={update}
triggerAction={props.triggerAction}
/>
</React.Fragment>
))}
<h2>
<FormattedMessage id="app.draftSettings" />
</h2>
</div>
</div>
);
};
GistConfigurator.propTypes = {
pattern: PropTypes.oneOf(patternList),
units: PropTypes.oneOf(["metric", "imperial"]).isRequired
};
GistConfigurator.defaultProps = {};
export default GistConfigurator;

View file

@ -0,0 +1,18 @@
import React from "react";
import { storiesOf } from "@storybook/react";
import GistConfigurator from ".";
//import { IntlProvider } from "react-intl";
//import { strings } from "@freesewing/i18n";
const props = {
triggerAction: (type, data) =>
console.log(`Action of type ${type} triggered, data passed is`, data)
};
storiesOf("GistConfigurator", module)
.add("Simon metric", () => (
<GistConfigurator pattern="simon" gist={false} units="metric" />
))
.add("Trayvon imperial", () => (
<GistConfigurator pattern="trayvon" gist={false} units="imperial" />
));

View file

@ -0,0 +1,94 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import Pct from "../PatternOptionPercentage";
import Deg from "../PatternOptionDegree";
import Mm from "../PatternOptionMillimeter";
import Bool from "../PatternOptionBool";
import List from "../PatternOptionList";
import Count from "../PatternOptionCount";
import { optionType } from "../utils";
import { FormattedMessage } from "react-intl";
import { injectIntl } from "react-intl";
const OptionGroup = props => {
const update = (name, value) => props.updateValue("option", name, value);
const renderOption = name => {
let option = props.pattern.config.options[name];
let type = optionType(option);
let stringKey = `options.${props.pattern.config.name}.${name}.`;
let extraProps = {
name,
dflt: props.dflts.options[name],
units: props.units,
updateValue: update,
triggerAction: props.triggerAction,
title: <FormattedMessage id={stringKey + "title"} />,
desc: <FormattedMessage id={stringKey + "description"} />,
intl: props.intl,
pattern: props.pattern.config.name
};
let noyes = [
<FormattedMessage id="app.no" />,
<FormattedMessage id="app.yes" />
];
switch (type) {
case "pct":
return <Pct {...option} {...extraProps} key={name} />;
break;
case "deg":
return <Deg {...option} {...extraProps} key={name} />;
break;
case "mm":
return (
<Mm {...option} {...extraProps} key={name} units={props.units} />
);
break;
case "bool":
return <Bool {...option} {...extraProps} key={name} labels={noyes} />;
break;
case "list":
return <List {...option} {...extraProps} key={name} />;
break;
case "count":
return <Count {...option} {...extraProps} key={name} />;
break;
default:
throw new Error("Unsupport option type: " + type);
}
};
return (
<div className="optiongroup">
{props.options.map(name => {
let output = [];
if (typeof name === "object") {
// Subgroup
for (let subGroup of Object.keys(name)) {
output.push(
<h4 key={subGroup + "-title"}>
<FormattedMessage id={"optiongroups." + subGroup} />
</h4>
);
for (let option of name[subGroup])
output.push(renderOption(option));
}
} else output.push(renderOption(name));
return output;
})}
</div>
);
};
OptionGroup.propTypes = {
pattern: PropTypes.object.isRequired,
dflts: PropTypes.object.isRequired,
options: PropTypes.array.isRequired,
units: PropTypes.oneOf(["metric", "imperial"]).isRequired
};
OptionGroup.defaultProps = {};
export default injectIntl(OptionGroup);

View file

@ -3,6 +3,7 @@ import PropTypes from "prop-types";
import IconButton from "@material-ui/core/IconButton";
import ResetIcon from "@material-ui/icons/SettingsBackupRestore";
import HelpIcon from "@material-ui/icons/Help";
import { injectIntl } from "react-intl";
const OptionPreamble = props => {
const styles = {
@ -18,6 +19,15 @@ const OptionPreamble = props => {
right: { margin: "0 0.5rem" }
};
const resetLabel = props.intl.formatMessage({
id: "app.restoreDefaults",
defaultMessage: " ♻️ "
});
const docsLabel = props.intl.formatMessage({
id: "app.docs",
defaultMessage: " 🤔 "
});
return (
<React.Fragment>
<div style={styles.container}>
@ -36,8 +46,8 @@ const OptionPreamble = props => {
</div>
<div style={styles.right}>
<IconButton
title={props.resetLabel}
aria-label={props.resetLabel}
title={resetLabel}
aria-label={resetLabel}
color="primary"
disabled={props.value === props.dflt ? true : false}
onClick={props.reset}
@ -45,8 +55,8 @@ const OptionPreamble = props => {
<ResetIcon />
</IconButton>
<IconButton
title={props.docsLabel}
aria-label={props.docsLabel}
title={docsLabel}
aria-label={docsLabel}
color="primary"
onClick={props.showHelp}
>
@ -67,17 +77,10 @@ OptionPreamble.propTypes = {
PropTypes.number.isRequired,
PropTypes.string.isRequired
]),
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
resetLabel: PropTypes.string,
docsLabel: PropTypes.string,
title: PropTypes.node.isRequired,
desc: PropTypes.node.isRequired,
reset: PropTypes.func.isRequired,
showHelp: PropTypes.func.isRequired
};
OptionPreamble.defaultProps = {
resetLabel: "♻️",
docsLabel: "🤔"
};
export default OptionPreamble;
export default injectIntl(OptionPreamble);

View file

@ -3,7 +3,7 @@ import PropTypes from "prop-types";
import FormFieldBool from "../FormFieldBool";
import OptionPreamble from "../OptionPreamble";
const DraftSettingBool = props => {
const PatternOptionBool = props => {
const [value, setValue] = useState(props.dflt);
const update = (name, newValue, evt) => {
@ -17,7 +17,7 @@ const DraftSettingBool = props => {
};
return (
<div className={"pattern-option list"}>
<div className={"pattern-option bool"}>
<OptionPreamble
dflt={props.dflt}
value={value}
@ -46,23 +46,17 @@ const DraftSettingBool = props => {
);
};
DraftSettingBool.propTypes = {
PatternOptionBool.propTypes = {
triggerAction: PropTypes.func.isRequired,
updateValue: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
dflt: PropTypes.oneOfType([
PropTypes.number.isRequired,
PropTypes.string.isRequired
]),
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
resetLabel: PropTypes.string,
docsLabel: PropTypes.string,
list: PropTypes.object.isRequired
title: PropTypes.node.isRequired,
desc: PropTypes.node.isRequired,
labels: PropTypes.array.isRequired
};
DraftSettingBool.defaultProps = {
title: false,
desc: false
};
export default DraftSettingBool;
export default PatternOptionBool;

View file

@ -7,15 +7,15 @@ const props = {
console.log(`Action of type ${type} triggered, data passed is`, data),
updateValue: (name, value) =>
console.log(`Updated pct/deg/count option ${name}, value is now: ${value}`),
name: "exampleDraftSettingBool",
name: "examplePatternOptionBool",
dflt: false,
title:
"I am a boolean draft setting. This is my title. I'm wrapped in an h4 tag",
"I am a boolean pattern option. This is my title. I'm wrapped in an h4 tag",
desc:
"This is the description. I'm wrapped in a p tag. This component only sets the CSS class on a non-default value. It's up to you to supply the CSS to style it.",
labels: ["No", "Yes"]
};
storiesOf("DraftSettingBool", module)
storiesOf("PatternOptionBool", module)
.add("Basic", () => <Bool {...props} />)
.add("Yes as default", () => <Bool {...props} dflt={true} />);

View file

@ -29,6 +29,15 @@ const PatternOptionList = props => {
right: { margin: "0 0.5rem" }
};
// Add translations
let stringKey = `options.${props.pattern}.${props.name}.options.`;
let list = {};
for (let item of props.list)
list[item] = props.intl.formatMessage({
id: stringKey + item,
defaultMessage: item
});
return (
<div className={"pattern-option list"}>
<OptionPreamble
@ -37,7 +46,7 @@ const PatternOptionList = props => {
desc={props.desc}
title={props.title}
id={"po-list-" + props.name}
displayValue={props.list[value]}
displayValue={list[value]}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
@ -53,7 +62,7 @@ const PatternOptionList = props => {
onChange={update}
label={"po-list-" + props.name}
updateValue={update}
list={props.list}
list={list}
/>
</div>
);
@ -66,16 +75,9 @@ PatternOptionList.propTypes = {
PropTypes.number.isRequired,
PropTypes.string.isRequired
]),
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
resetLabel: PropTypes.string,
docsLabel: PropTypes.string,
list: PropTypes.object.isRequired
};
PatternOptionList.defaultProps = {
title: false,
desc: false
title: PropTypes.node.isRequired,
desc: PropTypes.node.isRequired,
list: PropTypes.array.isRequired
};
export default PatternOptionList;

View file

@ -1,5 +1,12 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import {
sliderStep,
roundMm,
roundMmUp,
roundMmDown,
formatMm
} from "../utils";
import FormFieldSlider from "../FormFieldSlider";
import OptionPreamble from "../OptionPreamble";
@ -7,26 +14,8 @@ const PatternOptionMillimeter = props => {
const [value, setValue] = useState(props.dflt);
const [previousValue, setPreviousValue] = useState(props.dflt);
const smallestImperialStep = 0.396875;
const round = val => {
if (props.units === "imperial") {
return Math.round(val * 1000000) / 1000000;
} else return Math.round(val * 10) / 10;
};
const roundDown = val => {
if (props.units === "imperial") {
return val - (val % smallestImperialStep);
} else return Math.floor(val * 10) / 10;
};
const roundUp = val => {
if (props.units === "imperial") {
return val - (val % 0.396875);
} else return Math.ceil(val * 10) / 10;
};
const update = (name, newValue, evt) => {
newValue = round(newValue);
newValue = roundMm(newValue, props.units);
// Sometimes, when sliding, the rapid succession of updates
// causes a weird timing issue to result in a value that is NaN.
// If that's the case, just ignore this update and keep the
@ -44,51 +33,6 @@ const PatternOptionMillimeter = props => {
props.updateValue(props.name, props.dflt);
};
const formatValue = val => {
val = round(val);
if (props.units === "imperial") {
if (val == 0) return 0 + '"';
let negative = "";
let inches = "";
let rest = "";
let fraction = val / 25.4;
if (fraction < 0) {
fraction = fraction * -1;
negative = "-";
}
if (Math.abs(fraction) < 1) {
inches = "";
rest = fraction;
} else {
inches = Math.floor(fraction);
rest = fraction - inches;
}
inches += " ";
let fraction128 = Math.round(rest * 128);
// eslint-disable-next-line
if (fraction128 == 0) return negative + inches;
// eslint-disable-next-line
if (fraction128 % 64 == 0)
return negative + inches + fraction128 / 64 + "/2";
// eslint-disable-next-line
if (fraction128 % 32 == 0)
return negative + inches + fraction128 / 32 + "/2";
// eslint-disable-next-line
if (fraction128 % 16 == 0)
return negative + inches + fraction128 / 16 + "/8";
// eslint-disable-next-line
if (fraction128 % 8 == 0)
return negative + inches + fraction128 / 8 + "/16";
// eslint-disable-next-line
if (fraction128 % 4 == 0)
return negative + inches + fraction128 / 4 + "/32";
// eslint-disable-next-line
if (fraction128 % 2 == 0)
return negative + inches + fraction128 / 2 + "/64";
else return negative + fraction;
} else return val + "cm";
};
const styles = {
container: {
display: "flex",
@ -110,7 +54,7 @@ const PatternOptionMillimeter = props => {
desc={props.desc}
title={props.title}
id={"po-mm-" + props.name}
displayValue={formatValue(value)}
displayValue={formatMm(value, props.units)}
reset={reset}
showHelp={() =>
props.triggerAction("showHelp", {
@ -122,9 +66,9 @@ const PatternOptionMillimeter = props => {
<FormFieldSlider
name={props.name}
value={value}
min={roundUp(props.min)}
max={roundDown(props.max)}
step={props.units === "imperial" ? smallestImperialStep : 0.1}
min={roundMmUp(props.min, props.units)}
max={roundMmDown(props.max, props.units)}
step={sliderStep[props.units]}
onChange={update}
label={"po-mm-" + props.name}
updateValue={update}
@ -139,10 +83,8 @@ PatternOptionMillimeter.propTypes = {
updateValue: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
dflt: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
resetLabel: PropTypes.string,
docsLabel: PropTypes.string,
title: PropTypes.node.isRequired,
desc: PropTypes.node.isRequired,
units: PropTypes.oneOf(["metric", "imperial"]).isRequired
};

View file

@ -82,10 +82,8 @@ PatternOptionPctDegCount.propTypes = {
updateValue: PropTypes.func.isRequired,
name: PropTypes.string.isRequired,
dflt: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
desc: PropTypes.string.isRequired,
resetLabel: PropTypes.string,
docsLabel: PropTypes.string,
title: PropTypes.node.isRequired,
desc: PropTypes.node.isRequired,
type: PropTypes.oneOf(["pct", "deg", "count"])
};
@ -93,8 +91,6 @@ PatternOptionPctDegCount.defaultProps = {
min: 0,
max: 100,
step: 0.1,
title: false,
desc: false,
type: "pct"
};

View file

@ -0,0 +1,161 @@
// Needed to use JSX
import React from "react";
export const smallestImperialStep = 0.396875;
export const roundMm = (val, units) => {
if (units === "imperial") return Math.round(val * 1000000) / 1000000;
else return Math.round(val * 10) / 10;
};
export const roundMmDown = (val, units) => {
if (units === "imperial") return val - (val % smallestImperialStep);
else return Math.floor(val * 10) / 10;
};
export const roundMmUp = (val, units) => {
if (units === "imperial") return val - (val % 0.396875);
else return Math.ceil(val * 10) / 10;
};
const formatImperial = (
neg,
inch,
numo = false,
deno = false,
format = "html"
) => {
if (format === "html") {
if (numo)
return (
<span>
{neg}
{inch} <sup>{numo}</sup>/<sub>{deno}</sub>"
</span>
);
else
return (
<span>
{neg}
{inch}"
</span>
);
} else {
if (numo) return neg + inch;
else return neg + inch + " " + numo + "/" + deno;
}
};
export const formatMm = (val, units, format = "html") => {
val = roundMm(val);
let H = format === "html" ? true : false;
if (units === "imperial") {
if (val == 0) return formatImperial("", 0, false, false, format);
let negative = "";
let inches = "";
let rest = "";
let fraction = val / 25.4;
if (fraction < 0) {
fraction = fraction * -1;
negative = "-";
}
if (Math.abs(fraction) < 1) rest = fraction;
else {
inches = Math.floor(fraction);
rest = fraction - inches;
}
let fraction128 = Math.round(rest * 128);
if (fraction128 == 0) return formatImperial(negative, inches);
if (fraction128 % 64 == 0)
return formatImperial(negative, inches, fraction128 / 64, 2);
if (fraction128 % 32 == 0)
return formatImperial(negative, inches, fraction128 / 32, 4);
if (fraction128 % 16 == 0)
return formatImperial(negative, inches, fraction128 / 16, 8);
if (fraction128 % 8 == 0)
return formatImperial(negative, inches, fraction128 / 8, 16);
if (fraction128 % 4 == 0)
return formatImperial(negative, inches, fraction128 / 4, 32);
if (fraction128 % 2 == 0)
return formatImperial(negative, inches, fraction128 / 2, 64);
return negative + fraction;
} else {
if (format === "html") return roundMm(val / 10) + "cm";
else return roundMm(val / 10);
}
};
export const defaultSa = {
imperial: 15.875,
metric: 10
};
export const sliderStep = {
metric: 0.1,
imperial: 0.396875
};
export const optionType = option => {
if (typeof option === "object") {
if (typeof option.pct !== "undefined") return "pct";
if (typeof option.mm !== "undefined") return "mm";
if (typeof option.deg !== "undefined") return "deg";
if (typeof option.count !== "undefined") return "count";
if (typeof option.bool !== "undefined") return "bool";
if (typeof option.list !== "undefined") return "list";
return "unknown";
}
return "constant";
};
export const defaultGist = {
settings: {
embed: true,
sa: 0,
complete: true,
paperless: false,
locale: "en",
units: "metric",
margin: 2,
options: {}
}
};
export const gistDefaults = (config, gist) => {
let options = {};
for (let option of Object.keys(config.options)) {
if (
typeof gist.options !== "undefined" &&
typeof gist.options[option] !== undefined
)
options[option] = gist.options[option];
else options[option] = optionDefault(config.options[option]);
}
delete gist.options;
let settings = JSON.parse(JSON.stringify(defaultGist.settings));
delete settings.locale;
delete settings.units;
for (let setting of Object.keys(settings)) {
if (typeof gist.settings[setting] !== undefined) {
settings[setting] = gist.settings[setting];
}
}
settings.options = options;
return settings;
};
export const optionDefault = option => {
let type = optionType(option);
switch (optionType(option)) {
case "constant":
return option;
break;
case "list":
return option.dflt;
break;
default:
return option[type];
}
};