slicer.ts 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. /**
  2. * Utility for opening files in slicer applications
  3. *
  4. * Protocol handler URL formats (from BambuStudio/OrcaSlicer source code):
  5. *
  6. * Bambu Studio has TWO separate URL handlers:
  7. * 1. post_init() [Windows/Linux CLI args]: bambustudio://open?file=<URL>
  8. * - Checks: starts_with("bambustudio://open")
  9. * - Calls url_decode(), then split_str(url, "file=")
  10. * 2. MacOpenURL() [macOS Apple Events]: bambustudioopen://<encoded-URL>
  11. * - Checks: starts_with("bambustudioopen://")
  12. * - Strips prefix, then url_decode()
  13. *
  14. * OrcaSlicer Downloader accepts both formats via regex:
  15. * - (orcaslicer|bambustudio|...)://open?file=<URL>
  16. * - bambustudioopen://<URL>
  17. *
  18. * Key insight: Using ?file= query format, the browser's URL parser preserves
  19. * http:// in the query string without any encoding. Only the macOS-specific
  20. * bambustudioopen:// format needs encodeURIComponent (BS calls url_decode).
  21. */
  22. export type SlicerType = 'bambu_studio' | 'orcaslicer';
  23. type Platform = 'windows' | 'macos' | 'linux' | 'unknown';
  24. /**
  25. * Detect the user's operating system
  26. */
  27. export function detectPlatform(): Platform {
  28. const userAgent = navigator.userAgent.toLowerCase();
  29. const platform = navigator.platform?.toLowerCase() || '';
  30. if (userAgent.includes('win') || platform.includes('win')) {
  31. return 'windows';
  32. }
  33. if (userAgent.includes('mac') || platform.includes('mac')) {
  34. return 'macos';
  35. }
  36. if (userAgent.includes('linux') || platform.includes('linux')) {
  37. return 'linux';
  38. }
  39. return 'unknown';
  40. }
  41. /**
  42. * Open a URL in the specified slicer application.
  43. * @param downloadUrl - The URL to the file to open
  44. * @param slicer - Which slicer to use (defaults to bambu_studio)
  45. */
  46. export function openInSlicer(downloadUrl: string, slicer: SlicerType = 'bambu_studio'): void {
  47. let url: string;
  48. if (slicer === 'orcaslicer') {
  49. // OrcaSlicer: ?file= query format — http:// preserved in query string
  50. url = `orcaslicer://open?file=${downloadUrl}`;
  51. } else {
  52. const platform = detectPlatform();
  53. if (platform === 'macos') {
  54. // macOS only: bambustudioopen scheme via MacOpenURL() callback.
  55. // Must encode because bare http:// in authority gets mangled by browser.
  56. // BS calls url_decode() after stripping "bambustudioopen://" prefix.
  57. url = `bambustudioopen://${encodeURIComponent(downloadUrl)}`;
  58. } else {
  59. // Windows/Linux: bambustudio://open?file= via post_init() CLI args.
  60. // The ?file= query format preserves http:// without encoding.
  61. // IMPORTANT: On Linux, BS only handles "bambustudio://open" prefix —
  62. // it does NOT process "bambustudioopen://" (that's macOS-only).
  63. url = `bambustudio://open?file=${downloadUrl}`;
  64. }
  65. }
  66. // Use a temporary <a> element to trigger the protocol handler.
  67. // This avoids navigating away from the page (unlike window.location.href).
  68. const link = document.createElement('a');
  69. link.href = url;
  70. link.style.display = 'none';
  71. document.body.appendChild(link);
  72. link.click();
  73. document.body.removeChild(link);
  74. }
  75. /**
  76. * Build a full download URL for a file
  77. * @param path - The API path (e.g., from api.getArchiveForSlicer())
  78. */
  79. export function buildDownloadUrl(path: string): string {
  80. return `${window.location.origin}${path}`;
  81. }
  82. /**
  83. * Convenience function to open an archive in the slicer
  84. * @param path - The API path to the archive
  85. * @param slicer - Which slicer to use (defaults to bambu_studio)
  86. */
  87. export function openArchiveInSlicer(path: string, slicer: SlicerType = 'bambu_studio'): void {
  88. const downloadUrl = buildDownloadUrl(path);
  89. openInSlicer(downloadUrl, slicer);
  90. }