Browse Source

fix(file-manager): list-view actions clipped + preview modal slice button now respects Slicer API setting

  Two small bugs reported in chat:

  1. List-view actions column was clipped off the right edge. The grid
     template fixed the actions column at 80px - a sliced 3MF row renders
     7 icon buttons (Print, Schedule, Slice, 3D Preview, Download, Rename,
     Delete) which need ~220px. The wrapper's overflow-hidden (there for
     rounded corners) then swallowed everything past column 80px.

     Switched the trailing column from 80px to min-content so it sizes to
     the widest action strip across all rows, and replaced overflow-hidden
     with overflow-x-auto so narrow viewports get a scrollbar instead of
     vanished icons. Rounded corners survive because the border-radius is
     on the wrapper itself, not on the overflow box.

  2. The 3MF preview modal's "Open in Slicer" button always launched the
     external slicer (BambuStudio / Orca) regardless of Settings -> Workflow
     -> Slicer -> Use Slicer API. With the API enabled, the file-row Cog
     already opens the in-app Bambuddy SliceModal - the preview-modal
     button should match.

     Added an optional onSliceWithBambuddy callback to ModelViewerModal.
     When set AND settings.use_slicer_api is true AND we're in library mode
     on a sliceable type (.3mf/.stl/.step/.stp), the header button becomes
     a Cog labelled "Slice" and calls the in-app handler. Otherwise it
     falls back to the existing ExternalLink "Open in Slicer" button.
     FileManagerPage wires the callback to close the viewer and open the
     SliceModal with the same file, gated on the same isSliceableFilename
     + library:upload permission checks the row's Cog already uses. No new
     i18n keys - reuses slice.action ("Slice") that the file row already
     ships in all 9 locales.
maziggy 4 days ago
parent
commit
5f473801f8

+ 32 - 6
frontend/src/components/ModelViewerModal.tsx

@@ -1,7 +1,7 @@
 import { useState, useEffect, useRef, useMemo } from 'react';
 import { useTranslation } from 'react-i18next';
 import { useQuery } from '@tanstack/react-query';
-import { X, ExternalLink, Box, Code2, Loader2, Layers, Check, Maximize2, Minimize2 } from 'lucide-react';
+import { X, ExternalLink, Box, Code2, Cog, Loader2, Layers, Check, Maximize2, Minimize2 } from 'lucide-react';
 import { ModelViewer } from './ModelViewer';
 import { GcodeViewer } from './GcodeViewer';
 import { Button } from './Button';
