Просмотр исходного кода

- All archive page filters are now persitent

maziggy 8 месяцев назад
Родитель
Сommit
a008be96ee
3 измененных файлов с 188 добавлено и 56 удалено
  1. 187 55
      frontend/src/pages/ArchivesPage.tsx
  2. 0 0
      static/assets/index-303ohz8i.js
  3. 1 1
      static/index.html

+ 187 - 55
frontend/src/pages/ArchivesPage.tsx

@@ -210,26 +210,42 @@ function ArchiveCard({
     setContextMenu({ x: e.clientX, y: e.clientY });
     setContextMenu({ x: e.clientX, y: e.clientY });
   };
   };
 
 
+  const isGcodeFile = archive.filename?.toLowerCase().includes('.gcode.');
+
   const contextMenuItems: ContextMenuItem[] = [
   const contextMenuItems: ContextMenuItem[] = [
-    {
-      label: 'Print',
-      icon: <Printer className="w-4 h-4" />,
-      onClick: () => setShowReprint(true),
-    },
-    {
-      label: 'Schedule',
-      icon: <Calendar className="w-4 h-4" />,
-      onClick: () => setShowSchedule(true),
-    },
-    {
-      label: 'Open in Bambu Studio',
-      icon: <ExternalLink className="w-4 h-4" />,
-      onClick: () => {
-        const filename = archive.print_name || archive.filename || 'model';
-        const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
-        window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
+    // For gcode files: show Print option
+    // For source files: show Slice as the primary action
+    ...(isGcodeFile ? [
+      {
+        label: 'Print',
+        icon: <Printer className="w-4 h-4" />,
+        onClick: () => setShowReprint(true),
       },
       },
-    },
+      {
+        label: 'Schedule',
+        icon: <Calendar className="w-4 h-4" />,
+        onClick: () => setShowSchedule(true),
+      },
+      {
+        label: 'Open in Bambu Studio',
+        icon: <ExternalLink className="w-4 h-4" />,
+        onClick: () => {
+          const filename = archive.print_name || archive.filename || 'model';
+          const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
+          window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
+        },
+      },
+    ] : [
+      {
+        label: 'Slice',
+        icon: <ExternalLink className="w-4 h-4" />,
+        onClick: () => {
+          const filename = archive.print_name || archive.filename || 'model';
+          const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
+          window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
+        },
+      },
+    ]),
     {
     {
       label: 'View on MakerWorld',
       label: 'View on MakerWorld',
       icon: <Globe className="w-4 h-4" />,
       icon: <Globe className="w-4 h-4" />,
@@ -648,29 +664,49 @@ function ArchiveCard({
 
 
         {/* Actions */}
         {/* Actions */}
         <div className="flex gap-1 mt-3">
         <div className="flex gap-1 mt-3">
-          <Button
-            variant="primary"
-            size="sm"
-            className="flex-1 min-w-0"
-            onClick={() => setShowReprint(true)}
-          >
-            <Printer className="w-3 h-3 flex-shrink-0" />
-            <span className="hidden sm:inline">Print</span>
-          </Button>
-          <Button
-            variant="secondary"
-            size="sm"
-            className="min-w-0 p-1 sm:p-1.5"
-            onClick={() => {
-              // Use bambustudioopen:// protocol like MakerWorld does
-              const filename = archive.print_name || archive.filename || 'model';
-              const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
-              window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
-            }}
-            title="Open in Bambu Studio"
-          >
-            <ExternalLink className="w-3 h-3 sm:w-4 sm:h-4" />
-          </Button>
+          {archive.filename?.toLowerCase().includes('.gcode.') ? (
+            // Sliced file - can print directly
+            <>
+              <Button
+                variant="primary"
+                size="sm"
+                className="flex-1 min-w-0"
+                onClick={() => setShowReprint(true)}
+              >
+                <Printer className="w-3 h-3 flex-shrink-0" />
+                <span className="hidden sm:inline">Print</span>
+              </Button>
+              <Button
+                variant="secondary"
+                size="sm"
+                className="min-w-0 p-1 sm:p-1.5"
+                onClick={() => {
+                  const filename = archive.print_name || archive.filename || 'model';
+                  const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
+                  window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
+                }}
+                title="Open in Bambu Studio"
+              >
+                <ExternalLink className="w-3 h-3 sm:w-4 sm:h-4" />
+              </Button>
+            </>
+          ) : (
+            // Source file only - must open in slicer first
+            <Button
+              variant="primary"
+              size="sm"
+              className="flex-1 min-w-0"
+              onClick={() => {
+                const filename = archive.print_name || archive.filename || 'model';
+                const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
+                window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
+              }}
+              title="Open in Bambu Studio to slice"
+            >
+              <ExternalLink className="w-3 h-3 flex-shrink-0" />
+              <span className="hidden sm:inline">Slice</span>
+            </Button>
+          )}
           <Button
           <Button
             variant="secondary"
             variant="secondary"
             size="sm"
             size="sm"
@@ -947,13 +983,32 @@ export function ArchivesPage() {
   const { showToast } = useToast();
   const { showToast } = useToast();
   const searchInputRef = useRef<HTMLInputElement>(null);
   const searchInputRef = useRef<HTMLInputElement>(null);
   const [search, setSearch] = useState('');
   const [search, setSearch] = useState('');
-  const [filterPrinter, setFilterPrinter] = useState<number | null>(null);
-  const [filterMaterial, setFilterMaterial] = useState<string | null>(null);
-  const [filterColors, setFilterColors] = useState<Set<string>>(new Set());
-  const [colorFilterMode, setColorFilterMode] = useState<'or' | 'and'>('or');
-  const [filterFavorites, setFilterFavorites] = useState(false);
-  const [hideFailed, setHideFailed] = useState(() => localStorage.getItem('archiveHideFailed') === 'true');
-  const [filterTag, setFilterTag] = useState<string | null>(null);
+  const [filterPrinter, setFilterPrinter] = useState<number | null>(() => {
+    const saved = localStorage.getItem('archiveFilterPrinter');
+    return saved ? Number(saved) : null;
+  });
+  const [filterMaterial, setFilterMaterial] = useState<string | null>(() =>
+    localStorage.getItem('archiveFilterMaterial')
+  );
+  const [filterColors, setFilterColors] = useState<Set<string>>(() => {
+    const saved = localStorage.getItem('archiveFilterColors');
+    return saved ? new Set(JSON.parse(saved)) : new Set();
+  });
+  const [colorFilterMode, setColorFilterMode] = useState<'or' | 'and'>(() =>
+    (localStorage.getItem('archiveColorFilterMode') as 'or' | 'and') || 'or'
+  );
+  const [filterFavorites, setFilterFavorites] = useState(() =>
+    localStorage.getItem('archiveFilterFavorites') === 'true'
+  );
+  const [hideFailed, setHideFailed] = useState(() =>
+    localStorage.getItem('archiveHideFailed') === 'true'
+  );
+  const [filterTag, setFilterTag] = useState<string | null>(() =>
+    localStorage.getItem('archiveFilterTag')
+  );
+  const [filterFileType, setFilterFileType] = useState<'all' | 'gcode' | 'source'>(() =>
+    (localStorage.getItem('archiveFilterFileType') as 'all' | 'gcode' | 'source') || 'all'
+  );
   const [showUpload, setShowUpload] = useState(false);
   const [showUpload, setShowUpload] = useState(false);
   const [uploadFiles, setUploadFiles] = useState<File[]>([]);
   const [uploadFiles, setUploadFiles] = useState<File[]>([]);
   const [isDraggingOver, setIsDraggingOver] = useState(false);
   const [isDraggingOver, setIsDraggingOver] = useState(false);
@@ -961,9 +1016,15 @@ export function ArchivesPage() {
   const [isSelectionMode, setIsSelectionMode] = useState(false);
   const [isSelectionMode, setIsSelectionMode] = useState(false);
   const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
   const [showBulkDeleteConfirm, setShowBulkDeleteConfirm] = useState(false);
   const [showBatchTag, setShowBatchTag] = useState(false);
   const [showBatchTag, setShowBatchTag] = useState(false);
-  const [viewMode, setViewMode] = useState<ViewMode>('grid');
-  const [sortBy, setSortBy] = useState<SortOption>('date-desc');
-  const [collection, setCollection] = useState<Collection>('all');
+  const [viewMode, setViewMode] = useState<ViewMode>(() =>
+    (localStorage.getItem('archiveViewMode') as ViewMode) || 'grid'
+  );
+  const [sortBy, setSortBy] = useState<SortOption>(() =>
+    (localStorage.getItem('archiveSortBy') as SortOption) || 'date-desc'
+  );
+  const [collection, setCollection] = useState<Collection>(() =>
+    (localStorage.getItem('archiveCollection') as Collection) || 'all'
+  );
   const [showExportMenu, setShowExportMenu] = useState(false);
   const [showExportMenu, setShowExportMenu] = useState(false);
   const [isExporting, setIsExporting] = useState(false);
   const [isExporting, setIsExporting] = useState(false);
   const [showCompareModal, setShowCompareModal] = useState(false);
   const [showCompareModal, setShowCompareModal] = useState(false);
@@ -998,11 +1059,63 @@ export function ArchivesPage() {
     },
     },
   });
   });
 
 
-  // Persist hideFailed filter to localStorage
+  // Persist all filters to localStorage
+  useEffect(() => {
+    if (filterPrinter !== null) {
+      localStorage.setItem('archiveFilterPrinter', filterPrinter.toString());
+    } else {
+      localStorage.removeItem('archiveFilterPrinter');
+    }
+  }, [filterPrinter]);
+
+  useEffect(() => {
+    if (filterMaterial) {
+      localStorage.setItem('archiveFilterMaterial', filterMaterial);
+    } else {
+      localStorage.removeItem('archiveFilterMaterial');
+    }
+  }, [filterMaterial]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveFilterColors', JSON.stringify([...filterColors]));
+  }, [filterColors]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveColorFilterMode', colorFilterMode);
+  }, [colorFilterMode]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveFilterFavorites', filterFavorites.toString());
+  }, [filterFavorites]);
+
   useEffect(() => {
   useEffect(() => {
     localStorage.setItem('archiveHideFailed', hideFailed.toString());
     localStorage.setItem('archiveHideFailed', hideFailed.toString());
   }, [hideFailed]);
   }, [hideFailed]);
 
 
