@imsovikde/source-grab 1.0.1

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 (481) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +21 -0
  3. package/README.md +281 -0
  4. package/background/capture-registry.js +306 -0
  5. package/background/debugger-capture.js +171 -0
  6. package/background/download-engine.js +316 -0
  7. package/background/service-worker.js +426 -0
  8. package/content-scripts/auto-scroll.js +51 -0
  9. package/content-scripts/isolated-bridge.js +36 -0
  10. package/content-scripts/main-world-hook.js +278 -0
  11. package/design-tokens.css +67 -0
  12. package/icons/2-double-tap.svg +12 -0
  13. package/icons/2-scroll-left.svg +11 -0
  14. package/icons/2-scroll-right.svg +11 -0
  15. package/icons/2-scroll-up-1.svg +11 -0
  16. package/icons/2-scroll-up.svg +11 -0
  17. package/icons/2-tap-1.svg +15 -0
  18. package/icons/2-tap.svg +11 -0
  19. package/icons/2-touch-hold.svg +13 -0
  20. package/icons/3-scroll-down.svg +11 -0
  21. package/icons/3-scroll-up.svg +11 -0
  22. package/icons/3-tap-scroll.svg +13 -0
  23. package/icons/3-tap.svg +11 -0
  24. package/icons/analytics.svg +10 -0
  25. package/icons/anchor.svg +10 -0
  26. package/icons/apple.svg +11 -0
  27. package/icons/arrow-circle-down.svg +4 -0
  28. package/icons/arrow-circle-left.svg +4 -0
  29. package/icons/arrow-circle-right.svg +4 -0
  30. package/icons/arrow-circle-up.svg +4 -0
  31. package/icons/arrow-down-2.svg +3 -0
  32. package/icons/arrow-down.svg +11 -0
  33. package/icons/arrow-left.svg +3 -0
  34. package/icons/arrow-ne.svg +3 -0
  35. package/icons/arrow-nw.svg +3 -0
  36. package/icons/arrow-right.svg +3 -0
  37. package/icons/arrow-se.svg +3 -0
  38. package/icons/arrow-single-down.svg +3 -0
  39. package/icons/arrow-single-left.svg +3 -0
  40. package/icons/arrow-single-right.svg +3 -0
  41. package/icons/arrow-single-up.svg +3 -0
  42. package/icons/arrow-square-down.svg +4 -0
  43. package/icons/arrow-square-left.svg +4 -0
  44. package/icons/arrow-square-right.svg +4 -0
  45. package/icons/arrow-square-up.svg +4 -0
  46. package/icons/arrow-sw.svg +3 -0
  47. package/icons/arrow-up-2.svg +3 -0
  48. package/icons/arrow-up.svg +11 -0
  49. package/icons/arrow.svg +10 -0
  50. package/icons/at-the-rate.svg +10 -0
  51. package/icons/automation.svg +4 -0
  52. package/icons/backward.svg +11 -0
  53. package/icons/bag-2.svg +3 -0
  54. package/icons/bag.svg +3 -0
  55. package/icons/balloon-2.svg +10 -0
  56. package/icons/balloon.svg +10 -0
  57. package/icons/bank.svg +10 -0
  58. package/icons/basket.svg +6 -0
  59. package/icons/behance.svg +12 -0
  60. package/icons/bell-2.svg +10 -0
  61. package/icons/bell.svg +10 -0
  62. package/icons/bill.svg +19 -0
  63. package/icons/bisexual.svg +10 -0
  64. package/icons/blood-bag.svg +10 -0
  65. package/icons/blood.svg +12 -0
  66. package/icons/bluetooth.svg +10 -0
  67. package/icons/bookmark.svg +10 -0
  68. package/icons/bot.svg +6 -0
  69. package/icons/bottle.svg +15 -0
  70. package/icons/box-2.svg +6 -0
  71. package/icons/box.svg +3 -0
  72. package/icons/bug.svg +3 -0
  73. package/icons/bulb.svg +10 -0
  74. package/icons/burger.svg +16 -0
  75. package/icons/bus.svg +8 -0
  76. package/icons/cake.svg +10 -0
  77. package/icons/calculator.svg +15 -0
  78. package/icons/calendar.svg +15 -0
  79. package/icons/call.svg +12 -0
  80. package/icons/camera-2.svg +10 -0
  81. package/icons/camera.svg +12 -0
  82. package/icons/candy.svg +10 -0
  83. package/icons/car.svg +8 -0
  84. package/icons/card-2.svg +9 -0
  85. package/icons/card-3.svg +6 -0
  86. package/icons/card.svg +4 -0
  87. package/icons/cart-add.svg +4 -0
  88. package/icons/cart-delete.svg +4 -0
  89. package/icons/cart-remove.svg +4 -0
  90. package/icons/cash.svg +11 -0
  91. package/icons/caution.svg +12 -0
  92. package/icons/center-align-2.svg +10 -0
  93. package/icons/center-align.svg +13 -0
  94. package/icons/checklist.svg +4 -0
  95. package/icons/chevrons-down.svg +4 -0
  96. package/icons/chevrons-left.svg +4 -0
  97. package/icons/chevrons-right.svg +4 -0
  98. package/icons/chevrons-up.svg +4 -0
  99. package/icons/chip.svg +4 -0
  100. package/icons/clap.svg +14 -0
  101. package/icons/clock.svg +11 -0
  102. package/icons/cloud-down.svg +10 -0
  103. package/icons/cloud-up.svg +10 -0
  104. package/icons/cloud.svg +10 -0
  105. package/icons/cloudy-day.svg +15 -0
  106. package/icons/cloudy-night.svg +10 -0
  107. package/icons/codepen.svg +10 -0
  108. package/icons/coffee-cup-1.svg +7 -0
  109. package/icons/coffee-cup-2.svg +6 -0
  110. package/icons/coin-2.svg +11 -0
  111. package/icons/coin.svg +11 -0
  112. package/icons/confused-emoji.svg +8 -0
  113. package/icons/conveyor-belt.svg +8 -0
  114. package/icons/cookie.svg +14 -0
  115. package/icons/cool-emoji.svg +4 -0
  116. package/icons/copy.svg +10 -0
  117. package/icons/crop.svg +10 -0
  118. package/icons/cross.svg +10 -0
  119. package/icons/crown.svg +14 -0
  120. package/icons/crying-emoji.svg +4 -0
  121. package/icons/cut.svg +10 -0
  122. package/icons/cutlery.svg +11 -0
  123. package/icons/dashboard-2.svg +10 -0
  124. package/icons/dashboard-3.svg +11 -0
  125. package/icons/dashboard-4.svg +11 -0
  126. package/icons/dashboard.svg +11 -0
  127. package/icons/delete.svg +12 -0
  128. package/icons/diamond.svg +10 -0
  129. package/icons/dish.svg +10 -0
  130. package/icons/doc-add.svg +11 -0
  131. package/icons/doc-remove.svg +11 -0
  132. package/icons/doc.svg +15 -0
  133. package/icons/dollar-2.svg +11 -0
  134. package/icons/dollar.svg +10 -0
  135. package/icons/double-tap.svg +12 -0
  136. package/icons/download.svg +11 -0
  137. package/icons/drawer.svg +10 -0
  138. package/icons/dribbble.svg +10 -0
  139. package/icons/drink.svg +10 -0
  140. package/icons/dropbox.svg +10 -0
  141. package/icons/dropper.svg +10 -0
  142. package/icons/egg.svg +11 -0
  143. package/icons/eject.svg +12 -0
  144. package/icons/eraser.svg +10 -0
  145. package/icons/euro.svg +11 -0
  146. package/icons/facebook-2.svg +10 -0
  147. package/icons/facebook.svg +3 -0
  148. package/icons/factory.svg +7 -0
  149. package/icons/fast-forward.svg +10 -0
  150. package/icons/fast-rewind.svg +10 -0
  151. package/icons/fb-messenger.svg +11 -0
  152. package/icons/femaile.svg +10 -0
  153. package/icons/file-attachment.svg +10 -0
  154. package/icons/file-audio.svg +13 -0
  155. package/icons/file-code.svg +13 -0
  156. package/icons/file-contract.svg +15 -0
  157. package/icons/file-csv.svg +13 -0
  158. package/icons/file-figma.svg +14 -0
  159. package/icons/file-form.svg +15 -0
  160. package/icons/file-image-2.svg +12 -0
  161. package/icons/file-image.svg +12 -0
  162. package/icons/file-invoice.svg +15 -0
  163. package/icons/file-jpg.svg +13 -0
  164. package/icons/file-list.svg +17 -0
  165. package/icons/file-mov.svg +13 -0
  166. package/icons/file-mp4.svg +13 -0
  167. package/icons/file-notes.svg +12 -0
  168. package/icons/file-pdf.svg +13 -0
  169. package/icons/file-png.svg +13 -0
  170. package/icons/file-spreadsheet.svg +11 -0
  171. package/icons/file-svg.svg +13 -0
  172. package/icons/file-text.svg +14 -0
  173. package/icons/file-vector.svg +11 -0
  174. package/icons/file-zip.svg +10 -0
  175. package/icons/file.svg +10 -0
  176. package/icons/filter.svg +10 -0
  177. package/icons/fire.svg +5 -0
  178. package/icons/first-aid.svg +11 -0
  179. package/icons/firstaid-2.svg +12 -0
  180. package/icons/flag-2.svg +10 -0
  181. package/icons/flag.svg +10 -0
  182. package/icons/flashlight.svg +8 -0
  183. package/icons/flask-round.svg +10 -0
  184. package/icons/flask.svg +10 -0
  185. package/icons/flip.svg +12 -0
  186. package/icons/floppy.svg +11 -0
  187. package/icons/folded-hands.svg +12 -0
  188. package/icons/folder-add.svg +11 -0
  189. package/icons/folder-delete.svg +11 -0
  190. package/icons/folder-empty.svg +10 -0
  191. package/icons/folder-remove.svg +11 -0
  192. package/icons/folder.svg +10 -0
  193. package/icons/fork.svg +10 -0
  194. package/icons/forward.svg +11 -0
  195. package/icons/frame.svg +11 -0
  196. package/icons/franc.svg +11 -0
  197. package/icons/free-drag.svg +11 -0
  198. package/icons/gay.svg +10 -0
  199. package/icons/genderless.svg +10 -0
  200. package/icons/gift.svg +10 -0
  201. package/icons/globe.svg +10 -0
  202. package/icons/google.svg +10 -0
  203. package/icons/grid-2.svg +10 -0
  204. package/icons/grid.svg +10 -0
  205. package/icons/grinning-squinting-emoji.svg +6 -0
  206. package/icons/guitar.svg +10 -0
  207. package/icons/hand-truck.svg +5 -0
  208. package/icons/hand.svg +10 -0
  209. package/icons/happy-emoji.svg +8 -0
  210. package/icons/headphone.svg +10 -0
  211. package/icons/heart-beat.svg +10 -0
  212. package/icons/heart-eyes-emoji.svg +6 -0
  213. package/icons/heart.svg +10 -0
  214. package/icons/hetro.svg +10 -0
  215. package/icons/hide.svg +12 -0
  216. package/icons/home-1.svg +10 -0
  217. package/icons/home.svg +10 -0
  218. package/icons/horizontal-scroll.svg +12 -0
  219. package/icons/hot-air-balloon.svg +3 -0
  220. package/icons/ice-cream.svg +10 -0
  221. package/icons/icon.svg +12 -0
  222. package/icons/info.svg +5 -0
  223. package/icons/injection.svg +10 -0
  224. package/icons/instagram.svg +12 -0
  225. package/icons/intersex.svg +10 -0
  226. package/icons/key.svg +11 -0
  227. package/icons/laugh-emoji.svg +8 -0
  228. package/icons/layer.svg +10 -0
  229. package/icons/left-align-2.svg +12 -0
  230. package/icons/left-align.svg +13 -0
  231. package/icons/lesbian.svg +10 -0
  232. package/icons/link.svg +11 -0
  233. package/icons/linkedin.svg +13 -0
  234. package/icons/list.svg +15 -0
  235. package/icons/location-pin.svg +10 -0
  236. package/icons/location.svg +4 -0
  237. package/icons/lock.svg +11 -0
  238. package/icons/login.svg +11 -0
  239. package/icons/logout.svg +11 -0
  240. package/icons/lungs.svg +10 -0
  241. package/icons/magic-wand.svg +13 -0
  242. package/icons/mail-gift.svg +10 -0
  243. package/icons/mail-open.svg +10 -0
  244. package/icons/mail-surprise.svg +10 -0
  245. package/icons/mail.svg +10 -0
  246. package/icons/male.svg +10 -0
  247. package/icons/map.svg +13 -0
  248. package/icons/maximize.svg +13 -0
  249. package/icons/medical-logo.svg +10 -0
  250. package/icons/megaphone.svg +10 -0
  251. package/icons/menu-2.svg +12 -0
  252. package/icons/menu.svg +12 -0
  253. package/icons/message-2.svg +13 -0
  254. package/icons/message.svg +13 -0
  255. package/icons/mic-2.svg +11 -0
  256. package/icons/mic.svg +10 -0
  257. package/icons/microscope.svg +10 -0
  258. package/icons/minimize.svg +13 -0
  259. package/icons/money-plant.svg +11 -0
  260. package/icons/move.svg +10 -0
  261. package/icons/movie-clapper.svg +13 -0
  262. package/icons/multi-touch.svg +11 -0
  263. package/icons/music-2.svg +10 -0
  264. package/icons/music-3.svg +10 -0
  265. package/icons/music.svg +10 -0
  266. package/icons/mute.svg +11 -0
  267. package/icons/navigation-2.svg +10 -0
  268. package/icons/navigation.svg +10 -0
  269. package/icons/night.svg +10 -0
  270. package/icons/non-binary-transgender.svg +10 -0
  271. package/icons/note.svg +11 -0
  272. package/icons/offer.svg +9 -0
  273. package/icons/ok.svg +10 -0
  274. package/icons/other-gender.svg +10 -0
  275. package/icons/page-move.svg +11 -0
  276. package/icons/paint-brush-2.svg +10 -0
  277. package/icons/paint-brush.svg +10 -0
  278. package/icons/paint-bucket-2.svg +10 -0
  279. package/icons/paint-bucket.svg +10 -0
  280. package/icons/paint-roller.svg +10 -0
  281. package/icons/paper-clip-2.svg +10 -0
  282. package/icons/paper-clip.svg +10 -0
  283. package/icons/paste.svg +10 -0
  284. package/icons/pause.svg +11 -0
  285. package/icons/paypal.svg +10 -0
  286. package/icons/pen-tool.svg +10 -0
  287. package/icons/pen.svg +10 -0
  288. package/icons/pencil-2.svg +10 -0
  289. package/icons/pencil-3.svg +11 -0
  290. package/icons/pencil-ruler.svg +11 -0
  291. package/icons/pencil.svg +10 -0
  292. package/icons/phone-setting.svg +12 -0
  293. package/icons/phone.svg +11 -0
  294. package/icons/photo.svg +11 -0
  295. package/icons/pie.svg +11 -0
  296. package/icons/piggy-bank.svg +13 -0
  297. package/icons/pills.svg +10 -0
  298. package/icons/pin.svg +10 -0
  299. package/icons/pinterest.svg +10 -0
  300. package/icons/pizza-2.svg +10 -0
  301. package/icons/pizza.svg +33 -0
  302. package/icons/plane.svg +3 -0
  303. package/icons/play.svg +10 -0
  304. package/icons/png.svg +11 -0
  305. package/icons/point-down.svg +10 -0
  306. package/icons/point-left.svg +10 -0
  307. package/icons/point-right.svg +3 -0
  308. package/icons/point-up.svg +10 -0
  309. package/icons/popsicle.svg +10 -0
  310. package/icons/pound.svg +11 -0
  311. package/icons/product-hunt.svg +10 -0
  312. package/icons/puzzle-2.svg +10 -0
  313. package/icons/puzzle.svg +10 -0
  314. package/icons/qr.svg +21 -0
  315. package/icons/question-2.svg +11 -0
  316. package/icons/question.svg +11 -0
  317. package/icons/rain-heavy.svg +17 -0
  318. package/icons/rain-light.svg +13 -0
  319. package/icons/record.svg +11 -0
  320. package/icons/rectangle.svg +10 -0
  321. package/icons/right-align-2.svg +12 -0
  322. package/icons/right-align.svg +13 -0
  323. package/icons/rocket.svg +7 -0
  324. package/icons/rotate.svg +11 -0
  325. package/icons/rss.svg +13 -0
  326. package/icons/ruble.svg +11 -0
  327. package/icons/ruler.svg +10 -0
  328. package/icons/rupee.svg +11 -0
  329. package/icons/sad-emoji.svg +8 -0
  330. package/icons/safe.svg +13 -0
  331. package/icons/sale.svg +7 -0
  332. package/icons/satellite.svg +3 -0
  333. package/icons/saving.svg +15 -0
  334. package/icons/scan-2.svg +15 -0
  335. package/icons/scan-fingerprint.svg +20 -0
  336. package/icons/scan.svg +14 -0
  337. package/icons/screen-rotate.svg +12 -0
  338. package/icons/scroll-down.svg +11 -0
  339. package/icons/scroll-left-1.svg +11 -0
  340. package/icons/scroll-left.svg +11 -0
  341. package/icons/scroll-up.svg +11 -0
  342. package/icons/search.svg +10 -0
  343. package/icons/send-2.svg +10 -0
  344. package/icons/send-3.svg +11 -0
  345. package/icons/send.svg +10 -0
  346. package/icons/server-2.svg +15 -0
  347. package/icons/server.svg +3 -0
  348. package/icons/setting-2.svg +11 -0
  349. package/icons/setting-3.svg +10 -0
  350. package/icons/setting.svg +12 -0
  351. package/icons/shape.svg +10 -0
  352. package/icons/shield-2.svg +11 -0
  353. package/icons/shield.svg +11 -0
  354. package/icons/ship.svg +4 -0
  355. package/icons/shocked-emoji.svg +8 -0
  356. package/icons/shop.svg +4 -0
  357. package/icons/shopping-cart-2.svg +4 -0
  358. package/icons/shopping-cart-3.svg +4 -0
  359. package/icons/shopping-cart.svg +5 -0
  360. package/icons/shuffle.svg +10 -0
  361. package/icons/signal.svg +14 -0
  362. package/icons/skype.svg +11 -0
  363. package/icons/smiling-emoji.svg +6 -0
  364. package/icons/smiling-with-eyes-emoji.svg +8 -0
  365. package/icons/snapchat.svg +10 -0
  366. package/icons/snow.svg +22 -0
  367. package/icons/snowflake.svg +10 -0
  368. package/icons/snowman.svg +16 -0
  369. package/icons/sofa.svg +10 -0
  370. package/icons/speaker.svg +12 -0
  371. package/icons/spoon.svg +10 -0
  372. package/icons/spotify.svg +13 -0
  373. package/icons/square.svg +10 -0
  374. package/icons/star.svg +10 -0
  375. package/icons/stethoscope.svg +11 -0
  376. package/icons/stopwatch.svg +11 -0
  377. package/icons/suitcase.svg +10 -0
  378. package/icons/sun-2.svg +11 -0
  379. package/icons/sun-3.svg +18 -0
  380. package/icons/sun-4.svg +11 -0
  381. package/icons/sun.svg +18 -0
  382. package/icons/sunny.svg +18 -0
  383. package/icons/surprised-emoji.svg +8 -0
  384. package/icons/swipe-left.svg +11 -0
  385. package/icons/swipe-right.svg +11 -0
  386. package/icons/switch-1.svg +11 -0
  387. package/icons/switch.svg +10 -0
  388. package/icons/sync.svg +11 -0
  389. package/icons/tablet-2.svg +19 -0
  390. package/icons/tablet.svg +11 -0
  391. package/icons/tag-2.svg +3 -0
  392. package/icons/tag.svg +4 -0
  393. package/icons/tap-1.svg +15 -0
  394. package/icons/tap.svg +11 -0
  395. package/icons/target-2.svg +11 -0
  396. package/icons/target.svg +13 -0
  397. package/icons/test-tube.svg +10 -0
  398. package/icons/thumbs-down.svg +11 -0
  399. package/icons/thumbs-up-2.svg +11 -0
  400. package/icons/thumbs-up.svg +11 -0
  401. package/icons/thunderstorm.svg +11 -0
  402. package/icons/tick-2.svg +4 -0
  403. package/icons/tick.svg +3 -0
  404. package/icons/tik-tok.svg +10 -0
  405. package/icons/tooth.svg +12 -0
  406. package/icons/tornado.svg +25 -0
  407. package/icons/touch-hold.svg +13 -0
  408. package/icons/transform.svg +10 -0
  409. package/icons/transgender-2.svg +10 -0
  410. package/icons/transgender.svg +10 -0
  411. package/icons/tree-2.svg +10 -0
  412. package/icons/tree.svg +10 -0
  413. package/icons/trend-down-square.svg +11 -0
  414. package/icons/trend-down.svg +10 -0
  415. package/icons/trend-up-square.svg +11 -0
  416. package/icons/trend-up.svg +10 -0
  417. package/icons/trophy-2.svg +3 -0
  418. package/icons/trophy.svg +10 -0
  419. package/icons/truck.svg +4 -0
  420. package/icons/tumblr.svg +11 -0
  421. package/icons/tv.svg +11 -0
  422. package/icons/twitch.svg +13 -0
  423. package/icons/twitter.svg +10 -0
  424. package/icons/uber.svg +10 -0
  425. package/icons/unbox.svg +3 -0
  426. package/icons/unhide.svg +11 -0
  427. package/icons/unlink.svg +12 -0
  428. package/icons/upload.svg +11 -0
  429. package/icons/user-add.svg +12 -0
  430. package/icons/user-caution.svg +13 -0
  431. package/icons/user-delete.svg +12 -0
  432. package/icons/user-female.svg +11 -0
  433. package/icons/user-male.svg +11 -0
  434. package/icons/user-remove.svg +12 -0
  435. package/icons/user.svg +11 -0
  436. package/icons/v.svg +10 -0
  437. package/icons/vector.svg +10 -0
  438. package/icons/vibrate.svg +15 -0
  439. package/icons/video-camera.svg +10 -0
  440. package/icons/volume-down.svg +11 -0
  441. package/icons/volume-up.svg +12 -0
  442. package/icons/wallet.svg +11 -0
  443. package/icons/warehouse.svg +3 -0
  444. package/icons/water.svg +10 -0
  445. package/icons/wave-left.svg +11 -0
  446. package/icons/wave-right.svg +11 -0
  447. package/icons/webflow.svg +10 -0
  448. package/icons/whatsapp.svg +11 -0
  449. package/icons/wheelchair.svg +11 -0
  450. package/icons/wind.svg +14 -0
  451. package/icons/windows.svg +10 -0
  452. package/icons/wink-emoji.svg +6 -0
  453. package/icons/won.svg +11 -0
  454. package/icons/worried-emoji.svg +8 -0
  455. package/icons/y-combinator.svg +10 -0
  456. package/icons/yen.svg +11 -0
  457. package/icons/youtube.svg +11 -0
  458. package/icons/zap.svg +10 -0
  459. package/icons/zoom-in-2.svg +12 -0
  460. package/icons/zoom-in.svg +11 -0
  461. package/icons/zoom-out-1.svg +11 -0
  462. package/icons/zoom-out-2.svg +11 -0
  463. package/icons/zoom-out.svg +11 -0
  464. package/manifest.json +43 -0
  465. package/package.json +53 -0
  466. package/sidepanel/app.js +437 -0
  467. package/sidepanel/components/bottom-dock.js +148 -0
  468. package/sidepanel/components/empty-state.js +40 -0
  469. package/sidepanel/components/file-list-item.js +58 -0
  470. package/sidepanel/components/file-list.js +134 -0
  471. package/sidepanel/components/filter-bar.js +261 -0
  472. package/sidepanel/components/progress-overlay.js +79 -0
  473. package/sidepanel/components/recording-indicator.js +32 -0
  474. package/sidepanel/components/stats-header.js +37 -0
  475. package/sidepanel/icons/file-type-icons.svg +55 -0
  476. package/sidepanel/icons/ui-icons.svg +85 -0
  477. package/sidepanel/index.html +37 -0
  478. package/sidepanel/styles.css +934 -0
  479. package/sidepanel/utils/clipboard.js +33 -0
  480. package/sidepanel/utils/format.js +23 -0
  481. package/sidepanel/utils/virtual-scroll.js +107 -0
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@imsovikde/source-grab",
3
+ "version": "1.0.1",
4
+ "description": "Chrome extension that captures every file your browser loads in real-time using Chrome DevTools Protocol. Filter, search, and download individually or as ZIP.",
5
+ "main": "background/service-worker.js",
6
+ "type": "module",
7
+ "scripts": {
8
+ "test": "echo \"Error: no test specified\" && exit 0"
9
+ },
10
+ "files": [
11
+ "manifest.json",
12
+ "background",
13
+ "content-scripts",
14
+ "sidepanel",
15
+ "icons",
16
+ "design-tokens.css",
17
+ "LICENSE",
18
+ "README.md",
19
+ "CHANGELOG.md"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/imsovikde/source-grab.git"
24
+ },
25
+ "keywords": [
26
+ "chrome-extension",
27
+ "devtools-protocol",
28
+ "cdp",
29
+ "resource-sniffer",
30
+ "network-interceptor",
31
+ "asset-downloader",
32
+ "media-grabber",
33
+ "mv3",
34
+ "sidepanel"
35
+ ],
36
+ "author": "Souvik Dey <imsovikde@gmail.com> (https://github.com/imsovikde)",
37
+ "license": "MIT",
38
+ "bugs": {
39
+ "url": "https://github.com/imsovikde/source-grab/issues"
40
+ },
41
+ "homepage": "https://github.com/imsovikde/source-grab#readme",
42
+ "funding": {
43
+ "type": "github",
44
+ "url": "https://github.com/sponsors/imsovikde"
45
+ },
46
+ "engines": {
47
+ "node": ">=18.0.0"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ }
53
+ }
@@ -0,0 +1,437 @@
1
+ // sidepanel/app.js
2
+ // v2.0 — Master clear, no blinking, empty state properly hidden, all buttons always visible
3
+
4
+ import { RecordingIndicator } from './components/recording-indicator.js';
5
+ import { StatsHeader } from './components/stats-header.js';
6
+ import { FilterBar } from './components/filter-bar.js';
7
+ import { FileList } from './components/file-list.js';
8
+ import { BottomDock } from './components/bottom-dock.js';
9
+ import { ProgressOverlay } from './components/progress-overlay.js';
10
+ import { EmptyState } from './components/empty-state.js';
11
+ import { copyFileNames } from './utils/clipboard.js';
12
+
13
+ let activeTabId = null;
14
+ let captureState = { active: false, deepScan: false, tabUrl: '' };
15
+
16
+ const components = {
17
+ recordingIndicator: new RecordingIndicator(),
18
+ statsHeader: new StatsHeader(),
19
+ filterBar: new FilterBar(),
20
+ fileList: new FileList(),
21
+ bottomDock: new BottomDock(),
22
+ progressOverlay: new ProgressOverlay(),
23
+ emptyState: new EmptyState()
24
+ };
25
+
26
+ // ─── Toast Notification System ───
27
+
28
+ function showToast(message) {
29
+ const container = document.getElementById('toast-container');
30
+ if (!container) return;
31
+ const toast = document.createElement('div');
32
+ toast.className = 'toast';
33
+ toast.textContent = message;
34
+ container.appendChild(toast);
35
+ setTimeout(() => {
36
+ toast.style.opacity = '0';
37
+ toast.style.transform = 'translateY(20px)';
38
+ toast.style.transition = 'all 0.25s ease-out';
39
+ setTimeout(() => toast.remove(), 300);
40
+ }, 3000);
41
+ }
42
+
43
+ // ─── Master Clear (header button) ───
44
+
45
+ function masterClear() {
46
+ if (!activeTabId) return;
47
+ chrome.runtime.sendMessage({ type: 'CLEAR_CAPTURE', tabId: activeTabId });
48
+ components.fileList.setResources([], components.filterBar.getCurrentFilter());
49
+ components.fileList.selectNone();
50
+ components.filterBar.updateCounts({ '*': 0 });
51
+ components.statsHeader.update({ total: 0, newSinceLastBatch: 0, tabUrl: '' });
52
+ checkEmptyState();
53
+ updateBottomDock();
54
+ showToast('All capture data cleared');
55
+ }
56
+
57
+ // ─── Initialization ───
58
+
59
+ async function init() {
60
+ // Mount all components
61
+ components.recordingIndicator.init(document.getElementById('recording-indicator'));
62
+ components.statsHeader.init(document.getElementById('stats-header'));
63
+ components.filterBar.init(document.getElementById('filter-bar'));
64
+ components.fileList.init(document.getElementById('file-list-container'));
65
+ components.bottomDock.init(document.getElementById('bottom-dock'));
66
+ components.progressOverlay.init(document.getElementById('progress-overlay'));
67
+ components.emptyState.init(document.getElementById('empty-state'));
68
+
69
+ // Wire master clear button
70
+ const masterClearBtn = document.getElementById('master-clear-btn');
71
+ if (masterClearBtn) {
72
+ masterClearBtn.addEventListener('click', masterClear);
73
+ }
74
+
75
+ // Get active tab
76
+ await refreshActiveTab();
77
+
78
+ // Load existing state
79
+ await loadInitialState();
80
+
81
+ // Set up event wiring
82
+ setupFilterWiring();
83
+ setupDockWiring();
84
+ setupStorageListener();
85
+ setupProgressOverlayWiring();
86
+ setupTabSwitchListener();
87
+ setupVisibilityListener();
88
+
89
+ // Show initial empty state
90
+ checkEmptyState();
91
+ updateBottomDock();
92
+ }
93
+
94
+ // ─── Active Tab Detection ───
95
+
96
+ async function refreshActiveTab() {
97
+ try {
98
+ const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
99
+ if (tabs.length > 0) {
100
+ activeTabId = tabs[0].id;
101
+ }
102
+ } catch (e) {
103
+ console.error('Failed to get active tab:', e);
104
+ }
105
+ }
106
+
107
+ // ─── Tab Switch Listener ───
108
+
109
+ function setupTabSwitchListener() {
110
+ chrome.tabs.onActivated.addListener(async (activeInfo) => {
111
+ const oldTabId = activeTabId;
112
+ activeTabId = activeInfo.tabId;
113
+
114
+ if (oldTabId !== activeTabId) {
115
+ await loadInitialState();
116
+ checkEmptyState();
117
+ updateBottomDock();
118
+ }
119
+ });
120
+ }
121
+
122
+ // ─── Visibility Listener ───
123
+
124
+ function setupVisibilityListener() {
125
+ document.addEventListener('visibilitychange', async () => {
126
+ if (document.visibilityState === 'visible') {
127
+ await refreshActiveTab();
128
+ await loadInitialState();
129
+ checkEmptyState();
130
+ updateBottomDock();
131
+ }
132
+ });
133
+ }
134
+
135
+ // ─── Initial State Loading ───
136
+
137
+ async function loadInitialState() {
138
+ if (!activeTabId) return;
139
+
140
+ try {
141
+ const sessionKeys = [`capture_state_${activeTabId}`, `download_progress_${activeTabId}`];
142
+ const sessionData = await chrome.storage.session.get(sessionKeys);
143
+
144
+ if (sessionData[`capture_state_${activeTabId}`]) {
145
+ captureState = sessionData[`capture_state_${activeTabId}`];
146
+ } else {
147
+ captureState = { active: false, deepScan: false, tabUrl: '' };
148
+ }
149
+ components.recordingIndicator.update(captureState);
150
+
151
+ const localData = await chrome.storage.local.get(`registry_${activeTabId}`);
152
+ if (localData[`registry_${activeTabId}`]) {
153
+ handleRegistryUpdate(localData[`registry_${activeTabId}`]);
154
+ } else {
155
+ components.fileList.setResources([], components.filterBar.getCurrentFilter());
156
+ }
157
+
158
+ if (sessionData[`download_progress_${activeTabId}`]) {
159
+ const prog = sessionData[`download_progress_${activeTabId}`];
160
+ if (prog.completed < prog.total) {
161
+ components.progressOverlay.show(prog);
162
+ }
163
+ }
164
+ } catch (e) {
165
+ console.error('Failed to load initial state:', e);
166
+ }
167
+ }
168
+
169
+ // ─── Filter Wiring ───
170
+
171
+ function setupFilterWiring() {
172
+ components.filterBar.onFilterChange((filterState) => {
173
+ components.fileList.setResources(components.fileList.allResources, filterState);
174
+ checkEmptyState();
175
+ updateBottomDock();
176
+ });
177
+
178
+ components.fileList.onSelectionChange(({ selectedCount, totalFiltered }) => {
179
+ updateBottomDock();
180
+ });
181
+ }
182
+
183
+ // ─── Bottom Dock Actions ───
184
+
185
+ function setupDockWiring() {
186
+ components.bottomDock.onAction(async (action) => {
187
+ if (!activeTabId && action !== 'start-capture') return;
188
+
189
+ switch (action) {
190
+ case 'start-capture': {
191
+ try {
192
+ const hostGranted = await chrome.permissions.request({ origins: ['<all_urls>'] });
193
+ if (!hostGranted) {
194
+ showToast('Permission denied — cannot capture without host access');
195
+ return;
196
+ }
197
+
198
+ const debuggerGranted = await chrome.permissions.contains({ permissions: ['debugger'] });
199
+
200
+ await refreshActiveTab();
201
+
202
+ if (!activeTabId) {
203
+ showToast('No active tab found');
204
+ return;
205
+ }
206
+
207
+ const response = await chrome.runtime.sendMessage({ type: 'START_CAPTURE', tabId: activeTabId });
208
+ if (response?.success) {
209
+ showToast(debuggerGranted ? 'Capture started with Deep Scan' : 'Capture started');
210
+ } else {
211
+ showToast('Failed to start capture: ' + (response?.error || 'unknown'));
212
+ }
213
+ } catch (e) {
214
+ console.error('Start capture failed:', e);
215
+ showToast('Failed to start capture');
216
+ }
217
+ break;
218
+ }
219
+
220
+ case 'stop-capture': {
221
+ try {
222
+ await chrome.runtime.sendMessage({ type: 'STOP_CAPTURE', tabId: activeTabId });
223
+ showToast('Capture stopped');
224
+ } catch (e) {
225
+ showToast('Error stopping capture');
226
+ }
227
+ break;
228
+ }
229
+
230
+ case 'select-all':
231
+ components.fileList.selectAll();
232
+ updateBottomDock();
233
+ break;
234
+
235
+ case 'select-none':
236
+ components.fileList.selectNone();
237
+ updateBottomDock();
238
+ break;
239
+
240
+ case 'copy-names': {
241
+ const resources = components.fileList.getFilteredResources();
242
+ if (resources.length === 0) {
243
+ showToast('No files to copy');
244
+ return;
245
+ }
246
+ const { success, count } = await copyFileNames(resources);
247
+ if (success) {
248
+ showToast(`Copied ${count} filename${count !== 1 ? 's' : ''} to clipboard`);
249
+ components.bottomDock.showCopySuccess();
250
+ } else {
251
+ showToast('Failed to copy to clipboard');
252
+ }
253
+ break;
254
+ }
255
+
256
+ case 'download-selected': {
257
+ let urls = components.fileList.getSelectedUrls();
258
+ if (urls.length === 0) {
259
+ urls = components.fileList.getFilteredResources().map(r => r.url);
260
+ }
261
+ if (urls.length === 0) {
262
+ showToast('No files to download');
263
+ return;
264
+ }
265
+ chrome.runtime.sendMessage({
266
+ type: 'DOWNLOAD_SELECTED',
267
+ tabId: activeTabId,
268
+ urls
269
+ });
270
+ showToast(`Downloading ${urls.length} file${urls.length !== 1 ? 's' : ''}...`);
271
+ break;
272
+ }
273
+
274
+ case 'download-zip': {
275
+ let urls = components.fileList.getSelectedUrls();
276
+ if (urls.length === 0) {
277
+ urls = components.fileList.getFilteredResources().map(r => r.url);
278
+ }
279
+ if (urls.length === 0) {
280
+ showToast('No files to download');
281
+ return;
282
+ }
283
+ const now = new Date();
284
+ const dateStr = now.toISOString().slice(0, 10);
285
+ const timeStr = now.toTimeString().slice(0, 8).replace(/:/g, '-');
286
+ const zipName = `SourceGrab-${dateStr}_${timeStr}.zip`;
287
+ chrome.runtime.sendMessage({
288
+ type: 'DOWNLOAD_ZIP',
289
+ tabId: activeTabId,
290
+ urls,
291
+ zipFilename: zipName
292
+ });
293
+ showToast(`Building ZIP with ${urls.length} files...`);
294
+ break;
295
+ }
296
+
297
+ case 'auto-scroll': {
298
+ chrome.runtime.sendMessage({ type: 'AUTO_SCROLL', tabId: activeTabId });
299
+ showToast('Auto-scrolling page to discover lazy content...');
300
+ break;
301
+ }
302
+
303
+ case 'stop-auto-scroll': {
304
+ chrome.runtime.sendMessage({ type: 'STOP_AUTO_SCROLL', tabId: activeTabId });
305
+ showToast('Auto-scroll stopped');
306
+ break;
307
+ }
308
+
309
+ case 'clear-capture': {
310
+ masterClear();
311
+ break;
312
+ }
313
+ }
314
+ });
315
+ }
316
+
317
+ // ─── Progress Overlay Wiring ───
318
+
319
+ function setupProgressOverlayWiring() {
320
+ components.progressOverlay.onRetry((url) => {
321
+ chrome.runtime.sendMessage({ type: 'RETRY_DOWNLOAD', tabId: activeTabId, url });
322
+ });
323
+
324
+ components.progressOverlay.onCancel(() => {
325
+ // Cancel just hides the overlay; downloads in progress will complete
326
+ });
327
+ }
328
+
329
+ // ─── Storage Change Listener (real-time updates) ───
330
+
331
+ function setupStorageListener() {
332
+ chrome.storage.onChanged.addListener((changes, area) => {
333
+ if (!activeTabId) return;
334
+
335
+ // Registry changes from chrome.storage.local → update file list
336
+ if (area === 'local') {
337
+ const registryKey = `registry_${activeTabId}`;
338
+ if (changes[registryKey]) {
339
+ handleRegistryUpdate(changes[registryKey].newValue);
340
+ }
341
+ }
342
+
343
+ // Session storage: capture state + download progress
344
+ if (area === 'session') {
345
+ const stateKey = `capture_state_${activeTabId}`;
346
+ if (changes[stateKey]) {
347
+ captureState = changes[stateKey].newValue || { active: false, deepScan: false, tabUrl: '' };
348
+ components.recordingIndicator.update(captureState);
349
+ updateBottomDock();
350
+ checkEmptyState();
351
+ }
352
+
353
+ const progressKey = `download_progress_${activeTabId}`;
354
+ if (changes[progressKey]) {
355
+ const prog = changes[progressKey].newValue;
356
+ if (prog) {
357
+ components.progressOverlay.show(prog);
358
+ if (prog.completed >= prog.total && prog.failed === 0) {
359
+ setTimeout(() => {
360
+ components.progressOverlay.hide();
361
+ showToast(`Downloaded ${prog.completed} file${prog.completed !== 1 ? 's' : ''} successfully`);
362
+ }, 1500);
363
+ }
364
+ } else {
365
+ components.progressOverlay.hide();
366
+ }
367
+ }
368
+ }
369
+ });
370
+ }
371
+
372
+ // ─── Registry Update Handler ───
373
+
374
+ function handleRegistryUpdate(registry) {
375
+ if (!registry || !registry.resources) return;
376
+
377
+ const resourcesArray = Object.values(registry.resources)
378
+ .sort((a, b) => (a.index || 0) - (b.index || 0));
379
+
380
+ components.fileList.setResources(resourcesArray, components.filterBar.getCurrentFilter());
381
+
382
+ // Calculate category counts
383
+ const counts = { '*': resourcesArray.length };
384
+ let newCount = 0;
385
+ resourcesArray.forEach(r => {
386
+ counts[r.category] = (counts[r.category] || 0) + 1;
387
+ if (r.isNew) newCount++;
388
+ });
389
+ components.filterBar.updateCounts(counts);
390
+
391
+ // Update stats header
392
+ components.statsHeader.update({
393
+ total: resourcesArray.length,
394
+ newSinceLastBatch: newCount,
395
+ tabUrl: captureState.tabUrl || ''
396
+ });
397
+
398
+ checkEmptyState();
399
+ updateBottomDock();
400
+ }
401
+
402
+ // ─── UI State Helpers ───
403
+
404
+ function updateBottomDock() {
405
+ const selectedCount = components.fileList.selectedUrls?.size || 0;
406
+ const totalFiltered = components.fileList.filteredResources?.length || 0;
407
+ components.bottomDock.updateState({
408
+ capturing: captureState.active,
409
+ selectedCount,
410
+ totalFiltered,
411
+ deepScan: captureState.deepScan
412
+ });
413
+ }
414
+
415
+ function checkEmptyState() {
416
+ const allCount = components.fileList.allResources?.length || 0;
417
+ const filteredCount = components.fileList.filteredResources?.length || 0;
418
+
419
+ if (allCount > 0) {
420
+ if (filteredCount === 0) {
421
+ components.emptyState.show('no-results');
422
+ } else {
423
+ // IMPORTANT: Always hide empty state when there are files to show
424
+ components.emptyState.hide();
425
+ }
426
+ } else {
427
+ if (captureState.active) {
428
+ components.emptyState.show('capturing');
429
+ } else {
430
+ components.emptyState.show('idle');
431
+ }
432
+ }
433
+ }
434
+
435
+ // ─── Boot ───
436
+
437
+ document.addEventListener('DOMContentLoaded', init);
@@ -0,0 +1,148 @@
1
+ // sidepanel/components/bottom-dock.js
2
+ // v2.0 — ALL buttons visible ALL the time. No conditional hiding.
3
+
4
+ export class BottomDock {
5
+ constructor() {
6
+ this.container = null;
7
+ this.actionCallback = null;
8
+ this.isScrolling = false;
9
+ this._rendered = false;
10
+ }
11
+
12
+ init(container) {
13
+ this.container = container;
14
+ }
15
+
16
+ render(state) {
17
+ const { capturing, selectedCount, totalFiltered } = state;
18
+ const dlCount = selectedCount > 0 ? selectedCount : totalFiltered;
19
+
20
+ this.container.innerHTML = `
21
+ <button class="dock-btn" id="btn-all" title="Select All">
22
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-select-all"></use></svg>
23
+ <span class="dock-btn-label">All</span>
24
+ </button>
25
+ <button class="dock-btn" id="btn-none" title="Deselect All">
26
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-deselect"></use></svg>
27
+ <span class="dock-btn-label">None</span>
28
+ </button>
29
+ <button class="dock-btn" id="btn-copy" title="Copy Filenames">
30
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-copy"></use></svg>
31
+ <span class="dock-btn-label">Copy</span>
32
+ </button>
33
+ <button class="dock-btn ${this.isScrolling ? 'success' : ''}" id="btn-scroll" title="${this.isScrolling ? 'Stop Scrolling' : 'Auto-Scroll'}">
34
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-auto-scroll"></use></svg>
35
+ <span class="dock-btn-label">${this.isScrolling ? 'Stop' : 'Scroll'}</span>
36
+ </button>
37
+ <button class="dock-btn" id="btn-zip" title="Download as ZIP">
38
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-download-zip"></use></svg>
39
+ <span class="dock-btn-label">ZIP</span>
40
+ </button>
41
+ <button class="dock-btn" id="btn-download" title="Download">
42
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-download"></use></svg>
43
+ <span class="dock-btn-label">${dlCount || 0}</span>
44
+ </button>
45
+ <button class="dock-btn ${capturing ? 'danger' : 'primary'}" id="btn-capture-toggle" title="${capturing ? 'Stop Capture' : 'Start Capture'}">
46
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#${capturing ? 'icon-stop' : 'icon-record'}"></use></svg>
47
+ <span class="dock-btn-label">${capturing ? 'Stop' : 'Start'}</span>
48
+ </button>
49
+ <button class="dock-btn" id="btn-clear" title="Clear All">
50
+ <svg width="18" height="18"><use href="icons/ui-icons.svg#icon-clear"></use></svg>
51
+ <span class="dock-btn-label">Clear</span>
52
+ </button>
53
+ `;
54
+ this._rendered = true;
55
+ this.attachEvents(capturing);
56
+ }
57
+
58
+ updateState(state) {
59
+ const { capturing, selectedCount, totalFiltered } = state;
60
+
61
+ if (!this._rendered) {
62
+ this.render(state);
63
+ return;
64
+ }
65
+
66
+ // Update download count without full re-render
67
+ const dlCount = selectedCount > 0 ? selectedCount : totalFiltered;
68
+ const dlLabel = this.container.querySelector('#btn-download .dock-btn-label');
69
+ if (dlLabel) dlLabel.textContent = dlCount || 0;
70
+
71
+ // Update capture toggle button without full re-render
72
+ const capBtn = this.container.querySelector('#btn-capture-toggle');
73
+ if (capBtn) {
74
+ const wasCapturing = capBtn.classList.contains('danger');
75
+ if (capturing !== wasCapturing) {
76
+ // State changed — need to update button appearance
77
+ capBtn.classList.toggle('danger', capturing);
78
+ capBtn.classList.toggle('primary', !capturing);
79
+ capBtn.title = capturing ? 'Stop Capture' : 'Start Capture';
80
+ const svg = capBtn.querySelector('use');
81
+ if (svg) svg.setAttribute('href', `icons/ui-icons.svg#${capturing ? 'icon-stop' : 'icon-record'}`);
82
+ const label = capBtn.querySelector('.dock-btn-label');
83
+ if (label) label.textContent = capturing ? 'Stop' : 'Start';
84
+ // Re-attach this one button's event
85
+ capBtn.onclick = () => this.emit(capturing ? 'stop-capture' : 'start-capture');
86
+ }
87
+ }
88
+
89
+ // Update scroll button
90
+ const scrollBtn = this.container.querySelector('#btn-scroll');
91
+ if (scrollBtn) {
92
+ scrollBtn.classList.toggle('success', this.isScrolling);
93
+ const scrollLabel = scrollBtn.querySelector('.dock-btn-label');
94
+ if (scrollLabel) scrollLabel.textContent = this.isScrolling ? 'Stop' : 'Scroll';
95
+ }
96
+ }
97
+
98
+ onAction(callback) {
99
+ this.actionCallback = callback;
100
+ }
101
+
102
+ emit(action) {
103
+ if (this.actionCallback) this.actionCallback(action);
104
+ }
105
+
106
+ showCopySuccess() {
107
+ const copyBtn = this.container.querySelector('#btn-copy');
108
+ if (copyBtn) {
109
+ copyBtn.classList.add('copied');
110
+ setTimeout(() => copyBtn.classList.remove('copied'), 1500);
111
+ }
112
+ }
113
+
114
+ attachEvents(capturing) {
115
+ const bind = (id, action) => {
116
+ const el = this.container.querySelector(id);
117
+ if (el) el.onclick = () => this.emit(action);
118
+ };
119
+
120
+ // Capture toggle — single button that starts or stops
121
+ const capBtn = this.container.querySelector('#btn-capture-toggle');
122
+ if (capBtn) {
123
+ capBtn.onclick = () => this.emit(capturing ? 'stop-capture' : 'start-capture');
124
+ }
125
+
126
+ bind('#btn-all', 'select-all');
127
+ bind('#btn-none', 'select-none');
128
+ bind('#btn-copy', 'copy-names');
129
+ bind('#btn-zip', 'download-zip');
130
+ bind('#btn-download', 'download-selected');
131
+ bind('#btn-clear', 'clear-capture');
132
+
133
+ // Auto-scroll toggle
134
+ const scrollBtn = this.container.querySelector('#btn-scroll');
135
+ if (scrollBtn) {
136
+ scrollBtn.onclick = () => {
137
+ if (this.isScrolling) {
138
+ this.isScrolling = false;
139
+ this.emit('stop-auto-scroll');
140
+ } else {
141
+ this.isScrolling = true;
142
+ this.emit('auto-scroll');
143
+ setTimeout(() => { this.isScrolling = false; }, 30000);
144
+ }
145
+ };
146
+ }
147
+ }
148
+ }
@@ -0,0 +1,40 @@
1
+ export class EmptyState {
2
+ constructor() {
3
+ this.container = null;
4
+ }
5
+
6
+ init(container) {
7
+ this.container = container;
8
+ }
9
+
10
+ show(state) {
11
+ let title = '';
12
+ let subtitle = '';
13
+
14
+ switch (state) {
15
+ case 'idle':
16
+ title = 'Click Start Capture to begin';
17
+ subtitle = 'SourceGrab will detect every file loaded by this page';
18
+ break;
19
+ case 'capturing':
20
+ title = 'Waiting for resources...';
21
+ subtitle = 'Browse or scroll the page to trigger loading';
22
+ break;
23
+ case 'no-results':
24
+ title = 'No files match your filter';
25
+ subtitle = 'Try adjusting your search or category filter';
26
+ break;
27
+ }
28
+
29
+ this.container.innerHTML = `
30
+ <div class="empty-decoration"></div>
31
+ <div class="empty-title">${title}</div>
32
+ <div class="empty-subtitle">${subtitle}</div>
33
+ `;
34
+ this.container.classList.add('active');
35
+ }
36
+
37
+ hide() {
38
+ this.container.classList.remove('active');
39
+ }
40
+ }