@embedpdf/plugin-thumbnail 1.3.4 → 1.3.6
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.
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +90 -21
- package/dist/index.js.map +1 -1
- package/dist/lib/thumbnail-plugin.d.ts +11 -3
- package/dist/lib/types.d.ts +23 -4
- package/dist/preact/index.cjs +1 -1
- package/dist/preact/index.cjs.map +1 -1
- package/dist/preact/index.js +38 -29
- package/dist/preact/index.js.map +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.cjs.map +1 -1
- package/dist/react/index.js +38 -29
- package/dist/react/index.js.map +1 -1
- package/dist/shared-preact/components/thumbnails-pane.d.ts +3 -1
- package/dist/shared-react/components/thumbnails-pane.d.ts +3 -1
- package/dist/vue/components/index.d.ts +2 -0
- package/dist/vue/components/thumbnail-img.vue.d.ts +6 -0
- package/dist/vue/components/thumbnails-pane.vue.d.ts +14 -0
- package/dist/vue/hooks/index.d.ts +1 -0
- package/dist/vue/hooks/use-thumbnail.d.ts +3 -0
- package/dist/vue/index.cjs +2 -0
- package/dist/vue/index.cjs.map +1 -0
- package/dist/vue/index.d.ts +3 -0
- package/dist/vue/index.js +151 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +15 -7
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("@embedpdf/core"),i=require("@embedpdf/models"),e="thumbnail",s={id:e,name:"Thumbnail Plugin",version:"1.0.0",provides:["thumbnail"],requires:["render"],optional:["scroll"],defaultConfig:{enabled:!0,width:150,gap:10,buffer:3,labelHeight:16,autoScroll:!0,scrollBehavior:"smooth",imagePadding:0,paddingY:0}},o=class extends t.BasePlugin{constructor(i,e,s){var o;super(i,e),this.cfg=s,this.scrollCapability=null,this.thumbs=[],this.window=null,this.viewportH=0,this.scrollY=0,this.emitWindow=t.createBehaviorEmitter(),this.refreshPages$=t.createEmitter(),this.taskCache=new Map,this.canAutoScroll=!0,this.scrollTo$=t.createBehaviorEmitter(),this.renderCapability=this.registry.getPlugin("render").provides(),this.scrollCapability=(null==(o=this.registry.getPlugin("scroll"))?void 0:o.provides())??null,this.coreStore.onAction(t.SET_DOCUMENT,((t,i)=>{this.taskCache.clear(),this.setWindowState(i)})),this.coreStore.onAction(t.REFRESH_PAGES,(t=>{this.refreshPages$.emit(t.payload);for(const i of t.payload)this.taskCache.delete(i)})),this.scrollCapability&&!1!==this.cfg.autoScroll&&(this.scrollCapability.onPageChangeState((({isChanging:t,targetPage:i})=>{this.canAutoScroll=!t,t||this.scrollToThumb(i-1)})),this.scrollCapability.onPageChange((({pageNumber:t})=>{this.canAutoScroll&&this.scrollToThumb(t-1)})))}async initialize(){}onRefreshPages(t){return this.refreshPages$.on(t)}onWindow(t){return this.emitWindow.on(t)}onScrollTo(t){return this.scrollTo$.on(t)}setWindowState(t){const i=t.core;if(!i.document)return;const e=this.cfg.width??120,s=this.cfg.labelHeight??16,o=this.cfg.gap??8,h=this.cfg.imagePadding??0,r=this.cfg.paddingY??0,a=Math.max(1,e-2*h);let n=r;this.thumbs=i.document.pages.map((t=>{const i=t.size.height/t.size.width,e=Math.round(a*i),r=h+e+h+s,l={pageIndex:t.index,width:a,height:e,wrapperHeight:r,top:n,labelHeight:s,padding:h};return n+=r+o,l})),this.window={start:-1,end:-1,items:[],totalHeight:n-o+r},this.viewportH>0?this.updateWindow(this.scrollY,this.viewportH):this.emitWindow.emit(this.window)}buildCapability(){return{renderThumb:(t,i)=>this.renderThumb(t,i),scrollToThumb:t=>this.scrollToThumb(t)}}updateWindow(t,i){const e=this.cfg.buffer??3;if(this.scrollY=t,this.viewportH=i,!this.window||0===this.thumbs.length)return;let s=0,o=this.thumbs.length-1,h=0;for(;s<=o;){const i=s+o>>1,e=this.thumbs[i];e.top+e.wrapperHeight<t?s=i+1:(h=i,o=i-1)}let r=h;const a=t+i;for(;r+1<this.thumbs.length&&this.thumbs[r].top<a;)r++;r=Math.min(this.thumbs.length-1,r+e);const n=Math.max(0,h-e);n===this.window.start&&r===this.window.end||(this.window={start:n,end:r,items:this.thumbs.slice(n,r+1),totalHeight:this.window.totalHeight},this.emitWindow.emit(this.window))}scrollToThumb(t){if(!this.window)return;const i=this.thumbs[t];if(!i)return;const e=this.cfg.scrollBehavior??"smooth",s=this.cfg.paddingY??0;if(this.viewportH<=0){const t=Math.max(s,i.top-i.wrapperHeight);return void this.scrollTo$.emit({top:t,behavior:e})}const o=i.top,h=i.top+i.wrapperHeight,r=o<this.scrollY+8+s,a=h>this.scrollY+this.viewportH-8;r?this.scrollTo$.emit({top:Math.max(0,o-s),behavior:e}):a&&this.scrollTo$.emit({top:Math.max(0,h-this.viewportH+s),behavior:e})}renderThumb(t,e){if(this.taskCache.has(t))return this.taskCache.get(t);const s=this.coreState.core.document.pages[t],o=this.cfg.width??120,h=this.cfg.imagePadding??0,r=Math.max(1,o-2*h)/s.size.width,a=this.renderCapability.renderPageRect({pageIndex:t,rect:{origin:{x:0,y:0},size:s.size},options:{scaleFactor:r,dpr:e}});return this.taskCache.set(t,a),a.wait(i.ignore,(()=>this.taskCache.delete(t))),a}};o.id="thumbnail";let h=o;const r={manifest:s,create:(t,i)=>new h(e,t,i),reducer:()=>{},initialState:{}};exports.THUMBNAIL_PLUGIN_ID=e,exports.ThumbnailPlugin=h,exports.ThumbnailPluginPackage=r,exports.manifest=s;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/thumbnail-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { ThumbnailPluginConfig } from './types';\n\nexport const THUMBNAIL_PLUGIN_ID = 'thumbnail';\n\nexport const manifest: PluginManifest<ThumbnailPluginConfig> = {\n id: THUMBNAIL_PLUGIN_ID,\n name: 'Thumbnail Plugin',\n version: '1.0.0',\n provides: ['thumbnail'],\n requires: ['render'],\n optional: [],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n createEmitter,\n PluginRegistry,\n REFRESH_PAGES,\n SET_DOCUMENT,\n StoreState,\n Unsubscribe,\n} from '@embedpdf/core';\nimport { ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n private cfg: ThumbnailPluginConfig,\n ) {\n super(id, registry);\n\n this.renderCapability = this.registry.getPlugin<RenderPlugin>('render')!.provides();\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n this.refreshPages$.emit(action.payload);\n for (const pageIdx of action.payload) {\n this.taskCache.delete(pageIdx);\n }\n });\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n public onRefreshPages(fn: (pages: number[]) => void): Unsubscribe {\n return this.refreshPages$.on(fn);\n }\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n\n if (!core.document) return;\n\n const W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16; // label\n const GAP = this.cfg.gap ?? 8;\n\n let offset = 0;\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const thumbH = Math.round(W * ratio);\n const wrapH = thumbH + L; // no GAP here\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: W,\n height: thumbH,\n wrapperHeight: wrapH,\n top: offset,\n labelHeight: L,\n };\n offset += wrapH + GAP; // GAP added *after* wrapper\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP, // last item has no gap below\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n onWindow: this.emitWindow.on,\n setViewport: (y, h) => this.updateWindow(y, h),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n };\n }\n\n /* ------------ windowing math ------------------------------------- */\n private updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n /* -------- find first visible wrapper ---------- */\n let low = 0,\n high = this.thumbs.length - 1,\n first = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const m = this.thumbs[mid];\n if (m.top + m.wrapperHeight < scrollY) low = mid + 1;\n else {\n first = mid;\n high = mid - 1;\n }\n }\n\n /* -------- find last visible + buffer ---------- */\n let last = first;\n const limit = scrollY + viewportH;\n while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;\n last = Math.min(this.thumbs.length - 1, last + BUF);\n\n const start = Math.max(0, first - BUF);\n if (this.window && start === this.window.start && last === this.window.end) return;\n\n this.window = {\n start,\n end: last,\n items: this.thumbs.slice(start, last + 1),\n totalHeight: this.window!.totalHeight,\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ thumbnail raster ----------------------------------- */\n private renderThumb(idx: number, dpr: number) {\n if (this.taskCache.has(idx)) return this.taskCache.get(idx)!;\n\n const core = this.coreState.core;\n const page = core.document!.pages[idx];\n const scale = (this.cfg.width ?? 120) / page.size.width;\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n options: {\n scaleFactor: scale,\n dpr,\n },\n });\n\n this.taskCache.set(idx, task);\n\n task.wait(ignore, () => {\n this.taskCache.delete(idx);\n });\n\n return task;\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, THUMBNAIL_PLUGIN_ID } from './manifest';\nimport { ThumbnailPluginConfig } from './types';\nimport { ThumbnailPlugin } from './thumbnail-plugin';\n\nexport const ThumbnailPluginPackage: PluginPackage<ThumbnailPlugin, ThumbnailPluginConfig> = {\n manifest,\n create: (registry, config) => new ThumbnailPlugin(THUMBNAIL_PLUGIN_ID, registry, config),\n reducer: () => {},\n initialState: {},\n};\n\nexport * from './thumbnail-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":["THUMBNAIL_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","enabled","width","gap","buffer","labelHeight","_ThumbnailPlugin","BasePlugin","constructor","registry","cfg","super","this","thumbs","window","emitWindow","createBehaviorEmitter","refreshPages$","createEmitter","taskCache","Map","renderCapability","getPlugin","coreStore","onAction","SET_DOCUMENT","_action","state","clear","setWindowState","REFRESH_PAGES","action","emit","payload","pageIdx","delete","initialize","onRefreshPages","fn","on","core","document","W","L","GAP","offset","pages","map","p","ratio","size","height","thumbH","Math","round","wrapH","meta","pageIndex","index","wrapperHeight","top","start","end","items","totalHeight","buildCapability","onWindow","setViewport","y","h","updateWindow","renderThumb","idx","dpr","scrollY","viewportH","BUF","low","high","length","first","mid","m","last","limit","min","max","slice","has","get","page","coreState","scale","task","renderPageRect","rect","origin","x","options","scaleFactor","set","wait","ignore","ThumbnailPlugin","ThumbnailPluginPackage","create","config","reducer","initialState"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/lib/manifest.ts","../src/lib/thumbnail-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { ThumbnailPluginConfig } from './types';\n\nexport const THUMBNAIL_PLUGIN_ID = 'thumbnail';\n\nexport const manifest: PluginManifest<ThumbnailPluginConfig> = {\n id: THUMBNAIL_PLUGIN_ID,\n name: 'Thumbnail Plugin',\n version: '1.0.0',\n provides: ['thumbnail'],\n requires: ['render'],\n optional: ['scroll'],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n autoScroll: true,\n scrollBehavior: 'smooth',\n imagePadding: 0,\n paddingY: 0,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n createEmitter,\n PluginRegistry,\n REFRESH_PAGES,\n SET_DOCUMENT,\n StoreState,\n Unsubscribe,\n} from '@embedpdf/core';\nimport { ScrollToOptions, ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\nimport { ScrollCapability, ScrollPlugin } from '@embedpdf/plugin-scroll';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private scrollCapability: ScrollCapability | null = null;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n\n // track viewport metrics for scroll decisions\n private viewportH: number = 0;\n private scrollY: number = 0;\n\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\n private canAutoScroll = true;\n // ask pane to scroll to a specific top\n private readonly scrollTo$ = createBehaviorEmitter<ScrollToOptions>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n public cfg: ThumbnailPluginConfig,\n ) {\n super(id, registry);\n\n this.renderCapability = this.registry.getPlugin<RenderPlugin>('render')!.provides();\n this.scrollCapability = this.registry.getPlugin<ScrollPlugin>('scroll')?.provides() ?? null;\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n this.refreshPages$.emit(action.payload);\n for (const pageIdx of action.payload) {\n this.taskCache.delete(pageIdx);\n }\n });\n\n // auto-scroll thumbnails when the main scroller's current page changes\n if (this.scrollCapability && this.cfg.autoScroll !== false) {\n this.scrollCapability.onPageChangeState(({ isChanging, targetPage }) => {\n this.canAutoScroll = !isChanging;\n if (!isChanging) {\n this.scrollToThumb(targetPage - 1);\n }\n });\n this.scrollCapability.onPageChange(({ pageNumber }) => {\n if (this.canAutoScroll) {\n this.scrollToThumb(pageNumber - 1);\n }\n });\n }\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n public onRefreshPages(fn: (pages: number[]) => void): Unsubscribe {\n return this.refreshPages$.on(fn);\n }\n\n public onWindow(cb: (w: WindowState) => void): Unsubscribe {\n return this.emitWindow.on(cb);\n }\n\n public onScrollTo(cb: (o: ScrollToOptions) => void): Unsubscribe {\n return this.scrollTo$.on(cb);\n }\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n if (!core.document) return;\n\n const OUTER_W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16;\n const GAP = this.cfg.gap ?? 8;\n const P = this.cfg.imagePadding ?? 0;\n const PADDING_Y = this.cfg.paddingY ?? 0;\n\n // Inner bitmap width cannot go below 1px\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n let offset = PADDING_Y; // Start with top padding\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const imgH = Math.round(INNER_W * ratio);\n const wrapH = P + imgH + P + L; // padding + image + padding + label\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: INNER_W, // bitmap width (for <img> size)\n height: imgH, // bitmap height (for <img> size)\n wrapperHeight: wrapH, // full row height used by virtualizer\n top: offset, // top of the row\n labelHeight: L,\n padding: P, // NEW\n };\n offset += wrapH + GAP;\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP + PADDING_Y, // Add bottom padding to total height\n };\n\n if (this.viewportH > 0) {\n this.updateWindow(this.scrollY, this.viewportH);\n } else {\n this.emitWindow.emit(this.window);\n }\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n scrollToThumb: (pageIdx) => this.scrollToThumb(pageIdx),\n };\n }\n\n /* ------------ windowing & viewport state ------------------------- */\n public updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n // remember latest viewport metrics for scroll decisions\n this.scrollY = scrollY;\n this.viewportH = viewportH;\n\n // Early return if window state hasn't been initialized yet\n if (!this.window || this.thumbs.length === 0) return;\n\n /* find first visible */\n let low = 0,\n high = this.thumbs.length - 1,\n first = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const m = this.thumbs[mid];\n if (m.top + m.wrapperHeight < scrollY) low = mid + 1;\n else {\n first = mid;\n high = mid - 1;\n }\n }\n\n /* find last visible + buffer */\n let last = first;\n const limit = scrollY + viewportH;\n while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;\n last = Math.min(this.thumbs.length - 1, last + BUF);\n\n const start = Math.max(0, first - BUF);\n if (start === this.window.start && last === this.window.end) return;\n\n this.window = {\n start,\n end: last,\n items: this.thumbs.slice(start, last + 1),\n totalHeight: this.window.totalHeight,\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ scroll helper now in plugin ------------------------ */\n private scrollToThumb(pageIdx: number) {\n if (!this.window) return;\n const item = this.thumbs[pageIdx];\n if (!item) return;\n\n const behavior = this.cfg.scrollBehavior ?? 'smooth';\n const PADDING_Y = this.cfg.paddingY ?? 0; // Include padding in scroll calculations\n\n if (this.viewportH <= 0) {\n // Center the thumbnail in the viewport\n const top = Math.max(PADDING_Y, item.top - item.wrapperHeight);\n this.scrollTo$.emit({ top, behavior });\n return;\n }\n\n const margin = 8;\n const top = item.top;\n const bottom = item.top + item.wrapperHeight;\n\n const needsUp = top < this.scrollY + margin + PADDING_Y;\n const needsDown = bottom > this.scrollY + this.viewportH - margin;\n\n if (needsUp) {\n this.scrollTo$.emit({ top: Math.max(0, top - PADDING_Y), behavior });\n } else if (needsDown) {\n this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH + PADDING_Y), behavior });\n }\n }\n\n /* ------------ thumbnail raster ----------------------------------- */\n private renderThumb(idx: number, dpr: number) {\n if (this.taskCache.has(idx)) return this.taskCache.get(idx)!;\n\n const core = this.coreState.core;\n const page = core.document!.pages[idx];\n\n const OUTER_W = this.cfg.width ?? 120;\n const P = this.cfg.imagePadding ?? 0;\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n const scale = INNER_W / page.size.width; // scale to inner width (excludes padding)\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n options: {\n scaleFactor: scale,\n dpr,\n },\n });\n\n this.taskCache.set(idx, task);\n task.wait(ignore, () => this.taskCache.delete(idx));\n return task;\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, THUMBNAIL_PLUGIN_ID } from './manifest';\nimport { ThumbnailPluginConfig } from './types';\nimport { ThumbnailPlugin } from './thumbnail-plugin';\n\nexport const ThumbnailPluginPackage: PluginPackage<ThumbnailPlugin, ThumbnailPluginConfig> = {\n manifest,\n create: (registry, config) => new ThumbnailPlugin(THUMBNAIL_PLUGIN_ID, registry, config),\n reducer: () => {},\n initialState: {},\n};\n\nexport * from './thumbnail-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":["THUMBNAIL_PLUGIN_ID","manifest","id","name","version","provides","requires","optional","defaultConfig","enabled","width","gap","buffer","labelHeight","autoScroll","scrollBehavior","imagePadding","paddingY","_ThumbnailPlugin","BasePlugin","constructor","registry","cfg","super","this","scrollCapability","thumbs","window","viewportH","scrollY","emitWindow","createBehaviorEmitter","refreshPages$","createEmitter","taskCache","Map","canAutoScroll","scrollTo$","renderCapability","getPlugin","_a","coreStore","onAction","SET_DOCUMENT","_action","state","clear","setWindowState","REFRESH_PAGES","action","emit","payload","pageIdx","delete","onPageChangeState","isChanging","targetPage","scrollToThumb","onPageChange","pageNumber","initialize","onRefreshPages","fn","on","onWindow","cb","onScrollTo","core","document","OUTER_W","L","GAP","P","PADDING_Y","INNER_W","Math","max","offset","pages","map","p","ratio","size","height","imgH","round","wrapH","meta","pageIndex","index","wrapperHeight","top","padding","start","end","items","totalHeight","updateWindow","buildCapability","renderThumb","idx","dpr","BUF","length","low","high","first","mid","m","last","limit","min","slice","item","behavior","bottom","needsUp","needsDown","has","get","page","coreState","scale","task","renderPageRect","rect","origin","x","y","options","scaleFactor","set","wait","ignore","ThumbnailPlugin","ThumbnailPluginPackage","create","config","reducer","initialState"],"mappings":"gJAGaA,EAAsB,YAEtBC,EAAkD,CAC7DC,GAAIF,EACJG,KAAM,mBACNC,QAAS,QACTC,SAAU,CAAC,aACXC,SAAU,CAAC,UACXC,SAAU,CAAC,UACXC,cAAe,CACbC,SAAS,EACTC,MAAO,IACPC,IAAK,GACLC,OAAQ,EACRC,YAAa,GACbC,YAAY,EACZC,eAAgB,SAChBC,aAAc,EACdC,SAAU,ICJDC,EAAN,cAA8BC,EAAAA,WAmBnC,WAAAC,CACElB,EACAmB,EACOC,SAEPC,MAAMrB,EAAImB,GAFHG,KAAAF,IAAAA,EAlBTE,KAAQC,iBAA4C,KACpDD,KAAQE,OAAsB,GAC9BF,KAAQG,OAA6B,KAGrCH,KAAQI,UAAoB,EAC5BJ,KAAQK,QAAkB,EAETL,KAAAM,WAAaC,0BACbP,KAAAQ,cAAgBC,kBAChBT,KAAAU,cAAgBC,IACjCX,KAAQY,eAAgB,EAEPZ,KAAAa,UAAYN,0BAS3BP,KAAKc,iBAAmBd,KAAKH,SAASkB,UAAwB,UAAWlC,WACzEmB,KAAKC,kBAAmB,OAAAe,EAAKhB,KAAAH,SAASkB,UAAwB,oBAAWlC,aAAc,KAEvFmB,KAAKiB,UAAUC,SAASC,EAAcA,cAAA,CAACC,EAASC,KAC9CrB,KAAKU,UAAUY,QACftB,KAAKuB,eAAeF,EAAK,IAG3BrB,KAAKiB,UAAUC,SAASM,EAAeA,eAACC,IACjCzB,KAAAQ,cAAckB,KAAKD,EAAOE,SACpB,IAAA,MAAAC,KAAWH,EAAOE,QACtB3B,KAAAU,UAAUmB,OAAOD,EAAO,IAK7B5B,KAAKC,mBAA4C,IAAxBD,KAAKF,IAAIR,aACpCU,KAAKC,iBAAiB6B,mBAAkB,EAAGC,aAAYC,iBACrDhC,KAAKY,eAAiBmB,EACjBA,GACE/B,KAAAiC,cAAcD,EAAa,EAAC,IAGrChC,KAAKC,iBAAiBiC,cAAa,EAAGC,iBAChCnC,KAAKY,eACFZ,KAAAiC,cAAcE,EAAa,EAAC,IAGvC,CAIF,gBAAMC,GAA4B,CAE3B,cAAAC,CAAeC,GACb,OAAAtC,KAAKQ,cAAc+B,GAAGD,EAAE,CAG1B,QAAAE,CAASC,GACP,OAAAzC,KAAKM,WAAWiC,GAAGE,EAAE,CAGvB,UAAAC,CAAWD,GACT,OAAAzC,KAAKa,UAAU0B,GAAGE,EAAE,CAGrB,cAAAlB,CAAeF,GACrB,MAAMsB,EAAOtB,EAAMsB,KACf,IAACA,EAAKC,SAAU,OAEd,MAAAC,EAAU7C,KAAKF,IAAIZ,OAAS,IAC5B4D,EAAI9C,KAAKF,IAAIT,aAAe,GAC5B0D,EAAM/C,KAAKF,IAAIX,KAAO,EACtB6D,EAAIhD,KAAKF,IAAIN,cAAgB,EAC7ByD,EAAYjD,KAAKF,IAAIL,UAAY,EAGjCyD,EAAUC,KAAKC,IAAI,EAAGP,EAAU,EAAIG,GAE1C,IAAIK,EAASJ,EACbjD,KAAKE,OAASyC,EAAKC,SAASU,MAAMC,KAAKC,IACrC,MAAMC,EAAQD,EAAEE,KAAKC,OAASH,EAAEE,KAAKxE,MAC/B0E,EAAOT,KAAKU,MAAMX,EAAUO,GAC5BK,EAAQd,EAAIY,EAAOZ,EAAIF,EAEvBiB,EAAkB,CACtBC,UAAWR,EAAES,MACb/E,MAAOgE,EACPS,OAAQC,EACRM,cAAeJ,EACfK,IAAKd,EACLhE,YAAayD,EACbsB,QAASpB,GAGJ,OADPK,GAAUS,EAAQf,EACXgB,CAAA,IAGT/D,KAAKG,OAAS,CACZkE,OAAO,EACPC,KAAK,EACLC,MAAO,GACPC,YAAanB,EAASN,EAAME,GAG1BjD,KAAKI,UAAY,EACnBJ,KAAKyE,aAAazE,KAAKK,QAASL,KAAKI,WAEhCJ,KAAAM,WAAWoB,KAAK1B,KAAKG,OAC5B,CAIQ,eAAAuE,GACD,MAAA,CACLC,YAAa,CAACC,EAAKC,IAAQ7E,KAAK2E,YAAYC,EAAKC,GACjD5C,cAAgBL,GAAY5B,KAAKiC,cAAcL,GACjD,CAIK,YAAA6C,CAAapE,EAAiBD,GAC7B,MAAA0E,EAAM9E,KAAKF,IAAIV,QAAU,EAO/B,GAJAY,KAAKK,QAAUA,EACfL,KAAKI,UAAYA,GAGZJ,KAAKG,QAAiC,IAAvBH,KAAKE,OAAO6E,OAAc,OAG9C,IAAIC,EAAM,EACRC,EAAOjF,KAAKE,OAAO6E,OAAS,EAC5BG,EAAQ,EACV,KAAOF,GAAOC,GAAM,CACZ,MAAAE,EAAOH,EAAMC,GAAS,EACtBG,EAAIpF,KAAKE,OAAOiF,GAClBC,EAAEjB,IAAMiB,EAAElB,cAAgB7D,IAAe8E,EAAM,GAEzCD,EAAAC,EACRF,EAAOE,EAAM,EACf,CAIF,IAAIE,EAAOH,EACX,MAAMI,EAAQjF,EAAUD,EACjB,KAAAiF,EAAO,EAAIrF,KAAKE,OAAO6E,QAAU/E,KAAKE,OAAOmF,GAAMlB,IAAMmB,GAAOD,IACvEA,EAAOlC,KAAKoC,IAAIvF,KAAKE,OAAO6E,OAAS,EAAGM,EAAOP,GAE/C,MAAMT,EAAQlB,KAAKC,IAAI,EAAG8B,EAAQJ,GAC9BT,IAAUrE,KAAKG,OAAOkE,OAASgB,IAASrF,KAAKG,OAAOmE,MAExDtE,KAAKG,OAAS,CACZkE,QACAC,IAAKe,EACLd,MAAOvE,KAAKE,OAAOsF,MAAMnB,EAAOgB,EAAO,GACvCb,YAAaxE,KAAKG,OAAOqE,aAEtBxE,KAAAM,WAAWoB,KAAK1B,KAAKG,QAAM,CAI1B,aAAA8B,CAAcL,GAChB,IAAC5B,KAAKG,OAAQ,OACZ,MAAAsF,EAAOzF,KAAKE,OAAO0B,GACzB,IAAK6D,EAAM,OAEL,MAAAC,EAAW1F,KAAKF,IAAIP,gBAAkB,SACtC0D,EAAYjD,KAAKF,IAAIL,UAAY,EAEnC,GAAAO,KAAKI,WAAa,EAAG,CAEvB,MAAM+D,EAAMhB,KAAKC,IAAIH,EAAWwC,EAAKtB,IAAMsB,EAAKvB,eAEhD,YADAlE,KAAKa,UAAUa,KAAK,CAAEyC,IAAAA,EAAKuB,YAC3B,CAGF,MACMvB,EAAMsB,EAAKtB,IACXwB,EAASF,EAAKtB,IAAMsB,EAAKvB,cAEzB0B,EAAUzB,EAAMnE,KAAKK,QAJZ,EAI+B4C,EACxC4C,EAAYF,EAAS3F,KAAKK,QAAUL,KAAKI,UALhC,EAOXwF,EACG5F,KAAAa,UAAUa,KAAK,CAAEyC,IAAKhB,KAAKC,IAAI,EAAGe,EAAMlB,GAAYyC,aAChDG,GACT7F,KAAKa,UAAUa,KAAK,CAAEyC,IAAKhB,KAAKC,IAAI,EAAGuC,EAAS3F,KAAKI,UAAY6C,GAAYyC,YAC/E,CAIM,WAAAf,CAAYC,EAAaC,GAC3B,GAAA7E,KAAKU,UAAUoF,IAAIlB,GAAa,OAAA5E,KAAKU,UAAUqF,IAAInB,GAEjDjC,MACAqD,EADOhG,KAAKiG,UAAUtD,KACVC,SAAUU,MAAMsB,GAE5B/B,EAAU7C,KAAKF,IAAIZ,OAAS,IAC5B8D,EAAIhD,KAAKF,IAAIN,cAAgB,EAG7B0G,EAFU/C,KAAKC,IAAI,EAAGP,EAAU,EAAIG,GAElBgD,EAAKtC,KAAKxE,MAE5BiH,EAAOnG,KAAKc,iBAAiBsF,eAAe,CAChDpC,UAAWY,EACXyB,KAAM,CAAEC,OAAQ,CAAEC,EAAG,EAAGC,EAAG,GAAK9C,KAAMsC,EAAKtC,MAC3C+C,QAAS,CACPC,YAAaR,EACbrB,SAMG,OAFF7E,KAAAU,UAAUiG,IAAI/B,EAAKuB,GACnBA,EAAAS,KAAKC,UAAQ,IAAM7G,KAAKU,UAAUmB,OAAO+C,KACvCuB,CAAA,GA9NTzG,EAAgBhB,GAAK,YADhB,IAAMoI,EAANpH,ECZA,MAAMqH,EAAgF,CAC3FtI,WACAuI,OAAQ,CAACnH,EAAUoH,IAAW,IAAIH,EAAgBtI,EAAqBqB,EAAUoH,GACjFC,QAAS,OACTC,aAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -7,25 +7,36 @@ const manifest = {
|
|
|
7
7
|
version: "1.0.0",
|
|
8
8
|
provides: ["thumbnail"],
|
|
9
9
|
requires: ["render"],
|
|
10
|
-
optional: [],
|
|
10
|
+
optional: ["scroll"],
|
|
11
11
|
defaultConfig: {
|
|
12
12
|
enabled: true,
|
|
13
13
|
width: 150,
|
|
14
14
|
gap: 10,
|
|
15
15
|
buffer: 3,
|
|
16
|
-
labelHeight: 16
|
|
16
|
+
labelHeight: 16,
|
|
17
|
+
autoScroll: true,
|
|
18
|
+
scrollBehavior: "smooth",
|
|
19
|
+
imagePadding: 0,
|
|
20
|
+
paddingY: 0
|
|
17
21
|
}
|
|
18
22
|
};
|
|
19
23
|
const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
20
24
|
constructor(id, registry, cfg) {
|
|
25
|
+
var _a;
|
|
21
26
|
super(id, registry);
|
|
22
27
|
this.cfg = cfg;
|
|
28
|
+
this.scrollCapability = null;
|
|
23
29
|
this.thumbs = [];
|
|
24
30
|
this.window = null;
|
|
31
|
+
this.viewportH = 0;
|
|
32
|
+
this.scrollY = 0;
|
|
25
33
|
this.emitWindow = createBehaviorEmitter();
|
|
26
34
|
this.refreshPages$ = createEmitter();
|
|
27
35
|
this.taskCache = /* @__PURE__ */ new Map();
|
|
36
|
+
this.canAutoScroll = true;
|
|
37
|
+
this.scrollTo$ = createBehaviorEmitter();
|
|
28
38
|
this.renderCapability = this.registry.getPlugin("render").provides();
|
|
39
|
+
this.scrollCapability = ((_a = this.registry.getPlugin("scroll")) == null ? void 0 : _a.provides()) ?? null;
|
|
29
40
|
this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {
|
|
30
41
|
this.taskCache.clear();
|
|
31
42
|
this.setWindowState(state);
|
|
@@ -36,6 +47,19 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
|
36
47
|
this.taskCache.delete(pageIdx);
|
|
37
48
|
}
|
|
38
49
|
});
|
|
50
|
+
if (this.scrollCapability && this.cfg.autoScroll !== false) {
|
|
51
|
+
this.scrollCapability.onPageChangeState(({ isChanging, targetPage }) => {
|
|
52
|
+
this.canAutoScroll = !isChanging;
|
|
53
|
+
if (!isChanging) {
|
|
54
|
+
this.scrollToThumb(targetPage - 1);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
this.scrollCapability.onPageChange(({ pageNumber }) => {
|
|
58
|
+
if (this.canAutoScroll) {
|
|
59
|
+
this.scrollToThumb(pageNumber - 1);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
39
63
|
}
|
|
40
64
|
/* ------------ init ------------------------------------------------ */
|
|
41
65
|
async initialize() {
|
|
@@ -43,24 +67,39 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
|
43
67
|
onRefreshPages(fn) {
|
|
44
68
|
return this.refreshPages$.on(fn);
|
|
45
69
|
}
|
|
70
|
+
onWindow(cb) {
|
|
71
|
+
return this.emitWindow.on(cb);
|
|
72
|
+
}
|
|
73
|
+
onScrollTo(cb) {
|
|
74
|
+
return this.scrollTo$.on(cb);
|
|
75
|
+
}
|
|
46
76
|
setWindowState(state) {
|
|
47
77
|
const core = state.core;
|
|
48
78
|
if (!core.document) return;
|
|
49
|
-
const
|
|
79
|
+
const OUTER_W = this.cfg.width ?? 120;
|
|
50
80
|
const L = this.cfg.labelHeight ?? 16;
|
|
51
81
|
const GAP = this.cfg.gap ?? 8;
|
|
52
|
-
|
|
82
|
+
const P = this.cfg.imagePadding ?? 0;
|
|
83
|
+
const PADDING_Y = this.cfg.paddingY ?? 0;
|
|
84
|
+
const INNER_W = Math.max(1, OUTER_W - 2 * P);
|
|
85
|
+
let offset = PADDING_Y;
|
|
53
86
|
this.thumbs = core.document.pages.map((p) => {
|
|
54
87
|
const ratio = p.size.height / p.size.width;
|
|
55
|
-
const
|
|
56
|
-
const wrapH =
|
|
88
|
+
const imgH = Math.round(INNER_W * ratio);
|
|
89
|
+
const wrapH = P + imgH + P + L;
|
|
57
90
|
const meta = {
|
|
58
91
|
pageIndex: p.index,
|
|
59
|
-
width:
|
|
60
|
-
|
|
92
|
+
width: INNER_W,
|
|
93
|
+
// bitmap width (for <img> size)
|
|
94
|
+
height: imgH,
|
|
95
|
+
// bitmap height (for <img> size)
|
|
61
96
|
wrapperHeight: wrapH,
|
|
97
|
+
// full row height used by virtualizer
|
|
62
98
|
top: offset,
|
|
63
|
-
|
|
99
|
+
// top of the row
|
|
100
|
+
labelHeight: L,
|
|
101
|
+
padding: P
|
|
102
|
+
// NEW
|
|
64
103
|
};
|
|
65
104
|
offset += wrapH + GAP;
|
|
66
105
|
return meta;
|
|
@@ -69,22 +108,28 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
|
69
108
|
start: -1,
|
|
70
109
|
end: -1,
|
|
71
110
|
items: [],
|
|
72
|
-
totalHeight: offset - GAP
|
|
73
|
-
//
|
|
111
|
+
totalHeight: offset - GAP + PADDING_Y
|
|
112
|
+
// Add bottom padding to total height
|
|
74
113
|
};
|
|
75
|
-
|
|
114
|
+
if (this.viewportH > 0) {
|
|
115
|
+
this.updateWindow(this.scrollY, this.viewportH);
|
|
116
|
+
} else {
|
|
117
|
+
this.emitWindow.emit(this.window);
|
|
118
|
+
}
|
|
76
119
|
}
|
|
77
120
|
/* ------------ capability ----------------------------------------- */
|
|
78
121
|
buildCapability() {
|
|
79
122
|
return {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
renderThumb: (idx, dpr) => this.renderThumb(idx, dpr)
|
|
123
|
+
renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),
|
|
124
|
+
scrollToThumb: (pageIdx) => this.scrollToThumb(pageIdx)
|
|
83
125
|
};
|
|
84
126
|
}
|
|
85
|
-
/* ------------ windowing
|
|
127
|
+
/* ------------ windowing & viewport state ------------------------- */
|
|
86
128
|
updateWindow(scrollY, viewportH) {
|
|
87
129
|
const BUF = this.cfg.buffer ?? 3;
|
|
130
|
+
this.scrollY = scrollY;
|
|
131
|
+
this.viewportH = viewportH;
|
|
132
|
+
if (!this.window || this.thumbs.length === 0) return;
|
|
88
133
|
let low = 0, high = this.thumbs.length - 1, first = 0;
|
|
89
134
|
while (low <= high) {
|
|
90
135
|
const mid = low + high >> 1;
|
|
@@ -100,7 +145,7 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
|
100
145
|
while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;
|
|
101
146
|
last = Math.min(this.thumbs.length - 1, last + BUF);
|
|
102
147
|
const start = Math.max(0, first - BUF);
|
|
103
|
-
if (
|
|
148
|
+
if (start === this.window.start && last === this.window.end) return;
|
|
104
149
|
this.window = {
|
|
105
150
|
start,
|
|
106
151
|
end: last,
|
|
@@ -109,12 +154,38 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
|
109
154
|
};
|
|
110
155
|
this.emitWindow.emit(this.window);
|
|
111
156
|
}
|
|
157
|
+
/* ------------ scroll helper now in plugin ------------------------ */
|
|
158
|
+
scrollToThumb(pageIdx) {
|
|
159
|
+
if (!this.window) return;
|
|
160
|
+
const item = this.thumbs[pageIdx];
|
|
161
|
+
if (!item) return;
|
|
162
|
+
const behavior = this.cfg.scrollBehavior ?? "smooth";
|
|
163
|
+
const PADDING_Y = this.cfg.paddingY ?? 0;
|
|
164
|
+
if (this.viewportH <= 0) {
|
|
165
|
+
const top2 = Math.max(PADDING_Y, item.top - item.wrapperHeight);
|
|
166
|
+
this.scrollTo$.emit({ top: top2, behavior });
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const margin = 8;
|
|
170
|
+
const top = item.top;
|
|
171
|
+
const bottom = item.top + item.wrapperHeight;
|
|
172
|
+
const needsUp = top < this.scrollY + margin + PADDING_Y;
|
|
173
|
+
const needsDown = bottom > this.scrollY + this.viewportH - margin;
|
|
174
|
+
if (needsUp) {
|
|
175
|
+
this.scrollTo$.emit({ top: Math.max(0, top - PADDING_Y), behavior });
|
|
176
|
+
} else if (needsDown) {
|
|
177
|
+
this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH + PADDING_Y), behavior });
|
|
178
|
+
}
|
|
179
|
+
}
|
|
112
180
|
/* ------------ thumbnail raster ----------------------------------- */
|
|
113
181
|
renderThumb(idx, dpr) {
|
|
114
182
|
if (this.taskCache.has(idx)) return this.taskCache.get(idx);
|
|
115
183
|
const core = this.coreState.core;
|
|
116
184
|
const page = core.document.pages[idx];
|
|
117
|
-
const
|
|
185
|
+
const OUTER_W = this.cfg.width ?? 120;
|
|
186
|
+
const P = this.cfg.imagePadding ?? 0;
|
|
187
|
+
const INNER_W = Math.max(1, OUTER_W - 2 * P);
|
|
188
|
+
const scale = INNER_W / page.size.width;
|
|
118
189
|
const task = this.renderCapability.renderPageRect({
|
|
119
190
|
pageIndex: idx,
|
|
120
191
|
rect: { origin: { x: 0, y: 0 }, size: page.size },
|
|
@@ -124,9 +195,7 @@ const _ThumbnailPlugin = class _ThumbnailPlugin extends BasePlugin {
|
|
|
124
195
|
}
|
|
125
196
|
});
|
|
126
197
|
this.taskCache.set(idx, task);
|
|
127
|
-
task.wait(ignore, () =>
|
|
128
|
-
this.taskCache.delete(idx);
|
|
129
|
-
});
|
|
198
|
+
task.wait(ignore, () => this.taskCache.delete(idx));
|
|
130
199
|
return task;
|
|
131
200
|
}
|
|
132
201
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/lib/manifest.ts","../src/lib/thumbnail-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { ThumbnailPluginConfig } from './types';\n\nexport const THUMBNAIL_PLUGIN_ID = 'thumbnail';\n\nexport const manifest: PluginManifest<ThumbnailPluginConfig> = {\n id: THUMBNAIL_PLUGIN_ID,\n name: 'Thumbnail Plugin',\n version: '1.0.0',\n provides: ['thumbnail'],\n requires: ['render'],\n optional: [],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n createEmitter,\n PluginRegistry,\n REFRESH_PAGES,\n SET_DOCUMENT,\n StoreState,\n Unsubscribe,\n} from '@embedpdf/core';\nimport { ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n private cfg: ThumbnailPluginConfig,\n ) {\n super(id, registry);\n\n this.renderCapability = this.registry.getPlugin<RenderPlugin>('render')!.provides();\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n this.refreshPages$.emit(action.payload);\n for (const pageIdx of action.payload) {\n this.taskCache.delete(pageIdx);\n }\n });\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n public onRefreshPages(fn: (pages: number[]) => void): Unsubscribe {\n return this.refreshPages$.on(fn);\n }\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n\n if (!core.document) return;\n\n const W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16; // label\n const GAP = this.cfg.gap ?? 8;\n\n let offset = 0;\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const thumbH = Math.round(W * ratio);\n const wrapH = thumbH + L; // no GAP here\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: W,\n height: thumbH,\n wrapperHeight: wrapH,\n top: offset,\n labelHeight: L,\n };\n offset += wrapH + GAP; // GAP added *after* wrapper\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP, // last item has no gap below\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n onWindow: this.emitWindow.on,\n setViewport: (y, h) => this.updateWindow(y, h),\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n };\n }\n\n /* ------------ windowing math ------------------------------------- */\n private updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n /* -------- find first visible wrapper ---------- */\n let low = 0,\n high = this.thumbs.length - 1,\n first = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const m = this.thumbs[mid];\n if (m.top + m.wrapperHeight < scrollY) low = mid + 1;\n else {\n first = mid;\n high = mid - 1;\n }\n }\n\n /* -------- find last visible + buffer ---------- */\n let last = first;\n const limit = scrollY + viewportH;\n while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;\n last = Math.min(this.thumbs.length - 1, last + BUF);\n\n const start = Math.max(0, first - BUF);\n if (this.window && start === this.window.start && last === this.window.end) return;\n\n this.window = {\n start,\n end: last,\n items: this.thumbs.slice(start, last + 1),\n totalHeight: this.window!.totalHeight,\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ thumbnail raster ----------------------------------- */\n private renderThumb(idx: number, dpr: number) {\n if (this.taskCache.has(idx)) return this.taskCache.get(idx)!;\n\n const core = this.coreState.core;\n const page = core.document!.pages[idx];\n const scale = (this.cfg.width ?? 120) / page.size.width;\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n options: {\n scaleFactor: scale,\n dpr,\n },\n });\n\n this.taskCache.set(idx, task);\n\n task.wait(ignore, () => {\n this.taskCache.delete(idx);\n });\n\n return task;\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, THUMBNAIL_PLUGIN_ID } from './manifest';\nimport { ThumbnailPluginConfig } from './types';\nimport { ThumbnailPlugin } from './thumbnail-plugin';\n\nexport const ThumbnailPluginPackage: PluginPackage<ThumbnailPlugin, ThumbnailPluginConfig> = {\n manifest,\n create: (registry, config) => new ThumbnailPlugin(THUMBNAIL_PLUGIN_ID, registry, config),\n reducer: () => {},\n initialState: {},\n};\n\nexport * from './thumbnail-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/lib/manifest.ts","../src/lib/thumbnail-plugin.ts","../src/lib/index.ts"],"sourcesContent":["import { PluginManifest } from '@embedpdf/core';\nimport { ThumbnailPluginConfig } from './types';\n\nexport const THUMBNAIL_PLUGIN_ID = 'thumbnail';\n\nexport const manifest: PluginManifest<ThumbnailPluginConfig> = {\n id: THUMBNAIL_PLUGIN_ID,\n name: 'Thumbnail Plugin',\n version: '1.0.0',\n provides: ['thumbnail'],\n requires: ['render'],\n optional: ['scroll'],\n defaultConfig: {\n enabled: true,\n width: 150,\n gap: 10,\n buffer: 3,\n labelHeight: 16,\n autoScroll: true,\n scrollBehavior: 'smooth',\n imagePadding: 0,\n paddingY: 0,\n },\n};\n","import {\n BasePlugin,\n CoreState,\n createBehaviorEmitter,\n createEmitter,\n PluginRegistry,\n REFRESH_PAGES,\n SET_DOCUMENT,\n StoreState,\n Unsubscribe,\n} from '@embedpdf/core';\nimport { ScrollToOptions, ThumbMeta, ThumbnailPluginConfig, WindowState } from './types';\nimport { ThumbnailCapability } from './types';\nimport { ignore, PdfErrorReason, Task } from '@embedpdf/models';\nimport { RenderCapability, RenderPlugin } from '@embedpdf/plugin-render';\nimport { ScrollCapability, ScrollPlugin } from '@embedpdf/plugin-scroll';\n\nexport class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {\n static readonly id = 'thumbnail' as const;\n\n private renderCapability: RenderCapability;\n private scrollCapability: ScrollCapability | null = null;\n private thumbs: ThumbMeta[] = [];\n private window: WindowState | null = null;\n\n // track viewport metrics for scroll decisions\n private viewportH: number = 0;\n private scrollY: number = 0;\n\n private readonly emitWindow = createBehaviorEmitter<WindowState>();\n private readonly refreshPages$ = createEmitter<number[]>();\n private readonly taskCache = new Map<number, Task<Blob, PdfErrorReason>>();\n private canAutoScroll = true;\n // ask pane to scroll to a specific top\n private readonly scrollTo$ = createBehaviorEmitter<ScrollToOptions>();\n\n constructor(\n id: string,\n registry: PluginRegistry,\n public cfg: ThumbnailPluginConfig,\n ) {\n super(id, registry);\n\n this.renderCapability = this.registry.getPlugin<RenderPlugin>('render')!.provides();\n this.scrollCapability = this.registry.getPlugin<ScrollPlugin>('scroll')?.provides() ?? null;\n\n this.coreStore.onAction(SET_DOCUMENT, (_action, state) => {\n this.taskCache.clear();\n this.setWindowState(state);\n });\n\n this.coreStore.onAction(REFRESH_PAGES, (action) => {\n this.refreshPages$.emit(action.payload);\n for (const pageIdx of action.payload) {\n this.taskCache.delete(pageIdx);\n }\n });\n\n // auto-scroll thumbnails when the main scroller's current page changes\n if (this.scrollCapability && this.cfg.autoScroll !== false) {\n this.scrollCapability.onPageChangeState(({ isChanging, targetPage }) => {\n this.canAutoScroll = !isChanging;\n if (!isChanging) {\n this.scrollToThumb(targetPage - 1);\n }\n });\n this.scrollCapability.onPageChange(({ pageNumber }) => {\n if (this.canAutoScroll) {\n this.scrollToThumb(pageNumber - 1);\n }\n });\n }\n }\n\n /* ------------ init ------------------------------------------------ */\n async initialize(): Promise<void> {}\n\n public onRefreshPages(fn: (pages: number[]) => void): Unsubscribe {\n return this.refreshPages$.on(fn);\n }\n\n public onWindow(cb: (w: WindowState) => void): Unsubscribe {\n return this.emitWindow.on(cb);\n }\n\n public onScrollTo(cb: (o: ScrollToOptions) => void): Unsubscribe {\n return this.scrollTo$.on(cb);\n }\n\n private setWindowState(state: StoreState<CoreState>) {\n const core = state.core;\n if (!core.document) return;\n\n const OUTER_W = this.cfg.width ?? 120;\n const L = this.cfg.labelHeight ?? 16;\n const GAP = this.cfg.gap ?? 8;\n const P = this.cfg.imagePadding ?? 0;\n const PADDING_Y = this.cfg.paddingY ?? 0;\n\n // Inner bitmap width cannot go below 1px\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n let offset = PADDING_Y; // Start with top padding\n this.thumbs = core.document.pages.map((p) => {\n const ratio = p.size.height / p.size.width;\n const imgH = Math.round(INNER_W * ratio);\n const wrapH = P + imgH + P + L; // padding + image + padding + label\n\n const meta: ThumbMeta = {\n pageIndex: p.index,\n width: INNER_W, // bitmap width (for <img> size)\n height: imgH, // bitmap height (for <img> size)\n wrapperHeight: wrapH, // full row height used by virtualizer\n top: offset, // top of the row\n labelHeight: L,\n padding: P, // NEW\n };\n offset += wrapH + GAP;\n return meta;\n });\n\n this.window = {\n start: -1,\n end: -1,\n items: [],\n totalHeight: offset - GAP + PADDING_Y, // Add bottom padding to total height\n };\n\n if (this.viewportH > 0) {\n this.updateWindow(this.scrollY, this.viewportH);\n } else {\n this.emitWindow.emit(this.window);\n }\n }\n\n /* ------------ capability ----------------------------------------- */\n protected buildCapability(): ThumbnailCapability {\n return {\n renderThumb: (idx, dpr) => this.renderThumb(idx, dpr),\n scrollToThumb: (pageIdx) => this.scrollToThumb(pageIdx),\n };\n }\n\n /* ------------ windowing & viewport state ------------------------- */\n public updateWindow(scrollY: number, viewportH: number) {\n const BUF = this.cfg.buffer ?? 3;\n\n // remember latest viewport metrics for scroll decisions\n this.scrollY = scrollY;\n this.viewportH = viewportH;\n\n // Early return if window state hasn't been initialized yet\n if (!this.window || this.thumbs.length === 0) return;\n\n /* find first visible */\n let low = 0,\n high = this.thumbs.length - 1,\n first = 0;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const m = this.thumbs[mid];\n if (m.top + m.wrapperHeight < scrollY) low = mid + 1;\n else {\n first = mid;\n high = mid - 1;\n }\n }\n\n /* find last visible + buffer */\n let last = first;\n const limit = scrollY + viewportH;\n while (last + 1 < this.thumbs.length && this.thumbs[last].top < limit) last++;\n last = Math.min(this.thumbs.length - 1, last + BUF);\n\n const start = Math.max(0, first - BUF);\n if (start === this.window.start && last === this.window.end) return;\n\n this.window = {\n start,\n end: last,\n items: this.thumbs.slice(start, last + 1),\n totalHeight: this.window.totalHeight,\n };\n this.emitWindow.emit(this.window);\n }\n\n /* ------------ scroll helper now in plugin ------------------------ */\n private scrollToThumb(pageIdx: number) {\n if (!this.window) return;\n const item = this.thumbs[pageIdx];\n if (!item) return;\n\n const behavior = this.cfg.scrollBehavior ?? 'smooth';\n const PADDING_Y = this.cfg.paddingY ?? 0; // Include padding in scroll calculations\n\n if (this.viewportH <= 0) {\n // Center the thumbnail in the viewport\n const top = Math.max(PADDING_Y, item.top - item.wrapperHeight);\n this.scrollTo$.emit({ top, behavior });\n return;\n }\n\n const margin = 8;\n const top = item.top;\n const bottom = item.top + item.wrapperHeight;\n\n const needsUp = top < this.scrollY + margin + PADDING_Y;\n const needsDown = bottom > this.scrollY + this.viewportH - margin;\n\n if (needsUp) {\n this.scrollTo$.emit({ top: Math.max(0, top - PADDING_Y), behavior });\n } else if (needsDown) {\n this.scrollTo$.emit({ top: Math.max(0, bottom - this.viewportH + PADDING_Y), behavior });\n }\n }\n\n /* ------------ thumbnail raster ----------------------------------- */\n private renderThumb(idx: number, dpr: number) {\n if (this.taskCache.has(idx)) return this.taskCache.get(idx)!;\n\n const core = this.coreState.core;\n const page = core.document!.pages[idx];\n\n const OUTER_W = this.cfg.width ?? 120;\n const P = this.cfg.imagePadding ?? 0;\n const INNER_W = Math.max(1, OUTER_W - 2 * P);\n\n const scale = INNER_W / page.size.width; // scale to inner width (excludes padding)\n\n const task = this.renderCapability.renderPageRect({\n pageIndex: idx,\n rect: { origin: { x: 0, y: 0 }, size: page.size },\n options: {\n scaleFactor: scale,\n dpr,\n },\n });\n\n this.taskCache.set(idx, task);\n task.wait(ignore, () => this.taskCache.delete(idx));\n return task;\n }\n}\n","import { PluginPackage } from '@embedpdf/core';\nimport { manifest, THUMBNAIL_PLUGIN_ID } from './manifest';\nimport { ThumbnailPluginConfig } from './types';\nimport { ThumbnailPlugin } from './thumbnail-plugin';\n\nexport const ThumbnailPluginPackage: PluginPackage<ThumbnailPlugin, ThumbnailPluginConfig> = {\n manifest,\n create: (registry, config) => new ThumbnailPlugin(THUMBNAIL_PLUGIN_ID, registry, config),\n reducer: () => {},\n initialState: {},\n};\n\nexport * from './thumbnail-plugin';\nexport * from './types';\nexport * from './manifest';\n"],"names":["top"],"mappings":"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"}
|
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
import { BasePlugin, PluginRegistry, Unsubscribe } from '@embedpdf/core';
|
|
2
|
-
import { ThumbnailPluginConfig, ThumbnailCapability } from './types';
|
|
2
|
+
import { ScrollToOptions, ThumbnailPluginConfig, WindowState, ThumbnailCapability } from './types';
|
|
3
3
|
export declare class ThumbnailPlugin extends BasePlugin<ThumbnailPluginConfig, ThumbnailCapability> {
|
|
4
|
-
|
|
4
|
+
cfg: ThumbnailPluginConfig;
|
|
5
5
|
static readonly id: "thumbnail";
|
|
6
6
|
private renderCapability;
|
|
7
|
+
private scrollCapability;
|
|
7
8
|
private thumbs;
|
|
8
9
|
private window;
|
|
10
|
+
private viewportH;
|
|
11
|
+
private scrollY;
|
|
9
12
|
private readonly emitWindow;
|
|
10
13
|
private readonly refreshPages$;
|
|
11
14
|
private readonly taskCache;
|
|
15
|
+
private canAutoScroll;
|
|
16
|
+
private readonly scrollTo$;
|
|
12
17
|
constructor(id: string, registry: PluginRegistry, cfg: ThumbnailPluginConfig);
|
|
13
18
|
initialize(): Promise<void>;
|
|
14
19
|
onRefreshPages(fn: (pages: number[]) => void): Unsubscribe;
|
|
20
|
+
onWindow(cb: (w: WindowState) => void): Unsubscribe;
|
|
21
|
+
onScrollTo(cb: (o: ScrollToOptions) => void): Unsubscribe;
|
|
15
22
|
private setWindowState;
|
|
16
23
|
protected buildCapability(): ThumbnailCapability;
|
|
17
|
-
|
|
24
|
+
updateWindow(scrollY: number, viewportH: number): void;
|
|
25
|
+
private scrollToThumb;
|
|
18
26
|
private renderThumb;
|
|
19
27
|
}
|
package/dist/lib/types.d.ts
CHANGED
|
@@ -1,18 +1,40 @@
|
|
|
1
1
|
import { BasePluginConfig } from '@embedpdf/core';
|
|
2
2
|
import { PdfErrorReason, Task } from '@embedpdf/models';
|
|
3
|
+
import { ScrollBehavior } from '@embedpdf/plugin-scroll';
|
|
3
4
|
export interface ThumbnailPluginConfig extends BasePluginConfig {
|
|
5
|
+
/** Thumbnail width in CSS pixels. Controls the display width of each thumbnail image. Default: 120 */
|
|
4
6
|
width?: number;
|
|
7
|
+
/** Vertical gap between thumbnails in CSS pixels. Controls spacing between each thumbnail row. Default: 8 */
|
|
5
8
|
gap?: number;
|
|
9
|
+
/** Number of extra thumbnail rows to render above and below the visible viewport. Higher values improve scrolling smoothness but use more memory. Default: 3 */
|
|
6
10
|
buffer?: number;
|
|
11
|
+
/** Height reserved for the page number label below each thumbnail in CSS pixels. Set to 0 to hide labels. Default: 16 */
|
|
7
12
|
labelHeight?: number;
|
|
13
|
+
/** Whether to automatically scroll the thumbnail pane when the main document's current page changes. Default: true */
|
|
14
|
+
autoScroll?: boolean;
|
|
15
|
+
/** Scroll animation behavior when auto-scrolling or programmatically scrolling to thumbnails. Default: 'smooth' */
|
|
16
|
+
scrollBehavior?: ScrollBehavior;
|
|
17
|
+
/** Internal padding around each thumbnail image in CSS pixels. Creates visual spacing between the image and its border. Default: 0 */
|
|
18
|
+
imagePadding?: number;
|
|
19
|
+
/** Vertical padding for the entire thumbnail pane in CSS pixels. Adds space at the top and bottom of the scrollable area. Default: 0 */
|
|
20
|
+
paddingY?: number;
|
|
21
|
+
}
|
|
22
|
+
export interface ScrollToOptions {
|
|
23
|
+
top: number;
|
|
24
|
+
behavior?: ScrollBehavior;
|
|
8
25
|
}
|
|
9
26
|
export interface ThumbMeta {
|
|
10
27
|
pageIndex: number;
|
|
28
|
+
/** Inner bitmap size (excludes padding). */
|
|
11
29
|
width: number;
|
|
12
30
|
height: number;
|
|
31
|
+
/** Total row height (padding*2 + image height + labelHeight). */
|
|
13
32
|
wrapperHeight: number;
|
|
33
|
+
/** Top offset of the entire row (including padding + label). */
|
|
14
34
|
top: number;
|
|
15
35
|
labelHeight: number;
|
|
36
|
+
/** Padding applied around the image (px). */
|
|
37
|
+
padding?: number;
|
|
16
38
|
}
|
|
17
39
|
export interface WindowState {
|
|
18
40
|
start: number;
|
|
@@ -21,10 +43,7 @@ export interface WindowState {
|
|
|
21
43
|
totalHeight: number;
|
|
22
44
|
}
|
|
23
45
|
export interface ThumbnailCapability {
|
|
24
|
-
|
|
25
|
-
setViewport(offsetY: number, viewportH: number): void;
|
|
26
|
-
/** listen to window changes */
|
|
27
|
-
onWindow(cb: (w: WindowState) => void): () => void;
|
|
46
|
+
scrollToThumb(pageIdx: number): void;
|
|
28
47
|
/** lazily render one thumb */
|
|
29
48
|
renderThumb(pageIdx: number, dpr: number): Task<Blob, PdfErrorReason>;
|
|
30
49
|
}
|
package/dist/preact/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/preact"),t=require("@embedpdf/plugin-thumbnail"),r=require("preact/jsx-runtime");require("preact");const n=require("preact/hooks"),
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("@embedpdf/core/preact"),t=require("@embedpdf/plugin-thumbnail"),r=require("preact/jsx-runtime");require("preact");const n=require("preact/hooks"),o=require("@embedpdf/models"),l=()=>e.usePlugin(t.ThumbnailPlugin.id),i=()=>e.useCapability(t.ThumbnailPlugin.id);exports.ThumbImg=function({meta:e,style:t,...u}){const{provides:s}=i(),{plugin:c}=l(),[d,a]=n.useState(),p=n.useRef(null),[f,g]=n.useState(0);return n.useEffect((()=>{if(c)return c.onRefreshPages((t=>{t.includes(e.pageIndex)&&g((e=>e+1))}))}),[c]),n.useEffect((()=>{const t=null==s?void 0:s.renderThumb(e.pageIndex,window.devicePixelRatio);return null==t||t.wait((e=>{const t=URL.createObjectURL(e);p.current=t,a(t)}),o.ignore),()=>{p.current?(URL.revokeObjectURL(p.current),p.current=null):null==t||t.abort({code:o.PdfErrorCode.Cancelled,message:"canceled render task"})}}),[s,e.pageIndex,f]),d?r.jsx("img",{src:d,onLoad:()=>{p.current&&(URL.revokeObjectURL(p.current),p.current=null)},style:t,...u}):null},exports.ThumbnailsPane=function({style:e,scrollOptions:t,selectedPage:o,...i}){const{plugin:u}=l(),s=n.useRef(null),[c,d]=n.useState(null);n.useEffect((()=>null==u?void 0:u.onWindow(d)),[u]),n.useEffect((()=>{const e=s.current;if(!e)return;const t=()=>null==u?void 0:u.updateWindow(e.scrollTop,e.clientHeight);return e.addEventListener("scroll",t),()=>e.removeEventListener("scroll",t)}),[u]),n.useEffect((()=>{const e=s.current;if(!e||!u)return;const t=new ResizeObserver((()=>{u.updateWindow(e.scrollTop,e.clientHeight)}));return t.observe(e),()=>t.disconnect()}),[u]),n.useEffect((()=>{const e=s.current;e&&u&&u.updateWindow(e.scrollTop,e.clientHeight)}),[c,u]),n.useEffect((()=>{const e=s.current;if(e&&u&&c)return u.onScrollTo((({top:t,behavior:r})=>{e.scrollTo({top:t,behavior:r})}))}),[u,!!c]);const a=(null==u?void 0:u.cfg.paddingY)??0;return r.jsx("div",{ref:s,style:{overflowY:"auto",position:"relative",paddingTop:a,paddingBottom:a,height:"100%",...e},...i,children:r.jsx("div",{style:{height:(null==c?void 0:c.totalHeight)??0,position:"relative"},children:null==c?void 0:c.items.map((e=>i.children(e)))})})},exports.useThumbnailCapability=i,exports.useThumbnailPlugin=l,Object.keys(t).forEach((e=>{"default"===e||Object.prototype.hasOwnProperty.call(exports,e)||Object.defineProperty(exports,e,{enumerable:!0,get:()=>t[e]})}));
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnail-img.tsx","../../src/shared/components/thumbnails-pane.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport {
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../../src/shared/hooks/use-thumbnail.ts","../../src/shared/components/thumbnail-img.tsx","../../src/shared/components/thumbnails-pane.tsx"],"sourcesContent":["import { useCapability, usePlugin } from '@embedpdf/core/@framework';\nimport { ThumbnailPlugin } from '@embedpdf/plugin-thumbnail';\n\nexport const useThumbnailPlugin = () => usePlugin<ThumbnailPlugin>(ThumbnailPlugin.id);\nexport const useThumbnailCapability = () => useCapability<ThumbnailPlugin>(ThumbnailPlugin.id);\n","import { useEffect, useState, useRef, HTMLAttributes, CSSProperties, Fragment } from '@framework';\nimport { ThumbMeta } from '@embedpdf/plugin-thumbnail';\nimport { ignore, PdfErrorCode } from '@embedpdf/models';\nimport { useThumbnailCapability, useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailImgProps = Omit<HTMLAttributes<HTMLImageElement>, 'style'> & {\n style?: CSSProperties;\n meta: ThumbMeta;\n};\n\nexport function ThumbImg({ meta, style, ...props }: ThumbnailImgProps) {\n const { provides: thumbs } = useThumbnailCapability();\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const [url, setUrl] = useState<string>();\n const urlRef = useRef<string | null>(null);\n const [refreshTick, setRefreshTick] = useState(0);\n\n useEffect(() => {\n if (!thumbnailPlugin) return;\n return thumbnailPlugin.onRefreshPages((pages) => {\n if (pages.includes(meta.pageIndex)) {\n setRefreshTick((tick) => tick + 1);\n }\n });\n }, [thumbnailPlugin]);\n\n useEffect(() => {\n const task = thumbs?.renderThumb(meta.pageIndex, window.devicePixelRatio);\n task?.wait((blob) => {\n const objectUrl = URL.createObjectURL(blob);\n urlRef.current = objectUrl;\n setUrl(objectUrl);\n }, ignore);\n\n return () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n } else {\n task?.abort({\n code: PdfErrorCode.Cancelled,\n message: 'canceled render task',\n });\n }\n };\n }, [thumbs, meta.pageIndex, refreshTick]);\n\n const handleImageLoad = () => {\n if (urlRef.current) {\n URL.revokeObjectURL(urlRef.current);\n urlRef.current = null;\n }\n };\n\n return url ? <img src={url} onLoad={handleImageLoad} style={style} {...props} /> : null;\n}\n","import { useEffect, useRef, useState, HTMLAttributes, CSSProperties, ReactNode } from '@framework';\nimport { ThumbMeta, WindowState } from '@embedpdf/plugin-thumbnail';\nimport { useThumbnailPlugin } from '../hooks';\n\ntype ThumbnailsProps = Omit<HTMLAttributes<HTMLDivElement>, 'style' | 'children'> & {\n style?: CSSProperties;\n children: (m: ThumbMeta) => ReactNode;\n /** @deprecated use scrollToThumb via capability or rely on autoScroll */\n selectedPage?: number;\n /** @deprecated behavior is now controlled by ThumbnailPluginConfig.scrollBehavior */\n scrollOptions?: ScrollIntoViewOptions;\n};\n\nexport function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }: ThumbnailsProps) {\n const { plugin: thumbnailPlugin } = useThumbnailPlugin();\n const viewportRef = useRef<HTMLDivElement>(null);\n\n const [window, setWindow] = useState<WindowState | null>(null);\n\n // 1) subscribe once to window updates\n useEffect(() => thumbnailPlugin?.onWindow(setWindow), [thumbnailPlugin]);\n\n // 2) keep plugin in sync while the user scrolls\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp) return;\n const onScroll = () => thumbnailPlugin?.updateWindow(vp.scrollTop, vp.clientHeight);\n vp.addEventListener('scroll', onScroll);\n return () => vp.removeEventListener('scroll', onScroll);\n }, [thumbnailPlugin]);\n\n // 2.5) keep plugin in sync when viewport resizes (e.g., menu opens/closes)\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n const resizeObserver = new ResizeObserver(() => {\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n });\n resizeObserver.observe(vp);\n\n return () => resizeObserver.disconnect();\n }, [thumbnailPlugin]);\n\n // 3) kick-start after document change\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin) return;\n\n // push initial metrics\n thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);\n }, [window, thumbnailPlugin]);\n\n // 4) let plugin drive scroll – only after window is set, and only once\n useEffect(() => {\n const vp = viewportRef.current;\n if (!vp || !thumbnailPlugin || !window) return;\n\n return thumbnailPlugin.onScrollTo(({ top, behavior }) => {\n vp.scrollTo({ top, behavior });\n });\n }, [thumbnailPlugin, !!window]); // Note: !!window to prevent re-subscription on window updates\n\n const paddingY = thumbnailPlugin?.cfg.paddingY ?? 0;\n\n return (\n <div\n ref={viewportRef}\n style={{\n overflowY: 'auto',\n position: 'relative',\n paddingTop: paddingY,\n paddingBottom: paddingY,\n height: '100%',\n ...style,\n }}\n {...props}\n >\n <div style={{ height: window?.totalHeight ?? 0, position: 'relative' }}>\n {window?.items.map((m) => props.children(m))}\n </div>\n </div>\n );\n}\n"],"names":["useThumbnailPlugin","usePlugin","ThumbnailPlugin","id","useThumbnailCapability","useCapability","meta","style","props","provides","thumbs","plugin","thumbnailPlugin","url","setUrl","useState","urlRef","useRef","refreshTick","setRefreshTick","useEffect","onRefreshPages","pages","includes","pageIndex","tick","task","renderThumb","window","devicePixelRatio","wait","blob","objectUrl","URL","createObjectURL","current","ignore","revokeObjectURL","abort","code","PdfErrorCode","Cancelled","message","jsx","src","onLoad","scrollOptions","selectedPage","viewportRef","setWindow","onWindow","vp","onScroll","updateWindow","scrollTop","clientHeight","addEventListener","removeEventListener","resizeObserver","ResizeObserver","observe","disconnect","onScrollTo","top","behavior","scrollTo","paddingY","cfg","jsxRuntime","ref","overflowY","position","paddingTop","paddingBottom","height","children","totalHeight","items","map","m"],"mappings":"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"}
|
package/dist/preact/index.js
CHANGED
|
@@ -7,47 +7,56 @@ import { useRef, useState, useEffect } from "preact/hooks";
|
|
|
7
7
|
import { ignore, PdfErrorCode } from "@embedpdf/models";
|
|
8
8
|
const useThumbnailPlugin = () => usePlugin(ThumbnailPlugin.id);
|
|
9
9
|
const useThumbnailCapability = () => useCapability(ThumbnailPlugin.id);
|
|
10
|
-
function ThumbnailsPane({
|
|
11
|
-
|
|
12
|
-
selectedPage,
|
|
13
|
-
scrollOptions = { behavior: "smooth", block: "nearest", inline: "nearest" },
|
|
14
|
-
...props
|
|
15
|
-
}) {
|
|
16
|
-
const { provides: thumbs } = useThumbnailCapability();
|
|
10
|
+
function ThumbnailsPane({ style, scrollOptions, selectedPage, ...props }) {
|
|
11
|
+
const { plugin: thumbnailPlugin } = useThumbnailPlugin();
|
|
17
12
|
const viewportRef = useRef(null);
|
|
18
13
|
const [window2, setWindow] = useState(null);
|
|
19
|
-
useEffect(() =>
|
|
14
|
+
useEffect(() => thumbnailPlugin == null ? void 0 : thumbnailPlugin.onWindow(setWindow), [thumbnailPlugin]);
|
|
20
15
|
useEffect(() => {
|
|
21
16
|
const vp = viewportRef.current;
|
|
22
17
|
if (!vp) return;
|
|
23
|
-
const onScroll = () =>
|
|
18
|
+
const onScroll = () => thumbnailPlugin == null ? void 0 : thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);
|
|
24
19
|
vp.addEventListener("scroll", onScroll);
|
|
25
20
|
return () => vp.removeEventListener("scroll", onScroll);
|
|
26
|
-
}, [
|
|
21
|
+
}, [thumbnailPlugin]);
|
|
27
22
|
useEffect(() => {
|
|
28
23
|
const vp = viewportRef.current;
|
|
29
|
-
if (!vp || !
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
24
|
+
if (!vp || !thumbnailPlugin) return;
|
|
25
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
26
|
+
thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);
|
|
27
|
+
});
|
|
28
|
+
resizeObserver.observe(vp);
|
|
29
|
+
return () => resizeObserver.disconnect();
|
|
30
|
+
}, [thumbnailPlugin]);
|
|
34
31
|
useEffect(() => {
|
|
35
|
-
if (!selectedPage || !window2) return;
|
|
36
|
-
const item = window2.items.find((it) => it.pageIndex + 1 === selectedPage);
|
|
37
|
-
if (!item) return;
|
|
38
32
|
const vp = viewportRef.current;
|
|
39
|
-
if (!vp) return;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
33
|
+
if (!vp || !thumbnailPlugin) return;
|
|
34
|
+
thumbnailPlugin.updateWindow(vp.scrollTop, vp.clientHeight);
|
|
35
|
+
}, [window2, thumbnailPlugin]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
const vp = viewportRef.current;
|
|
38
|
+
if (!vp || !thumbnailPlugin || !window2) return;
|
|
39
|
+
return thumbnailPlugin.onScrollTo(({ top, behavior }) => {
|
|
40
|
+
vp.scrollTo({ top, behavior });
|
|
41
|
+
});
|
|
42
|
+
}, [thumbnailPlugin, !!window2]);
|
|
43
|
+
const paddingY = (thumbnailPlugin == null ? void 0 : thumbnailPlugin.cfg.paddingY) ?? 0;
|
|
44
|
+
return /* @__PURE__ */ jsx(
|
|
45
|
+
"div",
|
|
46
|
+
{
|
|
47
|
+
ref: viewportRef,
|
|
48
|
+
style: {
|
|
49
|
+
overflowY: "auto",
|
|
50
|
+
position: "relative",
|
|
51
|
+
paddingTop: paddingY,
|
|
52
|
+
paddingBottom: paddingY,
|
|
53
|
+
height: "100%",
|
|
54
|
+
...style
|
|
55
|
+
},
|
|
56
|
+
...props,
|
|
57
|
+
children: /* @__PURE__ */ jsx("div", { style: { height: (window2 == null ? void 0 : window2.totalHeight) ?? 0, position: "relative" }, children: window2 == null ? void 0 : window2.items.map((m) => props.children(m)) })
|
|
48
58
|
}
|
|
49
|
-
|
|
50
|
-
return /* @__PURE__ */ jsx("div", { ref: viewportRef, style: { overflowY: "auto", position: "relative", ...style }, ...props, children: /* @__PURE__ */ jsx("div", { style: { height: (window2 == null ? void 0 : window2.totalHeight) ?? 0, position: "relative" }, children: window2 == null ? void 0 : window2.items.map((m) => props.children(m)) }) });
|
|
59
|
+
);
|
|
51
60
|
}
|
|
52
61
|
function ThumbImg({ meta, style, ...props }) {
|
|
53
62
|
const { provides: thumbs } = useThumbnailCapability();
|