mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-09-30 03:01:21 +02:00
Merge branch 'worktree-fix-2791-cursor-pointer' into dev
This commit is contained in:
+1
-1
@@ -15,11 +15,11 @@ All notable changes to Bambuddy will be documented in this file.
|
||||
### Changed
|
||||
- **The MQTT debug log now records the commands sent to a printer, not only what it reports back** — **Printer → Debug → MQTT** captured one side of the conversation. Bambuddy listens on both of a printer's topics, but the one carrying commands returned before anything was written to the log, so a capture could show every status push the printer made and nothing it was ever told — including the commands Bambu Studio sends over the local network, which is the only place they can be observed at all. Those now appear alongside Bambuddy's own, grouped under the outgoing filter. It is what lets a question like "which value does Studio put in this field?" be answered from a user's capture instead of guessed at, and it is why #2774 could not be taken further. Commands Bambuddy sends appear twice, once as it publishes and once as the broker echoes it back, and the pair is itself evidence the command reached the broker. Logging is off until switched on, as before. Covered by backend tests.
|
||||
- **The L and XL printer cards now scale their text and icons, not just their width (#1848, reporter @misterff1)** — Switching a card from M to XL made it wider, enlarged the printer name and the thumbnail, and left everything else exactly as it was: the AMS slot labels, temperatures, filament names, status text and every small button stayed pinned between 8 and 11 pixels, well under the smallest size used anywhere else in Bambuddy. The result was a full-width card carrying the same tiny text as the compact one, which is precisely the opposite of what someone reaching for a bigger card is asking for. Browser zoom is not an answer to this, since it enlarges the entire page and so preserves the very disparity being complained about. The card body now scales along with the card: L draws it 20% larger and XL 40% larger, icons included, so the controls grow with the text rather than staying fiddly to hit. The AMS-HT card needed two adjustments of its own, since its temperature and humidity readings sit beside the slot rather than under it. Its single slot was the only thing on that row able to grow, so it swallowed every spare pixel and pushed the readings hard against the card's edge — it is now capped at roughly two ordinary slots, which keeps them clear at any card width. The card itself also gained a ceiling of one full AMS card's width, so a unit that wraps onto a line of its own no longer stretches that single slot across the whole card. S and M are deliberately untouched — S is the dense fleet view where density is the point, and M is the default, so an existing install looks identical until you reach for a size that is already asking for more room. Wiki updated. Covered by frontend tests.
|
||||
|
||||
- **Error and warning toasts now stay up twice as long** — Every pop-up notification disappeared after three seconds regardless of what it said. That is about right for "Settings saved", which confirms something you just did and is skimmed rather than read, but errors and warnings are a different kind of message: they carry a reason, often one relayed from the printer or the backend, and they run to a couple of lines. Three seconds was not long enough to finish reading one, and a missed error message is gone for good — there is no notification history to go back to. Errors and warnings now hold for six seconds. Success and informational toasts keep the three-second default, so the common case of clicking something and seeing it confirmed is unchanged, and the close button and the manual dismiss work exactly as before on all of them. The background print-dispatch toast is unaffected: it stays up while it has work in progress and clears itself shortly after the last job settles. Covered by frontend tests.
|
||||
|
||||
### Fixed
|
||||
- **"We need you to confirm you are not a robot" on Bambu Cloud sign-in is now explained instead of just repeated (#2790)** — A reporter tried to connect to Bambu Cloud and got that sentence as an error toast, with no CAPTCHA anywhere to answer and nothing to click. It is Bambu's sentence, not Bambuddy's: their anti-abuse layer had flagged the network and was answering the sign-in with `HTTP 418` and a challenge body. Bambuddy had no idea what that was — the reply is well-formed JSON, so the existing Cloudflare-interstitial detector never fired on it, and the generic error path simply lifted Bambu's text out and showed it. The user was left to conclude their password was wrong, or that Bambuddy was broken; four sign-in attempts inside eighteen seconds appear in their log, each one more evidence for the thing that had flagged them. Bambuddy now recognises the challenge by its shape rather than by its wording, and the login form says what is actually happening: your email and password are not the problem, the block is tied to your public IP address rather than to your account, it normally clears by itself within a few hours, and retrying repeatedly extends it. The panel stays on screen — a toast is the wrong shape for a problem you cannot act on — and carries a one-click route to **Use access token instead**, which is the one way to connect while it lasts, since the token path does not go through the challenged endpoint. Sign-in requests are held back for five minutes after a challenge so Bambuddy stops making it worse, tracked per region and per host so a challenge on the API host cannot strand somebody halfway through a two-factor sign-in on the web one. MakerWorld imports, which meet the same challenge from the same edge, now share the detection instead of requiring the literal word "robot" in the error text. The System Health scanner has a matching signature, so the next support bundle from an affected install names the problem instead of coming back empty. The scanner's advice for a failed FTPS handshake was corrected at the same time: it still blamed firewalls and firmware, which last release's investigation (#2780) ruled out — it is the printer's own file service wedging, and the fix is to restart the printer.
|
||||
- **Buttons show a pointer cursor again, and the AMS slot menu stops reshuffling itself (#2791, reported by @AnthonyGrondin)** — Hovering most of Bambuddy gave you an arrow, not the little hand that says "this does something". Not everywhere, though, which is what made it read as sloppiness rather than a bug: the update pill was inert while the buttons beside it were fine, a bed or nozzle tile responded but the history-graph button tucked into its corner did not, and dropdowns went either way with no pattern behind it. The pattern was there. Tailwind v3 gave every button a pointer cursor as part of its baseline styling; Tailwind v4, which Bambuddy has used since the interface was built, deliberately dropped that rule to match what browsers do on their own — and browsers give a button the ordinary arrow. From then on a button only looked clickable if whoever wrote it had said so by hand. Fifteen of about nine hundred and thirty had. None of the hundred and forty-nine dropdowns had, and of the checkboxes and radio buttons, nineteen out of a hundred and thirty. The rule is now restored once, centrally, rather than pinned onto individual buttons for the rest of the project's life: buttons, dropdowns, checkboxes, radio buttons, disclosure arrows and anything explicitly marked up as a button all point again. It sits at the bottom of the styling order, so the places that deliberately show a "not allowed" cursor on a disabled control still win, and a control that is genuinely disabled is left alone. Modal backgrounds are deliberately untouched: clicking one closes the dialog, but a full-screen sheet that claims to be a button is worse than one that says nothing. Separately, and behind the same report: the menu on an AMS slot listed **Configure** above **Assign Spool** on an empty slot and the other way round on a filled one, because the two are drawn by different code that had quietly drifted apart — both now lead with the spool action, and a test pins each side so they cannot drift again. The buttons in that menu centred their own text, which left their icons in a ragged column; they are aligned to the left edge now. Their hover shading was a ten-percent step that was very hard to see, and is now twice that. And the star on **Add to favourites** turns yellow as you hover it, so it previews what clicking will do.
|
||||
- **A job queued to "Any {model}" now switches a printer on, like a job queued to one printer always has (#2786, reported by @TheUltimateC0der)** — Queue a print against a printer class -- **Any X1C**, or a Slicer Pipeline whose target type is **Printer class** -- with every printer of that class switched off at the wall, and nothing happened. The job sat pending, no smart plug was touched, and the only way out was to edit the item onto a specific printer, at which point Bambuddy powered that printer on immediately. The reporter's log holds that comparison exactly: thirteen minutes of the job being polled and passed over, then the edit, then a power-on on the very next check -- same job, same plug, same Auto Power On setting. Powering a printer on had only ever been written into the branch that handles a job pinned to one printer; the branch that picks a printer by model listed an offline one as a reason to keep waiting and never looked at its plugs. It does now. It also picks with a little more care than the older branch: a printer waiting for a plate-clear acknowledgment is passed over, because switching it on only leaves it idling behind that gate -- which is what the reporter's own log shows happening for the eighty minutes after their manual edit -- and a printer whose class the file cannot legally run on is never switched on at all. One printer comes up per queue check rather than a whole shelf at once, so several queued jobs wake several printers over the following minutes. Finally, a printer that is off and has no enabled Auto Power On plug now says that in the job's waiting reason instead of hiding behind the same "Offline" as the printers Bambuddy can bring back itself -- that distinction was the first question the reporter had to be asked.
|
||||
- **A printer whose file service stops answering is named as such instead of quietly emptying your archives (#2780, reported by @Utility9298 and @AntonPalmqvist)** — Two printers went on printing normally while every archive they produced arrived holding nothing but a filename: no filament totals, no layer count, no cover image, no timelapse. The Connection Diagnostic reported the file-transfer port as reachable, because it was — the printer accepted the connection and then answered it with something that was not TLS at all, and the sliced file could never be read back. Bambuddy said nothing about that on screen; it retried. Because each candidate location opened its own connection, one reporter's log carried 1813 identical handshake failures and another's 3511, against a printer that could not have answered any of them. This is not a model or a firmware problem — the same printers worked for days before and after the fault, and other installs run the same models untouched. It is the printer's own file service getting stuck, and a power-cycle clears it. Bambuddy now stops after the first failed handshake and leaves that printer alone for five minutes, so the log carries a handful of entries that say what went wrong and what to do about it rather than thousands that say neither. The Connection Diagnostic now completes a real handshake instead of merely opening the port, so a printer in this state reads as a warning that names a restart — not as a green tick. Scanning for a timelapse on such a printer reports the file service, where it used to return one error message that covered both "the printer is unreachable" and "this printer has no timelapse folder", and asking for a cover image says the same thing instead of the "no cover for this print" it used to claim. Printing is unaffected throughout: the control connection is a separate service, which is exactly why the fault was invisible.
|
||||
- **Prints queued from a Slicer Pipeline or the Library are checked for enough filament again (#2779, reported by @wylyn3d)** — A job needing 20.5 g was dispatched onto a spool holding 9 g, and the printer started. The same file, printed from the Print dialog, was correctly refused. The check that stands between the queue and the printer reads the sliced file to learn how much each slot needs, and it looked for that file in the wrong place: a file in the Library records where it lives relative to Bambuddy's data directory, and this one check read that as a path from wherever the process happened to be running. It found nothing, and a source it cannot find has always meant "nothing to verify" rather than "stop" — so the job passed a check that never actually ran. Every path that queues a Library file was affected: Slicer Pipeline jobs, which are always Library-backed, and anything added through the Library's **Add to queue**. Both the automatic dispatcher and the Play button on the queue were equally blind, so the deficit could not be caught by starting the job by hand either. Prints queued from print history were never affected, and neither was the Print dialog, which finds the file its own way. Two things changed: the check now resolves a Library file the same way the eleven other places that read one already did, and a source file that is configured but missing now writes a warning to the log naming the item and the path it looked at. That case still dispatches — the upload needs the same file moments later and fails there, where blocking would strand a queue on a file the user may have moved — but it no longer passes in silence, which is what let this go unnoticed. Covered by backend tests, including the reporter's exact 20.5 g against 9 g.
|
||||
|
||||
@@ -152,6 +152,28 @@ describe('FilamentHoverCard', () => {
|
||||
// removed that gate so users who don't want to scan via SpoolBuddy NFC
|
||||
// can still pick a BL spool from inventory the same way they pick a
|
||||
// third-party one.
|
||||
// Paired with the EmptySlotHoverCard assertion below (#2791) — together
|
||||
// they pin the two render paths to the same Assign-then-Configure order.
|
||||
it('lists Assign Spool above Configure (#2791)', async () => {
|
||||
renderWithHover(
|
||||
<FilamentHoverCard
|
||||
data={baseFilamentData}
|
||||
inventory={{ assignedSpool: null, onAssignSpool: vi.fn() }}
|
||||
configureSlot={{ enabled: true, onConfigure: vi.fn() }}
|
||||
>
|
||||
<div>trigger</div>
|
||||
</FilamentHoverCard>
|
||||
);
|
||||
vi.advanceTimersByTime(100);
|
||||
await waitFor(() => expect(screen.getByText(/assign spool/i)).toBeInTheDocument());
|
||||
|
||||
const assign = screen.getByText(/assign spool/i);
|
||||
const configure = screen.getByText(/^configure$/i);
|
||||
expect(assign.compareDocumentPosition(configure)).toBe(
|
||||
Node.DOCUMENT_POSITION_FOLLOWING
|
||||
);
|
||||
});
|
||||
|
||||
describe('inventory section vendor visibility (#1133)', () => {
|
||||
it('shows the assign-spool button on a Bambu Lab slot when the spool is unassigned', async () => {
|
||||
const onAssign = vi.fn();
|
||||
@@ -485,6 +507,31 @@ describe('EmptySlotHoverCard (#1133)', () => {
|
||||
expect(onAssign).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
// #2791: the empty-slot and filled-slot cards are separate render paths
|
||||
// that had drifted into opposite orders, so the menu reshuffled itself
|
||||
// depending on whether the slot happened to hold filament. Both now put
|
||||
// the spool action above the slot action; assert it on both paths so the
|
||||
// two can't drift apart again.
|
||||
it('lists Assign Spool above Configure, matching the filled-slot card (#2791)', async () => {
|
||||
const result = render(
|
||||
<EmptySlotHoverCard
|
||||
configureSlot={{ enabled: true, onConfigure: vi.fn() }}
|
||||
onAssignSpool={vi.fn()}
|
||||
>
|
||||
<div>trigger</div>
|
||||
</EmptySlotHoverCard>
|
||||
);
|
||||
fireEvent.mouseEnter(result.container.firstElementChild as HTMLElement);
|
||||
vi.advanceTimersByTime(100);
|
||||
await waitFor(() => expect(screen.getByText(/assign spool/i)).toBeInTheDocument());
|
||||
|
||||
const assign = screen.getByText(/assign spool/i);
|
||||
const configure = screen.getByText(/^configure$/i);
|
||||
expect(assign.compareDocumentPosition(configure)).toBe(
|
||||
Node.DOCUMENT_POSITION_FOLLOWING
|
||||
);
|
||||
});
|
||||
|
||||
// Same z-[60]-over-a-z-50-dialog problem as FilamentHoverCard (#2631).
|
||||
describe('dismissal when an action opens a dialog (#2631)', () => {
|
||||
it('closes the card when Configure is pressed, and still configures', async () => {
|
||||
|
||||
@@ -25,7 +25,7 @@ interface CardSectionProps {
|
||||
export function Card({ children, className = '', onClick, onContextMenu, ...rest }: CardProps) {
|
||||
return (
|
||||
<div
|
||||
className={`bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary card-shadow ${className}`}
|
||||
className={`bg-bambu-dark-secondary rounded-xl border border-bambu-dark-tertiary card-shadow ${onClick ? 'cursor-pointer' : ''} ${className}`}
|
||||
onClick={onClick}
|
||||
onContextMenu={onContextMenu}
|
||||
{...rest}
|
||||
|
||||
@@ -265,7 +265,7 @@ export function ContextMenu({ x, y, items, onClose }: ContextMenuProps) {
|
||||
}}
|
||||
disabled={item.disabled}
|
||||
title={item.title}
|
||||
className={`w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors ${
|
||||
className={`group w-full flex items-center gap-2 px-3 py-2 text-sm text-left transition-colors ${
|
||||
item.disabled
|
||||
? 'text-bambu-gray cursor-not-allowed'
|
||||
: item.danger
|
||||
|
||||
@@ -344,7 +344,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
dismiss();
|
||||
navigate(`/inventory?spool=${spoolman.linkedSpoolId}`);
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-green/20 hover:bg-bambu-green/30 text-bambu-green"
|
||||
className="w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-green/20 hover:bg-bambu-green/40 text-bambu-green"
|
||||
title={t('inventory.openInInventory')}
|
||||
>
|
||||
<Package className="w-3.5 h-3.5" />
|
||||
@@ -397,7 +397,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
dismiss();
|
||||
navigate(`/inventory?spool=${inventory.assignedSpool!.id}`);
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-green/20 hover:bg-bambu-green/30 text-bambu-green"
|
||||
className="w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-green/20 hover:bg-bambu-green/40 text-bambu-green"
|
||||
title={t('inventory.openInInventory')}
|
||||
>
|
||||
<Package className="w-3.5 h-3.5" />
|
||||
@@ -411,7 +411,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
dismiss();
|
||||
inventory.onUnassignSpool?.();
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-red-100 dark:bg-red-500/20 hover:bg-red-200 dark:hover:bg-red-500/30 text-red-700 dark:text-red-400"
|
||||
className="w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-red-100 dark:bg-red-500/20 hover:bg-red-200 dark:hover:bg-red-500/40 text-red-700 dark:text-red-400"
|
||||
>
|
||||
<Unlink className="w-3.5 h-3.5" />
|
||||
{t('inventory.unassignSpool')}
|
||||
@@ -426,8 +426,8 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
inventory.onAssignSpool?.();
|
||||
}}
|
||||
disabled={!!inventory.isAssigned}
|
||||
className={`w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 text-bambu-blue ${
|
||||
inventory.isAssigned ? 'opacity-50 cursor-not-allowed' : 'hover:bg-bambu-blue/30'
|
||||
className={`w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 text-bambu-blue ${
|
||||
inventory.isAssigned ? 'opacity-50 cursor-not-allowed' : 'hover:bg-bambu-blue/40'
|
||||
}`}
|
||||
>
|
||||
<Package className="w-3.5 h-3.5" />
|
||||
@@ -446,7 +446,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
dismiss();
|
||||
configureSlot.onConfigure?.();
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 hover:bg-bambu-blue/30 text-bambu-blue"
|
||||
className="w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 hover:bg-bambu-blue/40 text-bambu-blue"
|
||||
title={t('ams.configureSlot')}
|
||||
>
|
||||
<Settings2 className="w-3.5 h-3.5" />
|
||||
@@ -506,7 +506,7 @@ export function FilamentHoverCard({ data, children, disabled, className = '', sp
|
||||
spoolman?.onUnlinkSpool?.();
|
||||
setShowUnlinkConfirm(false);
|
||||
}}
|
||||
className="flex-1 px-3 py-2 text-sm font-medium rounded transition-colors bg-red-100 dark:bg-red-500/20 hover:bg-red-200 dark:hover:bg-red-500/30 text-red-700 dark:text-red-400"
|
||||
className="flex-1 px-3 py-2 text-sm font-medium rounded transition-colors bg-red-100 dark:bg-red-500/20 hover:bg-red-200 dark:hover:bg-red-500/40 text-red-700 dark:text-red-400"
|
||||
>
|
||||
{t('inventory.unassignSpool')}
|
||||
</button>
|
||||
@@ -622,6 +622,20 @@ export function EmptySlotHoverCard({ children, className = '', configureSlot, on
|
||||
{/* Configure slot button */}
|
||||
{(configureSlot?.enabled || onAssignSpool || actions) && (
|
||||
<div className="px-2 pb-2 space-y-1">
|
||||
{/* Assign before Configure, matching the filled-slot card
|
||||
above (#2791). The two cards are separate render paths
|
||||
and had drifted into opposite orders, so the menu
|
||||
reshuffled itself depending on whether the slot happened
|
||||
to hold filament. */}
|
||||
{onAssignSpool && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); dismiss(); onAssignSpool(); }}
|
||||
className="w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 hover:bg-bambu-blue/40 text-bambu-blue"
|
||||
>
|
||||
<Package className="w-3.5 h-3.5" />
|
||||
{t('inventory.assignSpool')}
|
||||
</button>
|
||||
)}
|
||||
{configureSlot?.enabled && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
@@ -629,22 +643,13 @@ export function EmptySlotHoverCard({ children, className = '', configureSlot, on
|
||||
dismiss();
|
||||
configureSlot.onConfigure?.();
|
||||
}}
|
||||
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 hover:bg-bambu-blue/30 text-bambu-blue"
|
||||
className="w-full flex items-center justify-start gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 hover:bg-bambu-blue/40 text-bambu-blue"
|
||||
title={t('ams.configureSlot')}
|
||||
>
|
||||
<Settings2 className="w-3.5 h-3.5" />
|
||||
{t('ams.configure')}
|
||||
</button>
|
||||
)}
|
||||
{onAssignSpool && (
|
||||
<button
|
||||
onClick={(e) => { e.stopPropagation(); dismiss(); onAssignSpool(); }}
|
||||
className="w-full flex items-center justify-center gap-1.5 px-2 py-1.5 text-xs font-medium rounded transition-colors bg-bambu-blue/20 hover:bg-bambu-blue/30 text-bambu-blue"
|
||||
>
|
||||
<Package className="w-3.5 h-3.5" />
|
||||
{t('inventory.assignSpool')}
|
||||
</button>
|
||||
)}
|
||||
{actions && (
|
||||
<div className="pt-1 mt-1 border-t border-bambu-dark-tertiary space-y-1">
|
||||
{actions}
|
||||
|
||||
@@ -26,6 +26,28 @@
|
||||
/* Enable class-based dark mode for Tailwind v4 */
|
||||
@custom-variant dark (&:where(.dark, .dark *));
|
||||
|
||||
/* Restore the pointer cursor on interactive controls (#2791). Tailwind v3's
|
||||
Preflight set `button { cursor: pointer }`; v4 dropped it to match the
|
||||
browser default of `cursor: default`, so every button in the app looked
|
||||
unclickable unless someone remembered to add `cursor-pointer` by hand.
|
||||
Only a handful of the ~930 buttons did, which is why the UI felt
|
||||
inconsistent rather than uniformly wrong.
|
||||
|
||||
This lives in `base`, the lowest of Tailwind's cascade layers, so the
|
||||
`cursor-not-allowed` / `disabled:cursor-*` utilities dotted around the
|
||||
codebase still win. The `:not(:disabled)` guard covers the elements that
|
||||
are disabled without also carrying such a utility. */
|
||||
@layer base {
|
||||
button:not(:disabled),
|
||||
select:not(:disabled),
|
||||
summary,
|
||||
input[type="checkbox"]:not(:disabled),
|
||||
input[type="radio"]:not(:disabled),
|
||||
[role="button"]:not([aria-disabled="true"]) {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@theme {
|
||||
/* Accent colors - use CSS variables for theming */
|
||||
--color-bambu-green: var(--accent);
|
||||
|
||||
@@ -740,7 +740,8 @@ function ArchiveCard({
|
||||
{ label: '', divider: true, onClick: () => {} },
|
||||
{
|
||||
label: archive.is_favorite ? t('archives.menu.removeFromFavorites') : t('archives.menu.addToFavorites'),
|
||||
icon: <Star className={`w-4 h-4 ${archive.is_favorite ? 'fill-yellow-400 text-yellow-400' : ''}`} />,
|
||||
// Preview the favourited state on hover so the row reads as clickable (#2791).
|
||||
icon: <Star className={`w-4 h-4 ${archive.is_favorite ? 'fill-yellow-400 text-yellow-400' : canModify('archives', 'update', archive.created_by_id) ? 'group-hover:text-yellow-400' : ''}`} />,
|
||||
onClick: () => favoriteMutation.mutate(),
|
||||
disabled: !canModify('archives', 'update', archive.created_by_id),
|
||||
title: !canModify('archives', 'update', archive.created_by_id) ? t('archives.permission.noUpdateArchives') : undefined,
|
||||
@@ -2138,7 +2139,8 @@ function ArchiveListRow({
|
||||
{ label: '', divider: true, onClick: () => {} },
|
||||
{
|
||||
label: archive.is_favorite ? t('archives.menu.removeFromFavorites') : t('archives.menu.addToFavorites'),
|
||||
icon: <Star className={`w-4 h-4 ${archive.is_favorite ? 'fill-yellow-400 text-yellow-400' : ''}`} />,
|
||||
// Preview the favourited state on hover so the row reads as clickable (#2791).
|
||||
icon: <Star className={`w-4 h-4 ${archive.is_favorite ? 'fill-yellow-400 text-yellow-400' : canModify('archives', 'update', archive.created_by_id) ? 'group-hover:text-yellow-400' : ''}`} />,
|
||||
onClick: () => favoriteMutation.mutate(),
|
||||
disabled: !canModify('archives', 'update', archive.created_by_id),
|
||||
title: !canModify('archives', 'update', archive.created_by_id) ? t('archives.permission.noUpdateArchives') : undefined,
|
||||
|
||||
@@ -466,7 +466,7 @@ function SortableQueueItem({
|
||||
${isPrinting ? 'border-blue-500/30 bg-gradient-to-r from-blue-500/5 to-transparent' : ''}
|
||||
${isSelected && isMobileSelectable ? 'sm:border-bambu-dark-tertiary border-bambu-green/40' : ''}
|
||||
${!isSelected && !isPrinting ? 'border-bambu-dark-tertiary hover:border-bambu-dark-tertiary/80' : ''}
|
||||
${isMobileSelectable ? 'sm:cursor-default' : ''}
|
||||
${isMobileSelectable ? 'cursor-pointer sm:cursor-default' : ''}
|
||||
`}
|
||||
onClick={isMobileSelectable ? () => {
|
||||
if (window.innerWidth < 640) onToggleSelect();
|
||||
|
||||
Reference in New Issue
Block a user