@l1yp/file-viewer 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/README.md +2 -0
  2. package/dist/FileViewer.js +2 -0
  3. package/dist/FileViewer.js.map +1 -0
  4. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  5. package/dist/FileViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  6. package/dist/components/AppxViewer.js +2 -0
  7. package/dist/components/AppxViewer.js.map +1 -0
  8. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  9. package/dist/components/AppxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  10. package/dist/components/ArchiveBrowser.js +2 -0
  11. package/dist/components/ArchiveBrowser.js.map +1 -0
  12. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  13. package/dist/components/ArchiveBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  14. package/dist/components/AudioViewer.js +2 -0
  15. package/dist/components/AudioViewer.js.map +1 -0
  16. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  17. package/dist/components/AudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  18. package/dist/components/BinObjBrowser.js +2 -0
  19. package/dist/components/BinObjBrowser.js.map +1 -0
  20. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  21. package/dist/components/BinObjBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  22. package/dist/components/CertViewer.js +2 -0
  23. package/dist/components/CertViewer.js.map +1 -0
  24. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  25. package/dist/components/CertViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  26. package/dist/components/ClassViewer.js +2 -0
  27. package/dist/components/ClassViewer.js.map +1 -0
  28. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  29. package/dist/components/ClassViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  30. package/dist/components/CodeBlock.js +2 -0
  31. package/dist/components/CodeBlock.js.map +1 -0
  32. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  33. package/dist/components/CodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  34. package/dist/components/CodeView.js +2 -0
  35. package/dist/components/CodeView.js.map +1 -0
  36. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js +2 -0
  37. package/dist/components/CodeView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  38. package/dist/components/CsvViewer.js +2 -0
  39. package/dist/components/CsvViewer.js.map +1 -0
  40. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  41. package/dist/components/CsvViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  42. package/dist/components/DicomViewer.js +2 -0
  43. package/dist/components/DicomViewer.js.map +1 -0
  44. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  45. package/dist/components/DicomViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  46. package/dist/components/DocxNavigationPane.js +9 -0
  47. package/dist/components/DocxNavigationPane.js.map +1 -0
  48. package/dist/components/DocxNavigationPane.vue.d.ts +23 -0
  49. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js +98 -0
  50. package/dist/components/DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  51. package/dist/components/DocxViewer.js +3 -1
  52. package/dist/components/DocxViewer.js.map +1 -0
  53. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js +72 -37
  54. package/dist/components/DocxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  55. package/dist/components/DrawioViewer.js +2 -0
  56. package/dist/components/DrawioViewer.js.map +1 -0
  57. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  58. package/dist/components/DrawioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  59. package/dist/components/DxfViewer.js +2 -0
  60. package/dist/components/DxfViewer.js.map +1 -0
  61. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  62. package/dist/components/DxfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  63. package/dist/components/EbookViewer.js +2 -0
  64. package/dist/components/EbookViewer.js.map +1 -0
  65. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  66. package/dist/components/EbookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  67. package/dist/components/EmlViewer.js +2 -0
  68. package/dist/components/EmlViewer.js.map +1 -0
  69. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  70. package/dist/components/EmlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  71. package/dist/components/EplViewer.js +2 -0
  72. package/dist/components/EplViewer.js.map +1 -0
  73. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  74. package/dist/components/EplViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  75. package/dist/components/FileViewerBody.js +3 -1
  76. package/dist/components/FileViewerBody.js.map +1 -0
  77. package/dist/components/FileViewerBody.vue.d.ts +5 -0
  78. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js +250 -247
  79. package/dist/components/FileViewerBody.vue_vue_type_script_setup_true_lang.js.map +1 -0
  80. package/dist/components/FileViewerPanel.js +2 -0
  81. package/dist/components/FileViewerPanel.js.map +1 -0
  82. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js +2 -0
  83. package/dist/components/FileViewerPanel.vue_vue_type_script_setup_true_lang.js.map +1 -0
  84. package/dist/components/FontViewer.js +2 -0
  85. package/dist/components/FontViewer.js.map +1 -0
  86. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  87. package/dist/components/FontViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  88. package/dist/components/FrontmatterCard.js +2 -0
  89. package/dist/components/FrontmatterCard.js.map +1 -0
  90. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js +2 -0
  91. package/dist/components/FrontmatterCard.vue_vue_type_script_setup_true_lang.js.map +1 -0
  92. package/dist/components/GeoViewer.js +2 -0
  93. package/dist/components/GeoViewer.js.map +1 -0
  94. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  95. package/dist/components/GeoViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  96. package/dist/components/HarViewer.js +2 -0
  97. package/dist/components/HarViewer.js.map +1 -0
  98. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  99. package/dist/components/HarViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  100. package/dist/components/HexViewer.js +2 -0
  101. package/dist/components/HexViewer.js.map +1 -0
  102. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  103. package/dist/components/HexViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  104. package/dist/components/HprofViewer.js +2 -0
  105. package/dist/components/HprofViewer.js.map +1 -0
  106. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  107. package/dist/components/HprofViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  108. package/dist/components/ImageLightbox.js +2 -0
  109. package/dist/components/ImageLightbox.js.map +1 -0
  110. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js +2 -0
  111. package/dist/components/ImageLightbox.vue_vue_type_script_setup_true_lang.js.map +1 -0
  112. package/dist/components/InlineText.js +2 -0
  113. package/dist/components/InlineText.js.map +1 -0
  114. package/dist/components/JksViewer.js +2 -0
  115. package/dist/components/JksViewer.js.map +1 -0
  116. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  117. package/dist/components/JksViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  118. package/dist/components/JsonlViewer.js +2 -0
  119. package/dist/components/JsonlViewer.js.map +1 -0
  120. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  121. package/dist/components/JsonlViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  122. package/dist/components/LogViewer.js +2 -0
  123. package/dist/components/LogViewer.js.map +1 -0
  124. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  125. package/dist/components/LogViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  126. package/dist/components/LrcViewer.js +2 -0
  127. package/dist/components/LrcViewer.js.map +1 -0
  128. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  129. package/dist/components/LrcViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  130. package/dist/components/MarkdownTable.js +2 -0
  131. package/dist/components/MarkdownTable.js.map +1 -0
  132. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js +2 -0
  133. package/dist/components/MarkdownTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  134. package/dist/components/MarkdownText.js +2 -0
  135. package/dist/components/MarkdownText.js.map +1 -0
  136. package/dist/components/MhtViewer.js +2 -0
  137. package/dist/components/MhtViewer.js.map +1 -0
  138. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  139. package/dist/components/MhtViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  140. package/dist/components/MindMapView.js +2 -0
  141. package/dist/components/MindMapView.js.map +1 -0
  142. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js +2 -0
  143. package/dist/components/MindMapView.vue_vue_type_script_setup_true_lang.js.map +1 -0
  144. package/dist/components/MissingDependency.js +2 -0
  145. package/dist/components/MissingDependency.js.map +1 -0
  146. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js +2 -0
  147. package/dist/components/MissingDependency.vue_vue_type_script_setup_true_lang.js.map +1 -0
  148. package/dist/components/ModelViewer.js +2 -0
  149. package/dist/components/ModelViewer.js.map +1 -0
  150. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  151. package/dist/components/ModelViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  152. package/dist/components/MsiViewer.js +2 -0
  153. package/dist/components/MsiViewer.js.map +1 -0
  154. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  155. package/dist/components/MsiViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  156. package/dist/components/NotebookViewer.js +2 -0
  157. package/dist/components/NotebookViewer.js.map +1 -0
  158. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  159. package/dist/components/NotebookViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  160. package/dist/components/PdfViewer.js +2 -0
  161. package/dist/components/PdfViewer.js.map +1 -0
  162. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  163. package/dist/components/PdfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  164. package/dist/components/PptxNavigationPane.js +9 -0
  165. package/dist/components/PptxNavigationPane.js.map +1 -0
  166. package/dist/components/PptxNavigationPane.vue.d.ts +20 -0
  167. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js +120 -0
  168. package/dist/components/PptxNavigationPane.vue_vue_type_script_setup_true_lang.js.map +1 -0
  169. package/dist/components/PptxViewer.js +3 -1
  170. package/dist/components/PptxViewer.js.map +1 -0
  171. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js +71 -39
  172. package/dist/components/PptxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  173. package/dist/components/PsdViewer.js +2 -0
  174. package/dist/components/PsdViewer.js.map +1 -0
  175. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  176. package/dist/components/PsdViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  177. package/dist/components/QmcAudioViewer.js +2 -0
  178. package/dist/components/QmcAudioViewer.js.map +1 -0
  179. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  180. package/dist/components/QmcAudioViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  181. package/dist/components/RasterViewer.js +2 -0
  182. package/dist/components/RasterViewer.js.map +1 -0
  183. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  184. package/dist/components/RasterViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  185. package/dist/components/RtfViewer.js +2 -0
  186. package/dist/components/RtfViewer.js.map +1 -0
  187. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  188. package/dist/components/RtfViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  189. package/dist/components/SevenZipBrowser.js +2 -0
  190. package/dist/components/SevenZipBrowser.js.map +1 -0
  191. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js +2 -0
  192. package/dist/components/SevenZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  193. package/dist/components/SqliteViewer.js +2 -0
  194. package/dist/components/SqliteViewer.js.map +1 -0
  195. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  196. package/dist/components/SqliteViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  197. package/dist/components/SubtitleViewer.js +2 -0
  198. package/dist/components/SubtitleViewer.js.map +1 -0
  199. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  200. package/dist/components/SubtitleViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  201. package/dist/components/XlsxChart.js +2 -0
  202. package/dist/components/XlsxChart.js.map +1 -0
  203. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js +2 -0
  204. package/dist/components/XlsxChart.vue_vue_type_script_setup_true_lang.js.map +1 -0
  205. package/dist/components/XlsxViewer.js +3 -1
  206. package/dist/components/XlsxViewer.js.map +1 -0
  207. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js +301 -304
  208. package/dist/components/XlsxViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  209. package/dist/components/XmindViewer.js +2 -0
  210. package/dist/components/XmindViewer.js.map +1 -0
  211. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js +2 -0
  212. package/dist/components/XmindViewer.vue_vue_type_script_setup_true_lang.js.map +1 -0
  213. package/dist/components/ZipBrowser.js +3 -1
  214. package/dist/components/ZipBrowser.js.map +1 -0
  215. package/dist/components/ZipBrowser.vue.d.ts +3 -1
  216. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js +154 -122
  217. package/dist/components/ZipBrowser.vue_vue_type_script_setup_true_lang.js.map +1 -0
  218. package/dist/components/ZoomImage.js +2 -0
  219. package/dist/components/ZoomImage.js.map +1 -0
  220. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js +2 -0
  221. package/dist/components/ZoomImage.vue_vue_type_script_setup_true_lang.js.map +1 -0
  222. package/dist/components/epl/EplCodeBlock.js +2 -0
  223. package/dist/components/epl/EplCodeBlock.js.map +1 -0
  224. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js +2 -0
  225. package/dist/components/epl/EplCodeBlock.vue_vue_type_script_setup_true_lang.js.map +1 -0
  226. package/dist/components/epl/EplDoc.js +2 -0
  227. package/dist/components/epl/EplDoc.js.map +1 -0
  228. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js +2 -0
  229. package/dist/components/epl/EplDoc.vue_vue_type_script_setup_true_lang.js.map +1 -0
  230. package/dist/components/epl/EplMethod.js +2 -0
  231. package/dist/components/epl/EplMethod.js.map +1 -0
  232. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js +2 -0
  233. package/dist/components/epl/EplMethod.vue_vue_type_script_setup_true_lang.js.map +1 -0
  234. package/dist/components/epl/EplResource.js +2 -0
  235. package/dist/components/epl/EplResource.js.map +1 -0
  236. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js +2 -0
  237. package/dist/components/epl/EplResource.vue_vue_type_script_setup_true_lang.js.map +1 -0
  238. package/dist/components/epl/EplTree.js +2 -0
  239. package/dist/components/epl/EplTree.js.map +1 -0
  240. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js +2 -0
  241. package/dist/components/epl/EplTree.vue_vue_type_script_setup_true_lang.js.map +1 -0
  242. package/dist/components/epl/EplVarTable.js +2 -0
  243. package/dist/components/epl/EplVarTable.js.map +1 -0
  244. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js +2 -0
  245. package/dist/components/epl/EplVarTable.vue_vue_type_script_setup_true_lang.js.map +1 -0
  246. package/dist/components/epl/numbering.js +2 -0
  247. package/dist/components/epl/numbering.js.map +1 -0
  248. package/dist/components/icons/Icon.js +2 -0
  249. package/dist/components/icons/Icon.js.map +1 -0
  250. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js +2 -0
  251. package/dist/components/icons/Icon.vue_vue_type_script_setup_true_lang.js.map +1 -0
  252. package/dist/components/icons/iconPaths.js +2 -0
  253. package/dist/components/icons/iconPaths.js.map +1 -0
  254. package/dist/components/optionalViewer.js +2 -0
  255. package/dist/components/optionalViewer.js.map +1 -0
  256. package/dist/components/ui/ClampBlock.js +2 -0
  257. package/dist/components/ui/ClampBlock.js.map +1 -0
  258. package/dist/composables/useElementNavigation.d.ts +21 -0
  259. package/dist/composables/useElementNavigation.js +63 -0
  260. package/dist/composables/useElementNavigation.js.map +1 -0
  261. package/dist/composables/useFileViewer.js +2 -0
  262. package/dist/composables/useFileViewer.js.map +1 -0
  263. package/dist/composables/useLightbox.js +2 -0
  264. package/dist/composables/useLightbox.js.map +1 -0
  265. package/dist/composables/useLogRules.js +2 -0
  266. package/dist/composables/useLogRules.js.map +1 -0
  267. package/dist/lib/aes.js +2 -0
  268. package/dist/lib/aes.js.map +1 -0
  269. package/dist/lib/appx.js +2 -0
  270. package/dist/lib/appx.js.map +1 -0
  271. package/dist/lib/archiveHtml.d.ts +49 -0
  272. package/dist/lib/archiveHtml.js +377 -0
  273. package/dist/lib/archiveHtml.js.map +1 -0
  274. package/dist/lib/asn1.js +2 -0
  275. package/dist/lib/asn1.js.map +1 -0
  276. package/dist/lib/binobj.js +2 -0
  277. package/dist/lib/binobj.js.map +1 -0
  278. package/dist/lib/brotli.js +2 -0
  279. package/dist/lib/brotli.js.map +1 -0
  280. package/dist/lib/bzip2.js +2 -0
  281. package/dist/lib/bzip2.js.map +1 -0
  282. package/dist/lib/classfile.js +2 -0
  283. package/dist/lib/classfile.js.map +1 -0
  284. package/dist/lib/codePreview.js +2 -0
  285. package/dist/lib/codePreview.js.map +1 -0
  286. package/dist/lib/codefold.js +2 -0
  287. package/dist/lib/codefold.js.map +1 -0
  288. package/dist/lib/codeformat.js +2 -0
  289. package/dist/lib/codeformat.js.map +1 -0
  290. package/dist/lib/csv.js +2 -0
  291. package/dist/lib/csv.js.map +1 -0
  292. package/dist/lib/des.js +2 -0
  293. package/dist/lib/des.js.map +1 -0
  294. package/dist/lib/diagram.js +2 -0
  295. package/dist/lib/diagram.js.map +1 -0
  296. package/dist/lib/dicom.js +2 -0
  297. package/dist/lib/dicom.js.map +1 -0
  298. package/dist/lib/dicomCodecs.js +2 -0
  299. package/dist/lib/dicomCodecs.js.map +1 -0
  300. package/dist/lib/dicomRender.js +2 -0
  301. package/dist/lib/dicomRender.js.map +1 -0
  302. package/dist/lib/docxNavigation.d.ts +31 -0
  303. package/dist/lib/docxNavigation.js +65 -0
  304. package/dist/lib/docxNavigation.js.map +1 -0
  305. package/dist/lib/dxf.js +2 -0
  306. package/dist/lib/dxf.js.map +1 -0
  307. package/dist/lib/eml.js +2 -0
  308. package/dist/lib/eml.js.map +1 -0
  309. package/dist/lib/encoding.js +2 -0
  310. package/dist/lib/encoding.js.map +1 -0
  311. package/dist/lib/epl/code/codeDataParser.js +2 -0
  312. package/dist/lib/epl/code/codeDataParser.js.map +1 -0
  313. package/dist/lib/epl/code/codeLines.js +2 -0
  314. package/dist/lib/epl/code/codeLines.js.map +1 -0
  315. package/dist/lib/epl/code/textCode.js +2 -0
  316. package/dist/lib/epl/code/textCode.js.map +1 -0
  317. package/dist/lib/epl/eplDocument.js +2 -0
  318. package/dist/lib/epl/eplDocument.js.map +1 -0
  319. package/dist/lib/epl/imageList.js +2 -0
  320. package/dist/lib/epl/imageList.js.map +1 -0
  321. package/dist/lib/epl/index.js +2 -0
  322. package/dist/lib/epl/index.js.map +1 -0
  323. package/dist/lib/epl/io/binaryReader.js +2 -0
  324. package/dist/lib/epl/io/binaryReader.js.map +1 -0
  325. package/dist/lib/epl/io/encoding.js +2 -0
  326. package/dist/lib/epl/io/encoding.js.map +1 -0
  327. package/dist/lib/epl/model/eplSystemId.js +2 -0
  328. package/dist/lib/epl/model/eplSystemId.js.map +1 -0
  329. package/dist/lib/epl/names/eplLibData.js +2 -0
  330. package/dist/lib/epl/names/eplLibData.js.map +1 -0
  331. package/dist/lib/epl/names/idToNameMap.js +2 -0
  332. package/dist/lib/epl/names/idToNameMap.js.map +1 -0
  333. package/dist/lib/epl/names/libNameProvider.js +2 -0
  334. package/dist/lib/epl/names/libNameProvider.js.map +1 -0
  335. package/dist/lib/epl/program.js +2 -0
  336. package/dist/lib/epl/program.js.map +1 -0
  337. package/dist/lib/epl/projectFileReader.js +2 -0
  338. package/dist/lib/epl/projectFileReader.js.map +1 -0
  339. package/dist/lib/fb2.js +2 -0
  340. package/dist/lib/fb2.js.map +1 -0
  341. package/dist/lib/fileType.js +2 -0
  342. package/dist/lib/fileType.js.map +1 -0
  343. package/dist/lib/fonts.js +2 -0
  344. package/dist/lib/fonts.js.map +1 -0
  345. package/dist/lib/format.js +2 -0
  346. package/dist/lib/format.js.map +1 -0
  347. package/dist/lib/frontmatter.js +2 -0
  348. package/dist/lib/frontmatter.js.map +1 -0
  349. package/dist/lib/geo.js +2 -0
  350. package/dist/lib/geo.js.map +1 -0
  351. package/dist/lib/har.js +2 -0
  352. package/dist/lib/har.js.map +1 -0
  353. package/dist/lib/highlight.js +2 -0
  354. package/dist/lib/highlight.js.map +1 -0
  355. package/dist/lib/highlightLangs.js +2 -0
  356. package/dist/lib/highlightLangs.js.map +1 -0
  357. package/dist/lib/hprof.js +2 -0
  358. package/dist/lib/hprof.js.map +1 -0
  359. package/dist/lib/ipynb.js +2 -0
  360. package/dist/lib/ipynb.js.map +1 -0
  361. package/dist/lib/jks.js +2 -0
  362. package/dist/lib/jks.js.map +1 -0
  363. package/dist/lib/jsonl.js +2 -0
  364. package/dist/lib/jsonl.js.map +1 -0
  365. package/dist/lib/lrc.js +2 -0
  366. package/dist/lib/lrc.js.map +1 -0
  367. package/dist/lib/lzma.js +2 -0
  368. package/dist/lib/lzma.js.map +1 -0
  369. package/dist/lib/markdown/blocks.js +2 -0
  370. package/dist/lib/markdown/blocks.js.map +1 -0
  371. package/dist/lib/markdown/inline.js +2 -0
  372. package/dist/lib/markdown/inline.js.map +1 -0
  373. package/dist/lib/markdown/math.js +2 -0
  374. package/dist/lib/markdown/math.js.map +1 -0
  375. package/dist/lib/markdown/segmentize.js +2 -0
  376. package/dist/lib/markdown/segmentize.js.map +1 -0
  377. package/dist/lib/markdown/styles.js +2 -0
  378. package/dist/lib/markdown/styles.js.map +1 -0
  379. package/dist/lib/mht.js +2 -0
  380. package/dist/lib/mht.js.map +1 -0
  381. package/dist/lib/midi.js +2 -0
  382. package/dist/lib/midi.js.map +1 -0
  383. package/dist/lib/mime.js +2 -0
  384. package/dist/lib/mime.js.map +1 -0
  385. package/dist/lib/mindmap.js +2 -0
  386. package/dist/lib/mindmap.js.map +1 -0
  387. package/dist/lib/mobi.js +2 -0
  388. package/dist/lib/mobi.js.map +1 -0
  389. package/dist/lib/msi.js +2 -0
  390. package/dist/lib/msi.js.map +1 -0
  391. package/dist/lib/optionalDep.js +2 -0
  392. package/dist/lib/optionalDep.js.map +1 -0
  393. package/dist/lib/pkcs12.js +2 -0
  394. package/dist/lib/pkcs12.js.map +1 -0
  395. package/dist/lib/pptxNavigation.d.ts +7 -0
  396. package/dist/lib/pptxNavigation.js +29 -0
  397. package/dist/lib/pptxNavigation.js.map +1 -0
  398. package/dist/lib/qmc.js +2 -0
  399. package/dist/lib/qmc.js.map +1 -0
  400. package/dist/lib/rar.js +2 -0
  401. package/dist/lib/rar.js.map +1 -0
  402. package/dist/lib/rawPreview.js +2 -0
  403. package/dist/lib/rawPreview.js.map +1 -0
  404. package/dist/lib/rc2.js +2 -0
  405. package/dist/lib/rc2.js.map +1 -0
  406. package/dist/lib/resolveInput.js +2 -0
  407. package/dist/lib/resolveInput.js.map +1 -0
  408. package/dist/lib/rtf.js +2 -0
  409. package/dist/lib/rtf.js.map +1 -0
  410. package/dist/lib/sevenZip.js +2 -0
  411. package/dist/lib/sevenZip.js.map +1 -0
  412. package/dist/lib/sha1.js +2 -0
  413. package/dist/lib/sha1.js.map +1 -0
  414. package/dist/lib/sha256.js +2 -0
  415. package/dist/lib/sha256.js.map +1 -0
  416. package/dist/lib/speechAudio.js +2 -0
  417. package/dist/lib/speechAudio.js.map +1 -0
  418. package/dist/lib/sqlite.js +2 -0
  419. package/dist/lib/sqlite.js.map +1 -0
  420. package/dist/lib/subtitle.js +2 -0
  421. package/dist/lib/subtitle.js.map +1 -0
  422. package/dist/lib/tarball.js +2 -0
  423. package/dist/lib/tarball.js.map +1 -0
  424. package/dist/lib/viewerFormats.js +2 -0
  425. package/dist/lib/viewerFormats.js.map +1 -0
  426. package/dist/lib/viewerKind.js +2 -0
  427. package/dist/lib/viewerKind.js.map +1 -0
  428. package/dist/lib/x509.js +2 -0
  429. package/dist/lib/x509.js.map +1 -0
  430. package/dist/lib/xlsx.js +2 -0
  431. package/dist/lib/xlsx.js.map +1 -0
  432. package/dist/lib/xlsxCharts.js +2 -0
  433. package/dist/lib/xlsxCharts.js.map +1 -0
  434. package/dist/lib/xmind.js +2 -0
  435. package/dist/lib/xmind.js.map +1 -0
  436. package/dist/lib/zip.js +2 -0
  437. package/dist/lib/zip.js.map +1 -0
  438. package/dist/style.css +1 -1
  439. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DicomViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DicomViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'\nimport { DicomError, readDicom, type DicomSlice, type ParsedDicom } from '@/lib/dicom'\nimport {\n decodeDicomFrame,\n transferSyntaxName,\n UnsupportedTransferSyntaxError,\n} from '@/lib/dicomCodecs'\nimport {\n autoWindow,\n buildFramePixels,\n renderToImageData,\n type FramePixels,\n} from '@/lib/dicomRender'\nimport { formatBytes } from '@/lib/format'\n\n/**\n * The side viewer's DICOM (.dcm) mode: fetch a medical image's bytes, parse + decode it entirely in-browser\n * (see lib/dicom.ts → lib/dicomCodecs.ts → lib/dicomRender.ts), and draw it to a canvas with the controls a\n * basic medical viewer needs:\n * - window/level (brightness/contrast): left-drag on the image — horizontal = width, vertical = center.\n * - frames: a slider for multi-frame series (each frame decoded on demand).\n * - invert / zoom (wheel or buttons) / fit / reset.\n * Grayscale (CT/MR/X-ray — MONOCHROME1/2) is the primary path; RGB and palette-colour images render too.\n * The bytes come from the same download endpoint as any other file and never leave the browser.\n *\n * Two frame sources, unified behind {@link framePixelsAt}:\n * • single file (`url`) — frames come from that one file (multi-frame DICOM, e.g. enhanced CT / US cine);\n * • series stack (`slices`) — a list of independent single-frame DICOM files browsed as one scrollable\n * series (the classic \"one folder = one series, one file per slice\" hospital export). The wheel/cine\n * then steps through *files*. Decoded frames are cached + background-prefetched either way.\n */\nconst props = defineProps<{\n url?: string\n name: string\n /** Single-file: that file's size. Stack: the whole series' total bytes (just for the info strip). */\n size?: number\n /** Series-stack mode: independent DICOM files browsed as one series. Takes precedence over `url`. */\n slices?: DicomSlice[]\n /** Initial slice index in stack mode (the file the user actually clicked). */\n start?: number\n}>()\n\n// Stack mode is active when given more than one slice; a lone slice falls back to plain single-file display.\nconst stackMode = computed(() => (props.slices?.length ?? 0) > 1)\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\n\nconst parsed = ref<ParsedDicom | null>(null)\nconst framePixels = ref<FramePixels | null>(null)\nconst frameIndex = ref(0)\nconst decoding = ref(false)\n\n// Cine playback (multi-frame): decoded frames are cached so scrubbing/auto-play is instant after the first\n// pass, and a background prefetch warms the cache so even the very first loop is smooth.\nconst frameCache = new Map<number, FramePixels>()\nconst playing = ref(false)\nconst cineFps = ref(5)\nlet playTimer = 0\nlet prefetchToken = 0\n\nconst windowCenter = ref(0)\nconst windowWidth = ref(1)\nconst invert = ref(false)\nconst zoom = ref(1)\n// Initial window (from the file or auto) so \"reset\" can restore it after the user drags.\nlet initialCenter = 0\nlet initialWidth = 1\n\nconst canvasRef = ref<HTMLCanvasElement | null>(null)\nconst stageRef = ref<HTMLDivElement | null>(null)\nlet fitScale = 1\n\nconst meta = computed(() => parsed.value?.meta ?? null)\n// Full metadata table (patient/study/series/…), shown on demand via the toolbar toggle. Off by default —\n// it can include patient demographics (PHI), so the user opts in per file.\nconst showMeta = ref(false)\nconst tagGroups = computed(() => parsed.value?.tags ?? [])\nconst isGray = computed(() => framePixels.value?.mode === 'gray')\nconst totalFrames = computed(() =>\n stackMode.value ? props.slices!.length : (meta.value?.numberOfFrames ?? 1),\n)\nconst tsName = computed(() => (meta.value ? transferSyntaxName(meta.value.transferSyntaxUid) : ''))\n\nasync function loadFile(): Promise<void> {\n status.value = 'loading'\n errorMsg.value = ''\n parsed.value = null\n framePixels.value = null\n zoom.value = 1\n invert.value = false\n showMeta.value = false\n stopPlay()\n frameCache.clear()\n prefetchToken++ // abort any prefetch still running for the previous file\n // Stack mode opens on the slice the user clicked; single-file always starts at frame 0.\n const startAt =\n stackMode.value && props.start != null\n ? Math.min(Math.max(0, props.start), props.slices!.length - 1)\n : 0\n frameIndex.value = startAt\n try {\n if (!stackMode.value) {\n const res = await fetch(props.url ?? '')\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n parsed.value = readDicom(await res.arrayBuffer())\n }\n // decodeFrame owns the ready/error transition (and waits for the canvas to mount before painting). In\n // stack mode it parses slice `startAt` itself and establishes the representative meta from it.\n if (await decodeFrame(startAt, true)) void prefetchFrames()\n } catch (e) {\n fail(e)\n }\n}\n\nfunction fail(e: unknown): void {\n errorMsg.value =\n e instanceof DicomError || e instanceof UnsupportedTransferSyntaxError || e instanceof Error\n ? e.message\n : String(e)\n status.value = 'error'\n}\n\n// Decode + display one frame. `resetWindow` re-initialises the window from the file/auto (first load and\n// reset); frame navigation keeps the user's current window.\n// Produce the pixels for index `i` from whichever frame source is active:\n// • stack mode → slice `i` is its own DICOM file: parse it and decode its single frame. The first slice\n// to be decoded also establishes the representative meta/tags shown in the info strip + info panel.\n// • single file → frame `i` of the one already-parsed file.\nasync function framePixelsAt(i: number): Promise<FramePixels> {\n if (stackMode.value) {\n const pd = readDicom(await props.slices![i].load())\n parsed.value ??= pd\n return buildFramePixels(await decodeDicomFrame(pd.meta, pd.frameBytes(0)), pd.meta)\n }\n const p = parsed.value!\n return buildFramePixels(await decodeDicomFrame(p.meta, p.frameBytes(i)), p.meta)\n}\n\nasync function decodeFrame(i: number, resetWindow: boolean): Promise<boolean> {\n if (!stackMode.value && !parsed.value) return false\n let fp = frameCache.get(i)\n if (!fp) {\n decoding.value = true\n try {\n fp = await framePixelsAt(i)\n frameCache.set(i, fp)\n } catch (e) {\n fail(e)\n return false\n } finally {\n decoding.value = false\n }\n }\n // A newer frame was requested while this one was decoding (fast scroll / cine) — drop the stale result.\n if (i !== frameIndex.value) return false\n framePixels.value = fp\n const m = parsed.value?.meta\n if (resetWindow && fp.mode === 'gray' && m) {\n const fileHasWindow = m.windowCenter != null && m.windowWidth != null && m.windowWidth > 0\n const w = fileHasWindow\n ? { center: m.windowCenter as number, width: m.windowWidth as number }\n : autoWindow(fp)\n windowCenter.value = w.center\n windowWidth.value = w.width\n initialCenter = w.center\n initialWidth = w.width\n }\n // First successful frame: flip to 'ready' so the <canvas> mounts, then wait a tick before the first paint\n // (otherwise draw() runs against an unmounted canvas and no-ops, leaving it black). Later frames just draw.\n const firstPaint = status.value !== 'ready'\n status.value = 'ready'\n if (firstPaint) {\n await nextTick()\n computeFit()\n }\n draw()\n return true\n}\n\n// Background-decode the remaining frames into the cache so cine playback and scrubbing are smooth from the\n// first loop. Yields between frames to keep the UI responsive; aborts if the file changes (token bump).\nasync function prefetchFrames(): Promise<void> {\n const n = totalFrames.value\n if (n <= 1 || (!stackMode.value && !parsed.value)) return\n const token = ++prefetchToken\n for (let i = 0; i < n; i++) {\n if (token !== prefetchToken) return // file changed — stop\n if (!frameCache.has(i)) {\n try {\n const fp = await framePixelsAt(i)\n if (token !== prefetchToken) return\n frameCache.set(i, fp)\n } catch {\n // Ignore — navigating to this frame on demand will surface the error if there is one.\n }\n }\n await new Promise((r) => setTimeout(r, 0)) // yield to the event loop between frames\n }\n}\n\n// --- drawing -------------------------------------------------------------------------------------------\nlet rafId = 0\nfunction scheduleDraw(): void {\n if (rafId) return\n rafId = requestAnimationFrame(() => {\n rafId = 0\n draw()\n })\n}\n\nfunction draw(): void {\n const fp = framePixels.value\n const cv = canvasRef.value\n if (!fp || !cv) return\n if (cv.width !== fp.width) cv.width = fp.width\n if (cv.height !== fp.height) cv.height = fp.height\n const ctx = cv.getContext('2d')\n if (!ctx) return\n const img = renderToImageData(fp, {\n windowCenter: windowCenter.value,\n windowWidth: windowWidth.value,\n invert: invert.value,\n })\n ctx.putImageData(img, 0, 0)\n applyDisplaySize()\n}\n\nfunction computeFit(): void {\n const st = stageRef.value\n const fp = framePixels.value\n if (!st || !fp) return\n const cw = Math.max(1, st.clientWidth - 24)\n const ch = Math.max(1, st.clientHeight - 24)\n const s = Math.min(cw / fp.width, ch / fp.height)\n fitScale = Number.isFinite(s) && s > 0 ? s : 1\n}\n\nfunction applyDisplaySize(): void {\n const cv = canvasRef.value\n const fp = framePixels.value\n if (!cv || !fp) return\n const scale = fitScale * zoom.value\n cv.style.width = `${Math.max(1, Math.round(fp.width * scale))}px`\n cv.style.height = `${Math.max(1, Math.round(fp.height * scale))}px`\n}\n\n// --- interaction ---------------------------------------------------------------------------------------\n// Left-drag = window/level (radiology convention: horizontal → width/contrast, vertical → center/brightness).\n// Disabled for RGB/palette images (no VOI window applies).\nfunction onPointerDown(e: PointerEvent): void {\n if (e.button !== 0 || !isGray.value) return\n e.preventDefault()\n const el = e.currentTarget as HTMLElement\n el.setPointerCapture(e.pointerId)\n const startX = e.clientX\n const startY = e.clientY\n const startWW = windowWidth.value\n const startWC = windowCenter.value\n const fp = framePixels.value\n const range = fp ? fp.maxValue - fp.minValue || 255 : 255\n const k = Math.max(1, range / 512)\n const move = (ev: PointerEvent) => {\n windowWidth.value = Math.max(1, startWW + (ev.clientX - startX) * k)\n windowCenter.value = startWC + (ev.clientY - startY) * k\n scheduleDraw()\n }\n const up = (ev: PointerEvent) => {\n el.releasePointerCapture(ev.pointerId)\n el.removeEventListener('pointermove', move)\n el.removeEventListener('pointerup', up)\n }\n el.addEventListener('pointermove', move)\n el.addEventListener('pointerup', up)\n}\n\nfunction onWheel(e: WheelEvent): void {\n e.preventDefault()\n // Multi-frame DICOM: the wheel scrubs through slices like a hospital viewer (hold Ctrl/Shift to zoom).\n // Single-frame: nothing to scrub, so the wheel zooms as before.\n if (totalFrames.value > 1 && !e.ctrlKey && !e.shiftKey) {\n stopPlay() // a manual scrub takes over from auto-play\n stepFrame(e.deltaY > 0 ? 1 : -1)\n return\n }\n const factor = e.deltaY < 0 ? 1.12 : 1 / 1.12\n zoom.value = Math.min(20, Math.max(0.1, zoom.value * factor))\n applyDisplaySize()\n}\n\n// --- cine (multi-frame playback) -----------------------------------------------------------------------\n// Wheel/buttons clamp at the ends; auto-play wraps so it loops like a cine clip.\nfunction stepFrame(delta: number): void {\n const n = totalFrames.value\n if (n <= 1) return\n frameIndex.value = Math.min(n - 1, Math.max(0, frameIndex.value + delta))\n}\n\nfunction togglePlay(): void {\n if (playing.value) stopPlay()\n else startPlay()\n}\nfunction startPlay(): void {\n if (totalFrames.value <= 1 || playing.value) return\n playing.value = true\n const tick = () => {\n if (!playing.value) return\n const n = totalFrames.value\n frameIndex.value = (frameIndex.value + 1) % n // wrap → loop\n playTimer = window.setTimeout(tick, Math.max(20, Math.round(1000 / cineFps.value)))\n }\n tick()\n}\nfunction stopPlay(): void {\n playing.value = false\n if (playTimer) {\n clearTimeout(playTimer)\n playTimer = 0\n }\n}\n\nfunction zoomBy(factor: number): void {\n zoom.value = Math.min(20, Math.max(0.1, zoom.value * factor))\n applyDisplaySize()\n}\n\nfunction fitToStage(): void {\n zoom.value = 1\n computeFit()\n applyDisplaySize()\n}\n\nfunction resetView(): void {\n zoom.value = 1\n invert.value = false\n if (framePixels.value?.mode === 'gray') {\n windowCenter.value = initialCenter\n windowWidth.value = initialWidth\n }\n computeFit()\n draw()\n}\n\nwatch(frameIndex, (i) => {\n if (status.value === 'ready') void decodeFrame(i, false)\n})\nwatch(invert, scheduleDraw)\n\n// Re-fit when the panel is resized (the splitter or window changes the stage size).\nlet ro: ResizeObserver | null = null\nonMounted(() => {\n void loadFile()\n if (typeof ResizeObserver !== 'undefined' && stageRef.value) {\n ro = new ResizeObserver(() => {\n computeFit()\n applyDisplaySize()\n })\n ro.observe(stageRef.value)\n }\n})\n// Reload when the source changes — a new single-file URL, or a new series stack (identity changes when the\n// browser rebuilds the slice list for a different folder / clicked file).\nwatch([() => props.url, () => props.slices], loadFile)\nonBeforeUnmount(() => {\n stopPlay()\n prefetchToken++ // stop any in-flight prefetch\n if (rafId) cancelAnimationFrame(rafId)\n ro?.disconnect()\n})\n\nconst wcLabel = computed(() => Math.round(windowCenter.value))\nconst wwLabel = computed(() => Math.round(windowWidth.value))\nconst zoomLabel = computed(() => `${Math.round(fitScale * zoom.value * 100)}%`)\n</script>\n\n<template>\n <div class=\"dv\">\n <p v-if=\"status === 'loading'\" class=\"dv__msg\">加载并解码 DICOM…</p>\n\n <div v-else-if=\"status === 'error'\" class=\"dv__msg dv__msg--error\">\n <p>{{ errorMsg }}</p>\n <a class=\"dv__dl\" :href=\"url\" :download=\"name\">下载原文件</a>\n <button type=\"button\" class=\"dv__retry\" @click=\"loadFile\">重试</button>\n </div>\n\n <template v-else>\n <!-- toolbar -->\n <div class=\"dv__bar\">\n <div class=\"dv__group\">\n <button type=\"button\" class=\"dv__btn\" title=\"缩小\" @click=\"zoomBy(1 / 1.2)\">−</button>\n <span class=\"dv__zoom\">{{ zoomLabel }}</span>\n <button type=\"button\" class=\"dv__btn\" title=\"放大\" @click=\"zoomBy(1.2)\">+</button>\n <button type=\"button\" class=\"dv__btn\" title=\"适应窗口\" @click=\"fitToStage\">适应</button>\n </div>\n <div class=\"dv__group\">\n <button\n type=\"button\"\n class=\"dv__btn\"\n :class=\"{ 'dv__btn--on': invert }\"\n :disabled=\"!isGray\"\n title=\"反相显示\"\n @click=\"invert = !invert\"\n >\n 反相\n </button>\n <button type=\"button\" class=\"dv__btn\" title=\"重置窗位/缩放\" @click=\"resetView\">\n 重置\n </button>\n <button\n type=\"button\"\n class=\"dv__btn\"\n :class=\"{ 'dv__btn--on': showMeta }\"\n :disabled=\"!tagGroups.length\"\n title=\"DICOM 元数据\"\n @click=\"showMeta = !showMeta\"\n >\n 信息\n </button>\n </div>\n </div>\n\n <!-- image canvas -->\n <div ref=\"stageRef\" class=\"dv__stage\" @wheel=\"onWheel\">\n <canvas\n ref=\"canvasRef\"\n class=\"dv__canvas\"\n :class=\"{ 'dv__canvas--wl': isGray }\"\n @pointerdown=\"onPointerDown\"\n />\n <span v-if=\"decoding\" class=\"dv__decoding\">解码中…</span>\n\n <!-- DICOM metadata overlay (toggled by the toolbar's 信息 button). Covers the image while open;\n scrolls on its own — @wheel.stop keeps the wheel from zooming the image underneath. -->\n <div v-if=\"showMeta\" class=\"dv__meta\" @wheel.stop>\n <div class=\"dv__meta-head\">\n <span>DICOM 元数据</span>\n <button type=\"button\" class=\"dv__meta-close\" title=\"关闭\" @click=\"showMeta = false\">\n ✕\n </button>\n </div>\n <div v-for=\"g in tagGroups\" :key=\"g.title\" class=\"dv__meta-group\">\n <h4 class=\"dv__meta-title\">{{ g.title }}</h4>\n <div v-for=\"r in g.rows\" :key=\"r.label\" class=\"dv__meta-row\">\n <span class=\"dv__meta-label\">{{ r.label }}</span>\n <span class=\"dv__meta-val\">{{ r.value }}</span>\n </div>\n </div>\n <p v-if=\"!tagGroups.length\" class=\"dv__meta-empty\">无可显示的元数据</p>\n </div>\n </div>\n\n <!-- multi-frame: cine playback + scrub slider (wheel over the image also scrubs) -->\n <div v-if=\"totalFrames > 1\" class=\"dv__frames\">\n <button\n type=\"button\"\n class=\"dv__btn\"\n :class=\"{ 'dv__btn--on': playing }\"\n :title=\"playing ? '暂停' : '播放(循环)'\"\n @click=\"togglePlay\"\n >\n {{ playing ? '⏸' : '▶' }}\n </button>\n <select v-model.number=\"cineFps\" class=\"dv__fps\" title=\"播放速度(帧/秒)\">\n <option :value=\"5\">5 fps</option>\n <option :value=\"10\">10 fps</option>\n <option :value=\"15\">15 fps</option>\n <option :value=\"24\">24 fps</option>\n </select>\n <button\n type=\"button\"\n class=\"dv__btn\"\n :disabled=\"frameIndex <= 0\"\n @click=\"(stopPlay(), (frameIndex = Math.max(0, frameIndex - 1)))\"\n >\n ‹\n </button>\n <input\n v-model.number=\"frameIndex\"\n type=\"range\"\n class=\"dv__range\"\n min=\"0\"\n :max=\"totalFrames - 1\"\n step=\"1\"\n @pointerdown=\"stopPlay\"\n />\n <button\n type=\"button\"\n class=\"dv__btn\"\n :disabled=\"frameIndex >= totalFrames - 1\"\n @click=\"(stopPlay(), (frameIndex = Math.min(totalFrames - 1, frameIndex + 1)))\"\n >\n ›\n </button>\n <span class=\"dv__frameno\">{{ frameIndex + 1 }} / {{ totalFrames }}</span>\n <span class=\"dv__hint\">滚轮切换层面</span>\n </div>\n\n <!-- metadata strip -->\n <div v-if=\"meta\" class=\"dv__info\">\n <span v-if=\"meta.modality\" class=\"dv__tag\">{{ meta.modality }}</span>\n <span v-if=\"stackMode\" class=\"dv__series\">序列 · {{ totalFrames }} 层</span>\n <span>{{ meta.columns }}×{{ meta.rows }}</span>\n <span>{{ meta.bitsStored }}/{{ meta.bitsAllocated }}-bit</span>\n <span>{{ meta.photometric }}</span>\n <span v-if=\"isGray\" class=\"dv__wl\">窗位 C {{ wcLabel }} · W {{ wwLabel }}</span>\n <span class=\"dv__ts\">{{ tsName }}</span>\n <span v-if=\"props.size\">{{ formatBytes(props.size) }}</span>\n <span v-if=\"meta.seriesDescription\" class=\"dv__desc\" :title=\"meta.seriesDescription\">{{\n meta.seriesDescription\n }}</span>\n </div>\n </template>\n </div>\n</template>\n\n<style scoped>\n.dv {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n\n.dv__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dv__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dv__dl,\n.dv__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n text-decoration: none;\n}\n.dv__dl:hover,\n.dv__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Toolbar --------------------------------------------------------------------------------------------- */\n.dv__bar {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 10px;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n}\n.dv__group {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n.dv__btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 30px;\n height: 28px;\n padding: 0 9px;\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n color: var(--muted);\n font-family: inherit;\n font-size: 12.5px;\n cursor: pointer;\n transition:\n background 0.15s,\n color 0.15s,\n border-color 0.15s;\n}\n.dv__btn:hover:not(:disabled) {\n border-color: var(--accent);\n}\n.dv__btn--on {\n color: #fff;\n background: var(--accent);\n border-color: var(--accent);\n}\n.dv__btn:disabled {\n opacity: 0.45;\n cursor: default;\n}\n.dv__zoom {\n min-width: 46px;\n text-align: center;\n font-size: 11.5px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n}\n\n/* Image stage --------------------------------------------------------------------------------------- */\n.dv__stage {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 12px;\n /* a neutral dark mat so grayscale medical images read with proper contrast */\n background: repeating-conic-gradient(#1a1a1a 0% 25%, #202020 0% 50%) 50% / 22px 22px;\n}\n.dv__canvas {\n display: block;\n margin: auto;\n image-rendering: auto;\n background: #000;\n box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.08);\n}\n.dv__canvas--wl {\n cursor: crosshair;\n touch-action: none;\n}\n.dv__decoding {\n position: absolute;\n top: 10px;\n right: 12px;\n padding: 3px 9px;\n border-radius: 999px;\n background: rgba(0, 0, 0, 0.6);\n color: #fff;\n font-size: 11px;\n}\n\n/* Metadata overlay --------------------------------------------------------------------------------- */\n.dv__meta {\n position: absolute;\n inset: 0;\n z-index: 3;\n overflow-y: auto;\n overscroll-behavior: contain;\n padding: 10px 14px 18px;\n background: color-mix(in srgb, var(--surface) 94%, transparent);\n backdrop-filter: blur(2px);\n}\n.dv__meta-head {\n position: sticky;\n top: 0;\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin: -10px -14px 8px;\n padding: 9px 14px;\n background: var(--surface);\n border-bottom: 1px solid var(--border);\n font-size: 12px;\n font-weight: 600;\n color: var(--text-strong);\n}\n.dv__meta-close {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 24px;\n height: 24px;\n background: transparent;\n border: none;\n border-radius: 6px;\n color: var(--faint);\n font-size: 13px;\n cursor: pointer;\n}\n.dv__meta-close:hover {\n color: var(--accent);\n background: var(--surface-2);\n}\n.dv__meta-group {\n margin-top: 12px;\n}\n.dv__meta-title {\n margin: 0 0 4px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.04em;\n color: var(--accent);\n}\n.dv__meta-row {\n display: flex;\n gap: 10px;\n padding: 3px 0;\n border-bottom: 1px solid var(--border);\n font-size: 12px;\n line-height: 1.5;\n}\n.dv__meta-label {\n flex: 0 0 38%;\n color: var(--faint);\n}\n.dv__meta-val {\n flex: 1 1 auto;\n min-width: 0;\n color: var(--text);\n word-break: break-word;\n font-variant-numeric: tabular-nums;\n}\n.dv__meta-empty {\n margin-top: 16px;\n font-size: 12.5px;\n color: var(--faint);\n}\n\n/* Frame slider -------------------------------------------------------------------------------------- */\n.dv__frames {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 12px;\n border-top: 1px solid var(--border);\n}\n.dv__range {\n flex: 1 1 auto;\n min-width: 0;\n accent-color: var(--accent);\n}\n.dv__fps {\n flex-shrink: 0;\n height: 28px;\n padding: 0 4px;\n background: var(--surface-2);\n border: 1px solid var(--border);\n border-radius: 7px;\n color: var(--muted);\n font-family: inherit;\n font-size: 11.5px;\n cursor: pointer;\n}\n.dv__frameno {\n flex-shrink: 0;\n font-size: 11.5px;\n color: var(--faint);\n font-variant-numeric: tabular-nums;\n}\n.dv__hint {\n flex-shrink: 0;\n font-size: 11px;\n color: var(--faint);\n opacity: 0.8;\n}\n\n/* Metadata strip ------------------------------------------------------------------------------------ */\n.dv__info {\n flex-shrink: 0;\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px 12px;\n padding: 9px 14px;\n border-top: 1px solid var(--border);\n font-size: 11.5px;\n color: var(--faint);\n}\n.dv__tag {\n padding: 1px 8px;\n border-radius: 999px;\n background: var(--accent);\n color: #fff;\n font-weight: 600;\n}\n.dv__series {\n padding: 1px 8px;\n border-radius: 999px;\n border: 1px solid var(--accent);\n color: var(--accent);\n font-weight: 600;\n}\n.dv__wl {\n color: var(--muted);\n font-variant-numeric: tabular-nums;\n}\n.dv__ts {\n color: var(--muted);\n}\n.dv__desc {\n max-width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n</style>\n"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import e from "../_virtual/_plugin-vue_export-helper.js";
