@printhive/react 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/chunk-RY4P4TFQ.js +28 -0
- package/dist/chunk-RY4P4TFQ.js.map +1 -0
- package/dist/index.cjs +918 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +62 -0
- package/dist/index.d.ts +62 -0
- package/dist/index.js +901 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +305 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +11 -0
- package/dist/server.d.ts +11 -0
- package/dist/server.js +262 -0
- package/dist/server.js.map +1 -0
- package/dist/styles.css +482 -0
- package/package.json +80 -4
- package/README.md +0 -3
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
FilamentManagement: () => FilamentManagement,
|
|
24
|
+
FilamentManagementError: () => FilamentManagementError,
|
|
25
|
+
PrintHiveProvider: () => PrintHiveProvider,
|
|
26
|
+
createFilamentManagementDataSource: () => createFilamentManagementDataSource
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/FilamentManagement.tsx
|
|
31
|
+
var import_react4 = require("react");
|
|
32
|
+
var import_react_query5 = require("@tanstack/react-query");
|
|
33
|
+
var import_button3 = require("@ui-sdk/react/button");
|
|
34
|
+
var import_dialog2 = require("@ui-sdk/react/dialog");
|
|
35
|
+
|
|
36
|
+
// src/presentation.ts
|
|
37
|
+
function layoutSlots(layout) {
|
|
38
|
+
return [
|
|
39
|
+
...layout.nozzles.flatMap(
|
|
40
|
+
(nozzle) => nozzle.sources.flatMap((source) => source.slots)
|
|
41
|
+
),
|
|
42
|
+
...layout.unboundSources.flatMap((source) => source.slots),
|
|
43
|
+
...layout.unmappedAssignments
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
function slotContext(layout, slotId) {
|
|
47
|
+
for (const nozzle of layout.nozzles) {
|
|
48
|
+
for (const source of nozzle.sources) {
|
|
49
|
+
if (source.slots.some((slot) => slot.id === slotId)) {
|
|
50
|
+
return layout.nozzles.length > 1 ? `${nozzle.label} \xB7 ${source.label}` : source.label;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return layout.unboundSources.find(
|
|
55
|
+
(source) => source.slots.some((slot) => slot.id === slotId)
|
|
56
|
+
)?.label ?? "Saved assignments";
|
|
57
|
+
}
|
|
58
|
+
function slotStatus(slot) {
|
|
59
|
+
if (slot.reading) return "Reading RFID";
|
|
60
|
+
if (slot.feeding) return "Feeding nozzle";
|
|
61
|
+
switch (slot.presentation) {
|
|
62
|
+
case "synced":
|
|
63
|
+
return "Matches printer";
|
|
64
|
+
case "mismatch":
|
|
65
|
+
return "Different from printer";
|
|
66
|
+
case "assigned-empty":
|
|
67
|
+
return "Assigned, no filament detected";
|
|
68
|
+
case "assigned-unknown":
|
|
69
|
+
return "Assignment saved, presence unknown";
|
|
70
|
+
case "presence-unknown":
|
|
71
|
+
return "Filament presence unknown";
|
|
72
|
+
case "printer-only":
|
|
73
|
+
return "Reported by printer, not assigned";
|
|
74
|
+
case "empty":
|
|
75
|
+
return "No filament detected";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function fillStyle(colors, kind) {
|
|
79
|
+
if (colors.length === 0) return {};
|
|
80
|
+
if (colors.length === 1) return { backgroundColor: colors[0] };
|
|
81
|
+
const stops = kind && kind !== "gradient" ? colors.map(
|
|
82
|
+
(color, index) => `${color} ${index / colors.length * 100}% ${(index + 1) / colors.length * 100}%`
|
|
83
|
+
) : colors;
|
|
84
|
+
return { backgroundImage: `linear-gradient(135deg, ${stops.join(", ")})` };
|
|
85
|
+
}
|
|
86
|
+
function assignmentNotice(result) {
|
|
87
|
+
const slot = result.slots[0];
|
|
88
|
+
if (!slot || slot.record !== "saved") {
|
|
89
|
+
return {
|
|
90
|
+
tone: "error",
|
|
91
|
+
message: "The assignment was not saved. Refresh this printer before trying again; its metadata may have changed."
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
if (slot.printerSync.status === "failed") {
|
|
95
|
+
return {
|
|
96
|
+
tone: "warning",
|
|
97
|
+
message: "Assignment saved in PrintHive. The printer metadata request could not be confirmed."
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (slot.printerSync.status === "skipped") {
|
|
101
|
+
return {
|
|
102
|
+
tone: "success",
|
|
103
|
+
message: "Assignment saved in PrintHive. Printer metadata was not changed."
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
if (slot.observation?.status === "observed") {
|
|
107
|
+
return {
|
|
108
|
+
tone: "success",
|
|
109
|
+
message: "Assignment saved and confirmed by the printer."
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (slot.observation?.status === "mismatch") {
|
|
113
|
+
return {
|
|
114
|
+
tone: "warning",
|
|
115
|
+
message: "Assignment saved. The printer still reports different filament."
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
tone: "success",
|
|
120
|
+
message: "Assignment saved. Metadata request sent; printer confirmation is still pending."
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function clearNotice(result) {
|
|
124
|
+
return result.slots.some((slot) => slot.record === "cleared") ? {
|
|
125
|
+
tone: "success",
|
|
126
|
+
message: "Assignment cleared. Printer metadata was not changed."
|
|
127
|
+
} : {
|
|
128
|
+
tone: "success",
|
|
129
|
+
message: "No saved assignment remained. Printer metadata was not changed."
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
function rfidNotice(result) {
|
|
133
|
+
switch (result.printerSync.status) {
|
|
134
|
+
case "failed":
|
|
135
|
+
return {
|
|
136
|
+
tone: "warning",
|
|
137
|
+
message: "The RFID request could not be confirmed. Refresh this printer before trying again."
|
|
138
|
+
};
|
|
139
|
+
case "skipped":
|
|
140
|
+
return {
|
|
141
|
+
tone: "warning",
|
|
142
|
+
message: "The RFID reread was not requested. Refresh this printer to check which actions are available."
|
|
143
|
+
};
|
|
144
|
+
case "sent":
|
|
145
|
+
return {
|
|
146
|
+
tone: "success",
|
|
147
|
+
message: "RFID reread requested. A completed tag read has not yet been confirmed."
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// src/provider.tsx
|
|
153
|
+
var import_react = require("react");
|
|
154
|
+
var import_react_query = require("@tanstack/react-query");
|
|
155
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
156
|
+
var DataSourceContext = (0, import_react.createContext)(null);
|
|
157
|
+
function PrintHiveProvider({
|
|
158
|
+
dataSource,
|
|
159
|
+
children
|
|
160
|
+
}) {
|
|
161
|
+
const [scope, setScope] = (0, import_react.useState)(() => ({
|
|
162
|
+
dataSource,
|
|
163
|
+
queryClient: createQueryClient()
|
|
164
|
+
}));
|
|
165
|
+
if (scope.dataSource !== dataSource) {
|
|
166
|
+
setScope({ dataSource, queryClient: createQueryClient() });
|
|
167
|
+
}
|
|
168
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DataSourceContext, { value: scope, children });
|
|
169
|
+
}
|
|
170
|
+
function createQueryClient() {
|
|
171
|
+
return new import_react_query.QueryClient({
|
|
172
|
+
defaultOptions: {
|
|
173
|
+
queries: { retry: false, refetchOnWindowFocus: false, gcTime: 0 },
|
|
174
|
+
mutations: { retry: false }
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
function useFilamentRuntime() {
|
|
179
|
+
const source = (0, import_react.useContext)(DataSourceContext);
|
|
180
|
+
if (source === null) {
|
|
181
|
+
throw new Error("FilamentManagement must be inside a PrintHiveProvider.");
|
|
182
|
+
}
|
|
183
|
+
return source;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// src/errors.ts
|
|
187
|
+
var FilamentManagementError = class extends Error {
|
|
188
|
+
code;
|
|
189
|
+
status;
|
|
190
|
+
constructor(message, options) {
|
|
191
|
+
super(message);
|
|
192
|
+
this.name = "FilamentManagementError";
|
|
193
|
+
this.code = options.code;
|
|
194
|
+
this.status = options.status;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
// src/layout-identity.ts
|
|
199
|
+
function requirePrinterLayout(layout, printerId) {
|
|
200
|
+
if (layout.printer.id.toLowerCase() !== printerId.toLowerCase()) {
|
|
201
|
+
throw new FilamentManagementError(
|
|
202
|
+
"The filament service returned a different printer. Refresh before making changes.",
|
|
203
|
+
{ code: "invalid_response", status: 502 }
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
return layout;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/queries.ts
|
|
210
|
+
function layoutQueryKey(printerId) {
|
|
211
|
+
return ["printhive-filaments", printerId];
|
|
212
|
+
}
|
|
213
|
+
function operationQueryKey(printerId) {
|
|
214
|
+
return ["printhive-filament-operation", printerId];
|
|
215
|
+
}
|
|
216
|
+
async function catalogDebounce(signal, delayMs) {
|
|
217
|
+
if (signal.aborted)
|
|
218
|
+
throw new DOMException("Catalog search aborted", "AbortError");
|
|
219
|
+
await new Promise((resolve, reject) => {
|
|
220
|
+
const finish = () => {
|
|
221
|
+
signal.removeEventListener("abort", cancel);
|
|
222
|
+
resolve();
|
|
223
|
+
};
|
|
224
|
+
const timer = setTimeout(finish, delayMs);
|
|
225
|
+
const cancel = () => {
|
|
226
|
+
clearTimeout(timer);
|
|
227
|
+
reject(new DOMException("Catalog search aborted", "AbortError"));
|
|
228
|
+
};
|
|
229
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// src/SlotEditor.tsx
|
|
234
|
+
var import_react3 = require("react");
|
|
235
|
+
var import_react_query4 = require("@tanstack/react-query");
|
|
236
|
+
var import_button2 = require("@ui-sdk/react/button");
|
|
237
|
+
var import_dialog = require("@ui-sdk/react/dialog");
|
|
238
|
+
|
|
239
|
+
// src/CatalogPicker.tsx
|
|
240
|
+
var import_react2 = require("react");
|
|
241
|
+
var import_react_query2 = require("@tanstack/react-query");
|
|
242
|
+
var import_button = require("@ui-sdk/react/button");
|
|
243
|
+
var import_combobox = require("@ui-sdk/react/combobox");
|
|
244
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
245
|
+
function CatalogPicker({
|
|
246
|
+
printerId,
|
|
247
|
+
selected,
|
|
248
|
+
onSelect,
|
|
249
|
+
disabled
|
|
250
|
+
}) {
|
|
251
|
+
const { dataSource, queryClient } = useFilamentRuntime();
|
|
252
|
+
const [query, setQuery] = (0, import_react2.useState)("");
|
|
253
|
+
const search = (0, import_react_query2.useQuery)(
|
|
254
|
+
{
|
|
255
|
+
queryKey: ["printhive-filament-catalog", printerId, query],
|
|
256
|
+
queryFn: async ({ signal }) => {
|
|
257
|
+
await catalogDebounce(signal, query.length === 0 ? 0 : 180);
|
|
258
|
+
return dataSource.search(printerId, query, { signal });
|
|
259
|
+
},
|
|
260
|
+
enabled: !disabled
|
|
261
|
+
},
|
|
262
|
+
queryClient
|
|
263
|
+
);
|
|
264
|
+
const state = disabled ? { kind: "ready", options: [] } : search.isError ? { kind: "failed" } : search.data !== void 0 ? { kind: "ready", options: search.data } : { kind: "loading" };
|
|
265
|
+
const results = state.kind === "ready" ? state.options : [];
|
|
266
|
+
const options = selected && !results.some((option) => option.id === selected.id) ? [selected, ...results] : results;
|
|
267
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ph-filament-catalog", children: [
|
|
268
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
269
|
+
import_combobox.Combobox,
|
|
270
|
+
{
|
|
271
|
+
items: options.map((option) => ({
|
|
272
|
+
value: option.id,
|
|
273
|
+
label: option.label
|
|
274
|
+
})),
|
|
275
|
+
value: selected?.id ?? null,
|
|
276
|
+
query,
|
|
277
|
+
onQueryChange: (next, details) => {
|
|
278
|
+
setQuery(next);
|
|
279
|
+
if (details.reason === "input-change") onSelect(null);
|
|
280
|
+
},
|
|
281
|
+
onValueChange: (next) => onSelect(options.find((option) => option.id === next) ?? null),
|
|
282
|
+
filter: false,
|
|
283
|
+
disabled,
|
|
284
|
+
children: [
|
|
285
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_combobox.ComboboxLabel, { children: "Assign filament" }),
|
|
286
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ph-filament-combobox-control", children: [
|
|
287
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
288
|
+
import_combobox.ComboboxInput,
|
|
289
|
+
{
|
|
290
|
+
placeholder: "Search your filament catalog",
|
|
291
|
+
autoComplete: "off"
|
|
292
|
+
}
|
|
293
|
+
),
|
|
294
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_combobox.ComboboxTrigger, { "aria-label": "Show filament catalog", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 16 16", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
295
|
+
"path",
|
|
296
|
+
{
|
|
297
|
+
d: "m4 6 4 4 4-4",
|
|
298
|
+
fill: "none",
|
|
299
|
+
stroke: "currentColor",
|
|
300
|
+
strokeWidth: "1.5"
|
|
301
|
+
}
|
|
302
|
+
) }) })
|
|
303
|
+
] }),
|
|
304
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_combobox.ComboboxContent, { className: "ph-filament-catalog-popup", sideOffset: 4, children: [
|
|
305
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_combobox.ComboboxEmpty, { children: state.kind === "loading" ? "Searching catalog\u2026" : "No matching filaments" }),
|
|
306
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_combobox.ComboboxList, { "aria-busy": state.kind === "loading", children: options.map((option) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
307
|
+
import_combobox.ComboboxItem,
|
|
308
|
+
{
|
|
309
|
+
value: option.id,
|
|
310
|
+
label: option.label,
|
|
311
|
+
"aria-label": option.label,
|
|
312
|
+
children: [
|
|
313
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
314
|
+
"span",
|
|
315
|
+
{
|
|
316
|
+
className: "ph-filament-swatch ph-filament-swatch-small",
|
|
317
|
+
style: fillStyle(
|
|
318
|
+
option.colors.length ? option.colors : option.color ? [option.color] : []
|
|
319
|
+
),
|
|
320
|
+
"aria-hidden": "true"
|
|
321
|
+
}
|
|
322
|
+
),
|
|
323
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { children: [
|
|
324
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ph-filament-option-label", children: option.label }),
|
|
325
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ph-filament-muted", children: [
|
|
326
|
+
option.materialType,
|
|
327
|
+
option.description ? ` \xB7 ${option.description}` : ""
|
|
328
|
+
] })
|
|
329
|
+
] })
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
option.id
|
|
333
|
+
)) })
|
|
334
|
+
] })
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
),
|
|
338
|
+
state.kind === "failed" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "ph-filament-inline-error", role: "alert", children: [
|
|
339
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "The filament catalog could not be loaded." }),
|
|
340
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_button.Button, { onClick: () => search.refetch(), children: "Retry catalog search" })
|
|
341
|
+
] })
|
|
342
|
+
] });
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// src/mutations.ts
|
|
346
|
+
var import_react_query3 = require("@tanstack/react-query");
|
|
347
|
+
function useFilamentOperation(printerId) {
|
|
348
|
+
const { dataSource, queryClient } = useFilamentRuntime();
|
|
349
|
+
return (0, import_react_query3.useMutation)(
|
|
350
|
+
{
|
|
351
|
+
mutationKey: operationQueryKey(printerId),
|
|
352
|
+
retry: false,
|
|
353
|
+
mutationFn: async (operation) => {
|
|
354
|
+
switch (operation.kind) {
|
|
355
|
+
case "assign":
|
|
356
|
+
return assignmentNotice(
|
|
357
|
+
await dataSource.assign(printerId, operation.input)
|
|
358
|
+
);
|
|
359
|
+
case "clear":
|
|
360
|
+
return clearNotice(
|
|
361
|
+
await dataSource.clear(printerId, operation.input)
|
|
362
|
+
);
|
|
363
|
+
case "rfid":
|
|
364
|
+
return rfidNotice(
|
|
365
|
+
await dataSource.refreshRfid(printerId, operation.input)
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
},
|
|
369
|
+
onSettled: () => {
|
|
370
|
+
void queryClient.invalidateQueries({
|
|
371
|
+
queryKey: layoutQueryKey(printerId)
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
queryClient
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// src/SlotEditor.tsx
|
|
380
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
381
|
+
function SlotComparison({ slot }) {
|
|
382
|
+
const assignment = slot.assignment;
|
|
383
|
+
const reported = "reported" in slot.observation ? slot.observation.reported : null;
|
|
384
|
+
const unavailable = slot.observation.status === "stale" ? "Last report is stale" : "Observation unavailable";
|
|
385
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ph-filament-comparison", children: [
|
|
386
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { children: [
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "Saved assignment" }),
|
|
388
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ph-filament-material", children: [
|
|
389
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
390
|
+
"span",
|
|
391
|
+
{
|
|
392
|
+
className: "ph-filament-swatch",
|
|
393
|
+
style: fillStyle(assignment?.color ? [assignment.color] : []),
|
|
394
|
+
"aria-hidden": "true"
|
|
395
|
+
}
|
|
396
|
+
),
|
|
397
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
398
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: assignment ? slot.displayName ?? assignment.materialType ?? "Assigned filament" : "No assignment" }),
|
|
399
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: assignment?.color ?? "No saved color" })
|
|
400
|
+
] })
|
|
401
|
+
] })
|
|
402
|
+
] }),
|
|
403
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { children: [
|
|
404
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { children: "Printer reports" }),
|
|
405
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ph-filament-material", children: [
|
|
406
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
407
|
+
"span",
|
|
408
|
+
{
|
|
409
|
+
className: "ph-filament-swatch",
|
|
410
|
+
style: fillStyle(reported?.color ? [reported.color] : []),
|
|
411
|
+
"aria-hidden": "true"
|
|
412
|
+
}
|
|
413
|
+
),
|
|
414
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
415
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("strong", { children: reported?.materialType ?? "No current report" }),
|
|
416
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { children: reported?.color ?? unavailable })
|
|
417
|
+
] })
|
|
418
|
+
] })
|
|
419
|
+
] })
|
|
420
|
+
] });
|
|
421
|
+
}
|
|
422
|
+
function ActionReason({
|
|
423
|
+
capability
|
|
424
|
+
}) {
|
|
425
|
+
return capability.kind === "unavailable" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "ph-filament-muted", children: capability.message }) : null;
|
|
426
|
+
}
|
|
427
|
+
function OperationFeedback({
|
|
428
|
+
operation
|
|
429
|
+
}) {
|
|
430
|
+
if (operation.isPending) {
|
|
431
|
+
const message = operation.variables.kind === "assign" ? "Saving assignment\u2026" : operation.variables.kind === "clear" ? "Clearing assignment\u2026" : "Requesting RFID reread\u2026";
|
|
432
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "ph-filament-muted", role: "status", children: message });
|
|
433
|
+
}
|
|
434
|
+
if (operation.isError) {
|
|
435
|
+
const message = operation.error instanceof FilamentManagementError ? operation.error.message : "The request could not be confirmed. Your draft is still here. Refresh this printer before trying again.";
|
|
436
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "ph-filament-notice", "data-tone": "error", role: "alert", children: message });
|
|
437
|
+
}
|
|
438
|
+
if (!operation.data) return null;
|
|
439
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
440
|
+
"p",
|
|
441
|
+
{
|
|
442
|
+
className: "ph-filament-notice",
|
|
443
|
+
"data-tone": operation.data.tone,
|
|
444
|
+
role: operation.data.tone === "error" ? "alert" : "status",
|
|
445
|
+
children: operation.data.message
|
|
446
|
+
}
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
function SlotEditor({
|
|
450
|
+
printerId,
|
|
451
|
+
slot,
|
|
452
|
+
context
|
|
453
|
+
}) {
|
|
454
|
+
const { queryClient } = useFilamentRuntime();
|
|
455
|
+
const operation = useFilamentOperation(printerId);
|
|
456
|
+
const busy = (0, import_react_query4.useIsMutating)({ mutationKey: operationQueryKey(printerId) }, queryClient) > 0;
|
|
457
|
+
const [selected, setSelected] = (0, import_react3.useState)(null);
|
|
458
|
+
const [clearTarget, setClearTarget] = (0, import_react3.useState)(
|
|
459
|
+
null
|
|
460
|
+
);
|
|
461
|
+
const assignAvailability = slot.capabilities.assign;
|
|
462
|
+
const clearAvailability = slot.capabilities.clearAssignment;
|
|
463
|
+
const rfidAvailability = slot.capabilities.refreshRfid;
|
|
464
|
+
const clearVersion = slot.assignment?.version ?? null;
|
|
465
|
+
const run = (intent) => {
|
|
466
|
+
if (queryClient.isMutating({ mutationKey: operationQueryKey(printerId) }) > 0)
|
|
467
|
+
return;
|
|
468
|
+
return operation.mutateAsync(intent, {
|
|
469
|
+
onSuccess: (notice, completed) => {
|
|
470
|
+
if (completed.kind === "assign" && notice.tone !== "error")
|
|
471
|
+
setSelected(null);
|
|
472
|
+
setClearTarget(null);
|
|
473
|
+
}
|
|
474
|
+
}).catch(() => void 0);
|
|
475
|
+
};
|
|
476
|
+
const requestClear = () => {
|
|
477
|
+
if (clearVersion === null) return;
|
|
478
|
+
setClearTarget({ slotId: slot.id, expectedVersion: clearVersion });
|
|
479
|
+
};
|
|
480
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
481
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ph-filament-editor-heading", children: [
|
|
482
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
483
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_dialog.DialogTitle, { children: [
|
|
484
|
+
context,
|
|
485
|
+
" \xB7 ",
|
|
486
|
+
slot.label
|
|
487
|
+
] }),
|
|
488
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dialog.DialogDescription, { children: "Manage the saved assignment and compare it with the printer report." })
|
|
489
|
+
] }),
|
|
490
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_dialog.DialogClose, { className: "ph-filament-close", disabled: busy, children: "Close" })
|
|
491
|
+
] }),
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SlotComparison, { slot }),
|
|
493
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
494
|
+
CatalogPicker,
|
|
495
|
+
{
|
|
496
|
+
printerId,
|
|
497
|
+
selected,
|
|
498
|
+
onSelect: setSelected,
|
|
499
|
+
disabled: busy || assignAvailability.kind !== "available"
|
|
500
|
+
}
|
|
501
|
+
),
|
|
502
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionReason, { capability: assignAvailability }),
|
|
503
|
+
slot.capabilities.writePrinterMetadata.kind === "unavailable" && assignAvailability.kind === "available" && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "ph-filament-muted", children: [
|
|
504
|
+
slot.capabilities.writePrinterMetadata.message,
|
|
505
|
+
" Saving an assignment still records it in PrintHive."
|
|
506
|
+
] }),
|
|
507
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(OperationFeedback, { operation }),
|
|
508
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "ph-filament-editor-primary", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
509
|
+
import_button2.Button,
|
|
510
|
+
{
|
|
511
|
+
className: "ph-filament-primary",
|
|
512
|
+
disabled: busy || selected === null || assignAvailability.kind !== "available",
|
|
513
|
+
onClick: () => selected ? run({
|
|
514
|
+
kind: "assign",
|
|
515
|
+
input: { slotId: slot.id, materialId: selected.id }
|
|
516
|
+
}) : void 0,
|
|
517
|
+
pendingAnnouncement: "Saving assignment",
|
|
518
|
+
children: "Save assignment"
|
|
519
|
+
}
|
|
520
|
+
) }),
|
|
521
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ph-filament-editor-secondary", children: [
|
|
522
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
523
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
524
|
+
import_button2.Button,
|
|
525
|
+
{
|
|
526
|
+
disabled: busy || rfidAvailability.kind !== "available",
|
|
527
|
+
onClick: () => run({ kind: "rfid", input: { slotId: slot.id } }),
|
|
528
|
+
pendingAnnouncement: "Requesting RFID reread",
|
|
529
|
+
children: "Read RFID"
|
|
530
|
+
}
|
|
531
|
+
),
|
|
532
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionReason, { capability: rfidAvailability })
|
|
533
|
+
] }),
|
|
534
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
535
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
536
|
+
import_button2.Button,
|
|
537
|
+
{
|
|
538
|
+
disabled: busy || clearVersion === null || clearAvailability.kind !== "available",
|
|
539
|
+
onClick: requestClear,
|
|
540
|
+
children: "Clear assignment"
|
|
541
|
+
}
|
|
542
|
+
),
|
|
543
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ActionReason, { capability: clearAvailability }),
|
|
544
|
+
slot.assignment && clearVersion === null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "ph-filament-muted", children: "Refresh this printer before clearing its assignment; the saved version is unavailable." })
|
|
545
|
+
] })
|
|
546
|
+
] }),
|
|
547
|
+
clearTarget && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
548
|
+
"section",
|
|
549
|
+
{
|
|
550
|
+
className: "ph-filament-clear-confirmation",
|
|
551
|
+
"aria-label": "Confirm clearing assignment",
|
|
552
|
+
children: [
|
|
553
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { children: [
|
|
554
|
+
"Clear the saved assignment for ",
|
|
555
|
+
slot.label,
|
|
556
|
+
"? This does not unload filament or change printer metadata."
|
|
557
|
+
] }),
|
|
558
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "ph-filament-actions", children: [
|
|
559
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
560
|
+
import_button2.Button,
|
|
561
|
+
{
|
|
562
|
+
className: "ph-filament-danger",
|
|
563
|
+
"data-agent-confirm": `Clears the saved filament assignment for ${context} \xB7 ${slot.label}. Printer metadata is unchanged.`,
|
|
564
|
+
disabled: busy,
|
|
565
|
+
onClick: () => run({ kind: "clear", input: clearTarget }),
|
|
566
|
+
children: "Confirm clear assignment"
|
|
567
|
+
}
|
|
568
|
+
),
|
|
569
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_button2.Button, { disabled: busy, onClick: () => setClearTarget(null), children: "Keep assignment" })
|
|
570
|
+
] })
|
|
571
|
+
]
|
|
572
|
+
}
|
|
573
|
+
)
|
|
574
|
+
] });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// src/FilamentManagement.tsx
|
|
578
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
579
|
+
function layoutState(query) {
|
|
580
|
+
if (query.data === void 0)
|
|
581
|
+
return query.isPending ? { kind: "loading" } : { kind: "failed" };
|
|
582
|
+
let refresh = { kind: "idle" };
|
|
583
|
+
if (query.isFetching) refresh = { kind: "pending" };
|
|
584
|
+
else if (query.isRefetchError) refresh = { kind: "failed" };
|
|
585
|
+
return { kind: "ready", layout: query.data, refresh };
|
|
586
|
+
}
|
|
587
|
+
function SlotCard({
|
|
588
|
+
slot,
|
|
589
|
+
context
|
|
590
|
+
}) {
|
|
591
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
592
|
+
import_dialog2.DialogTrigger,
|
|
593
|
+
{
|
|
594
|
+
render: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_button3.Button, {}),
|
|
595
|
+
payload: slot.id,
|
|
596
|
+
className: "ph-filament-slot",
|
|
597
|
+
"aria-label": `Manage ${context} \xB7 ${slot.label}`,
|
|
598
|
+
children: [
|
|
599
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ph-filament-slot-top", children: [
|
|
600
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ph-filament-slot-label", children: slot.label }),
|
|
601
|
+
slot.feeding && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ph-filament-feeding", children: "Feeding" })
|
|
602
|
+
] }),
|
|
603
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
604
|
+
"span",
|
|
605
|
+
{
|
|
606
|
+
className: "ph-filament-swatch",
|
|
607
|
+
style: fillStyle(slot.fill?.colors ?? [], slot.fill?.kind),
|
|
608
|
+
"aria-hidden": "true"
|
|
609
|
+
}
|
|
610
|
+
),
|
|
611
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: slot.displayName ?? slot.materialType ?? (slot.assignment ? "Assigned filament" : "No assignment") }),
|
|
612
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ph-filament-slot-status", "data-state": slot.presentation, children: slotStatus(slot) }),
|
|
613
|
+
slot.remainingPct !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ph-filament-muted", children: [
|
|
614
|
+
Math.round(slot.remainingPct),
|
|
615
|
+
"% remaining"
|
|
616
|
+
] })
|
|
617
|
+
]
|
|
618
|
+
}
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
function SourceUnit({
|
|
622
|
+
source,
|
|
623
|
+
context
|
|
624
|
+
}) {
|
|
625
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "ph-filament-source", "aria-label": source.label, children: [
|
|
626
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { children: [
|
|
627
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: source.label }),
|
|
628
|
+
source.humidityPct !== null && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ph-filament-muted", children: [
|
|
629
|
+
source.humidityPct,
|
|
630
|
+
"% humidity"
|
|
631
|
+
] })
|
|
632
|
+
] }),
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ph-filament-slots", "data-layout": source.layout, children: source.slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SlotCard, { slot, context }, slot.id)) })
|
|
634
|
+
] });
|
|
635
|
+
}
|
|
636
|
+
function HotendRack({ rack }) {
|
|
637
|
+
if (rack.kind === "no-rack-reported") return null;
|
|
638
|
+
if (rack.kind === "invalid-report")
|
|
639
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-muted", children: "Hotend rack information is unavailable." });
|
|
640
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "ph-filament-rack", "aria-label": "Hotend rack", children: [
|
|
641
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "Hotend rack" }),
|
|
642
|
+
rack.kind === "reported" && rack.tools === null && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-muted", children: "Hotend details unavailable." }),
|
|
643
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ph-filament-rack-tools", children: rack.kind === "legacy" ? rack.slots.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
644
|
+
"Holder ",
|
|
645
|
+
slot.id + 1,
|
|
646
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: slot.occupied ? slot.diameterMm !== null ? `${slot.diameterMm} mm` : "Hotend" : "Empty" }),
|
|
647
|
+
slot.active && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { children: "Active" })
|
|
648
|
+
] }, slot.id)) : (rack.tools ?? []).map((tool) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { children: [
|
|
649
|
+
tool.location === "toolhead" ? "Toolhead" : tool.holderAddress !== null ? `Holder ${tool.holderAddress + 1}` : "Rack",
|
|
650
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("strong", { children: tool.diameterMm !== null ? `${tool.diameterMm} mm` : tool.kind }),
|
|
651
|
+
tool.serialNumber && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("small", { children: tool.serialNumber })
|
|
652
|
+
] }, tool.id)) }),
|
|
653
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-muted", children: "Hardware information only. Filament assignments belong to the sources below." })
|
|
654
|
+
] });
|
|
655
|
+
}
|
|
656
|
+
function FilamentManagement({
|
|
657
|
+
printerId,
|
|
658
|
+
className
|
|
659
|
+
}) {
|
|
660
|
+
const { dataSource, queryClient } = useFilamentRuntime();
|
|
661
|
+
const [scope, setScope] = (0, import_react4.useState)({ dataSource, revision: 0 });
|
|
662
|
+
if (scope.dataSource !== dataSource) {
|
|
663
|
+
setScope({ dataSource, revision: scope.revision + 1 });
|
|
664
|
+
}
|
|
665
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_query5.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
666
|
+
PrinterFilamentManagement,
|
|
667
|
+
{
|
|
668
|
+
printerId,
|
|
669
|
+
className,
|
|
670
|
+
source: dataSource
|
|
671
|
+
},
|
|
672
|
+
`${printerId}:${scope.revision}`
|
|
673
|
+
) });
|
|
674
|
+
}
|
|
675
|
+
function PrinterFilamentManagement({
|
|
676
|
+
printerId,
|
|
677
|
+
className,
|
|
678
|
+
source
|
|
679
|
+
}) {
|
|
680
|
+
const { queryClient } = useFilamentRuntime();
|
|
681
|
+
const query = (0, import_react_query5.useQuery)(
|
|
682
|
+
{
|
|
683
|
+
queryKey: layoutQueryKey(printerId),
|
|
684
|
+
queryFn: async ({ signal }) => requirePrinterLayout(
|
|
685
|
+
await source.read(printerId, { signal }),
|
|
686
|
+
printerId
|
|
687
|
+
)
|
|
688
|
+
},
|
|
689
|
+
queryClient
|
|
690
|
+
);
|
|
691
|
+
const busy = (0, import_react_query5.useIsMutating)({ mutationKey: operationQueryKey(printerId) }, queryClient) > 0;
|
|
692
|
+
const refresh = () => {
|
|
693
|
+
void query.refetch();
|
|
694
|
+
};
|
|
695
|
+
const state = layoutState(query);
|
|
696
|
+
const rootClass = `ph-filament-management${className ? ` ${className}` : ""}`;
|
|
697
|
+
if (state.kind === "loading")
|
|
698
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
699
|
+
"section",
|
|
700
|
+
{
|
|
701
|
+
className: rootClass,
|
|
702
|
+
"aria-label": "Filament management",
|
|
703
|
+
"aria-busy": "true",
|
|
704
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "status", children: "Loading printer filaments\u2026" })
|
|
705
|
+
}
|
|
706
|
+
);
|
|
707
|
+
if (state.kind === "failed")
|
|
708
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: rootClass, "aria-label": "Filament management", children: [
|
|
709
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: "Filaments unavailable" }),
|
|
710
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { role: "alert", children: "The printer filaments could not be loaded. Check your connection and access to this printer." }),
|
|
711
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_button3.Button, { onClick: refresh, children: "Retry loading filaments" })
|
|
712
|
+
] });
|
|
713
|
+
const { layout } = state;
|
|
714
|
+
const slots = layoutSlots(layout);
|
|
715
|
+
const sources = layout.nozzles.flatMap((nozzle) => nozzle.sources).concat(layout.unboundSources);
|
|
716
|
+
const observedAt = "observedAt" in layout.freshness ? layout.freshness.observedAt : null;
|
|
717
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dialog2.Dialog, { dismissible: !busy, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
718
|
+
"section",
|
|
719
|
+
{
|
|
720
|
+
className: rootClass,
|
|
721
|
+
"aria-label": `Filaments for ${layout.printer.name}`,
|
|
722
|
+
children: [
|
|
723
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { className: "ph-filament-header", children: [
|
|
724
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { children: [
|
|
725
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-eyebrow", children: "Printer filaments" }),
|
|
726
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h2", { children: layout.printer.name }),
|
|
727
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("p", { className: "ph-filament-muted", children: [
|
|
728
|
+
layout.printer.make,
|
|
729
|
+
" ",
|
|
730
|
+
layout.printer.model
|
|
731
|
+
] })
|
|
732
|
+
] }),
|
|
733
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ph-filament-header-actions", children: [
|
|
734
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "ph-filament-connection", children: layout.printer.connected === true ? "Online" : layout.printer.connected === false ? "Offline" : "Connection unknown" }),
|
|
735
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
736
|
+
import_button3.Button,
|
|
737
|
+
{
|
|
738
|
+
disabled: state.refresh.kind === "pending" || busy,
|
|
739
|
+
onClick: refresh,
|
|
740
|
+
children: "Refresh"
|
|
741
|
+
}
|
|
742
|
+
)
|
|
743
|
+
] })
|
|
744
|
+
] }),
|
|
745
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: "ph-filament-freshness", children: [
|
|
746
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: layout.freshness.status === "fresh" ? "Current printer report" : layout.freshness.status === "stale" ? "Last known printer report" : "Live printer report unavailable" }),
|
|
747
|
+
observedAt && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("time", { dateTime: observedAt, children: new Date(observedAt).toLocaleString() })
|
|
748
|
+
] }),
|
|
749
|
+
state.refresh.kind === "pending" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-muted", role: "status", children: "Refreshing printer filaments\u2026" }),
|
|
750
|
+
state.refresh.kind === "failed" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-inline-error", role: "alert", children: "The latest state could not be loaded. Showing the previous snapshot." }),
|
|
751
|
+
layout.topology.kind === "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-inline-error", children: "The printer has not reported its hardware layout. Saved assignments are shown without guessing their nozzle or AMS unit." }),
|
|
752
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(HotendRack, { rack: layout.hotendRack }),
|
|
753
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ph-filament-nozzles", children: layout.nozzles.map((nozzle) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
754
|
+
"section",
|
|
755
|
+
{
|
|
756
|
+
className: "ph-filament-nozzle",
|
|
757
|
+
"aria-label": nozzle.label,
|
|
758
|
+
children: [
|
|
759
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("header", { children: [
|
|
760
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: nozzle.label }),
|
|
761
|
+
nozzle.hotend?.diameterMm !== null && nozzle.hotend?.diameterMm !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("span", { className: "ph-filament-muted", children: [
|
|
762
|
+
nozzle.hotend.diameterMm,
|
|
763
|
+
" mm",
|
|
764
|
+
nozzle.hotend.mount === "vortek" ? " \xB7 Vortek" : ""
|
|
765
|
+
] })
|
|
766
|
+
] }),
|
|
767
|
+
nozzle.sources.map((unit) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
768
|
+
SourceUnit,
|
|
769
|
+
{
|
|
770
|
+
source: unit,
|
|
771
|
+
context: layout.nozzles.length > 1 ? `${nozzle.label} \xB7 ${unit.label}` : unit.label
|
|
772
|
+
},
|
|
773
|
+
unit.id
|
|
774
|
+
))
|
|
775
|
+
]
|
|
776
|
+
},
|
|
777
|
+
nozzle.id
|
|
778
|
+
)) }),
|
|
779
|
+
layout.unboundSources.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "ph-filament-unbound", children: [
|
|
780
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "Not assigned to a nozzle" }),
|
|
781
|
+
layout.unboundSources.map((unit) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(SourceUnit, { source: unit, context: unit.label }, unit.id))
|
|
782
|
+
] }),
|
|
783
|
+
layout.unmappedAssignments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("section", { className: "ph-filament-unbound", children: [
|
|
784
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("h3", { children: "Saved assignments" }),
|
|
785
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "ph-filament-slots", "data-layout": "row4", children: layout.unmappedAssignments.map((slot) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
786
|
+
SlotCard,
|
|
787
|
+
{
|
|
788
|
+
slot,
|
|
789
|
+
context: "Saved assignments"
|
|
790
|
+
},
|
|
791
|
+
slot.id
|
|
792
|
+
)) })
|
|
793
|
+
] }),
|
|
794
|
+
sources.length === 0 && slots.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "ph-filament-muted", children: "No filament sources have been reported for this printer." }),
|
|
795
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dialog2.DialogBackdrop, { className: "ph-filament-backdrop" }),
|
|
796
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
797
|
+
import_dialog2.DialogContent,
|
|
798
|
+
{
|
|
799
|
+
className: "ph-filament-editor",
|
|
800
|
+
"aria-label": `Manage ${layout.printer.name} filaments`,
|
|
801
|
+
keepMounted: true,
|
|
802
|
+
children: (dialog) => {
|
|
803
|
+
const slot = slots.find(
|
|
804
|
+
(candidate) => candidate.id === dialog.payload
|
|
805
|
+
);
|
|
806
|
+
return dialog.open && slot ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
807
|
+
SlotEditor,
|
|
808
|
+
{
|
|
809
|
+
printerId,
|
|
810
|
+
slot,
|
|
811
|
+
context: slotContext(layout, slot.id)
|
|
812
|
+
},
|
|
813
|
+
slot.id
|
|
814
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
815
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dialog2.DialogTitle, { children: !dialog.open || dialog.payload === void 0 ? `Manage ${layout.printer.name} filaments` : "Slot unavailable" }),
|
|
816
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dialog2.DialogDescription, { children: "This slot is no longer part of the reported printer layout. Refresh the printer before making changes." }),
|
|
817
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_dialog2.DialogClose, { children: "Close" })
|
|
818
|
+
] });
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
)
|
|
822
|
+
]
|
|
823
|
+
}
|
|
824
|
+
) });
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// src/http-data-source.ts
|
|
828
|
+
var import_validation = require("@printhive/api/validation");
|
|
829
|
+
var import_zod = require("zod");
|
|
830
|
+
var failureSchema = import_zod.z.object({ error: import_zod.z.string(), message: import_zod.z.string() });
|
|
831
|
+
var layoutSchema = import_zod.z.custom(import_validation.isPrinterMaterialLayout);
|
|
832
|
+
var catalogSchema = import_zod.z.custom(import_validation.isFilamentSearchPage);
|
|
833
|
+
var assignmentSchema = import_zod.z.custom(
|
|
834
|
+
import_validation.isAssignFilamentsResult
|
|
835
|
+
);
|
|
836
|
+
var clearSchema = import_zod.z.custom(
|
|
837
|
+
import_validation.isClearFilamentAssignmentsResult
|
|
838
|
+
);
|
|
839
|
+
var rfidSchema = import_zod.z.custom(
|
|
840
|
+
import_validation.isRefreshMaterialSlotRfidResult
|
|
841
|
+
);
|
|
842
|
+
function invalidResponse(response) {
|
|
843
|
+
return new FilamentManagementError(
|
|
844
|
+
"The filament service returned an invalid response.",
|
|
845
|
+
{
|
|
846
|
+
code: "invalid_response",
|
|
847
|
+
status: response.status
|
|
848
|
+
}
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
function createFilamentManagementDataSource(options) {
|
|
852
|
+
const baseUrl = options.baseUrl.trim().replace(/\/+$/, "");
|
|
853
|
+
if (!options.baseUrl.trim()) throw new TypeError("baseUrl is required");
|
|
854
|
+
const fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
855
|
+
async function request(printerId, suffix, schema, signal, body) {
|
|
856
|
+
if (!printerId.trim()) throw new TypeError("printerId is required");
|
|
857
|
+
const response = await fetcher(
|
|
858
|
+
`${baseUrl}/printers/${encodeURIComponent(printerId)}/filaments${suffix}`,
|
|
859
|
+
{
|
|
860
|
+
method: body === void 0 ? "GET" : "POST",
|
|
861
|
+
credentials: "same-origin",
|
|
862
|
+
signal,
|
|
863
|
+
headers: body === void 0 ? void 0 : { "content-type": "application/json" },
|
|
864
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
865
|
+
}
|
|
866
|
+
);
|
|
867
|
+
const value = await response.json().catch(() => {
|
|
868
|
+
throw invalidResponse(response);
|
|
869
|
+
});
|
|
870
|
+
if (!response.ok) {
|
|
871
|
+
const failure = failureSchema.safeParse(value);
|
|
872
|
+
throw new FilamentManagementError(
|
|
873
|
+
failure.success ? failure.data.message : "The filament request could not be completed.",
|
|
874
|
+
{
|
|
875
|
+
code: failure.success ? failure.data.error : "request_failed",
|
|
876
|
+
status: response.status
|
|
877
|
+
}
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
const parsed = schema.safeParse(value);
|
|
881
|
+
if (!parsed.success) throw invalidResponse(response);
|
|
882
|
+
return parsed.data;
|
|
883
|
+
}
|
|
884
|
+
return {
|
|
885
|
+
read: async (id, options2) => requirePrinterLayout(
|
|
886
|
+
await request(id, "", layoutSchema, options2?.signal),
|
|
887
|
+
id
|
|
888
|
+
),
|
|
889
|
+
async search(id, query, options2) {
|
|
890
|
+
const params = new URLSearchParams({ q: query });
|
|
891
|
+
const page = await request(
|
|
892
|
+
id,
|
|
893
|
+
`/catalog?${params}`,
|
|
894
|
+
catalogSchema,
|
|
895
|
+
options2?.signal
|
|
896
|
+
);
|
|
897
|
+
return page.filaments.map((filament) => ({
|
|
898
|
+
id: filament.id,
|
|
899
|
+
label: filament.name,
|
|
900
|
+
materialType: filament.material_type,
|
|
901
|
+
color: filament.color,
|
|
902
|
+
colors: filament.colors,
|
|
903
|
+
description: [filament.brand, filament.colorName].filter(Boolean).join(" \xB7 ")
|
|
904
|
+
}));
|
|
905
|
+
},
|
|
906
|
+
assign: (id, input, options2) => request(id, "/assign", assignmentSchema, options2?.signal, input),
|
|
907
|
+
clear: (id, input, options2) => request(id, "/clear", clearSchema, options2?.signal, input),
|
|
908
|
+
refreshRfid: (id, input, options2) => request(id, "/refresh-rfid", rfidSchema, options2?.signal, input)
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
912
|
+
0 && (module.exports = {
|
|
913
|
+
FilamentManagement,
|
|
914
|
+
FilamentManagementError,
|
|
915
|
+
PrintHiveProvider,
|
|
916
|
+
createFilamentManagementDataSource
|
|
917
|
+
});
|
|
918
|
+
//# sourceMappingURL=index.cjs.map
|