Explorar o código

fix(gcode-viewer): add in-app back button (was browser-back only)

  Opening the GCode Viewer from a File Manager card or Archive card mounts
  GCodeViewerPage as a full-height iframe inside the Layout shell. The page
  rendered nothing but the iframe, so once the third-party viewer's UI took
  over the content area there was no in-app affordance to return to the
  originating list - only the browser's back button.

  Add a thin bar above the iframe with an ArrowLeft button. The label adapts
  to the entry point - "Back to Print Archives" when the URL carries
  ?archive=, "Back to File Manager" when it carries ?library_file=, generic
  "Back" otherwise. Click prefers navigate(-1) so the user lands back in
  their original list with scroll position and filters preserved; falls
  back to /archives or /files when the page was opened in a fresh tab and
  there's no SPA history to return to.

  New gcodeViewer.{back, backToArchives, backToFiles} i18n namespace added
  to all 8 locales with native translations.
maziggy hai 4 meses
pai
achega
77bf53b7f1

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
CHANGELOG.md


+ 5 - 0
frontend/src/i18n/locales/de.ts

@@ -5451,6 +5451,11 @@ export default {
       deleteFailed: 'Datei konnte nicht aus der Bibliothek entfernt werden.',
       deleteFailed: 'Datei konnte nicht aus der Bibliothek entfernt werden.',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: 'Zurück',
+    backToArchives: 'Zurück zum Druckarchiv',
+    backToFiles: 'Zurück zum Dateimanager',
+  },
   libraryTrash: {
   libraryTrash: {
     title: 'Papierkorb',
     title: 'Papierkorb',
     headerButton: 'Papierkorb',
     headerButton: 'Papierkorb',

+ 5 - 0
frontend/src/i18n/locales/en.ts

@@ -5460,6 +5460,11 @@ export default {
       deleteFailed: 'Could not remove the file from the library.',
       deleteFailed: 'Could not remove the file from the library.',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: 'Back',
+    backToArchives: 'Back to Print Archives',
+    backToFiles: 'Back to File Manager',
+  },
   libraryTrash: {
   libraryTrash: {
     title: 'Trash',
     title: 'Trash',
     headerButton: 'Trash',
     headerButton: 'Trash',

+ 5 - 0
frontend/src/i18n/locales/fr.ts

@@ -5440,6 +5440,11 @@ export default {
       deleteFailed: 'Impossible de retirer le fichier de la bibliothèque.',
       deleteFailed: 'Impossible de retirer le fichier de la bibliothèque.',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: 'Retour',
+    backToArchives: 'Retour aux archives d\'impression',
+    backToFiles: 'Retour au gestionnaire de fichiers',
+  },
   libraryTrash: {
   libraryTrash: {
     title: 'Corbeille',
     title: 'Corbeille',
     headerButton: 'Corbeille',
     headerButton: 'Corbeille',

+ 5 - 0
frontend/src/i18n/locales/it.ts

@@ -5439,6 +5439,11 @@ export default {
       deleteFailed: 'Impossibile rimuovere il file dalla libreria.',
       deleteFailed: 'Impossibile rimuovere il file dalla libreria.',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: 'Indietro',
+    backToArchives: 'Torna agli archivi di stampa',
+    backToFiles: 'Torna al gestore file',
+  },
   libraryTrash: {
   libraryTrash: {
     title: 'Cestino',
     title: 'Cestino',
     headerButton: 'Cestino',
     headerButton: 'Cestino',

+ 5 - 0
frontend/src/i18n/locales/ja.ts

@@ -5451,6 +5451,11 @@ export default {
       deleteFailed: 'ライブラリからファイルを削除できませんでした。',
       deleteFailed: 'ライブラリからファイルを削除できませんでした。',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: '戻る',
+    backToArchives: '印刷アーカイブに戻る',
+    backToFiles: 'ファイル管理に戻る',
+  },
   libraryTrash: {
   libraryTrash: {
     title: 'ゴミ箱',
     title: 'ゴミ箱',
     headerButton: 'ゴミ箱',
     headerButton: 'ゴミ箱',

+ 5 - 0
frontend/src/i18n/locales/pt-BR.ts

@@ -5439,6 +5439,11 @@ export default {
       deleteFailed: 'Não foi possível remover o arquivo da biblioteca.',
       deleteFailed: 'Não foi possível remover o arquivo da biblioteca.',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: 'Voltar',
+    backToArchives: 'Voltar para os arquivos de impressão',
+    backToFiles: 'Voltar para o gerenciador de arquivos',
+  },
   libraryTrash: {
   libraryTrash: {
     title: 'Lixeira',
     title: 'Lixeira',
     headerButton: 'Lixeira',
     headerButton: 'Lixeira',

+ 5 - 0
frontend/src/i18n/locales/zh-CN.ts

@@ -5438,6 +5438,11 @@ export default {
       deleteFailed: '无法从资料库中移除文件。',
       deleteFailed: '无法从资料库中移除文件。',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: '返回',
+    backToArchives: '返回打印归档',
+    backToFiles: '返回文件管理器',
+  },
   libraryTrash: {
   libraryTrash: {
     title: '回收站',
     title: '回收站',
     headerButton: '回收站',
     headerButton: '回收站',

+ 5 - 0
frontend/src/i18n/locales/zh-TW.ts

@@ -5438,6 +5438,11 @@ export default {
       deleteFailed: '無法從資料庫中移除檔案。',
       deleteFailed: '無法從資料庫中移除檔案。',
     },
     },
   },
   },
+  gcodeViewer: {
+    back: '返回',
+    backToArchives: '返回列印歸檔',
+    backToFiles: '返回檔案管理器',
+  },
   libraryTrash: {
   libraryTrash: {
     title: '資源回收筒',
     title: '資源回收筒',
     headerButton: '資源回收筒',
     headerButton: '資源回收筒',

+ 50 - 10
frontend/src/pages/GCodeViewerPage.tsx

@@ -1,4 +1,12 @@
+import { useNavigate, useSearchParams } from 'react-router-dom';
+import { ArrowLeft } from 'lucide-react';
+import { useTranslation } from 'react-i18next';
+
 export function GCodeViewerPage() {
 export function GCodeViewerPage() {
+  const navigate = useNavigate();
+  const [searchParams] = useSearchParams();
+  const { t } = useTranslation();
+
   // Safety guard: if this React app is itself inside an iframe (e.g. the
   // Safety guard: if this React app is itself inside an iframe (e.g. the
   // StaticFiles mount isn't registered and serve_spa returned us here),
   // StaticFiles mount isn't registered and serve_spa returned us here),
   // don't render another iframe — that would create an infinite loop.
   // don't render another iframe — that would create an infinite loop.
@@ -11,6 +19,26 @@ export function GCodeViewerPage() {
     );
     );
   }
   }
 
 
+  const cameFromArchive = searchParams.has('archive');
+  const cameFromLibrary = searchParams.has('library_file');
+  const fallbackPath = cameFromArchive ? '/archives' : cameFromLibrary ? '/files' : '/';
+  const backLabel = cameFromArchive
+    ? t('gcodeViewer.backToArchives')
+    : cameFromLibrary
+    ? t('gcodeViewer.backToFiles')
+    : t('gcodeViewer.back');
+
+  const handleBack = () => {
+    // Prefer browser history so we land where the user actually was (preserving
+    // scroll position, filters, etc.). Fall back to a sensible default route
+    // when the viewer was opened from a fresh tab / shared link.
+    if (window.history.length > 1) {
+      navigate(-1);
+    } else {
+      navigate(fallbackPath);
+    }
+  };
+
   // Forward the outer page's query string (e.g. ?archive=82) to the iframe so
   // Forward the outer page's query string (e.g. ?archive=82) to the iframe so
   // the adapter inside can pick up the archive to load. The iframe itself must
   // the adapter inside can pick up the archive to load. The iframe itself must
   // keep the trailing slash on /gcode-viewer/ so it hits the raw-viewer route;
   // keep the trailing slash on /gcode-viewer/ so it hits the raw-viewer route;
@@ -21,15 +49,27 @@ export function GCodeViewerPage() {
   return (
   return (
     // h-14 (3.5 rem) is the fixed header height defined in Layout.tsx.
     // h-14 (3.5 rem) is the fixed header height defined in Layout.tsx.
     // Subtracting it prevents a double scrollbar inside the layout shell.
     // Subtracting it prevents a double scrollbar inside the layout shell.
-    <iframe
-      src={iframeSrc}
-      title="GCode Viewer"
-      style={{
-        display: 'block',
-        width: '100%',
-        height: 'calc(100vh - 3.5rem)',
-        border: 'none',
-      }}
-    />
+    <div style={{ height: 'calc(100vh - 3.5rem)', display: 'flex', flexDirection: 'column' }}>
+      <div className="flex items-center gap-2 px-4 py-2 border-b border-gray-200 dark:border-gray-700 bg-white dark:bg-gray-900">
+        <button
+          type="button"
+          onClick={handleBack}
+          className="inline-flex items-center gap-1.5 text-sm text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white transition-colors"
+        >
+          <ArrowLeft className="w-4 h-4" />
+          {backLabel}
+        </button>
+      </div>
+      <iframe
+        src={iframeSrc}
+        title="GCode Viewer"
+        style={{
+          display: 'block',
+          width: '100%',
+          flex: 1,
+          border: 'none',
+        }}
+      />
+    </div>
   );
   );
 }
 }

A diferenza do arquivo foi suprimida porque é demasiado grande
+ 0 - 0
static/assets/index-CqLSsmpD.js


+ 1 - 1
static/index.html

@@ -26,7 +26,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-CvmCjsiR.js"></script>
+    <script type="module" crossorigin src="/assets/index-CqLSsmpD.js"></script>
     <link rel="stylesheet" crossorigin href="/assets/index-BSBzgKvT.css">
     <link rel="stylesheet" crossorigin href="/assets/index-BSBzgKvT.css">
   </head>
   </head>
   <body>
   <body>

Algúns arquivos non se mostraron porque demasiados arquivos cambiaron neste cambio