2
+ import t from "./DocxNavigationPane.vue_vue_type_script_setup_true_lang.js";
3
+ /* empty css */
4
+ //#region src/components/DocxNavigationPane.vue
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-82c26438"]]);
6
+ //#endregion
7
+ export { n as default };
8
+
9
+ //# sourceMappingURL=DocxNavigationPane.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocxNavigationPane.js","names":[],"sources":["../../src/components/DocxNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ElementNavigationItem } from '@/composables/useElementNavigation'\nimport Icon from '@/components/icons/Icon.vue'\n\nconst props = defineProps<{\n outlineItems: readonly ElementNavigationItem[]\n pageItems: readonly ElementNavigationItem[]\n activeOutlineId: string\n activePageId: string\n}>()\n\nconst emit = defineEmits<{ navigate: [item: ElementNavigationItem] }>()\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\n\nfunction isActive(item: ElementNavigationItem): boolean {\n return (view.value === 'outline' ? props.activeOutlineId : props.activePageId) === item.id\n}\n</script>\n\n<template>\n <aside class=\"dn\" :class=\"{ 'dn--collapsed': collapsed }\" aria-label=\"文档导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"dn__reopen\"\n aria-label=\"展开文档导航\"\n title=\"展开文档导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"dn__header\">\n <div>\n <strong class=\"dn__title\">导航</strong>\n <span class=\"dn__count\">{{ pageItems.length }} 页</span>\n </div>\n <button\n type=\"button\"\n class=\"dn__collapse\"\n aria-label=\"收起文档导航\"\n title=\"收起文档导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <div class=\"dn__tabs\" role=\"tablist\" aria-label=\"导航方式\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"dn__tab\"\n :class=\"{ 'dn__tab--active': view === 'outline' }\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"dn__tab\"\n :class=\"{ 'dn__tab--active': view === 'pages' }\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n\n <nav class=\"dn__list\" aria-label=\"文档目录\">\n <template v-if=\"view === 'outline'\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n type=\"button\"\n class=\"dn__outline-item\"\n :class=\"{ 'dn__item--active': isActive(item) }\"\n :style=\"{ paddingLeft: `${10 + item.level * 12}px` }\"\n :aria-current=\"isActive(item) ? 'location' : undefined\"\n :title=\"item.title\"\n @click=\"emit('navigate', item)\"\n >\n <span class=\"dn__marker\" />\n <span class=\"dn__label\">{{ item.title }}</span>\n </button>\n <p v-if=\"!outlineItems.length\" class=\"dn__empty\">\n 文档未设置标题层级,可切换到“页面”导航。\n </p>\n </template>\n\n <template v-else>\n <button\n v-for=\"(item, index) in pageItems\"\n :key=\"item.id\"\n type=\"button\"\n class=\"dn__page-item\"\n :class=\"{ 'dn__item--active': isActive(item) }\"\n :aria-current=\"isActive(item) ? 'page' : undefined\"\n @click=\"emit('navigate', item)\"\n >\n <span class=\"dn__page-sheet\">{{ index + 1 }}</span>\n <span class=\"dn__label\">{{ item.title }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.dn {\n position: relative;\n flex: 0 0 166px;\n width: 166px;\n min-width: 0;\n display: flex;\n flex-direction: column;\n border-right: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n transition: flex-basis 160ms ease;\n}\n.dn--collapsed {\n flex-basis: 0;\n width: 0;\n border-right: 0;\n}\n.dn__header {\n min-height: 45px;\n padding: 9px 8px 8px 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n.dn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n.dn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n.dn__collapse,\n.dn__reopen {\n border: 0;\n color: var(--muted);\n background: transparent;\n cursor: pointer;\n}\n.dn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n place-items: center;\n border-radius: 7px;\n}\n.dn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.dn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n background: var(--surface);\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n.dn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.dn__tabs {\n margin: 8px 8px 4px;\n padding: 2px;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2px;\n border-radius: 8px;\n background: var(--surface-2);\n}\n.dn__tab {\n min-width: 0;\n padding: 5px 4px;\n border: 0;\n border-radius: 6px;\n color: var(--muted);\n background: transparent;\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n}\n.dn__tab--active {\n color: var(--text-strong);\n background: var(--surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n.dn__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 4px 6px 18px;\n scrollbar-width: thin;\n}\n.dn__outline-item,\n.dn__page-item {\n position: relative;\n width: 100%;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 7px;\n border: 0;\n border-radius: 7px;\n color: var(--muted);\n background: transparent;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dn__outline-item {\n min-height: 31px;\n padding-top: 5px;\n padding-right: 7px;\n padding-bottom: 5px;\n}\n.dn__page-item {\n padding: 6px;\n}\n.dn__outline-item:hover,\n.dn__page-item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.dn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n.dn__marker {\n flex: 0 0 auto;\n width: 3px;\n height: 15px;\n border-radius: 999px;\n background: var(--border-2);\n}\n.dn__item--active .dn__marker {\n background: var(--accent);\n}\n.dn__page-sheet {\n flex: 0 0 auto;\n width: 26px;\n height: 34px;\n display: grid;\n place-items: center;\n border: 1px solid var(--border-2);\n border-radius: 3px;\n color: var(--faint);\n background: var(--surface);\n font-size: 9.5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n.dn__item--active .dn__page-sheet {\n border-color: var(--accent);\n color: var(--accent);\n}\n.dn__label {\n min-width: 0;\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n font-size: 11.5px;\n line-height: 1.35;\n}\n.dn__empty {\n margin: 10px 8px;\n color: var(--faint);\n font-size: 10.5px;\n line-height: 1.55;\n}\n\n@container (max-width: 500px) {\n .dn {\n flex-basis: 136px;\n width: 136px;\n }\n .dn--collapsed {\n flex-basis: 0;\n width: 0;\n }\n}\n</style>\n"],"mappings":""}
@@ -0,0 +1,23 @@
1
+ import type { ElementNavigationItem } from '@/composables/useElementNavigation';
2
+ type __VLS_Props = {
3
+ outlineItems: readonly ElementNavigationItem[];
4
+ pageItems: readonly ElementNavigationItem[];
5
+ activeOutlineId: string;
6
+ activePageId: string;
7
+ };
8
+ type __VLS_ModelProps = {
9
+ 'collapsed'?: boolean;
10
+ 'view'?: 'outline' | 'pages';
11
+ };
12
+ type __VLS_PublicProps = __VLS_Props & __VLS_ModelProps;
13
+ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
14
+ navigate: (item: ElementNavigationItem) => any;
15
+ "update:collapsed": (value: boolean) => any;
16
+ "update:view": (value: "outline" | "pages") => any;
17
+ }, string, import("vue").PublicProps, Readonly<__VLS_PublicProps> & Readonly<{
18
+ onNavigate?: ((item: ElementNavigationItem) => any) | undefined;
19
+ "onUpdate:collapsed"?: ((value: boolean) => any) | undefined;
20
+ "onUpdate:view"?: ((value: "outline" | "pages") => any) | undefined;
21
+ }>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
22
+ declare const _default: typeof __VLS_export;
23
+ export default _default;
@@ -0,0 +1,98 @@
1
+ import e from "./icons/Icon.js";
2
+ import { Fragment as t, createCommentVNode as n, createElementBlock as r, createElementVNode as i, createVNode as a, defineComponent as o, mergeModels as s, normalizeClass as c, normalizeStyle as l, openBlock as u, renderList as d, toDisplayString as f, useModel as p } from "vue";
3
+ //#region src/components/DocxNavigationPane.vue?vue&type=script&setup=true&lang.ts
4
+ var m = { class: "dn__header" }, h = { class: "dn__count" }, g = {
5
+ class: "dn__tabs",
6
+ role: "tablist",
7
+ "aria-label": "导航方式"
8
+ }, _ = ["aria-selected"], v = ["aria-selected"], y = {
9
+ class: "dn__list",
10
+ "aria-label": "文档目录"
11
+ }, b = [
12
+ "aria-current",
13
+ "title",
14
+ "onClick"
15
+ ], x = { class: "dn__label" }, S = {
16
+ key: 0,
17
+ class: "dn__empty"
18
+ }, C = ["aria-current", "onClick"], w = { class: "dn__page-sheet" }, T = { class: "dn__label" }, E = /*@__PURE__*/ o({
19
+ __name: "DocxNavigationPane",
20
+ props: /*@__PURE__*/ s({
21
+ outlineItems: {},
22
+ pageItems: {},
23
+ activeOutlineId: {},
24
+ activePageId: {}
25
+ }, {
26
+ collapsed: {
27
+ type: Boolean,
28
+ default: !1
29
+ },
30
+ collapsedModifiers: {},
31
+ view: { default: "outline" },
32
+ viewModifiers: {}
33
+ }),
34
+ emits: /*@__PURE__*/ s(["navigate"], ["update:collapsed", "update:view"]),
35
+ setup(o, { emit: s }) {
36
+ let E = o, D = s, O = p(o, "collapsed"), k = p(o, "view");
37
+ function A(e) {
38
+ return (k.value === "outline" ? E.activeOutlineId : E.activePageId) === e.id;
39
+ }
40
+ return (s, p) => (u(), r("aside", {
41
+ class: c(["dn", { "dn--collapsed": O.value }]),
42
+ "aria-label": "文档导航"
43
+ }, [O.value ? (u(), r("button", {
44
+ key: 0,
45
+ type: "button",
46
+ class: "dn__reopen",
47
+ "aria-label": "展开文档导航",
48
+ title: "展开文档导航",
49
+ onClick: p[0] ||= (e) => O.value = !1
50
+ }, [a(e, {
51
+ name: "chevronRight",
52
+ size: 15
53
+ })])) : (u(), r(t, { key: 1 }, [
54
+ i("header", m, [i("div", null, [p[4] ||= i("strong", { class: "dn__title" }, "导航", -1), i("span", h, f(o.pageItems.length) + " 页", 1)]), i("button", {
55
+ type: "button",
56
+ class: "dn__collapse",
57
+ "aria-label": "收起文档导航",
58
+ title: "收起文档导航",
59
+ onClick: p[1] ||= (e) => O.value = !0
60
+ }, [a(e, {
61
+ name: "chevronLeft",
62
+ size: 15
63
+ })])]),
64
+ i("div", g, [i("button", {
65
+ type: "button",
66
+ role: "tab",
67
+ class: c(["dn__tab", { "dn__tab--active": k.value === "outline" }]),
68
+ "aria-selected": k.value === "outline",
69
+ onClick: p[2] ||= (e) => k.value = "outline"
70
+ }, " 目录 ", 10, _), i("button", {
71
+ type: "button",
72
+ role: "tab",
73
+ class: c(["dn__tab", { "dn__tab--active": k.value === "pages" }]),
74
+ "aria-selected": k.value === "pages",
75
+ onClick: p[3] ||= (e) => k.value = "pages"
76
+ }, " 页面 ", 10, v)]),
77
+ i("nav", y, [k.value === "outline" ? (u(), r(t, { key: 0 }, [(u(!0), r(t, null, d(o.outlineItems, (e) => (u(), r("button", {
78
+ key: e.id,
79
+ type: "button",
80
+ class: c(["dn__outline-item", { "dn__item--active": A(e) }]),
81
+ style: l({ paddingLeft: `${10 + e.level * 12}px` }),
82
+ "aria-current": A(e) ? "location" : void 0,
83
+ title: e.title,
84
+ onClick: (t) => D("navigate", e)
85
+ }, [p[5] ||= i("span", { class: "dn__marker" }, null, -1), i("span", x, f(e.title), 1)], 14, b))), 128)), o.outlineItems.length ? n("", !0) : (u(), r("p", S, " 文档未设置标题层级,可切换到“页面”导航。 "))], 64)) : (u(!0), r(t, { key: 1 }, d(o.pageItems, (e, t) => (u(), r("button", {
86
+ key: e.id,
87
+ type: "button",
88
+ class: c(["dn__page-item", { "dn__item--active": A(e) }]),
89
+ "aria-current": A(e) ? "page" : void 0,
90
+ onClick: (t) => D("navigate", e)
91
+ }, [i("span", w, f(t + 1), 1), i("span", T, f(e.title), 1)], 10, C))), 128))])
92
+ ], 64))], 2));
93
+ }
94
+ });
95
+ //#endregion
96
+ export { E as default };
97
+
98
+ //# sourceMappingURL=DocxNavigationPane.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocxNavigationPane.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DocxNavigationPane.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport type { ElementNavigationItem } from '@/composables/useElementNavigation'\nimport Icon from '@/components/icons/Icon.vue'\n\nconst props = defineProps<{\n outlineItems: readonly ElementNavigationItem[]\n pageItems: readonly ElementNavigationItem[]\n activeOutlineId: string\n activePageId: string\n}>()\n\nconst emit = defineEmits<{ navigate: [item: ElementNavigationItem] }>()\nconst collapsed = defineModel<boolean>('collapsed', { default: false })\nconst view = defineModel<'outline' | 'pages'>('view', { default: 'outline' })\n\nfunction isActive(item: ElementNavigationItem): boolean {\n return (view.value === 'outline' ? props.activeOutlineId : props.activePageId) === item.id\n}\n</script>\n\n<template>\n <aside class=\"dn\" :class=\"{ 'dn--collapsed': collapsed }\" aria-label=\"文档导航\">\n <button\n v-if=\"collapsed\"\n type=\"button\"\n class=\"dn__reopen\"\n aria-label=\"展开文档导航\"\n title=\"展开文档导航\"\n @click=\"collapsed = false\"\n >\n <Icon name=\"chevronRight\" :size=\"15\" />\n </button>\n\n <template v-else>\n <header class=\"dn__header\">\n <div>\n <strong class=\"dn__title\">导航</strong>\n <span class=\"dn__count\">{{ pageItems.length }} 页</span>\n </div>\n <button\n type=\"button\"\n class=\"dn__collapse\"\n aria-label=\"收起文档导航\"\n title=\"收起文档导航\"\n @click=\"collapsed = true\"\n >\n <Icon name=\"chevronLeft\" :size=\"15\" />\n </button>\n </header>\n\n <div class=\"dn__tabs\" role=\"tablist\" aria-label=\"导航方式\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"dn__tab\"\n :class=\"{ 'dn__tab--active': view === 'outline' }\"\n :aria-selected=\"view === 'outline'\"\n @click=\"view = 'outline'\"\n >\n 目录\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"dn__tab\"\n :class=\"{ 'dn__tab--active': view === 'pages' }\"\n :aria-selected=\"view === 'pages'\"\n @click=\"view = 'pages'\"\n >\n 页面\n </button>\n </div>\n\n <nav class=\"dn__list\" aria-label=\"文档目录\">\n <template v-if=\"view === 'outline'\">\n <button\n v-for=\"item in outlineItems\"\n :key=\"item.id\"\n type=\"button\"\n class=\"dn__outline-item\"\n :class=\"{ 'dn__item--active': isActive(item) }\"\n :style=\"{ paddingLeft: `${10 + item.level * 12}px` }\"\n :aria-current=\"isActive(item) ? 'location' : undefined\"\n :title=\"item.title\"\n @click=\"emit('navigate', item)\"\n >\n <span class=\"dn__marker\" />\n <span class=\"dn__label\">{{ item.title }}</span>\n </button>\n <p v-if=\"!outlineItems.length\" class=\"dn__empty\">\n 文档未设置标题层级,可切换到“页面”导航。\n </p>\n </template>\n\n <template v-else>\n <button\n v-for=\"(item, index) in pageItems\"\n :key=\"item.id\"\n type=\"button\"\n class=\"dn__page-item\"\n :class=\"{ 'dn__item--active': isActive(item) }\"\n :aria-current=\"isActive(item) ? 'page' : undefined\"\n @click=\"emit('navigate', item)\"\n >\n <span class=\"dn__page-sheet\">{{ index + 1 }}</span>\n <span class=\"dn__label\">{{ item.title }}</span>\n </button>\n </template>\n </nav>\n </template>\n </aside>\n</template>\n\n<style scoped>\n.dn {\n position: relative;\n flex: 0 0 166px;\n width: 166px;\n min-width: 0;\n display: flex;\n flex-direction: column;\n border-right: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 94%, var(--surface-2));\n transition: flex-basis 160ms ease;\n}\n.dn--collapsed {\n flex-basis: 0;\n width: 0;\n border-right: 0;\n}\n.dn__header {\n min-height: 45px;\n padding: 9px 8px 8px 12px;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 6px;\n border-bottom: 1px solid var(--border);\n}\n.dn__title {\n display: block;\n color: var(--text-strong);\n font-size: 12.5px;\n font-weight: 650;\n}\n.dn__count {\n display: block;\n margin-top: 1px;\n color: var(--faint);\n font-size: 10.5px;\n}\n.dn__collapse,\n.dn__reopen {\n border: 0;\n color: var(--muted);\n background: transparent;\n cursor: pointer;\n}\n.dn__collapse {\n width: 27px;\n height: 27px;\n display: grid;\n place-items: center;\n border-radius: 7px;\n}\n.dn__collapse:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.dn__reopen {\n position: absolute;\n z-index: 3;\n top: 12px;\n right: -28px;\n width: 28px;\n height: 36px;\n padding: 0;\n display: grid;\n place-items: center;\n border: 1px solid var(--border-2);\n border-radius: 0 9px 9px 0;\n background: var(--surface);\n box-shadow: 2px 1px 6px rgba(0, 0, 0, 0.12);\n}\n.dn__reopen:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.dn__tabs {\n margin: 8px 8px 4px;\n padding: 2px;\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 2px;\n border-radius: 8px;\n background: var(--surface-2);\n}\n.dn__tab {\n min-width: 0;\n padding: 5px 4px;\n border: 0;\n border-radius: 6px;\n color: var(--muted);\n background: transparent;\n font: inherit;\n font-size: 11px;\n cursor: pointer;\n}\n.dn__tab--active {\n color: var(--text-strong);\n background: var(--surface);\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n.dn__list {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n padding: 4px 6px 18px;\n scrollbar-width: thin;\n}\n.dn__outline-item,\n.dn__page-item {\n position: relative;\n width: 100%;\n min-width: 0;\n display: flex;\n align-items: center;\n gap: 7px;\n border: 0;\n border-radius: 7px;\n color: var(--muted);\n background: transparent;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n.dn__outline-item {\n min-height: 31px;\n padding-top: 5px;\n padding-right: 7px;\n padding-bottom: 5px;\n}\n.dn__page-item {\n padding: 6px;\n}\n.dn__outline-item:hover,\n.dn__page-item:hover {\n color: var(--text-strong);\n background: var(--bubble);\n}\n.dn__item--active {\n color: var(--text-strong);\n background: var(--accent-soft);\n}\n.dn__marker {\n flex: 0 0 auto;\n width: 3px;\n height: 15px;\n border-radius: 999px;\n background: var(--border-2);\n}\n.dn__item--active .dn__marker {\n background: var(--accent);\n}\n.dn__page-sheet {\n flex: 0 0 auto;\n width: 26px;\n height: 34px;\n display: grid;\n place-items: center;\n border: 1px solid var(--border-2);\n border-radius: 3px;\n color: var(--faint);\n background: var(--surface);\n font-size: 9.5px;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);\n}\n.dn__item--active .dn__page-sheet {\n border-color: var(--accent);\n color: var(--accent);\n}\n.dn__label {\n min-width: 0;\n overflow: hidden;\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n font-size: 11.5px;\n line-height: 1.35;\n}\n.dn__empty {\n margin: 10px 8px;\n color: var(--faint);\n font-size: 10.5px;\n line-height: 1.55;\n}\n\n@container (max-width: 500px) {\n .dn {\n flex-basis: 136px;\n width: 136px;\n }\n .dn--collapsed {\n flex-basis: 0;\n width: 0;\n }\n}\n</style>\n"],"mappings":"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"}
@@ -2,6 +2,8 @@ import e from "../_virtual/_plugin-vue_export-helper.js";
2
2
  import t from "./DocxViewer.vue_vue_type_script_setup_true_lang.js";
