Merge branch 'multicolor'

This commit is contained in:
Donkie
2024-06-11 18:30:45 +02:00
13 changed files with 329 additions and 47 deletions

View File

@@ -205,6 +205,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"
}, },
@@ -214,7 +218,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",

View File

@@ -7,13 +7,12 @@ import { NumberFieldUnit, NumberFieldUnitRange } from "./numberField";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { DateField, TextField } from "@refinedev/antd"; 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 { useTranslate } from "@refinedev/core";
import { enrichText } from "../utils/parsing"; import { enrichText } from "../utils/parsing";
import { UseQueryResult } from "@tanstack/react-query"; import { UseQueryResult } from "@tanstack/react-query";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Field, FieldType } from "../utils/queryFields"; import { Field, FieldType } from "../utils/queryFields";
import SpoolIcon from "./spoolIcon";
dayjs.extend(utc); dayjs.extend(utc);
@@ -296,7 +295,7 @@ export function ActionsColumn<Obj extends Entity>(actionsFn: (record: Obj) => Ac
} }
interface SpoolIconColumnProps<Obj extends Entity> extends FilteredQueryColumnProps<Obj> { interface SpoolIconColumnProps<Obj extends Entity> extends FilteredQueryColumnProps<Obj> {
color: (record: Obj) => string | undefined; color: (record: Obj) => string | { colors: string[]; vertical: boolean } | undefined;
} }
export function SpoolIconColumn<Obj extends Entity>(props: SpoolIconColumnProps<Obj>) { export function SpoolIconColumn<Obj extends Entity>(props: SpoolIconColumnProps<Obj>) {
@@ -343,19 +342,12 @@ export function SpoolIconColumn<Obj extends Entity>(props: SpoolIconColumnProps<
}, },
render: (rawValue, record: Obj) => { render: (rawValue, record: Obj) => {
const value = props.transform ? props.transform(rawValue) : rawValue; const value = props.transform ? props.transform(rawValue) : rawValue;
const colorStr = props.color(record); const colorObj = props.color(record);
return ( return (
<Row wrap={false} justify="space-around" align="middle"> <Row wrap={false} justify="space-around" align="middle">
{colorStr && ( {colorObj && (
<Col flex="none"> <Col flex="none">
<Icon <SpoolIcon color={colorObj} />
component={SpoolIcon}
style={{
color: "#" + colorStr,
fontSize: 42,
marginRight: 0,
}}
/>
</Col> </Col>
)} )}
<Col flex="auto">{value}</Col> <Col flex="auto">{value}</Col>

View 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" style={{ marginTop: "1em" }}>
{pickers}
{values.length < (props.max ?? Infinity) && (
<Button
icon={<PlusOutlined />}
onClick={() => {
if (props.onChange) {
props.onChange(values.concat(generateRandomColor()).join(","));
}
}}
/>
)}
</Space>
</>
);
}

View File

@@ -0,0 +1,41 @@
.spool-icon {
display: flex;
width: 1.5em;
height: 2.5em;
gap: 2px;
margin: 0 0.5em;
}
.spool-icon.vertical {
flex-direction: column;
}
.spool-icon.large {
width: 4em;
height: 4em;
}
.spool-icon * {
flex: 1 1 0px;
border-radius: 2px;
}
.spool-icon.vertical *:first-child {
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
.spool-icon.vertical *:last-child {
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
}
.spool-icon.horizontal *:first-child {
border-top-left-radius: 6px;
border-bottom-left-radius: 6px;
}
.spool-icon.horizontal *:last-child {
border-top-right-radius: 6px;
border-bottom-right-radius: 6px;
}

View File

@@ -0,0 +1,32 @@
import "./spoolIcon.css";
interface Props {
color: string | { colors: string[]; vertical: boolean };
size?: "small" | "large";
}
export default function SpoolIcon(props: Props) {
let dirClass = "vertical";
let cols = [];
let size = props.size ? props.size : "small";
if (typeof props.color === "string") {
cols = [props.color];
} else {
dirClass = props.color.vertical ? "vertical" : "horizontal";
cols = props.color.colors;
}
return (
<div className={"spool-icon " + dirClass + " " + size}>
{cols.map((col) => (
<div
key={col}
style={{
backgroundColor: "#" + col.replace("#", ""),
}}
></div>
))}
</div>
);
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" version="1.1" viewBox="0 0 235 500" xml:space="preserve"><desc>Created with Fabric.js 5.3.0</desc><g id="z-gD78pOtWjQii31Q-6Ze"><path style="stroke:#6e0b30;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#343434;fill-rule:nonzero;opacity:1" stroke-linecap="round" d="M 0 -63.26949 C 34.92476 -63.26949 63.26949 -34.92476 63.26949 0 C 63.26949 34.92476 34.92476 63.26949 0 63.26949 C -34.92476 63.26949 -63.26949 34.92476 -63.26949 0 C -63.26949 -34.92476 -34.92476 -63.26949 0 -63.26949 z" transform="matrix(0.5875748337 0 0 3.9476902309 197.1354865303 250.0467513161) translate(0, 0)" vector-effect="non-scaling-stroke"/></g><g id="qCAOML5j7kpAVIP3Df73i"><path style="stroke:#6e0b30;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill-rule:nonzero;opacity:1" fill="current" stroke-linecap="round" d="M 0 -63.26949 C 34.92476 -63.26949 63.26949 -34.92476 63.26949 0 C 63.26949 34.92476 34.92476 63.26949 0 63.26949 C -34.92476 63.26949 -63.26949 34.92476 -63.26949 0 C -63.26949 -34.92476 -34.92476 -63.26949 0 -63.26949 z" transform="matrix(0.3815761198 0 0 3.4623174046 197.1354865303 250.0467382769) translate(0, 0)" vector-effect="non-scaling-stroke"/></g><g id="DwEIaQUSPW4DgKfkdPWoC"><path style="stroke:#000;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill-rule:nonzero;opacity:1" fill="current" stroke-linecap="round" d="M -38.50267 -65.24064 L 38.502680000000005 -65.24064 L 38.502680000000005 65.24064 L -38.50267 65.24064 z" transform="matrix(2.0736420266 0 0 3.3577085856 117.2947215155 250.0467440227) translate(-0.000005, 0)" vector-effect="non-scaling-stroke"/></g><g id="OvgM38-6CZm9R47a1TDIX" transform="matrix(0.5875748337 0 0 3.9476902309 37.4539565008 250.0467440227)"><filter id="SVGID_90" width="181.094%" height="149.483%" x="-40.547%" y="-24.742%"><feGaussianBlur in="SourceAlpha" stdDeviation="20"/><feOffset dx="30" dy="0" result="oBlur"/><feFlood flood-color="#000" flood-opacity=".8"/><feComposite in2="oBlur" operator="in"/><feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge></filter><path style="stroke:#6e0b30;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#343434;fill-rule:nonzero;opacity:1;filter:url(#SVGID_90)" stroke-linecap="round" d="M 0 -63.26949 C 34.92476 -63.26949 63.26949 -34.92476 63.26949 0 C 63.26949 34.92476 34.92476 63.26949 0 63.26949 C -34.92476 63.26949 -63.26949 34.92476 -63.26949 0 C -63.26949 -34.92476 -34.92476 -63.26949 0 -63.26949 z" transform="translate(0, 0)" vector-effect="non-scaling-stroke"/></g><g id="woBV3Wt7ijQ6puNeMgAQD"><path style="stroke:#6e0b30;stroke-width:0;stroke-dasharray:none;stroke-linecap:butt;stroke-dashoffset:0;stroke-linejoin:miter;stroke-miterlimit:4;fill:#111;fill-rule:nonzero;opacity:1" stroke-linecap="round" d="M 0 -63.26949 C 34.92476 -63.26949 63.26949 -34.92476 63.26949 0 C 63.26949 34.92476 34.92476 63.26949 0 63.26949 C -34.92476 63.26949 -63.26949 34.92476 -63.26949 0 C -63.26949 -34.92476 -34.92476 -63.26949 0 -63.26949 z" transform="matrix(0.2435597948 0 0 1.6363849628 37.4539565008 250.0467440227) translate(0, 0)" vector-effect="non-scaling-stroke"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -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,20 +162,62 @@ export const FilamentCreate: React.FC<IResourceComponentsProps & CreateOrClonePr
} }
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t("filament.fields.color_hex")}>
label={t("filament.fields.color_hex")} <Radio.Group
name={["color_hex"]} onChange={(value) => {
rules={[ setColorType(value.target.value);
{ }}
required: false, defaultValue={colorType}
}, value={colorType}
]} >
getValueFromEvent={(e) => { <Radio.Button value={"single"}>{t("filament.fields.single_color")}</Radio.Button>
return e?.toHex(); <Radio.Button value={"multi"}>{t("filament.fields.multi_color")}</Radio.Button>
}} </Radio.Group>
>
<ColorPicker format="hex" />
</Form.Item> </Form.Item>
{colorType == "single" && (
<Form.Item
name={"color_hex"}
rules={[
{
required: false,
},
]}
getValueFromEvent={(e) => {
return e?.toHex();
}}
>
<ColorPicker format="hex" />
</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")}

View File

@@ -1,7 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { HttpError, IResourceComponentsProps, useTranslate } from "@refinedev/core"; import { HttpError, IResourceComponentsProps, useTranslate } from "@refinedev/core";
import { Edit, useForm, useSelect } from "@refinedev/antd"; import { Edit, useForm, useSelect } from "@refinedev/antd";
import { Form, Input, DatePicker, Select, InputNumber, ColorPicker, message, Alert, Typography } from "antd"; import { Form, Input, DatePicker, Select, InputNumber, ColorPicker, message, Alert, Typography, Radio } from "antd";
import dayjs from "dayjs"; import dayjs from "dayjs";
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";
@@ -11,6 +11,7 @@ import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields"; import { ExtraFieldFormItem, StringifiedExtras } from "../../components/extraFields";
import { ParsedExtras } from "../../components/extraFields"; import { ParsedExtras } from "../../components/extraFields";
import { getCurrencySymbol, useCurrency } from "../../utils/settings"; import { getCurrencySymbol, useCurrency } from "../../utils/settings";
import { MultiColorPicker } from "../../components/multiColorPicker";
/* /*
The API returns the extra fields as JSON values, but we need to parse them into their real types The API returns the extra fields as JSON values, but we need to parse them into their real types
@@ -25,6 +26,7 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
const [hasChanged, setHasChanged] = useState(false); const [hasChanged, setHasChanged] = useState(false);
const extraFields = useGetFields(EntityType.filament); const extraFields = useGetFields(EntityType.filament);
const currency = useCurrency(); const currency = useCurrency();
const [colorType, setColorType] = React.useState<"single" | "multi">("single");
const { formProps, saveButtonProps } = useForm<IFilament, HttpError, IFilament, IFilament>({ const { formProps, saveButtonProps } = useForm<IFilament, HttpError, IFilament, IFilament>({
liveMode: "manual", liveMode: "manual",
@@ -49,6 +51,16 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
formProps.initialValues = ParsedExtras(formProps.initialValues); formProps.initialValues = ParsedExtras(formProps.initialValues);
} }
// Update colorType state
React.useEffect(() => {
console.log(formProps.initialValues?.multi_color_hexes);
if (formProps.initialValues?.multi_color_hexes) {
setColorType("multi");
} else {
setColorType("single");
}
}, [formProps.initialValues?.multi_color_hexes]);
// Override the form's onFinish method to stringify the extra fields // Override the form's onFinish method to stringify the extra fields
const originalOnFinish = formProps.onFinish; const originalOnFinish = formProps.onFinish;
formProps.onFinish = (allValues: IFilamentParsedExtras) => { formProps.onFinish = (allValues: IFilamentParsedExtras) => {
@@ -132,20 +144,62 @@ export const FilamentEdit: React.FC<IResourceComponentsProps> = () => {
} }
/> />
</Form.Item> </Form.Item>
<Form.Item <Form.Item label={t("filament.fields.color_hex")}>
label={t("filament.fields.color_hex")} <Radio.Group
name={["color_hex"]} onChange={(value) => {
rules={[ setColorType(value.target.value);
{ }}
required: false, defaultValue={colorType}
}, value={colorType}
]} >
getValueFromEvent={(e) => { <Radio.Button value={"single"}>{t("filament.fields.single_color")}</Radio.Button>
return e?.toHex(); <Radio.Button value={"multi"}>{t("filament.fields.multi_color")}</Radio.Button>
}} </Radio.Group>
>
<ColorPicker format="hex" />
</Form.Item> </Form.Item>
{colorType == "single" && (
<Form.Item
name={"color_hex"}
rules={[
{
required: false,
},
]}
getValueFromEvent={(e) => {
return e?.toHex();
}}
>
<ColorPicker format="hex" />
</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")}

View File

@@ -240,7 +240,13 @@ export const FilamentList: React.FC<IResourceComponentsProps> = () => {
...commonProps, ...commonProps,
id: "name", id: "name",
i18ncat: "filament", i18ncat: "filament",
color: (record: IFilamentCollapsed) => record.color_hex, color: (record: IFilamentCollapsed) =>
record.multi_color_hexes
? {
colors: record.multi_color_hexes.split(","),
vertical: record.multi_color_direction === "longitudinal",
}
: record.color_hex,
filterValueQuery: useSpoolmanFilamentNames(), filterValueQuery: useSpoolmanFilamentNames(),
}), }),
FilteredQueryColumn({ FilteredQueryColumn({

View File

@@ -16,6 +16,8 @@ export interface IFilament {
settings_extruder_temp?: number; settings_extruder_temp?: number;
settings_bed_temp?: number; settings_bed_temp?: number;
color_hex?: string; color_hex?: string;
multi_color_hexes?: string;
multi_color_direction?: string;
external_id?: string; external_id?: string;
extra: { [key: string]: string }; extra: { [key: string]: string };
} }

View File

@@ -11,6 +11,7 @@ import { useNavigate } from "react-router-dom";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldDisplay } from "../../components/extraFields"; import { ExtraFieldDisplay } from "../../components/extraFields";
import { useCurrency } from "../../utils/settings"; import { useCurrency } from "../../utils/settings";
import SpoolIcon from "../../components/spoolIcon";
dayjs.extend(utc); dayjs.extend(utc);
const { Title } = Typography; const { Title } = Typography;
@@ -49,6 +50,13 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
navigate(URL); navigate(URL);
}; };
const colorObj = record?.multi_color_hexes
? {
colors: record.multi_color_hexes.split(","),
vertical: record.multi_color_direction === "longitudinal",
}
: record?.color_hex;
return ( return (
<Show <Show
isLoading={isLoading} isLoading={isLoading}
@@ -80,7 +88,7 @@ export const FilamentShow: React.FC<IResourceComponentsProps> = () => {
<Title level={5}>{t("filament.fields.name")}</Title> <Title level={5}>{t("filament.fields.name")}</Title>
<TextField value={record?.name} /> <TextField value={record?.name} />
<Title level={5}>{t("filament.fields.color_hex")}</Title> <Title level={5}>{t("filament.fields.color_hex")}</Title>
<TextField value={record?.color_hex} /> {colorObj && <SpoolIcon color={colorObj} size="large" />}
<Title level={5}>{t("filament.fields.material")}</Title> <Title level={5}>{t("filament.fields.material")}</Title>
<TextField value={record?.material} /> <TextField value={record?.material} />
<Title level={5}>{t("filament.fields.price")}</Title> <Title level={5}>{t("filament.fields.price")}</Title>

View File

@@ -342,7 +342,13 @@ export const SpoolList: React.FC<IResourceComponentsProps> = () => {
...commonProps, ...commonProps,
id: "filament.combined_name", id: "filament.combined_name",
i18nkey: "spool.fields.filament_name", i18nkey: "spool.fields.filament_name",
color: (record: ISpoolCollapsed) => record.filament.color_hex, color: (record: ISpoolCollapsed) =>
record.filament.multi_color_hexes
? {
colors: record.filament.multi_color_hexes.split(","),
vertical: record.filament.multi_color_direction === "longitudinal",
}
: record.filament.color_hex,
dataId: "filament.combined_name", dataId: "filament.combined_name",
filterValueQuery: useSpoolmanFilamentFilter(), filterValueQuery: useSpoolmanFilamentFilter(),
}), }),

View File

@@ -11,6 +11,7 @@ import { IFilament } from "../filaments/model";
import { EntityType, useGetFields } from "../../utils/queryFields"; import { EntityType, useGetFields } from "../../utils/queryFields";
import { ExtraFieldDisplay } from "../../components/extraFields"; import { ExtraFieldDisplay } from "../../components/extraFields";
import { useCurrency } from "../../utils/settings"; import { useCurrency } from "../../utils/settings";
import SpoolIcon from "../../components/spoolIcon";
dayjs.extend(utc); dayjs.extend(utc);
@@ -66,12 +67,19 @@ export const SpoolShow: React.FC<IResourceComponentsProps> = () => {
}); });
}; };
const colorObj = record?.filament.multi_color_hexes
? {
colors: record.filament.multi_color_hexes.split(","),
vertical: record.filament.multi_color_direction === "longitudinal",
}
: record?.filament.color_hex;
return ( return (
<Show isLoading={isLoading} title={record ? formatTitle(record) : ""}> <Show isLoading={isLoading} title={record ? formatTitle(record) : ""}>
<Title level={5}>{t("spool.fields.id")}</Title> <Title level={5}>{t("spool.fields.id")}</Title>
<NumberField value={record?.id ?? ""} /> <NumberField value={record?.id ?? ""} />
<Title level={5}>{t("spool.fields.filament")}</Title> <Title level={5}>{t("spool.fields.filament")}</Title>
<TextField value={record ? filamentURL(record?.filament) : ""} /> {colorObj && <SpoolIcon color={colorObj} />} <TextField value={record ? filamentURL(record?.filament) : ""} />
<Title level={5}>{t("spool.fields.price")}</Title> <Title level={5}>{t("spool.fields.price")}</Title>
<NumberField <NumberField
value={record ? spoolPrice(record) : ""} value={record ? spoolPrice(record) : ""}