GCodeViewerPage.tsx 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. import { useNavigate, useSearchParams } from 'react-router-dom';
  2. import { ArrowLeft } from 'lucide-react';
  3. import { useTranslation } from 'react-i18next';
  4. export function GCodeViewerPage() {
  5. const navigate = useNavigate();
  6. const [searchParams] = useSearchParams();
  7. const { t } = useTranslation();
  8. // Safety guard: if this React app is itself inside an iframe (e.g. the
  9. // StaticFiles mount isn't registered and serve_spa returned us here),
  10. // don't render another iframe — that would create an infinite loop.
  11. if (window !== window.top) {
  12. return (
  13. <div style={{ padding: 32, color: '#f88' }}>
  14. GCode viewer static files not found. Check that the{' '}
  15. <code>gcode_viewer/</code> directory exists and restart uvicorn.
  16. </div>
  17. );
  18. }
  19. const cameFromArchive = searchParams.has('archive');
  20. const cameFromLibrary = searchParams.has('library_file');
  21. const fallbackPath = cameFromArchive ? '/archives' : cameFromLibrary ? '/files' : '/';
  22. const backLabel = cameFromArchive
  23. ? t('gcodeViewer.backToArchives')
  24. : cameFromLibrary
  25. ? t('gcodeViewer.backToFiles')
  26. : t('gcodeViewer.back');
  27. const handleBack = () => {
  28. // Prefer browser history so we land where the user actually was (preserving
  29. // scroll position, filters, etc.). Fall back to a sensible default route
  30. // when the viewer was opened from a fresh tab / shared link.
  31. if (window.history.length > 1) {
  32. navigate(-1);
  33. } else {
  34. navigate(fallbackPath);
  35. }
  36. };
  37. // Forward the outer page's query string (e.g. ?archive=82) to the iframe so
  38. // the adapter inside can pick up the archive to load. The iframe itself must
  39. // keep the trailing slash on /gcode-viewer/ so it hits the raw-viewer route;
  40. // the outer SPA URL uses no trailing slash so a reload falls through to the
  41. // SPA catch-all and keeps the Bambuddy layout shell.
  42. const iframeSrc = `/gcode-viewer/${window.location.search}`;
  43. return (
  44. // h-14 (3.5 rem) is the fixed header height defined in Layout.tsx.
  45. // Subtracting it prevents a double scrollbar inside the layout shell.
  46. <div style={{ height: 'calc(100vh - 3.5rem)', display: 'flex', flexDirection: 'column' }}>
  47. <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">
  48. <button
  49. type="button"
  50. onClick={handleBack}
  51. 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"
  52. >
  53. <ArrowLeft className="w-4 h-4" />
  54. {backLabel}
  55. </button>
  56. </div>
  57. <iframe
  58. src={iframeSrc}
  59. title="GCode Viewer"
  60. style={{
  61. display: 'block',
  62. width: '100%',
  63. flex: 1,
  64. border: 'none',
  65. }}
  66. />
  67. </div>
  68. );
  69. }