mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-19 07:33:18 +02:00
fix(slicer): preset visibility + lookup precedence + signed-out banner + AMS slot badges (#1712)
Four #1712 issues from the 2026-06-04 Orca Cloud integration: (1) Tier order put Orca Cloud above everything across SliceModal, auto-pick scoring, dropdown groups, the AMS slot picker, and the backend precedence. Bambu-Cloud-only users saw their profiles deprioritised behind an empty Orca tier. (2) Cross-tier dedup hid a same-named preset in all but the highest- priority tier. A user with both a local-imported and an Orca-synced "Bambu PLA Basic" couldn't see the Orca copy as a picker option. (3) CloudStatusBanner nagged signed-out users with a permanent "Sign in to Orca Cloud" line at the top of every slice -- even after explicit logout. Bambu Cloud had the symmetric problem. (4) ConfigureAmsSlotModal source badges were inconsistent: Orca rows showed only "Custom" (no source identity), Bambu Cloud built-in rows had no badge at all, and the orthogonal isUser-driven "Custom" badge collided with the source badge for cloud user presets. Order is local > orca_cloud > cloud > standard everywhere it lives (SliceModal SLICE_MODAL_TIER_ORDER + TIER_BONUS + dropdown tier list, ConfigureAmsSlotModal sourceOrder, and backend precedence). The order drives auto-pick + visual group rendering; it does NOT hide profiles. _dedupe_by_name replaced with _enrich_cloud_metadata: every tier returns its full list across all three slots (printer / process / filament). The function still backfills Bambu Cloud filament metadata from same-named local / orca_cloud / standard entries so cloud filaments score in pickFilamentForSlot. CloudStatusBanner silently no-ops on not_authenticated for both clouds; expired / unreachable still surface. The not_authenticated i18n keys stay in the locale files dormant. ConfigureAmsSlotModal: one source badge per row, one colour per source (green Local / purple Orca Cloud / bambu-blue Bambu Cloud / amber Built-in). The legacy isUser-driven "Custom" badge is gone; every row identifies its tier consistently.
This commit is contained in:
@@ -639,10 +639,11 @@ export function ConfigureAmsSlotModal({
|
||||
}
|
||||
}
|
||||
|
||||
// Sort: orca_cloud first (user-curated), then cloud user presets, then
|
||||
// cloud built-in, then local, then builtin fallback
|
||||
// Sort: local first (user explicitly imported them), then orca_cloud,
|
||||
// then bambu cloud, then builtin fallback. Matches the SliceModal
|
||||
// tier priority.
|
||||
return items.sort((a, b) => {
|
||||
const sourceOrder = { orca_cloud: 0, cloud: 1, local: 2, builtin: 3 };
|
||||
const sourceOrder = { local: 0, orca_cloud: 1, cloud: 2, builtin: 3 };
|
||||
if (a.source !== b.source) return sourceOrder[a.source] - sourceOrder[b.source];
|
||||
if (a.isUser && !b.isUser) return -1;
|
||||
if (!a.isUser && b.isUser) return 1;
|
||||
@@ -1099,16 +1100,21 @@ export function ConfigureAmsSlotModal({
|
||||
{t('profiles.localProfiles.badge')}
|
||||
</span>
|
||||
)}
|
||||
{preset.source === 'orca_cloud' && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-purple-500/20 text-purple-400">
|
||||
{t('configureAmsSlot.orcaCloud')}
|
||||
</span>
|
||||
)}
|
||||
{preset.source === 'cloud' && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
|
||||
{t('configureAmsSlot.bambuCloud')}
|
||||
</span>
|
||||
)}
|
||||
{preset.source === 'builtin' && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
|
||||
{t('configureAmsSlot.builtin')}
|
||||
</span>
|
||||
)}
|
||||
{preset.isUser && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
|
||||
{t('configureAmsSlot.custom')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
@@ -1334,16 +1340,21 @@ export function ConfigureAmsSlotModal({
|
||||
{t('profiles.localProfiles.badge')}
|
||||
</span>
|
||||
)}
|
||||
{preset.source === 'orca_cloud' && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-purple-500/20 text-purple-400">
|
||||
{t('configureAmsSlot.orcaCloud')}
|
||||
</span>
|
||||
)}
|
||||
{preset.source === 'cloud' && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
|
||||
{t('configureAmsSlot.bambuCloud')}
|
||||
</span>
|
||||
)}
|
||||
{preset.source === 'builtin' && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-amber-500/20 text-amber-400">
|
||||
{t('configureAmsSlot.builtin')}
|
||||
</span>
|
||||
)}
|
||||
{preset.isUser && (
|
||||
<span className="text-xs px-1.5 py-0.5 rounded bg-bambu-blue/20 text-bambu-blue">
|
||||
{t('configureAmsSlot.custom')}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
@@ -38,15 +38,14 @@ interface SliceModalProps {
|
||||
|
||||
type Slot = 'printer' | 'process' | 'filament';
|
||||
|
||||
// SliceModal-specific tier priority: orca_cloud → local → cloud → standard.
|
||||
// Imported (local) profiles are surfaced before Bambu Cloud because they're
|
||||
// metadata-tagged (Bambu Cloud isn't, by design — see
|
||||
// `_fetch_cloud_presets`'s rate-limit note). Orca Cloud comes first because
|
||||
// its sync_pull response inlines metadata too AND represents the user's
|
||||
// most-recently-curated source. Standard is the bundled fallback. This is
|
||||
// distinct from the listing endpoint's dedup order and only affects what
|
||||
// the SliceModal renders / pre-picks.
|
||||
const SLICE_MODAL_TIER_ORDER = ['orca_cloud', 'local', 'cloud', 'standard'] as const;
|
||||
// Lookup priority: local → orca_cloud → cloud → standard. Local imports
|
||||
// outrank everything else because the user explicitly imported them for
|
||||
// this install; Orca Cloud comes next; Bambu Cloud after that; standard
|
||||
// (bundled) is the final fallback. The backend does NOT dedup tiers —
|
||||
// every group renders its full set so the user can pick a same-named
|
||||
// preset from a lower-priority source if they want to override the
|
||||
// auto-pick.
|
||||
const SLICE_MODAL_TIER_ORDER = ['local', 'orca_cloud', 'cloud', 'standard'] as const;
|
||||
|
||||
function pickDefault(by: UnifiedPresetsResponse, slot: Slot): PresetRef | null {
|
||||
for (const tier of SLICE_MODAL_TIER_ORDER) {
|
||||
@@ -117,8 +116,8 @@ function pickProcessDefault(
|
||||
}
|
||||
|
||||
const TIER_BONUS: Record<PresetSource, number> = {
|
||||
orca_cloud: 1.75,
|
||||
local: 1.5,
|
||||
local: 1.75,
|
||||
orca_cloud: 1.5,
|
||||
cloud: 1.0,
|
||||
standard: 0.5,
|
||||
};
|
||||
@@ -999,7 +998,12 @@ function CloudStatusBanner({
|
||||
cloudName?: 'bambu' | 'orca';
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
if (status === 'ok') return null;
|
||||
// `ok` is the happy path. `not_authenticated` is silenced too: a user who
|
||||
// hasn't signed in (or has explicitly logged out — #1712) doesn't need a
|
||||
// permanent nag at the top of the modal; sign-in lives on the Profiles
|
||||
// page if they want it. Only `expired` and `unreachable` surface — those
|
||||
// are real breakage states a previously-signed-in user needs to see.
|
||||
if (status === 'ok' || status === 'not_authenticated') return null;
|
||||
|
||||
// Same status vocabulary for both Bambu and Orca Cloud — only the
|
||||
// user-facing text varies. The fallbacks below name each cloud explicitly
|
||||
@@ -1008,10 +1012,6 @@ function CloudStatusBanner({
|
||||
const messages =
|
||||
cloudName === 'orca'
|
||||
? {
|
||||
not_authenticated: {
|
||||
key: 'slice.orcaCloud.notAuthenticated',
|
||||
fallback: 'Sign in to Orca Cloud (Profiles → Orca Cloud) to see your Orca presets.',
|
||||
},
|
||||
expired: {
|
||||
key: 'slice.orcaCloud.expired',
|
||||
fallback: 'Orca Cloud session expired — sign in again to refresh your Orca presets.',
|
||||
@@ -1022,10 +1022,6 @@ function CloudStatusBanner({
|
||||
},
|
||||
}
|
||||
: {
|
||||
not_authenticated: {
|
||||
key: 'slice.cloud.notAuthenticated',
|
||||
fallback: 'Sign in to Bambu Cloud (Settings → Profiles → Cloud) to see your cloud presets.',
|
||||
},
|
||||
expired: {
|
||||
key: 'slice.cloud.expired',
|
||||
fallback: 'Bambu Cloud session expired — sign in again to refresh your cloud presets.',
|
||||
@@ -1036,11 +1032,7 @@ function CloudStatusBanner({
|
||||
},
|
||||
};
|
||||
|
||||
const tones: Record<Exclude<SlicerCloudStatus, 'ok'>, { tone: string; icon: typeof Cloud }> = {
|
||||
not_authenticated: {
|
||||
tone: 'border-bambu-dark-tertiary/40 bg-bambu-dark text-bambu-gray',
|
||||
icon: Cloud,
|
||||
},
|
||||
const tones: Record<'expired' | 'unreachable', { tone: string; icon: typeof Cloud }> = {
|
||||
expired: {
|
||||
tone: 'border-amber-700/40 bg-amber-900/20 text-amber-200',
|
||||
icon: CloudOff,
|
||||
@@ -1149,8 +1141,8 @@ function PresetDropdown({
|
||||
// empty sections collapse out.
|
||||
const { sections, otherEntries } = useMemo(() => {
|
||||
const tiers: { key: keyof UnifiedPresetsResponse; label: string; fallback: string }[] = [
|
||||
{ key: 'orca_cloud', label: 'slice.tier.orcaCloud', fallback: 'Orca Cloud' },
|
||||
{ key: 'local', label: 'slice.tier.local', fallback: 'Imported' },
|
||||
{ key: 'orca_cloud', label: 'slice.tier.orcaCloud', fallback: 'Orca Cloud' },
|
||||
{ key: 'cloud', label: 'slice.tier.cloud', fallback: 'Bambu Cloud' },
|
||||
{ key: 'standard', label: 'slice.tier.standard', fallback: 'Standard' },
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user