3
3
  /* empty css */
4
4
  //#region src/components/DocxViewer.vue
5
- var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-9bff4ace"]]);
5
+ var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-5e8d92a6"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=DocxViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocxViewer.js","names":[],"sources":["../../src/components/DocxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'\nimport { renderAsync } from 'docx-preview'\nimport DocxNavigationPane from '@/components/DocxNavigationPane.vue'\nimport { useElementNavigation } from '@/composables/useElementNavigation'\nimport {\n buildDocxNavigation,\n buildDocxPageNavigation,\n type ParsedDocxModel,\n} from '@/lib/docxNavigation'\n\n/**\n * Side-viewer Word (`.docx`) renderer. Fetches the document bytes from the download URL and renders the\n * OOXML to HTML entirely in-browser via docx-preview (https://github.com/VolodymyrBaydalka/docxjs) — a\n * paginated, paper-like preview (white pages on a neutral canvas, like Word). No CDN / network beyond the\n * file fetch: docx-preview bundles its own jszip + renderer, and images are inlined as base64.\n *\n * Two entry shapes mirror the other browsers (DICOM/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes. docx-preview injects its own CSS\n * into the render container, prefixed with the `className` ('docx') so it can't leak onto the page.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .docx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// The element docx-preview renders into. Always mounted (v-show, not v-if) so the ref exists when we render.\nconst docEl = shallowRef<HTMLElement | null>(null)\nconst navView = shallowRef<'outline' | 'pages'>('outline')\nconst navCollapsed = shallowRef(false)\nconst { outlineItems, pageItems, activeOutlineId, activePageId, setItems, navigate } =\n useElementNavigation(docEl)\n\nasync function render(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n setItems([], [])\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const document = (await renderAsync(buf, el, undefined, {\n className: 'docx', // CSS prefix → docx-preview's injected styles stay scoped to this subtree\n inWrapper: true, // wrap pages in the gray \"paper on desk\" canvas, like Word\n // The side panel is narrow (and resizable), so don't pin pages to the fixed A4 width — that overflows\n // and clips the left edge. Let pages fill the available width and reflow; height follows content.\n ignoreWidth: true,\n ignoreHeight: true,\n breakPages: true, // honor page breaks → still separate page sheets, stacked vertically\n renderHeaders: true,\n renderFooters: true,\n renderFootnotes: true,\n renderEndnotes: true,\n useBase64URL: true, // inline embedded images as data URLs (no blob-URL lifecycle to manage)\n })) as ParsedDocxModel\n if (props.url !== url || docEl.value !== el) return\n let navigation\n try {\n navigation = buildDocxNavigation(document, el)\n } catch (error) {\n // A malformed/unsupported Word model must never turn an otherwise rendered document into an error page.\n console.warn('DOCX outline navigation unavailable:', error)\n navigation = { outline: [], pages: buildDocxPageNavigation(el) }\n }\n setItems(navigation.outline, navigation.pages)\n navView.value = navigation.outline.length ? 'outline' : 'pages'\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nonMounted(render)\nwatch(() => props.url, render)\nonBeforeUnmount(() => {\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"dx\">\n <DocxNavigationPane\n v-if=\"status === 'ready'\"\n v-model:collapsed=\"navCollapsed\"\n v-model:view=\"navView\"\n :outline-items=\"outlineItems\"\n :page-items=\"pageItems\"\n :active-outline-id=\"activeOutlineId\"\n :active-page-id=\"activePageId\"\n @navigate=\"navigate\"\n />\n <div class=\"dx__stage\">\n <p v-if=\"status === 'loading'\" class=\"dx__msg\">正在渲染 Word 文档…</p>\n <div v-else-if=\"status === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"render\">重试</button>\n </div>\n <!-- Always mounted (v-show) so docEl is available to docx-preview; hidden until render completes. -->\n <div v-show=\"status === 'ready'\" ref=\"docEl\" class=\"dx__doc\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.dx {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.dx__stage {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.dx__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dx__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dx__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dx__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Scroll region holding the rendered document. The pages keep their native Word appearance (white paper,\n * black text) for fidelity; we only retint the surrounding \"desk\" to the app's neutral surface so it sits\n * in the panel naturally in both light and dark themes. :deep reaches docx-preview's injected DOM. */\n.dx__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n}\n.dx__doc :deep(.docx-wrapper) {\n background: var(--surface-2);\n padding: 22px 16px 40px;\n gap: 16px;\n}\n/* Each page: a white sheet with a soft shadow. With ignoreWidth the page reflows to the panel width, so\n * pin it to fill the wrapper (box-sizing so the doc's own margins stay inside the sheet, not overflowing). */\n.dx__doc :deep(.docx-wrapper > section.docx) {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n margin-bottom: 0;\n}\n/* Some Word docs anchor tables as \"floating\" objects (OOXML `w:tblpPr` — a table pinned to a fixed spot on\n * the page with body text wrapping around it). docx-preview approximates that with `float: left` + a fixed\n * point width. On a full A4 page the table lands where Word put it, but in this narrow, width-reflowing\n * panel the floats stack unpredictably and the surrounding paragraphs wrap into the gaps beside them — so on\n * resize the layout scrambles and dark cell shading appears to streak across (the reported \"背景乱串 / 排版\n * 混乱\"). Since we've already chosen reflow over pixel-exact A4 fidelity (ignoreWidth + section width:100%),\n * force every table back to a normal full-width block that stacks top-to-bottom at any panel width, and let\n * long cell content wrap instead of overflowing. */\n.dx__doc :deep(section.docx table) {\n float: none !important;\n width: 100% !important;\n max-width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n.dx__doc :deep(section.docx td),\n.dx__doc :deep(section.docx th) {\n overflow-wrap: break-word;\n}\n/* The page's own side margins (this doc uses ~90pt) would swallow most of a narrow panel now that tables\n * fill the width, so cap the horizontal padding while keeping the top/bottom page margin intact. */\n.dx__doc :deep(section.docx) {\n padding-left: clamp(14px, 5%, 90pt) !important;\n padding-right: clamp(14px, 5%, 90pt) !important;\n}\n</style>\n"],"mappings":""}
@@ -1,59 +1,94 @@
1
- import { createCommentVNode as e, createElementBlock as t, createElementVNode as n, defineComponent as r, onBeforeUnmount as i, onMounted as a, openBlock as o, ref as s, toDisplayString as c, vShow as l, watch as u, withDirectives as d } from "vue";
2
- import { renderAsync as f } from "docx-preview";
1
+ import e from "./DocxNavigationPane.js";
2
+ import { useElementNavigation as t } from "../composables/useElementNavigation.js";
3
+ import { buildDocxNavigation as n, buildDocxPageNavigation as r } from "../lib/docxNavigation.js";
4
+ import { createBlock as i, createCommentVNode as a, createElementBlock as o, createElementVNode as s, defineComponent as c, onBeforeUnmount as l, onMounted as u, openBlock as d, shallowRef as f, toDisplayString as p, unref as m, vShow as h, watch as g, withDirectives as _ } from "vue";
5
+ import { renderAsync as v } from "docx-preview";
3
6
  //#region src/components/DocxViewer.vue?vue&type=script&setup=true&lang.ts
