Added create filament page support for multi color
This commit is contained in:
@@ -204,6 +204,10 @@
|
|||||||
"settings_extruder_temp": "Extruder Temp",
|
"settings_extruder_temp": "Extruder Temp",
|
||||||
"settings_bed_temp": "Bed Temp",
|
"settings_bed_temp": "Bed Temp",
|
||||||
"color_hex": "Color",
|
"color_hex": "Color",
|
||||||
|
"single_color": "Single",
|
||||||
|
"multi_color": "Multi",
|
||||||
|
"coaxial": "Coextruded",
|
||||||
|
"longitudinal": "Longitudinal",
|
||||||
"external_id": "External ID",
|
"external_id": "External ID",
|
||||||
"spools": "Show Spools"
|
"spools": "Show Spools"
|
||||||
},
|
},
|
||||||
@@ -213,7 +217,8 @@
|
|||||||
"price": "Price of a full spool.",
|
"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.",
|
"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.",
|
"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": {
|
"titles": {
|
||||||
"create": "Create Filament",
|
"create": "Create Filament",
|
||||||
|
|||||||
85
client/src/components/multiColorPicker.tsx
Normal file
85
client/src/components/multiColorPicker.tsx
Normal file
@@ -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) => (
|
||||||
|
<Badge
|
||||||
|
key={idx}
|
||||||
|
count={
|
||||||
|
values.length > (props.min ?? 0) ? (
|
||||||
|
<span className="ant-badge-count">
|
||||||
|
<CloseOutlined
|
||||||
|
onClick={() => {
|
||||||
|
// Remove this picker
|
||||||
|
if (props.onChange) {
|
||||||
|
props.onChange(values.filter((v, i) => i !== idx).join(","));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<ColorPicker
|
||||||
|
format="hex"
|
||||||
|
value={value}
|
||||||
|
onChange={(_, newHex) => {
|
||||||
|
if (props.onChange) {
|
||||||
|
newHex = newHex.replace("#", "");
|
||||||
|
props.onChange(values.map((v, i) => (i === idx ? newHex : v)).join(","));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Badge>
|
||||||
|
));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Space direction="horizontal" size="middle">
|
||||||
|
{pickers}
|
||||||
|
{values.length < (props.max ?? Infinity) && (
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={() => {
|
||||||
|
if (props.onChange) {
|
||||||
|
props.onChange(values.concat(generateRandomColor()).join(","));
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { HttpError, IResourceComponentsProps, useInvalidate, useTranslate } from "@refinedev/core";
|
import { HttpError, IResourceComponentsProps, useInvalidate, useTranslate } from "@refinedev/core";
|
||||||
import { Create, useForm, useSelect } from "@refinedev/antd";
|
import { Create, useForm, useSelect } from "@refinedev/antd";
|
||||||
import { Form, Input, Select, InputNumber, ColorPicker, Button, Typography, Modal } from "antd";
|
import { Form, Input, Select, InputNumber, ColorPicker, Button, Typography, Modal, Radio } from "antd";
|
||||||
import TextArea from "antd/es/input/TextArea";
|
import TextArea from "antd/es/input/TextArea";
|
||||||
import { numberFormatter, numberParser } from "../../utils/parsing";
|
import { numberFormatter, numberParser } from "../../utils/parsing";
|
||||||
import { IVendor } from "../vendors/model";
|
import { IVendor } from "../vendors/model";
|
||||||
@@ -16,6 +16,7 @@ import { ExternalFilament, useGetExternalDBFilaments } from "../../utils/queryEx
|
|||||||
import { formatFilamentLabel } from "../spools/functions";
|
import { formatFilamentLabel } from "../spools/functions";
|
||||||
import { FilamentImportModal } from "../../components/filamentImportModal";
|
import { FilamentImportModal } from "../../components/filamentImportModal";
|
||||||
import { getOrCreateVendorFromExternal } from "../vendors/functions";
|
import { getOrCreateVendorFromExternal } from "../vendors/functions";
|
||||||
|
import { MultiColorPicker } from "../../components/multiColorPicker";
|
||||||
|
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
|
|
||||||
@@ -33,6 +34,7 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
|
|||||||
const currency = useCurrency();
|
const currency = useCurrency();
|
||||||
const [isImportExtOpen, setIsImportExtOpen] = React.useState(false);
|
const [isImportExtOpen, setIsImportExtOpen] = React.useState(false);
|
||||||
const invalidate = useInvalidate();
|
const invalidate = useInvalidate();
|
||||||
|
const [colorType, setColorType] = React.useState<"single" | "multi">("single");
|
||||||
|
|
||||||
const { form, formProps, formLoading, onFinish, redirect } = useForm<
|
const { form, formProps, formLoading, onFinish, redirect } = useForm<
|
||||||
IFilament,
|
IFilament,
|
||||||
@@ -160,9 +162,21 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
<Form.Item label={t("filament.fields.color_hex")}>
|
||||||
|
<Radio.Group
|
||||||
|
onChange={(value) => {
|
||||||
|
setColorType(value.target.value);
|
||||||
|
}}
|
||||||
|
defaultValue={colorType}
|
||||||
|
value={colorType}
|
||||||
|
>
|
||||||
|
<Radio.Button value={"single"}>{t("filament.fields.single_color")}</Radio.Button>
|
||||||
|
<Radio.Button value={"multi"}>{t("filament.fields.multi_color")}</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
{colorType == "single" && (
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("filament.fields.color_hex")}
|
name={"color_hex"}
|
||||||
name={["color_hex"]}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: false,
|
required: false,
|
||||||
@@ -174,6 +188,36 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
|
|||||||
>
|
>
|
||||||
<ColorPicker format="hex" />
|
<ColorPicker format="hex" />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{colorType == "multi" && (
|
||||||
|
<Form.Item
|
||||||
|
name={"multi_color_direction"}
|
||||||
|
help={t("filament.fields_help.multi_color_direction")}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
initialValue={"coaxial"}
|
||||||
|
>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio.Button value={"coaxial"}>{t("filament.fields.coaxial")}</Radio.Button>
|
||||||
|
<Radio.Button value={"longitudinal"}>{t("filament.fields.longitudinal")}</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
|
{colorType == "multi" && (
|
||||||
|
<Form.Item
|
||||||
|
name={"multi_color_hexes"}
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<MultiColorPicker min={2} max={14} />
|
||||||
|
</Form.Item>
|
||||||
|
)}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("filament.fields.material")}
|
label={t("filament.fields.material")}
|
||||||
help={t("filament.fields_help.material")}
|
help={t("filament.fields_help.material")}
|
||||||
|
|||||||
Reference in New Issue
Block a user