@@ -17,6 +17,11 @@ interface ModelViewerModalProps {
   title: string;
   fileType?: string;
   onClose: () => void;
+  // When set and `settings.use_slicer_api` is on, the header's slicer button
+  // becomes "Slice" and calls this instead of opening BambuStudio / Orca
+  // externally — so the preview modal's slice action matches the file row's
+  // Cog (in-app Bambuddy SliceModal) when the slicer API is enabled.
+  onSliceWithBambuddy?: () => void;
 }
 
 interface Capabilities {
@@ -27,7 +32,7 @@ interface Capabilities {
   filament_colors: string[];
 }
 
-export function ModelViewerModal({ archiveId, libraryFileId, title, fileType, onClose }: ModelViewerModalProps) {
+export function ModelViewerModal({ archiveId, libraryFileId, title, fileType, onClose, onSliceWithBambuddy }: ModelViewerModalProps) {
   const { t } = useTranslation();
   const { data: settings } = useQuery({ queryKey: ['settings'], queryFn: api.getSettings });
   const preferredSlicer: SlicerType = settings?.preferred_slicer || 'bambu_studio';
@@ -263,6 +268,20 @@ export function ModelViewerModal({ archiveId, libraryFileId, title, fileType, on
 
   const canOpenInSlicer = isLibrary ? (fileType || '').toLowerCase() === '3mf' : true;
 
+  // When the user has the in-app Slicer API enabled (Settings → Workflow →
+  // Slicer → Use Slicer API), library-mode previews route the header's slicer
+  // button into Bambuddy's own SliceModal — same behaviour as the Cog button
+  // in the file-row actions. Falls back to the external-slicer launcher when
+  // the API is off, when no in-app handler is wired (e.g. archive preview),
+  // or when the file type can't be sliced (.gcode / .gcode.3mf, etc.).
+  const sliceableType = (() => {
+    const t = (fileType || '').toLowerCase();
+    return t === '3mf' || t === 'stl' || t === 'step' || t === 'stp';
+  })();
+  const useBambuddySlicer = Boolean(
+    isLibrary && settings?.use_slicer_api && onSliceWithBambuddy && sliceableType,
+  );
+
   const handleOpenInSlicer = async () => {
     if (!canOpenInSlicer) return;
     const filename = title || 'model';
@@ -310,10 +329,17 @@ export function ModelViewerModal({ archiveId, libraryFileId, title, fileType, on
             )}
           </div>
           <div className="flex items-center gap-2">
-            <Button variant="secondary" size="sm" onClick={handleOpenInSlicer} disabled={!canOpenInSlicer}>
-              <ExternalLink className="w-4 h-4" />
-              {t('modelViewer.openInSlicer')}
-            </Button>
+            {useBambuddySlicer ? (
+              <Button variant="secondary" size="sm" onClick={onSliceWithBambuddy}>
+                <Cog className="w-4 h-4" />
+                {t('slice.action')}
+              </Button>
+            ) : (
+              <Button variant="secondary" size="sm" onClick={handleOpenInSlicer} disabled={!canOpenInSlicer}>
+                <ExternalLink className="w-4 h-4" />
+                {t('modelViewer.openInSlicer')}
+              </Button>
+            )}
             <Button
               variant="secondary"
               size="sm"

+ 22 - 4
frontend/src/pages/FileManagerPage.tsx

@@ -2001,9 +2001,16 @@ export function FileManagerPage() {
             </div>
           ) : (
             <div className="flex-1 lg:overflow-y-auto">
-              <div className="bg-bambu-dark-secondary rounded-lg border border-bambu-dark-tertiary overflow-hidden">
-                {/* List header - hidden on mobile, show simplified on small screens */}
-                <div className={`hidden sm:grid ${authEnabled ? 'grid-cols-[auto_1fr_120px_100px_100px_100px_80px]' : 'grid-cols-[auto_1fr_100px_100px_100px_80px]'} gap-4 px-4 py-2 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary text-xs text-bambu-gray font-medium`}>
+              {/* The wrapper has overflow-x-auto so a narrow viewport scrolls
+                  horizontally instead of clipping the actions column off the
+                  right edge. The previous `overflow-hidden` was there for the
+                  rounded corners but also swallowed any content the actions
+                  column couldn't fit (#1325 follow-up reported in chat). */}
+              <div className="bg-bambu-dark-secondary rounded-lg border border-bambu-dark-tertiary overflow-x-auto">
+                {/* List header - hidden on mobile, show simplified on small screens.
+                    The trailing column is `min-content` so it sizes to the widest
+                    action-icon strip across all rows (sliced 3MF = 7 icons ~220px). */}
+                <div className={`hidden sm:grid ${authEnabled ? 'grid-cols-[auto_1fr_120px_100px_100px_100px_min-content]' : 'grid-cols-[auto_1fr_100px_100px_100px_min-content]'} gap-4 px-4 py-2 bg-bambu-dark-secondary border-b border-bambu-dark-tertiary text-xs text-bambu-gray font-medium`}>
                   <div className="w-6" />
                   <div>{t('common.name')}</div>
                   {authEnabled && <div>{t('fileManager.uploadedBy', { defaultValue: 'Uploaded By' })}</div>}
@@ -2016,7 +2023,7 @@ export function FileManagerPage() {
                 {filteredAndSortedFiles.map((file) => (
                   <div
                     key={file.id}
-                    className={`grid ${authEnabled ? 'grid-cols-[auto_1fr_120px_100px_100px_100px_80px]' : 'grid-cols-[auto_1fr_100px_100px_100px_80px]'} gap-4 px-4 py-3 items-center border-b border-bambu-dark-tertiary last:border-b-0 cursor-pointer hover:bg-bambu-dark/50 transition-colors ${
+                    className={`grid ${authEnabled ? 'grid-cols-[auto_1fr_120px_100px_100px_100px_min-content]' : 'grid-cols-[auto_1fr_100px_100px_100px_min-content]'} gap-4 px-4 py-3 items-center border-b border-bambu-dark-tertiary last:border-b-0 cursor-pointer hover:bg-bambu-dark/50 transition-colors ${
                       selectedFiles.includes(file.id) ? 'bg-bambu-green/10' : ''
                     }`}
                     onClick={() => handleFileSelect(file.id)}
@@ -2355,6 +2362,17 @@ export function FileManagerPage() {
           title={viewerFile.print_name || viewerFile.filename}
           fileType={viewerFile.file_type}
           onClose={() => setViewerFile(null)}
+          onSliceWithBambuddy={
+            // Only offer in-app slicing on files the SliceModal can actually
+            // handle (matches the file-row Cog visibility check at :2127).
+            isSliceableFilename(viewerFile.filename) && hasPermission('library:upload')
+              ? () => {
+                  const f = viewerFile;
+                  setViewerFile(null);
+                  setSliceFile(f);
+                }
+              : undefined
+          }
         />
       )}
 

File diff suppressed because it is too large
+ 0 - 0
static/assets/index-BzucE4G0.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-HqxudbTf.css


File diff suppressed because it is too large
+ 0 - 0
static/assets/index-IAFhhAbT.js


+ 2 - 2
static/index.html

@@ -26,8 +26,8 @@
 
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-CUxNjqOx.js"></script>
-    <link rel="stylesheet" crossorigin href="/assets/index-BzucE4G0.css">
+    <script type="module" crossorigin src="/assets/index-IAFhhAbT.js"></script>
+    <link rel="stylesheet" crossorigin href="/assets/index-HqxudbTf.css">
   </head>
   <body>
     <div id="root"></div>

Some files were not shown because too many files changed in this diff