+  useEffect(() => {
+    if (filterTag) {
+      localStorage.setItem('archiveFilterTag', filterTag);
+    } else {
+      localStorage.removeItem('archiveFilterTag');
+    }
+  }, [filterTag]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveFilterFileType', filterFileType);
+  }, [filterFileType]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveViewMode', viewMode);
+  }, [viewMode]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveSortBy', sortBy);
+  }, [sortBy]);
+
+  useEffect(() => {
+    localStorage.setItem('archiveCollection', collection);
+  }, [collection]);
+
   const printerMap = new Map(printers?.map((p) => [p.id, p.name]) || []);
   const printerMap = new Map(printers?.map((p) => [p.id, p.name]) || []);
 
 
   // Extract unique materials and colors from archives
   // Extract unique materials and colors from archives
@@ -1070,7 +1183,13 @@ export function ArchivesPage() {
       const archiveTags = a.tags?.split(',').map(t => t.trim()) || [];
       const archiveTags = a.tags?.split(',').map(t => t.trim()) || [];
       const matchesTag = !filterTag || archiveTags.includes(filterTag);
       const matchesTag = !filterTag || archiveTags.includes(filterTag);
 
 
-      return matchesCollection && matchesSearch && matchesMaterial && matchesColor && matchesFavorites && matchesHideFailed && matchesTag;
+      // File type filter (gcode = sliced, source = project file only)
+      const isGcodeFile = a.filename?.toLowerCase().includes('.gcode.');
+      const matchesFileType = filterFileType === 'all' ||
+        (filterFileType === 'gcode' && isGcodeFile) ||
+        (filterFileType === 'source' && !isGcodeFile);
+
+      return matchesCollection && matchesSearch && matchesMaterial && matchesColor && matchesFavorites && matchesHideFailed && matchesTag && matchesFileType;
     })
     })
     .sort((a, b) => {
     .sort((a, b) => {
       switch (sortBy) {
       switch (sortBy) {
@@ -1139,9 +1258,10 @@ export function ArchivesPage() {
     setFilterFavorites(false);
     setFilterFavorites(false);
     setHideFailed(false);
     setHideFailed(false);
     setFilterTag(null);
     setFilterTag(null);
+    setFilterFileType('all');
   };
   };
 
 
-  const hasTopFilters = search || filterPrinter || filterMaterial || filterFavorites || hideFailed || filterTag;
+  const hasTopFilters = search || filterPrinter || filterMaterial || filterFavorites || hideFailed || filterTag || filterFileType !== 'all';
 
 
   // Drag & drop handlers for page-wide upload
   // Drag & drop handlers for page-wide upload
   const handleDragOver = useCallback((e: React.DragEvent) => {
   const handleDragOver = useCallback((e: React.DragEvent) => {
@@ -1452,6 +1572,18 @@ export function ArchivesPage() {
                 ))}
                 ))}
               </select>
               </select>
             </div>
             </div>
+            <div className="flex items-center gap-2 flex-shrink-0">
+              <FileCode className="w-4 h-4 text-bambu-gray hidden md:block" />
+              <select
+                className="px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
+                value={filterFileType}
+                onChange={(e) => setFilterFileType(e.target.value as 'all' | 'gcode' | 'source')}
+              >
+                <option value="all">All Files</option>
+                <option value="gcode">Sliced (GCODE)</option>
+                <option value="source">Source Only</option>
+              </select>
+            </div>
             <button
             <button
               onClick={() => setFilterFavorites(!filterFavorites)}
               onClick={() => setFilterFavorites(!filterFavorites)}
               className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors flex-shrink-0 ${
               className={`flex items-center gap-2 px-3 py-2 rounded-lg border transition-colors flex-shrink-0 ${
@@ -1716,7 +1848,7 @@ export function ArchivesPage() {
                       const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
                       const downloadUrl = `${window.location.origin}${api.getArchiveForSlicer(archive.id, filename)}`;
                       window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
                       window.location.href = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
                     }}
                     }}
-                    title="Open in Slicer"
+                    title="Slice"
                   >
                   >
                     <ExternalLink className="w-4 h-4" />
                     <ExternalLink className="w-4 h-4" />
                   </Button>
                   </Button>

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
static/assets/index-303ohz8i.js


+ 1 - 1
static/index.html

@@ -23,7 +23,7 @@
 
 
     <!-- Splash screens for iOS -->
     <!-- Splash screens for iOS -->
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
     <link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
-    <script type="module" crossorigin src="/assets/index-Bh0qWzko.js"></script>
+    <script type="module" crossorigin src="/assets/index-303ohz8i.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-CiDcmh7W.css">
     <link rel="stylesheet" crossorigin href="/assets/index-CiDcmh7W.css">
   </head>
   </head>
   <body>
   <body>

Некоторые файлы не были показаны из-за большого количества измененных файлов