mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-09 15:35:39 +02:00
fix(inventory): apply catalog color's gradient + effect, not just hex (#1340)
Picking a color preset from the catalog only copied color_name and rgba onto the spool — extra_colors (gradient stops) and effect_type (sparkle / wood / etc.) were silently dropped at three layers above the API: the SpoolFormModal state shape, the CatalogDisplayColor mapping in ColorSection, and the selectColor handler itself. All three widened to carry both fields through. Picking a catalog swatch now writes both fields from the entry, so solid presets cleanly replace previous gradients. Recent-colors and the hardcoded-fallback palette stay as plain hex pickers — they don't touch extras/effect since they aren't full presets. Also fixed the en-US `colour` → `color` drift in 8 locale files that the reporter flagged.
This commit is contained in:
@@ -14,6 +14,8 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
- **Spoolman weight tracking now uses per-print grams for all spools, matching the internal Filament Inventory** ([#1119](https://github.com/maziggy/bambuddy/issues/1119), reported by @Moskito99) — Spoolman previously had two mutually-exclusive weight paths: AMS remain%×tray_weight auto-sync (default; only worked for Bambu Lab spools with valid RFID tray_weight) and per-print 3MF-grams tracking (only enabled when "Disable AMS Weight Sync" was toggled on). Non-BL spools without RFID fell through both paths — AMS auto-sync had no tray_weight to multiply, and the inventory_remaining fallback was wiped because activating Spoolman deletes the internal `spool_assignment` table — so Spoolman never saw a weight update for them. The internal Filament Inventory has no such gap: it always uses per-print 3MF grams as the primary path with AMS-remain% delta as fallback, and it works for every spool type. Spoolman now does the same: per-print tracking runs whenever Spoolman is enabled and is the only writer of `remaining_weight`. AMS auto-sync continues to maintain spool metadata and slot assignments but no longer touches weight (eliminating the double-count that would otherwise occur for BL spools with both paths active). `store_print_data` ([`spoolman_tracking.py:159`](backend/app/services/spoolman_tracking.py)) had its `disable_weight_sync` early-return removed; the three `sync_ams_tray` callsites (`main.py:1450` auto-sync, `spoolman.py:318` per-printer manual, `spoolman.py:517` sync-all) now hard-code `disable_weight_sync=True`. The `spoolman_disable_weight_sync` setting is now deprecated and a no-op — kept in the DB/UI for backwards compat. Behavioral consequence for existing users on the default flag (False): live AMS-based remaining_weight updates between prints stop happening; weight updates now arrive once per print completion with 3MF gram precision. Regression test in `test_spoolman_tracking.py::test_stores_tracking_when_disable_weight_sync_is_false` proves the early-return is gone.
|
||||
|
||||
### Fixed
|
||||
- **Color catalog presets now apply `extra_colors` (gradient stops) and `effect_type` (sparkle / wood / marble / glow / matte) onto the spool, not just hex + name** ([#1340](https://github.com/maziggy/bambuddy/issues/1340), reported by @maugsburger) — Creating a catalog entry that pairs a base color with multi-color gradient stops and a visual effect, then clicking that swatch in the Edit Spool dialog, only copied `color_name` and `rgba` over — the `extra_colors` and `effect_type` fields were silently dropped. The data was flowing from the backend correctly (`GET /api/v1/inventory/color-catalog` returns both fields per the `ColorCatalogEntry` schema in [`frontend/src/api/client.ts`](frontend/src/api/client.ts)), but three layers above stripped them: (1) [`SpoolFormModal.tsx`](frontend/src/components/SpoolFormModal.tsx) typed its `colorCatalog` state with a narrower shape that omitted the two fields; (2) [`ColorSection.tsx`](frontend/src/components/spool-form/ColorSection.tsx) mapped catalog entries to `CatalogDisplayColor` (the typed-down shape rendered on swatches) without propagating them; (3) the `selectColor()` handler only set `rgba` + `color_name` on click. **Fix:** widened both types in [`spool-form/types.ts`](frontend/src/components/spool-form/types.ts) (`CatalogDisplayColor` + `ColorSectionProps.catalogColors`) to carry the optional `extra_colors` and `effect_type`, propagated them through the four `matchedCatalogColors` mapping callbacks (byBrand / exact full-material / normalized-trailing-`+` / base-material prefix), and extended `selectColor` to take optional `extraColors` / `effectType` parameters. **Semantic rule:** catalog swatches are complete presets — picking one writes BOTH gradient and effect from the entry (overwriting any existing values), so a gradient catalog entry applies its stops AND a solid catalog entry clears any old gradient that was on the spool. Recent-colors and the hardcoded-fallback palette are plain hex pickers — picking one keeps any existing `extra_colors` / `effect_type` untouched, since those swatches aren't presets, just color picks. **Bonus:** fixed the en-US spelling drift the reporter flagged in their nitpick — `'Extra colours'` and `'wrong colour loaded'` strings (which had been seeded into all 8 locale files as English fallbacks) standardized to `'Extra colors'` and `'wrong color loaded'`; matching comment blocks (`// Multi-colour ...`) normalized in the same pass. **Regression tests** in [`__tests__/components/spool-form/ColorSectionCatalogExtras.test.tsx`](frontend/src/__tests__/components/spool-form/ColorSectionCatalogExtras.test.tsx) (3 cases): catalog click with gradient + effect propagates all four fields to `updateField`, catalog click on a solid preset clears any pre-existing extras/effect (preset-replaces-look semantic), and fallback palette click leaves extras/effect untouched. All 23 spool-form tests + 8 i18n parity tests pass; build clean.
|
||||
|
||||
- **Assigning a spool to an unconfigured AMS slot no longer silently skips MQTT on A1 Mini / P1S firmware — and the "PETG over a PLA-configured slot won't reconfigure" symptom is fixed in the same change** ([#1322](https://github.com/maziggy/bambuddy/issues/1322), reported by @RosdasHH) — On the user's A1 Mini BMCU (firmware 01.07.02.00) and P1S Standard AMS (firmware 00.00.06.75), pressing "Assign Spool" on any slot left the slot unconfigured: the DB row was created with `pending_config=True`, the MQTT publish was skipped, and the log line `Pre-configured assignment: ... (slot empty, will configure on insert)` fired even though the spool was physically loaded. The same code path also blocked the "swap PLA to PETG in the same slot" flow — Bambuddy would keep treating the spool as PLA because the publish never reached the printer. **Root cause:** the empty-slot detection at [`backend/app/api/routes/inventory.py:1267`](backend/app/api/routes/inventory.py) preferred `tray.state == 11` ("filament fed to extruder") over `tray_type`, falling back to `tray_type` only when `state` was missing entirely. Reporter's AMS dumps showed `state == 3` on every slot — configured and unconfigured, on both printers — and `state` was never absent. So the state-only branch always fired, the result was always "empty", and MQTT was always skipped regardless of whether the slot was actually loaded. The "fingerprint_type empty → defer until insert" pre-config replay at [`backend/app/main.py:1026`](backend/app/main.py) had the same `cur_state == 11` gate, so even when the user manually configured the slot in Bambu Studio afterward (making `tray_type` go from `""` to `"PLA"`), the deferred MQTT publish never fired because state stayed at 3. **Fix:** both call sites now use a disjunction — the slot is treated as loaded when **either** `state == 11` **or** `tray_type` is non-empty. The "Reset slot" case (state=11 + tray_type="") that the original state-only check was protecting still works through the first clause; the configured-slot case (state=3 + tray_type="PLA") on firmwares that never set state=11 now works through the second; and truly empty unconfigured slots (state≠11 + tray_type="") still fall through to the pending-config path correctly. The on_ams_change replay's disjunction also fires the deferred publish when the user later configures the slot through Bambu Studio, since that flips `tray_type` non-empty even if state stays at 3. **Caveat:** for a truly empty slot with a 3rd-party non-RFID spool that the user physically inserted, neither signal points to "loaded" on these firmwares, so we still can't auto-fire the publish until the slot gets configured (manually or by another assign). The pending-config row persists in the DB and gets applied on the next AMS push that flips `tray_type` non-empty. **Regression tests:** 3 in [`test_inventory_assign.py`](backend/tests/integration/test_inventory_assign.py) — `test_state_never_eleven_firmware_with_loaded_tray_fires_mqtt` (state=3 + tray_type='PLA' → MQTT fires; pins the reporter's primary symptom and the PETG-over-PLA secondary symptom which goes through the same predicate), `test_state_never_eleven_firmware_with_empty_tray_marks_pending` (state=3 + tray_type='' still pending — confirms the disjunction didn't accidentally turn truly empty slots into the loaded branch), and `test_on_ams_change_fires_replay_when_tray_type_appears_without_state_11` (pre-existing SpoolBuddy-style assignment with empty fingerprint; tray_type going `''→'PLA'` on a state=3 firmware fires the deferred publish even though state never becomes 11). All 28 tests in the file pass; ruff clean.
|
||||
|
||||
- **Assign Spool / Inventory search: numeric spool ID lookup is back, and Unassign in Spoolman mode no longer stays permanently disabled** ([#1336](https://github.com/maziggy/bambuddy/issues/1336), reported by @S0liter) — Two independent regressions surfaced from the same report. **(1) Numeric ID search:** typing a Spoolman spool's numeric ID into the search box on the "Assign Spool" dialog (or on the Inventory page) returned no results. The shared search helper `spoolMatchesQuery` at [`frontend/src/utils/inventorySearch.ts:7`](frontend/src/utils/inventorySearch.ts) only checked the text fields (`material`, `brand`, `color_name`, `subtype`, `note`, `slicer_filament_name`, `storage_location`) — the spool's `id` was not part of the predicate, so a query like `12` only matched when "12" happened to be a substring of one of the text fields. One-line fix: the predicate now also tests `String(spool.id).includes(q)`, mirroring the case-insensitive substring semantics of the other fields. Covers both call sites: the Assign Spool dialog ([`AssignSpoolModal.tsx:255`](frontend/src/components/AssignSpoolModal.tsx) for local inventory + `:446` for Spoolman) and the main Inventory page ([`InventoryPage.tsx:871`](frontend/src/pages/InventoryPage.tsx)). New regression test in [`__tests__/utils/inventorySearch.test.ts`](frontend/src/__tests__/utils/inventorySearch.test.ts) pins exact-match (`'42'` → id 42), substring (`'4'` → id 42), and non-match (`'99'` → id 42 rejected) so the predicate can't drift back into "text only" silently. **(2) Unassign button stuck disabled in Spoolman mode:** opening the edit modal on a Spoolman spool that was assigned to an AMS slot left the Unassign button greyed out — the user had no way to release the spool back to "available". The modal at [`SpoolFormModal.tsx:526`](frontend/src/components/SpoolFormModal.tsx) only ever queried `api.getAssignments()` (the legacy local `spool_assignments` table) and looked up by `a.spool_id === spool.id`. In Spoolman mode the slot assignment lives in the separate `spoolman_slot_assignments` table, keyed by `spoolman_spool_id` — so the lookup always returned `undefined`, the button's `disabled={isPending || !spoolAssignment}` predicate stayed true forever, and `unassignMutation` was also pointing at the wrong API (`unassignSpool` instead of `unassignSpoolmanSlot`). Both the query and the mutation now branch on the existing `spoolmanMode` prop: Spoolman mode uses `getSpoolmanSlotAssignments()` + lookup by `spoolman_spool_id` + `unassignSpoolmanSlot(spool.id)` and invalidates the `spoolman-slot-assignments-all` / `spoolman-slot-assignments` query keys; local mode keeps the existing path unchanged. Two new regression tests in [`__tests__/components/SpoolFormModal.test.tsx`](frontend/src/__tests__/components/SpoolFormModal.test.tsx) (`SpoolFormModal — Unassign button (#1336)`): the button is enabled and clicking it calls `unassignSpoolmanSlot(42)` when a matching `spoolman_slot_assignment` exists, and the button stays disabled (no `unassignSpool` fallback) when no assignment exists. All 12 search-helper tests + 13 InventoryPage search tests + 27 SpoolFormModal tests pass; frontend build clean.
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
/**
|
||||
* Regression test for #1340: clicking a catalog color must apply its
|
||||
* extra_colors (gradient stops) and effect_type alongside hex + name.
|
||||
*
|
||||
* Previously only hex + name were copied onto the spool, so a catalog entry
|
||||
* configured as a multi-color gradient with a visual effect would degrade to
|
||||
* a flat solid swatch the moment the user picked it.
|
||||
*/
|
||||
|
||||
import { describe, it, expect, vi } from 'vitest';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { I18nextProvider } from 'react-i18next';
|
||||
import i18n from '../../../i18n';
|
||||
import { ColorSection } from '../../../components/spool-form/ColorSection';
|
||||
import { defaultFormData } from '../../../components/spool-form/types';
|
||||
|
||||
function renderColorSection(opts: {
|
||||
catalogColors: Parameters<typeof ColorSection>[0]['catalogColors'];
|
||||
formData?: Partial<typeof defaultFormData>;
|
||||
}) {
|
||||
const formData = {
|
||||
...defaultFormData,
|
||||
brand: 'Bambu Lab',
|
||||
material: 'PLA',
|
||||
...opts.formData,
|
||||
};
|
||||
const updateField = vi.fn();
|
||||
render(
|
||||
<I18nextProvider i18n={i18n}>
|
||||
<ColorSection
|
||||
formData={formData}
|
||||
updateField={updateField}
|
||||
recentColors={[]}
|
||||
onColorUsed={vi.fn()}
|
||||
catalogColors={opts.catalogColors}
|
||||
/>
|
||||
</I18nextProvider>,
|
||||
);
|
||||
return { updateField };
|
||||
}
|
||||
|
||||
describe('ColorSection — catalog color picker (#1340)', () => {
|
||||
it('applies extra_colors and effect_type from the catalog entry', () => {
|
||||
const { updateField } = renderColorSection({
|
||||
catalogColors: [
|
||||
{
|
||||
manufacturer: 'Bambu Lab',
|
||||
color_name: 'Galaxy PLA',
|
||||
hex_color: '#1a1a2e',
|
||||
material: 'PLA',
|
||||
extra_colors: 'ec984c,6cd4bc,a66eb9,d87694',
|
||||
effect_type: 'sparkle',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Catalog swatches are rendered as buttons with the hex as their background;
|
||||
// the most reliable handle is the title text built from name + manufacturer.
|
||||
const swatch = screen.getByTitle(/Galaxy PLA \(Bambu Lab/);
|
||||
fireEvent.click(swatch);
|
||||
|
||||
expect(updateField).toHaveBeenCalledWith('rgba', '1A1A2EFF');
|
||||
expect(updateField).toHaveBeenCalledWith('color_name', 'Galaxy PLA');
|
||||
expect(updateField).toHaveBeenCalledWith('extra_colors', 'ec984c,6cd4bc,a66eb9,d87694');
|
||||
expect(updateField).toHaveBeenCalledWith('effect_type', 'sparkle');
|
||||
});
|
||||
|
||||
it('clears existing extras when a catalog entry has none (preset replaces look)', () => {
|
||||
const { updateField } = renderColorSection({
|
||||
catalogColors: [
|
||||
{
|
||||
manufacturer: 'Bambu Lab',
|
||||
color_name: 'Plain Red',
|
||||
hex_color: '#ff0000',
|
||||
material: 'PLA',
|
||||
extra_colors: null,
|
||||
effect_type: null,
|
||||
},
|
||||
],
|
||||
formData: { extra_colors: 'aabbcc,ddeeff', effect_type: 'sparkle' },
|
||||
});
|
||||
|
||||
const swatch = screen.getByTitle(/Plain Red \(Bambu Lab/);
|
||||
fireEvent.click(swatch);
|
||||
|
||||
// The catalog entry is a complete preset — picking a solid preset must
|
||||
// wipe the previously-set gradient and effect, not leave them clinging.
|
||||
expect(updateField).toHaveBeenCalledWith('extra_colors', '');
|
||||
expect(updateField).toHaveBeenCalledWith('effect_type', '');
|
||||
});
|
||||
|
||||
it('leaves extras and effect untouched when a plain swatch is clicked', () => {
|
||||
// The fallback hardcoded palette renders when no catalog colors match the
|
||||
// brand/material. Those are plain hex pickers — they must NOT clobber an
|
||||
// existing gradient on the spool.
|
||||
const { updateField } = renderColorSection({
|
||||
catalogColors: [],
|
||||
formData: {
|
||||
brand: '',
|
||||
material: '',
|
||||
extra_colors: 'aabbcc,ddeeff',
|
||||
effect_type: 'sparkle',
|
||||
},
|
||||
});
|
||||
|
||||
// The QUICK_COLORS palette includes Black/White/etc. Pick any one.
|
||||
const whiteSwatch = screen.getByTitle('White');
|
||||
fireEvent.click(whiteSwatch);
|
||||
|
||||
expect(updateField).toHaveBeenCalledWith('rgba', 'FFFFFFFF');
|
||||
// No extra_colors / effect_type updates — those buttons aren't presets.
|
||||
const calledKeys = updateField.mock.calls.map(c => c[0]);
|
||||
expect(calledKeys).not.toContain('extra_colors');
|
||||
expect(calledKeys).not.toContain('effect_type');
|
||||
});
|
||||
});
|
||||
@@ -80,7 +80,16 @@ export function SpoolFormModal({
|
||||
const [builtinFilaments, setBuiltinFilaments] = useState<BuiltinFilament[]>([]);
|
||||
|
||||
// Color catalog
|
||||
const [colorCatalog, setColorCatalog] = useState<{ manufacturer: string; color_name: string; hex_color: string; material: string | null }[]>([]);
|
||||
const [colorCatalog, setColorCatalog] = useState<{
|
||||
manufacturer: string;
|
||||
color_name: string;
|
||||
hex_color: string;
|
||||
material: string | null;
|
||||
// #1340: gradient + effect carried from the catalog entry through to the
|
||||
// color picker so they're applied alongside hex + name on selection.
|
||||
extra_colors?: string | null;
|
||||
effect_type?: string | null;
|
||||
}[]>([]);
|
||||
|
||||
// Color state
|
||||
const [recentColors, setRecentColors] = useState<ColorPreset[]>([]);
|
||||
|
||||
@@ -44,10 +44,31 @@ export function ColorSection({
|
||||
return currentHex.toUpperCase() === hex.toUpperCase();
|
||||
};
|
||||
|
||||
const selectColor = (hex: string, name: string) => {
|
||||
const selectColor = (
|
||||
hex: string,
|
||||
name: string,
|
||||
// #1340: catalog entries carry an optional gradient + effect. Pass them in
|
||||
// (even as empty strings) to overwrite the spool's existing values — the
|
||||
// catalog entry is a complete preset, the user explicitly chose its look.
|
||||
// Pass `undefined` (the default, used by recent/fallback swatches) to
|
||||
// leave any existing gradient/effect untouched — those buttons are plain
|
||||
// hex pickers, not full presets.
|
||||
extraColors?: string | null,
|
||||
effectType?: string | null,
|
||||
) => {
|
||||
// Store as RRGGBBAA (with FF alpha)
|
||||
updateField('rgba', hex.toUpperCase() + 'FF');
|
||||
updateField('color_name', name);
|
||||
if (extraColors !== undefined) {
|
||||
const next = extraColors ?? '';
|
||||
setExtraColorsDraft(next);
|
||||
setExtraColorsErrors([]);
|
||||
lastCommittedExtraColorsRef.current = next;
|
||||
updateField('extra_colors', next);
|
||||
}
|
||||
if (effectType !== undefined) {
|
||||
updateField('effect_type', effectType ?? '');
|
||||
}
|
||||
onColorUsed({ name, hex });
|
||||
};
|
||||
|
||||
@@ -82,6 +103,8 @@ export function ColorSection({
|
||||
hex: c.hex_color.replace('#', '').substring(0, 6),
|
||||
manufacturer: c.manufacturer,
|
||||
material: typeof c.material === 'string' ? c.material : undefined,
|
||||
extra_colors: c.extra_colors ?? null,
|
||||
effect_type: c.effect_type ?? null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -101,6 +124,8 @@ export function ColorSection({
|
||||
hex: c.hex_color.replace('#', '').substring(0, 6),
|
||||
manufacturer: c.manufacturer,
|
||||
material: typeof c.material === 'string' ? c.material : undefined,
|
||||
extra_colors: c.extra_colors ?? null,
|
||||
effect_type: c.effect_type ?? null,
|
||||
}));
|
||||
}
|
||||
// Try without trailing "+" (e.g. "PLA Silk+" -> "PLA Silk")
|
||||
@@ -133,6 +158,8 @@ export function ColorSection({
|
||||
hex: c.hex_color.replace('#', '').substring(0, 6),
|
||||
manufacturer: c.manufacturer,
|
||||
material: typeof c.material === 'string' ? c.material : undefined,
|
||||
extra_colors: c.extra_colors ?? null,
|
||||
effect_type: c.effect_type ?? null,
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -271,7 +298,7 @@ export function ColorSection({
|
||||
<button
|
||||
key={`${color.hex}-${color.name}-${color.manufacturer ?? ''}`}
|
||||
type="button"
|
||||
onClick={() => selectColor(color.hex, color.name)}
|
||||
onClick={() => selectColor(color.hex, color.name, color.extra_colors, color.effect_type)}
|
||||
className={`w-6 h-6 rounded border-2 transition-all hover:scale-110 hover:z-20 relative group ${
|
||||
isSelected(color.hex)
|
||||
? 'border-bambu-green ring-1 ring-bambu-green/30 scale-110'
|
||||
|
||||
@@ -7,6 +7,11 @@ export interface CatalogDisplayColor {
|
||||
hex: string;
|
||||
manufacturer?: string;
|
||||
material?: string;
|
||||
// #1340: a catalog entry can carry a gradient + visual effect. When the user
|
||||
// picks the entry, we copy these onto the spool's color metadata — the bug
|
||||
// was that they were never propagated past the API layer.
|
||||
extra_colors?: string | null;
|
||||
effect_type?: string | null;
|
||||
}
|
||||
|
||||
// Form data structure
|
||||
@@ -117,7 +122,14 @@ export interface FilamentSectionProps extends SectionProps {
|
||||
export interface ColorSectionProps extends SectionProps {
|
||||
recentColors: ColorPreset[];
|
||||
onColorUsed: (color: ColorPreset) => void;
|
||||
catalogColors: { manufacturer: string; color_name: string; hex_color: string; material: string | null }[];
|
||||
catalogColors: {
|
||||
manufacturer: string;
|
||||
color_name: string;
|
||||
hex_color: string;
|
||||
material: string | null;
|
||||
extra_colors?: string | null;
|
||||
effect_type?: string | null;
|
||||
}[];
|
||||
}
|
||||
|
||||
// Additional section props
|
||||
|
||||
@@ -3588,8 +3588,8 @@ export default {
|
||||
showAll: 'Show all',
|
||||
noColorsFound: 'No colors match your search',
|
||||
noResults: 'No matches found',
|
||||
// Multi-colour gradient + visual effect (#1154)
|
||||
extraColorsLabel: 'Extra colours',
|
||||
// Multi-color gradient + visual effect (#1154)
|
||||
extraColorsLabel: 'Extra colors',
|
||||
extraColorsPlaceholder: 'EC984C,#6CD4BC,A66EB9,D87694',
|
||||
extraColorsHint: 'Paste 2 to 8 hex stops, separated by commas. Renders as a gradient.',
|
||||
extraColorsInvalid: 'Ignored invalid hex: {{tokens}}',
|
||||
@@ -3608,7 +3608,7 @@ export default {
|
||||
rainbow: 'Rainbow',
|
||||
metal: 'Metal',
|
||||
translucent: 'Translucent',
|
||||
// Multi-colour structural variants
|
||||
// Multi-color structural variants
|
||||
gradient: 'Gradient',
|
||||
dualColor: 'Dual Color',
|
||||
triColor: 'Tri Color',
|
||||
@@ -4238,7 +4238,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Tailscale integration',
|
||||
|
||||
@@ -3572,8 +3572,8 @@ export default {
|
||||
showAll: 'Toutes',
|
||||
noColorsFound: 'Aucune couleur correspondante',
|
||||
noResults: 'Aucun résultat',
|
||||
// Multi-colour gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colours',
|
||||
// Multi-color gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colors',
|
||||
extraColorsPlaceholder: 'EC984C,#6CD4BC,A66EB9,D87694',
|
||||
extraColorsHint: 'Paste 2 to 8 hex stops, separated by commas. Renders as a gradient.',
|
||||
extraColorsInvalid: 'Ignored invalid hex: {{tokens}}',
|
||||
@@ -4219,7 +4219,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Intégration Tailscale',
|
||||
|
||||
@@ -3571,8 +3571,8 @@ export default {
|
||||
showAll: 'Mostra tutto',
|
||||
noColorsFound: 'Nessun colore corrisponde alla ricerca',
|
||||
noResults: 'Nessun risultato trovato',
|
||||
// Multi-colour gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colours',
|
||||
// Multi-color gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colors',
|
||||
extraColorsPlaceholder: 'EC984C,#6CD4BC,A66EB9,D87694',
|
||||
extraColorsHint: 'Paste 2 to 8 hex stops, separated by commas. Renders as a gradient.',
|
||||
extraColorsInvalid: 'Ignored invalid hex: {{tokens}}',
|
||||
@@ -4218,7 +4218,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Integrazione Tailscale',
|
||||
|
||||
@@ -3583,8 +3583,8 @@ export default {
|
||||
showAll: 'すべて表示',
|
||||
noColorsFound: '一致する色がありません',
|
||||
noResults: '結果なし',
|
||||
// Multi-colour gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colours',
|
||||
// Multi-color gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colors',
|
||||
extraColorsPlaceholder: 'EC984C,#6CD4BC,A66EB9,D87694',
|
||||
extraColorsHint: 'Paste 2 to 8 hex stops, separated by commas. Renders as a gradient.',
|
||||
extraColorsInvalid: 'Ignored invalid hex: {{tokens}}',
|
||||
@@ -4230,7 +4230,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Tailscale統合',
|
||||
|
||||
@@ -3571,8 +3571,8 @@ export default {
|
||||
showAll: 'Mostrar tudo',
|
||||
noColorsFound: 'Nenhuma cor corresponde à sua pesquisa',
|
||||
noResults: 'Nenhum resultado encontrado',
|
||||
// Multi-colour gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colours',
|
||||
// Multi-color gradient + visual effect (#1154) — English fallback.
|
||||
extraColorsLabel: 'Extra colors',
|
||||
extraColorsPlaceholder: 'EC984C,#6CD4BC,A66EB9,D87694',
|
||||
extraColorsHint: 'Paste 2 to 8 hex stops, separated by commas. Renders as a gradient.',
|
||||
extraColorsInvalid: 'Ignored invalid hex: {{tokens}}',
|
||||
@@ -4218,7 +4218,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Integração Tailscale',
|
||||
|
||||
@@ -4218,7 +4218,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Tailscale 集成',
|
||||
|
||||
@@ -4218,7 +4218,7 @@ export default {
|
||||
},
|
||||
queueForceColorMatch: {
|
||||
title: 'Force color match',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong colour loaded.',
|
||||
description: 'Refuse to dispatch onto a printer that does not have the exact filament type and color loaded. Off by default — without this, the queue uses model-only matching and may pick a printer with the wrong color loaded.',
|
||||
},
|
||||
tailscaleDisabled: {
|
||||
title: 'Tailscale 整合',
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -26,7 +26,7 @@
|
||||
|
||||
<!-- Splash screens for iOS -->
|
||||
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
|
||||
<script type="module" crossorigin src="/assets/index-D3mBzx5f.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-oQDjU99K.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BkYu3kLs.css">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in New Issue
Block a user