diff --git a/client/public/locales/en/common.json b/client/public/locales/en/common.json index d06fced..9159501 100644 --- a/client/public/locales/en/common.json +++ b/client/public/locales/en/common.json @@ -205,6 +205,10 @@ "settings_extruder_temp": "Extruder Temp", "settings_bed_temp": "Bed Temp", "color_hex": "Color", + "single_color": "Single", + "multi_color": "Multi", + "coaxial": "Coextruded", + "longitudinal": "Longitudinal", "external_id": "External ID", "spools": "Show Spools" }, @@ -214,7 +218,8 @@ "price": "Price of a full spool.", "weight": "The filament weight of a full spool (net weight). This should not include the weight of the spool itself, only the filament. It is what is usually written on the packaging.", "spool_weight": "The weight of an empty spool. Used to determine measured weight of a spool.", - "article_number": "E.g. EAN, UPC, etc." + "article_number": "E.g. EAN, UPC, etc.", + "multi_color_direction": "Filaments can have multiple colors in two ways: either through coextrusion, like dual-color filaments with consistent multi-colors, or through longitudinal color changes, like gradient filaments that shift colors along the spool." }, "titles": { "create": "Create Filament", diff --git a/client/src/components/column.tsx b/client/src/components/column.tsx index 7824df6..b671018 100644 --- a/client/src/components/column.tsx +++ b/client/src/components/column.tsx @@ -7,13 +7,12 @@ import { NumberFieldUnit, NumberFieldUnitRange } from "./numberField"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc"; import { DateField, TextField } from "@refinedev/antd"; -import Icon from "@ant-design/icons"; -import SpoolIcon from "../icon_spool.svg?react"; import { useTranslate } from "@refinedev/core"; import { enrichText } from "../utils/parsing"; import { UseQueryResult } from "@tanstack/react-query"; import { Link } from "react-router-dom"; import { Field, FieldType } from "../utils/queryFields"; +import SpoolIcon from "./spoolIcon"; dayjs.extend(utc); @@ -296,7 +295,7 @@ export function ActionsColumn(actionsFn: (record: Obj) => Ac } interface SpoolIconColumnProps extends FilteredQueryColumnProps { - color: (record: Obj) => string | undefined; + color: (record: Obj) => string | { colors: string[]; vertical: boolean } | undefined; } export function SpoolIconColumn(props: SpoolIconColumnProps) { @@ -343,19 +342,12 @@ export function SpoolIconColumn(props: SpoolIconColumnProps< }, render: (rawValue, record: Obj) => { const value = props.transform ? props.transform(rawValue) : rawValue; - const colorStr = props.color(record); + const colorObj = props.color(record); return ( - {colorStr && ( + {colorObj && ( - + )} {value} diff --git a/client/src/components/multiColorPicker.tsx b/client/src/components/multiColorPicker.tsx new file mode 100644 index 0000000..3ac6a1e --- /dev/null +++ b/client/src/components/multiColorPicker.tsx @@ -0,0 +1,85 @@ +import { CloseOutlined, PlusOutlined } from "@ant-design/icons"; +import { Badge, Button, ColorPicker, InputNumber, Space } from "antd"; + +function generateRandomColor() { + return "000000".replace(/0/g, function () { + return (~~(Math.random() * 16)).toString(16); + }); +} + +function generateInitialColors(num: number) { + const colors = []; + for (let i = 0; i < num; i++) { + colors.push(generateRandomColor()); + } + return colors; +} + +/** + * An Ant Design compatible form input for multiple color pickers + * The value is a comma separated list of hex values, without hashtags + * @param props + * @returns + */ +export function MultiColorPicker(props: { + value?: string | null | undefined; + onChange?: (value: string | null | undefined) => void; + min?: number; + max?: number; +}) { + const values = props.value ? props.value.split(",") : generateInitialColors(props.min ?? 0); + if (!props.value && props.onChange) { + // Update value immediately + props.onChange(values.join(",")); + } + const pickers = values.map((value, idx) => ( + (props.min ?? 0) ? ( + + { + // Remove this picker + if (props.onChange) { + props.onChange(values.filter((v, i) => i !== idx).join(",")); + } + }} + /> + + ) : ( + <> + ) + } + > + { + if (props.onChange) { + newHex = newHex.replace("#", ""); + props.onChange(values.map((v, i) => (i === idx ? newHex : v)).join(",")); + } + }} + /> + + )); + + return ( + <> + + {pickers} + {values.length < (props.max ?? Infinity) && ( +