4
- var p = { class: "dx" }, m = {
7
+ var y = { class: "dx" }, b = { class: "dx__stage" }, x = {
5
8
  key: 0,
6
9
  class: "dx__msg"
7
- }, h = {
10
+ }, S = {
8
11
  key: 1,
9
12
  class: "dx__msg dx__msg--error"
10
- }, g = /*@__PURE__*/ r({
13
+ }, C = /*@__PURE__*/ c({
11
14
  __name: "DocxViewer",
12
15
  props: {
13
16
  url: {},
14
17
  name: {},
15
18
  size: {}
16
19
  },
17
- setup(r) {
18
- let g = r, _ = s("loading"), v = s(""), y = s(null);
19
- async function b() {
20
- let e = y.value;
21
- if (!(!g.url || !e)) {
22
- _.value = "loading", v.value = "", e.innerHTML = "";
20
+ setup(c) {
21
+ let C = c, w = f("loading"), T = f(""), E = f(null), D = f("outline"), O = f(!1), { outlineItems: k, pageItems: A, activeOutlineId: j, activePageId: M, setItems: N, navigate: P } = t(E);
22
+ async function F() {
23
+ let e = E.value;
24
+ if (!C.url || !e) return;
25
+ w.value = "loading", T.value = "", N([], []), e.innerHTML = "";
26
+ let t = C.url;
27
+ try {
28
+ let i = await fetch(t);
29
+ if (!i.ok) throw Error(`HTTP ${i.status}`);
30
+ let a = await i.arrayBuffer();
31
+ if (C.url !== t || E.value !== e) return;
32
+ let o = await v(a, e, void 0, {
33
+ className: "docx",
34
+ inWrapper: !0,
35
+ ignoreWidth: !0,
36
+ ignoreHeight: !0,
37
+ breakPages: !0,
38
+ renderHeaders: !0,
39
+ renderFooters: !0,
40
+ renderFootnotes: !0,
41
+ renderEndnotes: !0,
42
+ useBase64URL: !0
43
+ });
44
+ if (C.url !== t || E.value !== e) return;
45
+ let s;
23
46
  try {
24
- let t = await fetch(g.url);
25
- if (!t.ok) throw Error(`HTTP ${t.status}`);
26
- let n = await t.arrayBuffer();
27
- if (y.value !== e || (await f(n, e, void 0, {
28
- className: "docx",
29
- inWrapper: !0,
30
- ignoreWidth: !0,
31
- ignoreHeight: !0,
32
- breakPages: !0,
33
- renderHeaders: !0,
34
- renderFooters: !0,
35
- renderFootnotes: !0,
36
- renderEndnotes: !0,
37
- useBase64URL: !0
38
- }), y.value !== e)) return;
39
- _.value = "ready";
40
- } catch (e) {
41
- v.value = e instanceof Error ? e.message : String(e), _.value = "error";
47
+ s = n(o, e);
48
+ } catch (t) {
49
+ console.warn("DOCX outline navigation unavailable:", t), s = {
50
+ outline: [],
51
+ pages: r(e)
52
+ };
42
53
  }
54
+ N(s.outline, s.pages), D.value = s.outline.length ? "outline" : "pages", w.value = "ready";
55
+ } catch (e) {
56
+ T.value = e instanceof Error ? e.message : String(e), w.value = "error";
43
57
  }
44
58
  }
45
- return a(b), u(() => g.url, b), i(() => {
46
- y.value && (y.value.innerHTML = "");
47
- }), (r, i) => (o(), t("div", p, [_.value === "loading" ? (o(), t("p", m, "正在渲染 Word 文档…")) : _.value === "error" ? (o(), t("div", h, [n("p", null, "渲染失败:" + c(v.value), 1), n("button", {
59
+ return u(F), g(() => C.url, F), l(() => {
60
+ E.value && (E.value.innerHTML = "");
61
+ }), (t, n) => (d(), o("div", y, [w.value === "ready" ? (d(), i(e, {
62
+ key: 0,
63
+ collapsed: O.value,
64
+ "onUpdate:collapsed": n[0] ||= (e) => O.value = e,
65
+ view: D.value,
66
+ "onUpdate:view": n[1] ||= (e) => D.value = e,
67
+ "outline-items": m(k),
68
+ "page-items": m(A),
69
+ "active-outline-id": m(j),
70
+ "active-page-id": m(M),
71
+ onNavigate: m(P)
72
+ }, null, 8, [
73
+ "collapsed",
74
+ "view",
75
+ "outline-items",
76
+ "page-items",
77
+ "active-outline-id",
78
+ "active-page-id",
79
+ "onNavigate"
80
+ ])) : a("", !0), s("div", b, [w.value === "loading" ? (d(), o("p", x, "正在渲染 Word 文档…")) : w.value === "error" ? (d(), o("div", S, [s("p", null, "渲染失败:" + p(T.value), 1), s("button", {
48
81
  type: "button",
49
82
  class: "dx__retry",
50
- onClick: b
51
- }, "重试")])) : e("", !0), d(n("div", {
83
+ onClick: F
84
+ }, "重试")])) : a("", !0), _(s("div", {
52
85
  ref_key: "docEl",
53
- ref: y,
86
+ ref: E,
54
87
  class: "dx__doc"
55
- }, null, 512), [[l, _.value === "ready"]])]));
88
+ }, null, 512), [[h, w.value === "ready"]])])]));
56
89
  }
57
90
  });
58
91
  //#endregion
59
- export { g as default };
92
+ export { C as default };
93
+
94
+ //# sourceMappingURL=DocxViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocxViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DocxViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { onBeforeUnmount, onMounted, shallowRef, watch } from 'vue'\nimport { renderAsync } from 'docx-preview'\nimport DocxNavigationPane from '@/components/DocxNavigationPane.vue'\nimport { useElementNavigation } from '@/composables/useElementNavigation'\nimport {\n buildDocxNavigation,\n buildDocxPageNavigation,\n type ParsedDocxModel,\n} from '@/lib/docxNavigation'\n\n/**\n * Side-viewer Word (`.docx`) renderer. Fetches the document bytes from the download URL and renders the\n * OOXML to HTML entirely in-browser via docx-preview (https://github.com/VolodymyrBaydalka/docxjs) — a\n * paginated, paper-like preview (white pages on a neutral canvas, like Word). No CDN / network beyond the\n * file fetch: docx-preview bundles its own jszip + renderer, and images are inlined as base64.\n *\n * Two entry shapes mirror the other browsers (DICOM/zip): a real download URL for a held attachment, or a\n * blob URL minted by the archive browsers from already-extracted bytes. docx-preview injects its own CSS\n * into the render container, prefixed with the `className` ('docx') so it can't leak onto the page.\n */\nconst props = defineProps<{\n /** Download/blob URL of the .docx bytes. */\n url?: string\n name: string\n size?: number\n}>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = shallowRef<Status>('loading')\nconst errorMsg = shallowRef('')\n// The element docx-preview renders into. Always mounted (v-show, not v-if) so the ref exists when we render.\nconst docEl = shallowRef<HTMLElement | null>(null)\nconst navView = shallowRef<'outline' | 'pages'>('outline')\nconst navCollapsed = shallowRef(false)\nconst { outlineItems, pageItems, activeOutlineId, activePageId, setItems, navigate } =\n useElementNavigation(docEl)\n\nasync function render(): Promise<void> {\n const el = docEl.value\n if (!props.url || !el) return\n status.value = 'loading'\n errorMsg.value = ''\n setItems([], [])\n el.innerHTML = '' // drop any prior render (e.g. on retry / switching files)\n const url = props.url\n try {\n const res = await fetch(url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n // Stale guard: a newer render may have started while we awaited the fetch.\n if (props.url !== url || docEl.value !== el) return\n const document = (await renderAsync(buf, el, undefined, {\n className: 'docx', // CSS prefix → docx-preview's injected styles stay scoped to this subtree\n inWrapper: true, // wrap pages in the gray \"paper on desk\" canvas, like Word\n // The side panel is narrow (and resizable), so don't pin pages to the fixed A4 width — that overflows\n // and clips the left edge. Let pages fill the available width and reflow; height follows content.\n ignoreWidth: true,\n ignoreHeight: true,\n breakPages: true, // honor page breaks → still separate page sheets, stacked vertically\n renderHeaders: true,\n renderFooters: true,\n renderFootnotes: true,\n renderEndnotes: true,\n useBase64URL: true, // inline embedded images as data URLs (no blob-URL lifecycle to manage)\n })) as ParsedDocxModel\n if (props.url !== url || docEl.value !== el) return\n let navigation\n try {\n navigation = buildDocxNavigation(document, el)\n } catch (error) {\n // A malformed/unsupported Word model must never turn an otherwise rendered document into an error page.\n console.warn('DOCX outline navigation unavailable:', error)\n navigation = { outline: [], pages: buildDocxPageNavigation(el) }\n }\n setItems(navigation.outline, navigation.pages)\n navView.value = navigation.outline.length ? 'outline' : 'pages'\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nonMounted(render)\nwatch(() => props.url, render)\nonBeforeUnmount(() => {\n if (docEl.value) docEl.value.innerHTML = ''\n})\n</script>\n\n<template>\n <div class=\"dx\">\n <DocxNavigationPane\n v-if=\"status === 'ready'\"\n v-model:collapsed=\"navCollapsed\"\n v-model:view=\"navView\"\n :outline-items=\"outlineItems\"\n :page-items=\"pageItems\"\n :active-outline-id=\"activeOutlineId\"\n :active-page-id=\"activePageId\"\n @navigate=\"navigate\"\n />\n <div class=\"dx__stage\">\n <p v-if=\"status === 'loading'\" class=\"dx__msg\">正在渲染 Word 文档…</p>\n <div v-else-if=\"status === 'error'\" class=\"dx__msg dx__msg--error\">\n <p>渲染失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dx__retry\" @click=\"render\">重试</button>\n </div>\n <!-- Always mounted (v-show) so docEl is available to docx-preview; hidden until render completes. -->\n <div v-show=\"status === 'ready'\" ref=\"docEl\" class=\"dx__doc\" />\n </div>\n </div>\n</template>\n\n<style scoped>\n.dx {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: row;\n background: var(--surface);\n container-type: inline-size;\n}\n.dx__stage {\n flex: 1 1 auto;\n min-width: 0;\n min-height: 0;\n display: flex;\n flex-direction: column;\n}\n.dx__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dx__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dx__retry {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dx__retry:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n/* Scroll region holding the rendered document. The pages keep their native Word appearance (white paper,\n * black text) for fidelity; we only retint the surrounding \"desk\" to the app's neutral surface so it sits\n * in the panel naturally in both light and dark themes. :deep reaches docx-preview's injected DOM. */\n.dx__doc {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n background: var(--surface-2);\n}\n.dx__doc :deep(.docx-wrapper) {\n background: var(--surface-2);\n padding: 22px 16px 40px;\n gap: 16px;\n}\n/* Each page: a white sheet with a soft shadow. With ignoreWidth the page reflows to the panel width, so\n * pin it to fill the wrapper (box-sizing so the doc's own margins stay inside the sheet, not overflowing). */\n.dx__doc :deep(.docx-wrapper > section.docx) {\n width: 100%;\n max-width: 100%;\n box-sizing: border-box;\n box-shadow: 0 2px 14px rgba(0, 0, 0, 0.18);\n margin-bottom: 0;\n}\n/* Some Word docs anchor tables as \"floating\" objects (OOXML `w:tblpPr` — a table pinned to a fixed spot on\n * the page with body text wrapping around it). docx-preview approximates that with `float: left` + a fixed\n * point width. On a full A4 page the table lands where Word put it, but in this narrow, width-reflowing\n * panel the floats stack unpredictably and the surrounding paragraphs wrap into the gaps beside them — so on\n * resize the layout scrambles and dark cell shading appears to streak across (the reported \"背景乱串 / 排版\n * 混乱\"). Since we've already chosen reflow over pixel-exact A4 fidelity (ignoreWidth + section width:100%),\n * force every table back to a normal full-width block that stacks top-to-bottom at any panel width, and let\n * long cell content wrap instead of overflowing. */\n.dx__doc :deep(section.docx table) {\n float: none !important;\n width: 100% !important;\n max-width: 100%;\n margin-left: 0 !important;\n margin-right: 0 !important;\n}\n.dx__doc :deep(section.docx td),\n.dx__doc :deep(section.docx th) {\n overflow-wrap: break-word;\n}\n/* The page's own side margins (this doc uses ~90pt) would swallow most of a narrow panel now that tables\n * fill the width, so cap the horizontal padding while keeping the top/bottom page margin intact. */\n.dx__doc :deep(section.docx) {\n padding-left: clamp(14px, 5%, 90pt) !important;\n padding-right: clamp(14px, 5%, 90pt) !important;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;;;;EAqBA,IAAM,IAAQ,GAQR,IAAS,EAAmB,SAAS,GACrC,IAAW,EAAW,EAAE,GAExB,IAAQ,EAA+B,IAAI,GAC3C,IAAU,EAAgC,SAAS,GACnD,IAAe,EAAW,EAAK,GAC/B,EAAE,iBAAc,cAAW,oBAAiB,iBAAc,aAAU,gBACxE,EAAqB,CAAK;EAE5B,eAAe,IAAwB;GACrC,IAAM,IAAK,EAAM;GACjB,IAAI,CAAC,EAAM,OAAO,CAAC,GAAI;GAIvB,AAHA,EAAO,QAAQ,WACf,EAAS,QAAQ,IACjB,EAAS,CAAC,GAAG,CAAC,CAAC,GACf,EAAG,YAAY;GACf,IAAM,IAAM,EAAM;GAClB,IAAI;IACF,IAAM,IAAM,MAAM,MAAM,CAAG;IAC3B,IAAI,CAAC,EAAI,IAAI,MAAU,MAAM,QAAQ,EAAI,QAAQ;IACjD,IAAM,IAAM,MAAM,EAAI,YAAY;IAElC,IAAI,EAAM,QAAQ,KAAO,EAAM,UAAU,GAAI;IAC7C,IAAM,IAAY,MAAM,EAAY,GAAK,GAAI,KAAA,GAAW;KACtD,WAAW;KACX,WAAW;KAGX,aAAa;KACb,cAAc;KACd,YAAY;KACZ,eAAe;KACf,eAAe;KACf,iBAAiB;KACjB,gBAAgB;KAChB,cAAc;IAChB,CAAC;IACD,IAAI,EAAM,QAAQ,KAAO,EAAM,UAAU,GAAI;IAC7C,IAAI;IACJ,IAAI;KACF,IAAa,EAAoB,GAAU,CAAE;IAC/C,SAAS,GAAO;KAGd,AADA,QAAQ,KAAK,wCAAwC,CAAK,GAC1D,IAAa;MAAE,SAAS,CAAC;MAAG,OAAO,EAAwB,CAAE;KAAE;IACjE;IAGA,AAFA,EAAS,EAAW,SAAS,EAAW,KAAK,GAC7C,EAAQ,QAAQ,EAAW,QAAQ,SAAS,YAAY,SACxD,EAAO,QAAQ;GACjB,SAAS,GAAG;IAEV,AADA,EAAS,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC1D,EAAO,QAAQ;GACjB;EACF;SAEA,EAAU,CAAM,GAChB,QAAY,EAAM,KAAK,CAAM,GAC7B,QAAsB;GACpB,AAAI,EAAM,UAAO,EAAM,MAAM,YAAY;EAC3C,CAAC,mBAIC,EAoBM,OApBN,GAoBM,CAlBI,EAAA,UAAM,WAAA,EAAA,GADd,EASE,GAAA;;GAPQ,WAAW,EAAA;2CAAY,QAAA;GACvB,MAAM,EAAA;sCAAO,QAAA;GACpB,iBAAe,EAAA,CAAA;GACf,cAAY,EAAA,CAAA;GACZ,qBAAmB,EAAA,CAAA;GACnB,kBAAgB,EAAA,CAAA;GAChB,YAAU,EAAA,CAAA;;;;;;;;;mBAEb,EAQM,OARN,GAQM,CAPK,EAAA,UAAM,aAAA,EAAA,GAAf,EAAgE,KAAhE,GAA+C,eAAa,KAC5C,EAAA,UAAM,WAAA,EAAA,GAAtB,EAGM,OAHN,GAGM,CAFJ,EAA0B,KAAA,MAAvB,UAAK,EAAG,EAAA,KAAQ,GAAA,CAAA,GACnB,EAAmE,UAAA;GAA3D,MAAK;GAAS,OAAM;GAAa,SAAO;KAAQ,IAAE,CAAA,CAAA,KAAA,EAAA,IAAA,EAAA,GAAA,EAG5D,EAA+D,OAAA;YAA1B;GAAJ,KAAI;GAAQ,OAAM;sBAAtC,EAAA,UAAM,OAAA,CAAA,CAAA,CAAA,CAAA,CAAA,CAAA"}
@@ -5,3 +5,5 @@ import t from "./DrawioViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-70c38c16"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=DrawioViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DrawioViewer.js","names":[],"sources":["../../src/components/DrawioViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { renderDiagram, type DiagramKind } from '@/lib/diagram'\n\n/**\n * Renders a .drawio (mxGraph) or .excalidraw scene as an inline SVG (see lib/diagram.ts — hand-rolled,\n * zero-dependency, best-effort). Consumes the text the side viewer already fetched (no second download),\n * like the subtitle/geo/csv renderers. Rendering is async (drawio diagrams are usually DEFLATE-compressed).\n * The produced SVG is OUR markup built from the parsed geometry, so v-html is safe; on a parse failure the\n * user can fall back to the source toggle (raw XML/JSON) or download the file.\n */\nconst props = defineProps<{ text: string; name: string; kind: DiagramKind }>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst svg = ref('')\nconst errorMsg = ref('')\n\nasync function render(): Promise<void> {\n status.value = 'loading'\n errorMsg.value = ''\n svg.value = ''\n const t = props.text\n try {\n const out = await renderDiagram(t, props.kind)\n if (props.text !== t) return // a newer text superseded us mid-render\n svg.value = out\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nwatch(() => [props.text, props.kind], render, { immediate: true })\n</script>\n\n<template>\n <div class=\"dgm\">\n <p v-if=\"status === 'loading'\" class=\"dgm__msg\">正在渲染图表…</p>\n <div v-else-if=\"status === 'error'\" class=\"dgm__msg dgm__msg--error\">\n <p>无法渲染该图表:{{ errorMsg }}</p>\n <p class=\"dgm__hint\">\n 可切换到「源码」查看原始\n {{ kind === 'excalidraw' ? 'JSON' : 'XML' }},或下载后用对应工具打开。\n </p>\n </div>\n <!-- svg is our own generated markup (no upstream HTML), safe to inline -->\n <div v-else class=\"dgm__scroll\" v-html=\"svg\" />\n </div>\n</template>\n\n<style scoped>\n.dgm {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-2);\n}\n.dgm__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n}\n/* Contain the WHOLE diagram within the pane on BOTH axes so tall or wide diagrams never overflow the\n container (a tall flowchart used to fit-width and run off the bottom). The SVG keeps its aspect ratio via\n its viewBox and shrinks to fit; overflow:auto stays only as a safety net. */\n.dgm__scroll :deep(svg) {\n max-width: 100%;\n max-height: 100%;\n width: auto;\n height: auto;\n}\n.dgm__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dgm__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n color: var(--danger);\n}\n.dgm__hint {\n color: var(--faint);\n font-size: 12px;\n}\n</style>\n"],"mappings":""}
@@ -36,3 +36,5 @@ var c = { class: "dgm" }, l = {
36
36
  });
37
37
  //#endregion
38
38
  export { p as default };
39
+
40
+ //# sourceMappingURL=DrawioViewer.vue_vue_type_script_setup_true_lang.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DrawioViewer.vue_vue_type_script_setup_true_lang.js","names":[],"sources":["../../src/components/DrawioViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { ref, watch } from 'vue'\nimport { renderDiagram, type DiagramKind } from '@/lib/diagram'\n\n/**\n * Renders a .drawio (mxGraph) or .excalidraw scene as an inline SVG (see lib/diagram.ts — hand-rolled,\n * zero-dependency, best-effort). Consumes the text the side viewer already fetched (no second download),\n * like the subtitle/geo/csv renderers. Rendering is async (drawio diagrams are usually DEFLATE-compressed).\n * The produced SVG is OUR markup built from the parsed geometry, so v-html is safe; on a parse failure the\n * user can fall back to the source toggle (raw XML/JSON) or download the file.\n */\nconst props = defineProps<{ text: string; name: string; kind: DiagramKind }>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst svg = ref('')\nconst errorMsg = ref('')\n\nasync function render(): Promise<void> {\n status.value = 'loading'\n errorMsg.value = ''\n svg.value = ''\n const t = props.text\n try {\n const out = await renderDiagram(t, props.kind)\n if (props.text !== t) return // a newer text superseded us mid-render\n svg.value = out\n status.value = 'ready'\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\nwatch(() => [props.text, props.kind], render, { immediate: true })\n</script>\n\n<template>\n <div class=\"dgm\">\n <p v-if=\"status === 'loading'\" class=\"dgm__msg\">正在渲染图表…</p>\n <div v-else-if=\"status === 'error'\" class=\"dgm__msg dgm__msg--error\">\n <p>无法渲染该图表:{{ errorMsg }}</p>\n <p class=\"dgm__hint\">\n 可切换到「源码」查看原始\n {{ kind === 'excalidraw' ? 'JSON' : 'XML' }},或下载后用对应工具打开。\n </p>\n </div>\n <!-- svg is our own generated markup (no upstream HTML), safe to inline -->\n <div v-else class=\"dgm__scroll\" v-html=\"svg\" />\n </div>\n</template>\n\n<style scoped>\n.dgm {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface-2);\n}\n.dgm__scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n overscroll-behavior: contain;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 16px;\n}\n/* Contain the WHOLE diagram within the pane on BOTH axes so tall or wide diagrams never overflow the\n container (a tall flowchart used to fit-width and run off the bottom). The SVG keeps its aspect ratio via\n its viewBox and shrinks to fit; overflow:auto stays only as a safety net. */\n.dgm__scroll :deep(svg) {\n max-width: 100%;\n max-height: 100%;\n width: auto;\n height: auto;\n}\n.dgm__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dgm__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 8px;\n color: var(--danger);\n}\n.dgm__hint {\n color: var(--faint);\n font-size: 12px;\n}\n</style>\n"],"mappings":";;;;;;;;;;;;;;;;;EAWA,IAAM,IAAQ,GAGR,IAAS,EAAY,SAAS,GAC9B,IAAM,EAAI,EAAE,GACZ,IAAW,EAAI,EAAE;EAEvB,eAAe,IAAwB;GAGrC,AAFA,EAAO,QAAQ,WACf,EAAS,QAAQ,IACjB,EAAI,QAAQ;GACZ,IAAM,IAAI,EAAM;GAChB,IAAI;IACF,IAAM,IAAM,MAAM,EAAc,GAAG,EAAM,IAAI;IAC7C,IAAI,EAAM,SAAS,GAAG;IAEtB,AADA,EAAI,QAAQ,GACZ,EAAO,QAAQ;GACjB,SAAS,GAAG;IAEV,AADA,EAAS,QAAQ,aAAa,QAAQ,EAAE,UAAU,OAAO,CAAC,GAC1D,EAAO,QAAQ;GACjB;EACF;SAEA,QAAY,CAAC,EAAM,MAAM,EAAM,IAAI,GAAG,GAAQ,EAAE,WAAW,GAAK,CAAC,mBAI/D,EAWM,OAXN,GAWM,CAVK,EAAA,UAAM,aAAA,EAAA,GAAf,EAA2D,KAA3D,GAAgD,SAAO,KACvC,EAAA,UAAM,WAAA,EAAA,GAAtB,EAMM,OANN,GAMM,CALJ,EAA6B,KAAA,MAA1B,aAAQ,EAAG,EAAA,KAAQ,GAAA,CAAA,GACtB,EAGI,KAHJ,GAAqB,mBAEnB,EAAG,EAAA,SAAI,eAAA,SAAA,KAAA,IAAqC,kBAC9C,CAAA,CAAA,CAAA,MAAA,EAAA,GAGF,EAA+C,OAAA;;GAAnC,OAAM;GAAc,WAAQ,EAAA"}
@@ -5,3 +5,5 @@ import t from "./DxfViewer.vue_vue_type_script_setup_true_lang.js";
5
5
  var n = /*#__PURE__*/ e(t, [["__scopeId", "data-v-b18ead98"]]);
6
6
  //#endregion
7
7
  export { n as default };
8
+
9
+ //# sourceMappingURL=DxfViewer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DxfViewer.js","names":[],"sources":["../../src/components/DxfViewer.vue"],"sourcesContent":["<script setup lang=\"ts\">\nimport { computed, reactive, ref, watch } from 'vue'\nimport { parseDxf, type DxfPrim, type ParsedDxf } from '@/lib/dxf'\n\n/**\n * Side-viewer for AutoCAD DXF drawings (.dxf). Fetches the text, parses it in-browser (see lib/dxf.ts —\n * hand-rolled, zero-dependency) into drawable primitives, and draws them as an SVG. DXF Y is up, so the whole\n * drawing is rendered inside a y-flip group (text is flipped back locally to stay upright). Drag to pan, wheel\n * to zoom; toggle layers in the list. Binary DXF isn't supported.\n */\nconst props = defineProps<{ url?: string; name: string; size?: number }>()\n\ntype Status = 'loading' | 'ready' | 'error'\nconst status = ref<Status>('loading')\nconst errorMsg = ref('')\nconst parsed = ref<ParsedDxf | null>(null)\nconst hiddenLayers = reactive(new Set<string>())\n\nasync function load(): Promise<void> {\n if (!props.url) return\n status.value = 'loading'\n errorMsg.value = ''\n parsed.value = null\n hiddenLayers.clear()\n try {\n const res = await fetch(props.url)\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const buf = await res.arrayBuffer()\n const head = new TextDecoder('latin1').decode(new Uint8Array(buf.slice(0, 22)))\n if (head.startsWith('AutoCAD Binary DXF')) {\n throw new Error('暂不支持二进制 DXF(请用 ASCII DXF)')\n }\n const text = new TextDecoder('utf-8').decode(buf)\n const r = parseDxf(text)\n if (!r.prims.length || !r.bounds) throw new Error('未解析到可绘制的图元')\n parsed.value = r\n status.value = 'ready'\n fit()\n } catch (e) {\n errorMsg.value = e instanceof Error ? e.message : String(e)\n status.value = 'error'\n }\n}\n\n// Primitives grouped by layer (so a layer toggles as one <g>).\nconst byLayer = computed(() => {\n const groups = new Map<string, DxfPrim[]>()\n for (const p of parsed.value?.prims ?? []) {\n const g = groups.get(p.layer)\n if (g) g.push(p)\n else groups.set(p.layer, [p])\n }\n return groups\n})\n\n// ── viewBox (pan/zoom), in the FLIPPED coordinate space (content group is scaled y:-1) ──────────\nconst vb = ref({ x: 0, y: 0, w: 1, h: 1 })\n\nfunction fit(): void {\n const b = parsed.value?.bounds\n if (!b) return\n let w = b.maxX - b.minX\n let h = b.maxY - b.minY\n if (!(w > 0)) w = 1\n if (!(h > 0)) h = 1\n const pad = Math.max(w, h) * 0.05\n // flipped: visible y ranges over [-maxY, -minY]\n vb.value = { x: b.minX - pad, y: -b.maxY - pad, w: w + 2 * pad, h: h + 2 * pad }\n}\n\nconst viewBox = computed(() => `${vb.value.x} ${vb.value.y} ${vb.value.w} ${vb.value.h}`)\n\nfunction onWheel(e: WheelEvent): void {\n const f = e.deltaY > 0 ? 1.15 : 1 / 1.15\n const cx = vb.value.x + vb.value.w / 2\n const cy = vb.value.y + vb.value.h / 2\n const nw = vb.value.w * f\n const nh = vb.value.h * f\n vb.value = { x: cx - nw / 2, y: cy - nh / 2, w: nw, h: nh }\n}\n\nlet panning = false\nlet lastX = 0\nlet lastY = 0\nlet svgEl: SVGSVGElement | null = null\nfunction onDown(e: PointerEvent): void {\n panning = true\n lastX = e.clientX\n lastY = e.clientY\n svgEl = e.currentTarget as SVGSVGElement\n svgEl.setPointerCapture(e.pointerId)\n}\nfunction onMove(e: PointerEvent): void {\n if (!panning || !svgEl) return\n const rect = svgEl.getBoundingClientRect()\n const dx = ((e.clientX - lastX) * vb.value.w) / rect.width\n const dy = ((e.clientY - lastY) * vb.value.h) / rect.height\n vb.value = { ...vb.value, x: vb.value.x - dx, y: vb.value.y - dy }\n lastX = e.clientX\n lastY = e.clientY\n}\nfunction onUp(e: PointerEvent): void {\n panning = false\n try {\n svgEl?.releasePointerCapture(e.pointerId)\n } catch {\n /* already released */\n }\n}\n\nfunction toggleLayer(name: string): void {\n if (hiddenLayers.has(name)) hiddenLayers.delete(name)\n else hiddenLayers.add(name)\n}\n\n// ── primitive → SVG geometry helpers ──────────────────────────────────────────────────────────\nconst rnd = (n: number): number => Math.round(n * 1000) / 1000\nfunction polyPoints(pts: [number, number][]): string {\n return pts.map((p) => `${rnd(p[0])},${rnd(p[1])}`).join(' ')\n}\n// Tessellate an arc (angles in degrees, CCW) into a path, avoiding SVG arc-flag/flip pitfalls.\nfunction arcPath(cx: number, cy: number, r: number, a0: number, a1: number): string {\n let span = a1 - a0\n while (span <= 0) span += 360\n while (span > 360) span -= 360\n const steps = Math.max(2, Math.min(180, Math.ceil((span / 360) * 96)))\n let d = ''\n for (let i = 0; i <= steps; i++) {\n const a = ((a0 + (span * i) / steps) * Math.PI) / 180\n d += `${i ? 'L' : 'M'} ${rnd(cx + r * Math.cos(a))} ${rnd(cy + r * Math.sin(a))} `\n }\n return d\n}\n// Point size in user units, scaled to the current zoom so a POINT marker stays ~constant on screen.\nconst ptR = computed(() => Math.max(vb.value.w, vb.value.h) * 0.004)\n\nconst summary = computed(() => {\n const p = parsed.value\n if (!p) return ''\n const total = Object.values(p.counts).reduce((a, b) => a + b, 0)\n return `${total} 实体 · ${p.layers.length} 图层`\n})\n\nwatch(() => props.url, load, { immediate: true })\n</script>\n\n<template>\n <div class=\"dxf\">\n <p v-if=\"status === 'loading'\" class=\"dxf__msg\">正在解析 DXF…</p>\n <div v-else-if=\"status === 'error'\" class=\"dxf__msg dxf__msg--error\">\n <p>解析失败:{{ errorMsg }}</p>\n <button type=\"button\" class=\"dxf__btn\" @click=\"load\">重试</button>\n </div>\n\n <template v-else-if=\"parsed\">\n <div class=\"dxf__bar\">\n <span class=\"dxf__badge\">DXF</span>\n <span class=\"dxf__dim\">{{ summary }}</span>\n <span class=\"dxf__spacer\" />\n <button type=\"button\" class=\"dxf__reset\" @click=\"fit\">重置视图</button>\n </div>\n\n <div class=\"dxf__stage\">\n <svg\n class=\"dxf__svg\"\n :viewBox=\"viewBox\"\n preserveAspectRatio=\"xMidYMid meet\"\n @wheel.prevent=\"onWheel\"\n @pointerdown=\"onDown\"\n @pointermove=\"onMove\"\n @pointerup=\"onUp\"\n @pointercancel=\"onUp\"\n >\n <!-- DXF Y is up → flip the whole drawing; text is re-flipped locally to stay upright. -->\n <g transform=\"matrix(1 0 0 -1 0 0)\">\n <g v-for=\"[layer, prims] in byLayer\" v-show=\"!hiddenLayers.has(layer)\" :key=\"layer\">\n <template v-for=\"(p, i) in prims\" :key=\"i\">\n <line\n v-if=\"p.kind === 'line'\"\n :x1=\"rnd(p.x1)\"\n :y1=\"rnd(p.y1)\"\n :x2=\"rnd(p.x2)\"\n :y2=\"rnd(p.y2)\"\n :stroke=\"p.color\"\n vector-effect=\"non-scaling-stroke\"\n />\n <polygon\n v-else-if=\"p.kind === 'polyline' && p.closed\"\n :points=\"polyPoints(p.pts)\"\n fill=\"none\"\n :stroke=\"p.color\"\n vector-effect=\"non-scaling-stroke\"\n />\n <polyline\n v-else-if=\"p.kind === 'polyline'\"\n :points=\"polyPoints(p.pts)\"\n fill=\"none\"\n :stroke=\"p.color\"\n vector-effect=\"non-scaling-stroke\"\n />\n <circle\n v-else-if=\"p.kind === 'circle'\"\n :cx=\"rnd(p.cx)\"\n :cy=\"rnd(p.cy)\"\n :r=\"rnd(p.r)\"\n fill=\"none\"\n :stroke=\"p.color\"\n vector-effect=\"non-scaling-stroke\"\n />\n <path\n v-else-if=\"p.kind === 'arc'\"\n :d=\"arcPath(p.cx, p.cy, p.r, p.a0, p.a1)\"\n fill=\"none\"\n :stroke=\"p.color\"\n vector-effect=\"non-scaling-stroke\"\n />\n <polygon\n v-else-if=\"p.kind === 'polygon'\"\n :points=\"polyPoints(p.pts)\"\n :fill=\"p.color\"\n fill-opacity=\"0.5\"\n :stroke=\"p.color\"\n vector-effect=\"non-scaling-stroke\"\n />\n <circle\n v-else-if=\"p.kind === 'point'\"\n :cx=\"rnd(p.x)\"\n :cy=\"rnd(p.y)\"\n :r=\"ptR\"\n :fill=\"p.color\"\n />\n <g\n v-else-if=\"p.kind === 'text'\"\n :transform=\"`translate(${rnd(p.x)},${rnd(p.y)}) scale(1,-1) rotate(${rnd(-p.rot)})`\"\n >\n <text\n class=\"dxf__text\"\n :font-size=\"rnd(p.h)\"\n :fill=\"p.color\"\n :text-anchor=\"p.align === 'c' ? 'middle' : p.align === 'r' ? 'end' : 'start'\"\n >\n <tspan\n v-for=\"(ln, li) in p.text.split('\\n')\"\n :key=\"li\"\n x=\"0\"\n :dy=\"li === 0 ? 0 : p.h * 1.25\"\n >\n {{ ln }}\n </tspan>\n </text>\n </g>\n </template>\n </g>\n </g>\n </svg>\n </div>\n\n <ul v-if=\"parsed.layers.length\" class=\"dxf__layers\">\n <li v-for=\"l in parsed.layers\" :key=\"l.name\">\n <button\n type=\"button\"\n class=\"dxf__layer\"\n :class=\"{ 'dxf__layer--off': hiddenLayers.has(l.name) }\"\n @click=\"toggleLayer(l.name)\"\n >\n <span class=\"dxf__swatch\" :style=\"{ background: l.color }\" />\n <span class=\"dxf__lname\">{{ l.name }}</span>\n <span class=\"dxf__eye\">{{ hiddenLayers.has(l.name) ? '隐藏' : '显示' }}</span>\n </button>\n </li>\n </ul>\n </template>\n </div>\n</template>\n\n<style scoped>\n.dxf {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n flex-direction: column;\n background: var(--surface);\n}\n.dxf__msg {\n padding: 20px 16px;\n color: var(--muted);\n font-size: 13px;\n}\n.dxf__msg--error {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 10px;\n color: var(--danger);\n}\n.dxf__btn {\n font-family: inherit;\n font-size: 12.5px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 8px;\n padding: 5px 12px;\n cursor: pointer;\n}\n.dxf__btn:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.dxf__bar {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n padding: 8px 12px;\n border-bottom: 1px solid var(--border);\n background: var(--surface);\n font-size: 12px;\n color: var(--muted);\n}\n.dxf__badge {\n padding: 2px 9px;\n border-radius: 999px;\n background: var(--surface-2);\n color: var(--accent);\n font-weight: 600;\n}\n.dxf__dim {\n color: var(--faint);\n}\n.dxf__spacer {\n flex: 1 1 auto;\n}\n.dxf__reset {\n font-family: inherit;\n font-size: 12px;\n color: var(--text);\n background: var(--surface);\n border: 1px solid var(--border-2);\n border-radius: 7px;\n padding: 4px 10px;\n cursor: pointer;\n}\n.dxf__reset:hover {\n border-color: var(--accent);\n color: var(--accent);\n}\n\n.dxf__stage {\n flex: 1 1 auto;\n min-height: 0;\n display: flex;\n background: var(--surface-2);\n}\n.dxf__svg {\n width: 100%;\n height: 100%;\n touch-action: none;\n cursor: grab;\n}\n.dxf__svg:active {\n cursor: grabbing;\n}\n.dxf__text {\n font-family: var(--font-sans, sans-serif);\n dominant-baseline: alphabetic;\n}\n\n.dxf__layers {\n list-style: none;\n margin: 0;\n padding: 4px 0;\n flex-shrink: 0;\n max-height: 30%;\n overflow: auto;\n overscroll-behavior: contain;\n border-top: 1px solid var(--border);\n}\n.dxf__layer {\n display: flex;\n align-items: center;\n gap: 9px;\n width: 100%;\n padding: 6px 16px;\n background: transparent;\n border: none;\n font-family: inherit;\n text-align: left;\n color: var(--text);\n cursor: pointer;\n}\n.dxf__layer:hover {\n background: var(--surface-2);\n}\n.dxf__layer--off {\n opacity: 0.45;\n}\n.dxf__swatch {\n width: 12px;\n height: 12px;\n flex-shrink: 0;\n border-radius: 3px;\n border: 1px solid var(--border-2);\n}\n.dxf__lname {\n flex: 1 1 auto;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12.5px;\n}\n.dxf__eye {\n flex-shrink: 0;\n font-size: 11px;\n color: var(--faint);\n}\n</style>\n"],"mappings":""}
@@ -237,3 +237,5 @@ var ee = { class: "dxf" }, v = {
237
237
  });
238
238
  //#endregion
239
239
  export { F as default };
240
+
241
+ //# sourceMappingURL=DxfViewer.vue_vue_type_script_setup_true_lang.js.map