@embedpdf/viewer-chrome 3.0.0-next.13 → 3.0.0-next.15
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/README.md +175 -132
- package/dist/annotation-fonts-BqoLZ4Mp.js +80 -0
- package/dist/annotation-fonts-BqoLZ4Mp.js.map +1 -0
- package/dist/annotation-fonts-Dt0IQXhz.cjs +91 -0
- package/dist/annotation-fonts-Dt0IQXhz.cjs.map +1 -0
- package/dist/annotation-style--rPIZsuP.js +879 -0
- package/dist/annotation-style--rPIZsuP.js.map +1 -0
- package/dist/annotation-style-Bzvvp8-N.cjs +879 -0
- package/dist/annotation-style-Bzvvp8-N.cjs.map +1 -0
- package/dist/ar-D9XKAmDx.js +567 -0
- package/dist/ar-D9XKAmDx.js.map +1 -0
- package/dist/ar-DnEj5vbP.cjs +567 -0
- package/dist/ar-DnEj5vbP.cjs.map +1 -0
- package/dist/chrome.cjs +378 -221
- package/dist/chrome.cjs.map +1 -1
- package/dist/chrome.d.cts +5 -34
- package/dist/chrome.d.ts +5 -34
- package/dist/chrome.js +377 -218
- package/dist/chrome.js.map +1 -1
- package/dist/commands-BmgxhJ8B.js +953 -0
- package/dist/commands-BmgxhJ8B.js.map +1 -0
- package/dist/commands-CYt4SFUX.cjs +1060 -0
- package/dist/commands-CYt4SFUX.cjs.map +1 -0
- package/dist/commands.cjs +12 -595
- package/dist/commands.d.cts +29 -4
- package/dist/commands.d.ts +29 -4
- package/dist/commands.js +2 -593
- package/dist/comments-panel-MUVgLCBI.js +514 -0
- package/dist/comments-panel-MUVgLCBI.js.map +1 -0
- package/dist/comments-panel-pEcQg4Vs.cjs +514 -0
- package/dist/comments-panel-pEcQg4Vs.cjs.map +1 -0
- package/dist/de-Bdr4aWzm.js +566 -0
- package/dist/de-Bdr4aWzm.js.map +1 -0
- package/dist/de-Dv6zvbnd.cjs +566 -0
- package/dist/de-Dv6zvbnd.cjs.map +1 -0
- package/dist/dialog-B5GBisKv.js +55 -0
- package/dist/dialog-B5GBisKv.js.map +1 -0
- package/dist/dialog-t8F13BL_.cjs +60 -0
- package/dist/dialog-t8F13BL_.cjs.map +1 -0
- package/dist/es-BP09DR9w.cjs +566 -0
- package/dist/es-BP09DR9w.cjs.map +1 -0
- package/dist/es-BSXz5tBv.js +566 -0
- package/dist/es-BSXz5tBv.js.map +1 -0
- package/dist/form-design-panel-B5MXTZ96.cjs +418 -0
- package/dist/form-design-panel-B5MXTZ96.cjs.map +1 -0
- package/dist/form-design-panel-vWWU9uB-.js +418 -0
- package/dist/form-design-panel-vWWU9uB-.js.map +1 -0
- package/dist/fr-BBQQn7tJ.cjs +566 -0
- package/dist/fr-BBQQn7tJ.cjs.map +1 -0
- package/dist/fr-Ckn3K9v1.js +566 -0
- package/dist/fr-Ckn3K9v1.js.map +1 -0
- package/dist/he-DRsG7bH7.cjs +567 -0
- package/dist/he-DRsG7bH7.cjs.map +1 -0
- package/dist/he-wdGU5PkU.js +567 -0
- package/dist/he-wdGU5PkU.js.map +1 -0
- package/dist/icons-BrK5iVL8.js +856 -0
- package/dist/icons-BrK5iVL8.js.map +1 -0
- package/dist/icons-x0Tc05bs.cjs +1010 -0
- package/dist/icons-x0Tc05bs.cjs.map +1 -0
- package/dist/index.cjs +3162 -5106
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +636 -143
- package/dist/index.d.ts +636 -143
- package/dist/index.js +3083 -4975
- package/dist/index.js.map +1 -1
- package/dist/it-BuP4o0xQ.cjs +566 -0
- package/dist/it-BuP4o0xQ.cjs.map +1 -0
- package/dist/it-Us-VmvIu.js +566 -0
- package/dist/it-Us-VmvIu.js.map +1 -0
- package/dist/ja-Cizj0-5O.js +566 -0
- package/dist/ja-Cizj0-5O.js.map +1 -0
- package/dist/ja-DovEd6ts.cjs +566 -0
- package/dist/ja-DovEd6ts.cjs.map +1 -0
- package/dist/ko-B-qJEol2.cjs +566 -0
- package/dist/ko-B-qJEol2.cjs.map +1 -0
- package/dist/ko-DlrXliE1.js +566 -0
- package/dist/ko-DlrXliE1.js.map +1 -0
- package/dist/labels-BiM9wp9K.cjs +21 -0
- package/dist/labels-BiM9wp9K.cjs.map +1 -0
- package/dist/labels-C6_i87Oj.js +16 -0
- package/dist/labels-C6_i87Oj.js.map +1 -0
- package/dist/link-editor-CSjqPnj1.cjs +125 -0
- package/dist/link-editor-CSjqPnj1.cjs.map +1 -0
- package/dist/link-editor-SxM1nnJU.js +125 -0
- package/dist/link-editor-SxM1nnJU.js.map +1 -0
- package/dist/measurement-Bs61XWbJ.js +325 -0
- package/dist/measurement-Bs61XWbJ.js.map +1 -0
- package/dist/measurement-CcCsHPOX.cjs +326 -0
- package/dist/measurement-CcCsHPOX.cjs.map +1 -0
- package/dist/nl-0aF7M_0h.js +566 -0
- package/dist/nl-0aF7M_0h.js.map +1 -0
- package/dist/nl-D3KZDDiJ.cjs +566 -0
- package/dist/nl-D3KZDDiJ.cjs.map +1 -0
- package/dist/pl-BW--AaCI.js +566 -0
- package/dist/pl-BW--AaCI.js.map +1 -0
- package/dist/pl-DSFhAUqx.cjs +566 -0
- package/dist/pl-DSFhAUqx.cjs.map +1 -0
- package/dist/pt-C5S-8MC5.cjs +566 -0
- package/dist/pt-C5S-8MC5.cjs.map +1 -0
- package/dist/pt-DIhpU5pT.js +566 -0
- package/dist/pt-DIhpU5pT.js.map +1 -0
- package/dist/redact-confirm-B6j36TPw.js +83 -0
- package/dist/redact-confirm-B6j36TPw.js.map +1 -0
- package/dist/redact-confirm-CX_SY2ZN.cjs +83 -0
- package/dist/redact-confirm-CX_SY2ZN.cjs.map +1 -0
- package/dist/redaction-panel-BmoIl-Ll.cjs +96 -0
- package/dist/redaction-panel-BmoIl-Ll.cjs.map +1 -0
- package/dist/redaction-panel-IJDshSJI.js +96 -0
- package/dist/redaction-panel-IJDshSJI.js.map +1 -0
- package/dist/search-panel-HfIhJm1M.js +249 -0
- package/dist/search-panel-HfIhJm1M.js.map +1 -0
- package/dist/search-panel-s9IoiOtv.cjs +249 -0
- package/dist/search-panel-s9IoiOtv.cjs.map +1 -0
- package/dist/sheet-BDsa_Sup.cjs +442 -0
- package/dist/sheet-BDsa_Sup.cjs.map +1 -0
- package/dist/sheet-R6XDgyEf.js +365 -0
- package/dist/sheet-R6XDgyEf.js.map +1 -0
- package/dist/sign-dialog-CgnR7akj.cjs +155 -0
- package/dist/sign-dialog-CgnR7akj.cjs.map +1 -0
- package/dist/sign-dialog-GDqCoQhA.js +155 -0
- package/dist/sign-dialog-GDqCoQhA.js.map +1 -0
- package/dist/signature-inspector-DyO313Qf.cjs +166 -0
- package/dist/signature-inspector-DyO313Qf.cjs.map +1 -0
- package/dist/signature-inspector-okFz8do6.js +166 -0
- package/dist/signature-inspector-okFz8do6.js.map +1 -0
- package/dist/signature-maker-BhCmYyHD.js +393 -0
- package/dist/signature-maker-BhCmYyHD.js.map +1 -0
- package/dist/signature-maker-BoYa8vgA.cjs +393 -0
- package/dist/signature-maker-BoYa8vgA.cjs.map +1 -0
- package/dist/signatures-panel-B56PC6d9.cjs +300 -0
- package/dist/signatures-panel-B56PC6d9.cjs.map +1 -0
- package/dist/signatures-panel-CgnFfhFm.js +300 -0
- package/dist/signatures-panel-CgnFfhFm.js.map +1 -0
- package/dist/stamp-store-BHvRs-Do.cjs +147 -0
- package/dist/stamp-store-BHvRs-Do.cjs.map +1 -0
- package/dist/stamp-store-WuF-L3Jj.js +112 -0
- package/dist/stamp-store-WuF-L3Jj.js.map +1 -0
- package/dist/stamps-panel-B9NYGayn.cjs +231 -0
- package/dist/stamps-panel-B9NYGayn.cjs.map +1 -0
- package/dist/stamps-panel-CDO0FPyK.js +231 -0
- package/dist/stamps-panel-CDO0FPyK.js.map +1 -0
- package/dist/toolbar-CJu2wKTU.cjs +1659 -0
- package/dist/toolbar-CJu2wKTU.cjs.map +1 -0
- package/dist/toolbar-z9od3tKl.js +1564 -0
- package/dist/toolbar-z9od3tKl.js.map +1 -0
- package/dist/zh-cn-3tmMeHqv.cjs +566 -0
- package/dist/zh-cn-3tmMeHqv.cjs.map +1 -0
- package/dist/zh-cn-CxkUUBct.js +566 -0
- package/dist/zh-cn-CxkUUBct.js.map +1 -0
- package/package.json +6 -4
- package/src/styles.css +153 -89
- package/dist/commands.cjs.map +0 -1
- package/dist/commands.js.map +0 -1
- package/dist/es-DYwzfxyt.cjs +0 -293
- package/dist/es-DYwzfxyt.cjs.map +0 -1
- package/dist/es-sN6uqBgD.js +0 -293
- package/dist/es-sN6uqBgD.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"signatures-panel-CgnFfhFm.js","names":[],"sources":["../src/ui/signatures-panel.tsx"],"sourcesContent":["/**\n * The signatures sidebar (right panel), a signature sheet like Preview's.\n * One row per person: a stamp library of kind `signatures` holding a\n * full signature and, optionally, initials. Nothing is stored beyond the\n * file; rename/export/delete are the library verbs.\n *\n * pick a mark → with a target field (\"sign here\" was clicked, or a\n * signature widget selected) the mark goes into that field by the\n * plugin's mode; otherwise it arms — hover a page for the ghost, click a\n * signature field to sign it, click anywhere else to drop it as a stamp.\n *\n * Below the people: the signature fields of this document, signed or not,\n * with their verdicts — click an unsigned one to target it, a signed one to\n * inspect it.\n */\nimport { useEffect, useState } from 'react';\nimport { useInteractionState } from '@embedpdf/react/interaction';\nimport { useSelector } from '@embedpdf/react/runtime';\nimport { useT } from '@embedpdf/react/i18n';\nimport { useSurface } from '@embedpdf/react/shell';\nimport { useStamp, useStampAssetPreviewUrl, type StampAsset } from '@embedpdf/react/stamp';\nimport {\n SignatureToken,\n useSignature,\n useSignatureState,\n useSignatureEvent,\n useSignerRows,\n type SignatureDTO,\n type SignatureVerdict,\n type SignerRow,\n} from '@embedpdf/react/signature';\nimport { useSignatureSettings } from '../config-context';\nimport { Icon } from './icons';\nimport { restoreStampLibrariesOnce } from './stamp-store';\n\nconst fieldLabel = (\n field: { kind: 'fqn'; name: string } | { kind: 'objectNumber'; objectNumber: number },\n): string => (field.kind === 'fqn' ? field.name : `#${field.objectNumber}`);\n\nexport function SignaturesPanel() {\n const t = useT();\n const stamp = useStamp();\n const signature = useSignature();\n const config = useSignatureSettings();\n const rows = useSignerRows();\n const target = useSignatureState((state) => state.target);\n const busy = useSignatureState((state) => state.busy);\n const { activeToolId } = useInteractionState();\n const maker = useSurface('signature-maker');\n const [armedId, setArmedId] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n // Acrobat's warning: an unsaved edit just turned a signature that held into\n // one a save would invalidate. Shown until the next verdicts land.\n const [invalidating, setInvalidating] = useState<string | null>(null);\n useSignatureEvent(\n (signature) => signature.onInvalidationPredicted,\n (event) =>\n setInvalidating(signature.getSignature(event.field)?.fieldName ?? fieldLabel(event.field)),\n );\n useSignatureEvent(\n (signature) => signature.onValidated,\n (event) => {\n if (\n !event.verdicts.some(\n (verdict) =>\n verdict.summary === 'invalid' && verdict.modifications.basis === 'working-copy',\n )\n )\n setInvalidating(null);\n },\n );\n\n // The people live in the same store as the stamps: bring them back on\n // first open (a no-op when the store component already did).\n useEffect(() => {\n void restoreStampLibrariesOnce(stamp);\n }, [stamp]);\n useEffect(() => {\n if (activeToolId !== 'stamp') setArmedId(null);\n }, [activeToolId]);\n\n const pick = (asset: StampAsset) => {\n setError(null);\n if (target) {\n void signature.placeMark({ assetId: asset.id }, { field: target }).catch((error) => {\n console.error('[embedpdf] placing the mark failed:', error);\n setError(t('demo.signError'));\n });\n return;\n }\n setArmedId(asset.id);\n void stamp.armAsset(asset.id).catch((error) => {\n console.error('[embedpdf] arm mark failed:', error);\n setArmedId(null);\n setError(t('demo.stampsArmError'));\n });\n };\n\n const canAddPerson = (config.libraries ?? 'many') === 'many' || rows.length === 0;\n const wantsInitials = (config.kinds ?? ['signature', 'initials']).includes('initials');\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col\">\n <div className=\"min-h-0 flex-1 overflow-y-auto p-3\">\n {rows.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 px-4 py-8 text-center\">\n <Icon name=\"signature\" size={32} className=\"text-fg-muted\" />\n <p className=\"text-fg-muted text-sm\">{t('demo.signaturesEmpty')}</p>\n </div>\n ) : (\n <ul className=\"flex flex-col gap-3\">\n {rows.map((row) => (\n <SignerRowView\n key={row.libraryId}\n row={row}\n armedId={armedId}\n wantsInitials={wantsInitials}\n onPick={pick}\n onAddInitials={() =>\n maker.open({ exclusive: 'modal', props: { libraryId: row.libraryId } })\n }\n />\n ))}\n </ul>\n )}\n <DocumentSignatures />\n </div>\n\n {invalidating && (\n <div className=\"border-border-subtle shrink-0 border-t px-3 py-2 text-xs text-amber-700\">\n {t('demo.signaturesInvalidating', { params: { field: invalidating } })}\n </div>\n )}\n {(error || target || armedId) && (\n <div\n className={`border-border-subtle shrink-0 border-t px-3 py-2 text-xs ${\n error ? 'text-red-600' : 'text-fg-muted'\n }`}\n >\n {error ??\n (target\n ? busy\n ? t('demo.signSigning')\n : t('demo.signaturesTargetHint')\n : t('demo.signaturesArmedHint'))}\n </div>\n )}\n\n {canAddPerson ? (\n <div className=\"border-border-subtle shrink-0 border-t p-3\">\n <button\n type=\"button\"\n onClick={() => maker.open({ exclusive: 'modal' })}\n className=\"border-border text-fg hover:bg-hover flex w-full items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm\"\n >\n <Icon name=\"plus\" size={16} />\n {t('demo.signaturesNew')}\n </button>\n </div>\n ) : null}\n </div>\n );\n}\n\nfunction SignerRowView({\n row,\n armedId,\n wantsInitials,\n onPick,\n onAddInitials,\n}: {\n row: SignerRow;\n armedId: string | null;\n wantsInitials: boolean;\n onPick: (asset: StampAsset) => void;\n onAddInitials: () => void;\n}) {\n const t = useT();\n const stamp = useStamp();\n const [renaming, setRenaming] = useState<string | null>(null);\n\n const exportPdf = async () => {\n const bytes = await stamp.exportLibrary(row.libraryId).catch(() => null);\n if (!bytes) return;\n const blob = new Blob([bytes as BlobPart], { type: 'application/pdf' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = `${row.name || 'signature'}.pdf`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n const commitRename = () => {\n const name = renaming?.trim();\n setRenaming(null);\n if (name && name !== row.name) void stamp.updateLibrary(row.libraryId, { name });\n };\n const iconButton = 'text-fg-muted hover:text-fg grid h-6 w-6 shrink-0 place-items-center rounded';\n\n return (\n <li className=\"border-border-subtle rounded-md border p-2\">\n <div className=\"flex items-center gap-1\">\n {renaming !== null ? (\n <input\n autoFocus\n value={renaming}\n onChange={(event) => setRenaming(event.target.value)}\n onBlur={commitRename}\n onKeyDown={(event) => {\n if (event.key === 'Enter') commitRename();\n if (event.key === 'Escape') setRenaming(null);\n }}\n className=\"border-border bg-surface text-fg min-w-0 flex-1 rounded border px-1.5 py-0.5 text-sm\"\n />\n ) : (\n <span className=\"text-fg min-w-0 flex-1 truncate text-sm font-medium\">{row.name}</span>\n )}\n <button\n type=\"button\"\n onClick={() => setRenaming(row.name)}\n title={t('demo.signaturesRename')}\n className={iconButton}\n >\n <Icon name=\"pencilMarker\" size={13} />\n </button>\n <button\n type=\"button\"\n onClick={exportPdf}\n title={t('demo.signaturesExport')}\n className={iconButton}\n >\n <Icon name=\"download\" size={13} />\n </button>\n <button\n type=\"button\"\n onClick={() => void stamp.deleteLibrary(row.libraryId)}\n title={t('demo.signaturesRemove')}\n className={iconButton}\n >\n <Icon name=\"trash\" size={13} />\n </button>\n </div>\n <div className=\"mt-2 grid grid-cols-[2fr_1fr] gap-2\">\n {row.signatures.map((asset) => (\n <MarkThumb\n key={asset.id}\n asset={asset}\n label={t('demo.signatureLabel')}\n armed={armedId === asset.id}\n onPick={() => onPick(asset)}\n />\n ))}\n {wantsInitials ? (\n row.initials ? (\n <MarkThumb\n asset={row.initials}\n label={t('demo.initialsLabel')}\n armed={armedId === row.initials.id}\n onPick={() => onPick(row.initials!)}\n />\n ) : (\n <button\n type=\"button\"\n onClick={onAddInitials}\n title={t('demo.signaturesAddInitials')}\n className=\"border-border-subtle text-fg-muted hover:bg-hover flex h-16 items-center justify-center rounded-md border border-dashed text-xs\"\n >\n <Icon name=\"plus\" size={14} />\n </button>\n )\n ) : null}\n </div>\n </li>\n );\n}\n\nfunction MarkThumb({\n asset,\n label,\n armed,\n onPick,\n}: {\n asset: StampAsset;\n label: string;\n armed: boolean;\n onPick: () => void;\n}) {\n const url = useStampAssetPreviewUrl(asset.id);\n return (\n <button\n type=\"button\"\n onClick={onPick}\n title={label}\n aria-pressed={armed}\n className={`flex h-16 items-center justify-center rounded-md border p-1 ${\n armed\n ? 'border-accent bg-accent-light ring-accent ring-2'\n : 'border-border-subtle hover:border-border hover:bg-hover'\n }`}\n >\n {url ? (\n <img src={url} alt={label} className=\"max-h-full max-w-full object-contain\" />\n ) : (\n <span className=\"text-fg-muted text-xs\">{label}</span>\n )}\n </button>\n );\n}\n\n/** The signature fields of this document, with their state. */\nfunction DocumentSignatures() {\n const t = useT();\n const signature = useSignature();\n // Every signature field, signed or not; the signed ones carry their verdict in the state.\n const snapshot = useSelector(SignatureToken, (capability) => capability.getSnapshot());\n const signed = useSignatureState((state) => state.signatures);\n const target = useSignatureState((state) => state.target);\n const [validating, setValidating] = useState(false);\n const fields = snapshot?.signatures ?? [];\n const verdictOf = (dto: SignatureDTO) =>\n signed.find((entry) => entry.index === dto.index)?.verdict ?? null;\n // Three honest states: valid; valid on disk but unsaved edits would\n // invalidate it (the verdict judged the working copy); invalid on disk.\n const verdictLabel = (verdict: SignatureVerdict | null): string =>\n !verdict\n ? t('demo.signaturesSigned')\n : verdict.summary === 'valid'\n ? t('demo.verdictValid')\n : verdict.summary === 'valid-untrusted'\n ? t('demo.verdictValidUntrusted')\n : verdict.summary === 'indeterminate'\n ? t('demo.verdictIndeterminate')\n : verdict.modifications.basis === 'working-copy'\n ? t('demo.verdictWillInvalidate')\n : t('demo.verdictInvalid');\n const verdictTone = (verdict: SignatureVerdict | null): string =>\n !verdict || verdict.summary === 'indeterminate'\n ? 'text-fg-muted'\n : verdict.summary === 'invalid'\n ? verdict.modifications.basis === 'working-copy'\n ? 'text-amber-600'\n : 'text-red-600'\n : verdict.summary === 'valid-untrusted'\n ? 'text-amber-600'\n : 'text-green-600';\n const isTarget = (dto: SignatureDTO) =>\n !!target &&\n (target.kind === 'fqn'\n ? target.name === dto.fieldName\n : target.objectNumber === (dto.field.kind === 'objectNumber' ? dto.field.objectNumber : -1));\n const validate = () => {\n setValidating(true);\n void signature.validate().finally(() => setValidating(false));\n };\n\n return (\n <div className=\"mt-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-fg-muted text-xs font-semibold uppercase tracking-wide\">\n {t('demo.signaturesInDocument')}\n </span>\n {fields.some((signature) => signature.signed) ? (\n <button\n type=\"button\"\n disabled={validating}\n onClick={validate}\n className=\"text-accent text-xs hover:underline disabled:opacity-60\"\n >\n {validating ? t('demo.signaturesValidating') : t('demo.signaturesValidate')}\n </button>\n ) : null}\n </div>\n {fields.length === 0 ? (\n <p className=\"text-fg-muted mt-1 text-xs\">{t('demo.signaturesNone')}</p>\n ) : (\n <ul className=\"mt-1 flex flex-col gap-1\">\n {fields.map((dto) => {\n const verdict = verdictOf(dto);\n return (\n <li key={dto.index}>\n <button\n type=\"button\"\n onClick={() =>\n dto.signed\n ? signature.requestInspection(dto.field)\n : signature.setTarget(dto.field)\n }\n className={`hover:bg-hover flex w-full items-center gap-2 rounded-md px-2 py-1.5 text-start text-sm ${\n isTarget(dto) ? 'bg-accent-light' : ''\n }`}\n >\n <Icon\n name={dto.signed ? 'check' : 'signature'}\n size={14}\n className={dto.signed ? 'text-green-600' : 'text-fg-muted'}\n />\n <span className=\"text-fg min-w-0 flex-1 truncate\">{dto.fieldName}</span>\n <span\n className={`shrink-0 text-end text-xs ${dto.signed ? verdictTone(verdict) : 'text-fg-muted'}`}\n >\n {dto.signed ? verdictLabel(verdict) : t('demo.signaturesUnsigned')}\n </span>\n </button>\n </li>\n );\n })}\n </ul>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
let react = require("react");
|
|
2
|
+
let _embedpdf_react_stamp = require("@embedpdf/react/stamp");
|
|
3
|
+
let _embedpdf_react_i18n = require("@embedpdf/react/i18n");
|
|
4
|
+
let _embedpdf_default_stamps_library = require("@embedpdf/default-stamps/library");
|
|
5
|
+
//#region src/config/default-stamps.ts
|
|
6
|
+
/**
|
|
7
|
+
* The viewer's built-in stamp library: `@embedpdf/default-stamps`, the
|
|
8
|
+
* standard rubber stamps as one Acrobat-compatible PDF per locale.
|
|
9
|
+
*
|
|
10
|
+
* Delivery: the library ships inside whatever ships this viewer. The package
|
|
11
|
+
* exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
|
|
12
|
+
* so it travels through the module graph like any other code — every bundler
|
|
13
|
+
* splits it into a chunk served from the app's own origin, the CDN snippet
|
|
14
|
+
* carries it as a sibling chunk in its folder, and nothing is fetched from a
|
|
15
|
+
* third party. `plugins.stamp.standard` overrides that:
|
|
16
|
+
* - `false` → no built-in library, no request (air-gapped);
|
|
17
|
+
* - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
|
|
18
|
+
*
|
|
19
|
+
* Loading is lazy (first open of the stamps panel, never at boot) and
|
|
20
|
+
* locale-aware: the chrome locale leads, the browser languages break ties
|
|
21
|
+
* for the locales the chrome does not translate itself, and a locale change
|
|
22
|
+
* while a default library is loaded swaps it for the new one. The file names
|
|
23
|
+
* itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
|
|
24
|
+
*/
|
|
25
|
+
/** The built-in library's PieceInfo id — excluded from persistence (it is
|
|
26
|
+
* loaded again on every first open, in the locale of that moment). */
|
|
27
|
+
const DEFAULT_LIBRARY_ID = "embedpdf-standard";
|
|
28
|
+
/** The library a quick stamp's `library:name` names: `standard` is the standard stamps. */
|
|
29
|
+
const STANDARD_LIBRARY_NAME = "standard";
|
|
30
|
+
/** The asset id a `plugins.stamp.quick` entry names: `'standard:Approved'`, or `'<library id>:<name>'`. */
|
|
31
|
+
function quickStampAssetIdOf(entry) {
|
|
32
|
+
const prefix = `${STANDARD_LIBRARY_NAME}:`;
|
|
33
|
+
return entry.startsWith(prefix) ? `${DEFAULT_LIBRARY_ID}:${entry.slice(prefix.length)}` : entry;
|
|
34
|
+
}
|
|
35
|
+
/** Which shipped locale to show for the chrome's locale + the browser's. */
|
|
36
|
+
function resolveStampsLocale(chromeLocale) {
|
|
37
|
+
return (0, _embedpdf_react_i18n.negotiateLocale)(_embedpdf_default_stamps_library.LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
|
|
38
|
+
}
|
|
39
|
+
const fetchBytes = async (url) => {
|
|
40
|
+
const response = await fetch(url);
|
|
41
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
42
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
43
|
+
};
|
|
44
|
+
/** The library bytes for `locale`: your URL when you gave one, otherwise the
|
|
45
|
+
* copy that shipped with the viewer. Never anything else. */
|
|
46
|
+
const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : (0, _embedpdf_default_stamps_library.loadDefaultLibrary)(locale);
|
|
47
|
+
/** Per workspace: the locale the default library was last brought to, and
|
|
48
|
+
* the in-flight work — the panel mounts and unmounts with the sidebar, so
|
|
49
|
+
* this cannot live in component state. */
|
|
50
|
+
const loads = /* @__PURE__ */ new WeakMap();
|
|
51
|
+
/**
|
|
52
|
+
* Bring the built-in library to `locale`. Idempotent per workspace and
|
|
53
|
+
* locale; a locale change swaps the loaded default (remove, then import);
|
|
54
|
+
* a default the user removed in this session stays removed — the locale is
|
|
55
|
+
* recorded, nothing is re-added. An embedder that seeded a library under
|
|
56
|
+
* the same id is left alone.
|
|
57
|
+
*/
|
|
58
|
+
function ensureDefaultLibrary(stamp, locale, source) {
|
|
59
|
+
if (source === false) return Promise.resolve();
|
|
60
|
+
const url = typeof source === "string" ? source : void 0;
|
|
61
|
+
const current = loads.get(stamp);
|
|
62
|
+
if (current?.locale === locale) return current.promise;
|
|
63
|
+
const run = async () => {
|
|
64
|
+
await current?.promise.catch(() => void 0);
|
|
65
|
+
const loaded = stamp.getLibrary(DEFAULT_LIBRARY_ID);
|
|
66
|
+
if (loaded?.locale === locale) return;
|
|
67
|
+
if (current && !loaded) return;
|
|
68
|
+
if (loaded) await stamp.deleteLibrary(DEFAULT_LIBRARY_ID);
|
|
69
|
+
await stamp.importLibrary(await defaultLibraryBytes(locale, url));
|
|
70
|
+
};
|
|
71
|
+
const promise = run();
|
|
72
|
+
loads.set(stamp, {
|
|
73
|
+
locale,
|
|
74
|
+
promise
|
|
75
|
+
});
|
|
76
|
+
return promise;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/ui/stamp-store.tsx
|
|
80
|
+
/**
|
|
81
|
+
* The user's stamp libraries live in the browser (IndexedDB): restored once
|
|
82
|
+
* per workspace, written on every change, for as long as the viewer lives —
|
|
83
|
+
* a signature made while the stamps sidebar is closed is persisted exactly
|
|
84
|
+
* like a stamp imported while it is open. The built-in library is never
|
|
85
|
+
* stored (it is fetched again next time, in the locale of that moment); the
|
|
86
|
+
* panels that need it bring it in lazily through `ensureDefaultLibrary`.
|
|
87
|
+
*
|
|
88
|
+
* Mounted once in the Shell, workspace-scoped (the stamp plugin is).
|
|
89
|
+
*/
|
|
90
|
+
const store = typeof indexedDB === "undefined" ? null : (0, _embedpdf_react_stamp.indexedDbByteStore)("embedpdf-stamps", { storeName: "libraries" });
|
|
91
|
+
const restored = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
/** Bring the stored libraries back — once per workspace, whoever asks first. */
|
|
93
|
+
const restoreStampLibrariesOnce = (stamp) => {
|
|
94
|
+
let pending = restored.get(stamp);
|
|
95
|
+
if (!pending) {
|
|
96
|
+
pending = store ? (0, _embedpdf_react_stamp.restoreStampLibraries)(stamp, store) : Promise.resolve();
|
|
97
|
+
restored.set(stamp, pending);
|
|
98
|
+
}
|
|
99
|
+
return pending;
|
|
100
|
+
};
|
|
101
|
+
function StampLibraryStore() {
|
|
102
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
103
|
+
(0, react.useEffect)(() => {
|
|
104
|
+
restoreStampLibrariesOnce(stamp);
|
|
105
|
+
return store ? (0, _embedpdf_react_stamp.persistStampLibraries)(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0;
|
|
106
|
+
}, [stamp]);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
Object.defineProperty(exports, "DEFAULT_LIBRARY_ID", {
|
|
111
|
+
enumerable: true,
|
|
112
|
+
get: function() {
|
|
113
|
+
return DEFAULT_LIBRARY_ID;
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
Object.defineProperty(exports, "StampLibraryStore", {
|
|
117
|
+
enumerable: true,
|
|
118
|
+
get: function() {
|
|
119
|
+
return StampLibraryStore;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
Object.defineProperty(exports, "ensureDefaultLibrary", {
|
|
123
|
+
enumerable: true,
|
|
124
|
+
get: function() {
|
|
125
|
+
return ensureDefaultLibrary;
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
Object.defineProperty(exports, "quickStampAssetIdOf", {
|
|
129
|
+
enumerable: true,
|
|
130
|
+
get: function() {
|
|
131
|
+
return quickStampAssetIdOf;
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
Object.defineProperty(exports, "resolveStampsLocale", {
|
|
135
|
+
enumerable: true,
|
|
136
|
+
get: function() {
|
|
137
|
+
return resolveStampsLocale;
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
Object.defineProperty(exports, "restoreStampLibrariesOnce", {
|
|
141
|
+
enumerable: true,
|
|
142
|
+
get: function() {
|
|
143
|
+
return restoreStampLibrariesOnce;
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
//# sourceMappingURL=stamp-store-BHvRs-Do.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp-store-BHvRs-Do.cjs","names":["LOCALES"],"sources":["../src/config/default-stamps.ts","../src/ui/stamp-store.tsx"],"sourcesContent":["/**\n * The viewer's built-in stamp library: `@embedpdf/default-stamps`, the\n * standard rubber stamps as one Acrobat-compatible PDF per locale.\n *\n * Delivery: the library ships inside whatever ships this viewer. The package\n * exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),\n * so it travels through the module graph like any other code — every bundler\n * splits it into a chunk served from the app's own origin, the CDN snippet\n * carries it as a sibling chunk in its folder, and nothing is fetched from a\n * third party. `plugins.stamp.standard` overrides that:\n * - `false` → no built-in library, no request (air-gapped);\n * - a URL template → exactly that (`{locale}` slot), for self-hosted copies.\n *\n * Loading is lazy (first open of the stamps panel, never at boot) and\n * locale-aware: the chrome locale leads, the browser languages break ties\n * for the locales the chrome does not translate itself, and a locale change\n * while a default library is loaded swaps it for the new one. The file names\n * itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.\n */\nimport { LOCALES, loadDefaultLibrary } from '@embedpdf/default-stamps/library';\nimport { negotiateLocale } from '@embedpdf/react/i18n';\nimport type { StampCapability } from '@embedpdf/react/stamp';\n\n/** The built-in library's PieceInfo id — excluded from persistence (it is\n * loaded again on every first open, in the locale of that moment). */\nexport const DEFAULT_LIBRARY_ID = 'embedpdf-standard';\n\n/** Where the standard stamps come from: `plugins.stamp.standard` (`true` or left out: the shipped copy). */\nexport type DefaultLibrarySource = boolean | string | undefined;\n\n/** The library a quick stamp's `library:name` names: `standard` is the standard stamps. */\nconst STANDARD_LIBRARY_NAME = 'standard';\n\n/** The asset id a `plugins.stamp.quick` entry names: `'standard:Approved'`, or `'<library id>:<name>'`. */\nexport function quickStampAssetIdOf(entry: string): string {\n const prefix = `${STANDARD_LIBRARY_NAME}:`;\n return entry.startsWith(prefix) ? `${DEFAULT_LIBRARY_ID}:${entry.slice(prefix.length)}` : entry;\n}\n\n/** Which shipped locale to show for the chrome's locale + the browser's. */\nexport function resolveStampsLocale(chromeLocale: string): string {\n const languages = typeof navigator !== 'undefined' ? (navigator.languages ?? []) : [];\n return negotiateLocale(LOCALES, [chromeLocale, ...languages]) ?? 'en';\n}\n\nconst fetchBytes = async (url: string): Promise<Uint8Array> => {\n const response = await fetch(url);\n if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n};\n\n/** The library bytes for `locale`: your URL when you gave one, otherwise the\n * copy that shipped with the viewer. Never anything else. */\nconst defaultLibraryBytes = (locale: string, source: string | undefined) =>\n source ? fetchBytes(source.replace('{locale}', locale)) : loadDefaultLibrary(locale);\n\n/** Per workspace: the locale the default library was last brought to, and\n * the in-flight work — the panel mounts and unmounts with the sidebar, so\n * this cannot live in component state. */\nconst loads = new WeakMap<StampCapability, { locale: string; promise: Promise<void> }>();\n\n/**\n * Bring the built-in library to `locale`. Idempotent per workspace and\n * locale; a locale change swaps the loaded default (remove, then import);\n * a default the user removed in this session stays removed — the locale is\n * recorded, nothing is re-added. An embedder that seeded a library under\n * the same id is left alone.\n */\nexport function ensureDefaultLibrary(\n stamp: StampCapability,\n locale: string,\n source: DefaultLibrarySource,\n): Promise<void> {\n if (source === false) return Promise.resolve();\n const url = typeof source === 'string' ? source : undefined;\n const current = loads.get(stamp);\n if (current?.locale === locale) return current.promise;\n const run = async () => {\n await current?.promise.catch(() => undefined);\n const loaded = stamp.getLibrary(DEFAULT_LIBRARY_ID);\n if (loaded?.locale === locale) return;\n if (current && !loaded) return; // removed by the user this session\n if (loaded) await stamp.deleteLibrary(DEFAULT_LIBRARY_ID);\n await stamp.importLibrary(await defaultLibraryBytes(locale, url));\n };\n const promise = run();\n loads.set(stamp, { locale, promise });\n return promise;\n}\n","/**\n * The user's stamp libraries live in the browser (IndexedDB): restored once\n * per workspace, written on every change, for as long as the viewer lives —\n * a signature made while the stamps sidebar is closed is persisted exactly\n * like a stamp imported while it is open. The built-in library is never\n * stored (it is fetched again next time, in the locale of that moment); the\n * panels that need it bring it in lazily through `ensureDefaultLibrary`.\n *\n * Mounted once in the Shell, workspace-scoped (the stamp plugin is).\n */\nimport { useEffect } from 'react';\nimport {\n indexedDbByteStore,\n persistStampLibraries,\n restoreStampLibraries,\n useStamp,\n type StampCapability,\n} from '@embedpdf/react/stamp';\nimport { DEFAULT_LIBRARY_ID } from '../config/default-stamps';\n\nconst store =\n typeof indexedDB === 'undefined'\n ? null\n : indexedDbByteStore('embedpdf-stamps', { storeName: 'libraries' });\n\nconst restored = new WeakMap<StampCapability, Promise<unknown>>();\n\n/** Bring the stored libraries back — once per workspace, whoever asks first. */\nexport const restoreStampLibrariesOnce = (stamp: StampCapability): Promise<unknown> => {\n let pending = restored.get(stamp);\n if (!pending) {\n pending = store ? restoreStampLibraries(stamp, store) : Promise.resolve();\n restored.set(stamp, pending);\n }\n return pending;\n};\n\nexport function StampLibraryStore() {\n const stamp = useStamp();\n useEffect(() => {\n void restoreStampLibrariesOnce(stamp);\n return store\n ? persistStampLibraries(stamp, store, { except: [DEFAULT_LIBRARY_ID] })\n : undefined;\n }, [stamp]);\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,qBAAqB;;AAMlC,MAAM,wBAAwB;;AAG9B,SAAgB,oBAAoB,OAAuB;CACzD,MAAM,SAAS,GAAG,sBAAsB;CACxC,OAAO,MAAM,WAAW,MAAM,IAAI,GAAG,mBAAmB,GAAG,MAAM,MAAM,OAAO,MAAM,MAAM;AAC5F;;AAGA,SAAgB,oBAAoB,cAA8B;CAEhE,QAAA,GAAA,qBAAA,gBAAA,CAAuBA,iCAAAA,SAAS,CAAC,cAAc,GAD7B,OAAO,cAAc,cAAe,UAAU,aAAa,CAAC,IAAK,CAAC,CACzB,CAAC,KAAK;AACnE;AAEA,MAAM,aAAa,OAAO,QAAqC;CAC7D,MAAM,WAAW,MAAM,MAAM,GAAG;CAChC,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,SAAS,SAAS,QAAQ;CACnE,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACpD;;;AAIA,MAAM,uBAAuB,QAAgB,WAC3C,SAAS,WAAW,OAAO,QAAQ,YAAY,MAAM,CAAC,KAAA,GAAA,iCAAA,mBAAA,CAAuB,MAAM;;;;AAKrF,MAAM,wBAAQ,IAAI,QAAqE;;;;;;;;AASvF,SAAgB,qBACd,OACA,QACA,QACe;CACf,IAAI,WAAW,OAAO,OAAO,QAAQ,QAAQ;CAC7C,MAAM,MAAM,OAAO,WAAW,WAAW,SAAS,KAAA;CAClD,MAAM,UAAU,MAAM,IAAI,KAAK;CAC/B,IAAI,SAAS,WAAW,QAAQ,OAAO,QAAQ;CAC/C,MAAM,MAAM,YAAY;EACtB,MAAM,SAAS,QAAQ,YAAY,KAAA,CAAS;EAC5C,MAAM,SAAS,MAAM,WAAW,kBAAkB;EAClD,IAAI,QAAQ,WAAW,QAAQ;EAC/B,IAAI,WAAW,CAAC,QAAQ;EACxB,IAAI,QAAQ,MAAM,MAAM,cAAc,kBAAkB;EACxD,MAAM,MAAM,cAAc,MAAM,oBAAoB,QAAQ,GAAG,CAAC;CAClE;CACA,MAAM,UAAU,IAAI;CACpB,MAAM,IAAI,OAAO;EAAE;EAAQ;CAAQ,CAAC;CACpC,OAAO;AACT;;;;;;;;;;;;;ACpEA,MAAM,QACJ,OAAO,cAAc,cACjB,QAAA,GAAA,sBAAA,mBAAA,CACmB,mBAAmB,EAAE,WAAW,YAAY,CAAC;AAEtE,MAAM,2BAAW,IAAI,QAA2C;;AAGhE,MAAa,6BAA6B,UAA6C;CACrF,IAAI,UAAU,SAAS,IAAI,KAAK;CAChC,IAAI,CAAC,SAAS;EACZ,UAAU,SAAA,GAAA,sBAAA,sBAAA,CAA8B,OAAO,KAAK,IAAI,QAAQ,QAAQ;EACxE,SAAS,IAAI,OAAO,OAAO;CAC7B;CACA,OAAO;AACT;AAEA,SAAgB,oBAAoB;CAClC,MAAM,SAAA,GAAA,sBAAA,SAAA,CAAiB;CACvB,CAAA,GAAA,MAAA,UAAA,OAAgB;EACd,0BAA+B,KAAK;EACpC,OAAO,SAAA,GAAA,sBAAA,sBAAA,CACmB,OAAO,OAAO,EAAE,QAAQ,CAAC,kBAAkB,EAAE,CAAC,IACpE,KAAA;CACN,GAAG,CAAC,KAAK,CAAC;CACV,OAAO;AACT"}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { indexedDbByteStore, persistStampLibraries, restoreStampLibraries, useStamp } from "@embedpdf/react/stamp";
|
|
3
|
+
import { negotiateLocale } from "@embedpdf/react/i18n";
|
|
4
|
+
import { LOCALES, loadDefaultLibrary } from "@embedpdf/default-stamps/library";
|
|
5
|
+
//#region src/config/default-stamps.ts
|
|
6
|
+
/**
|
|
7
|
+
* The viewer's built-in stamp library: `@embedpdf/default-stamps`, the
|
|
8
|
+
* standard rubber stamps as one Acrobat-compatible PDF per locale.
|
|
9
|
+
*
|
|
10
|
+
* Delivery: the library ships inside whatever ships this viewer. The package
|
|
11
|
+
* exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),
|
|
12
|
+
* so it travels through the module graph like any other code — every bundler
|
|
13
|
+
* splits it into a chunk served from the app's own origin, the CDN snippet
|
|
14
|
+
* carries it as a sibling chunk in its folder, and nothing is fetched from a
|
|
15
|
+
* third party. `plugins.stamp.standard` overrides that:
|
|
16
|
+
* - `false` → no built-in library, no request (air-gapped);
|
|
17
|
+
* - a URL template → exactly that (`{locale}` slot), for self-hosted copies.
|
|
18
|
+
*
|
|
19
|
+
* Loading is lazy (first open of the stamps panel, never at boot) and
|
|
20
|
+
* locale-aware: the chrome locale leads, the browser languages break ties
|
|
21
|
+
* for the locales the chrome does not translate itself, and a locale change
|
|
22
|
+
* while a default library is loaded swaps it for the new one. The file names
|
|
23
|
+
* itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.
|
|
24
|
+
*/
|
|
25
|
+
/** The built-in library's PieceInfo id — excluded from persistence (it is
|
|
26
|
+
* loaded again on every first open, in the locale of that moment). */
|
|
27
|
+
const DEFAULT_LIBRARY_ID = "embedpdf-standard";
|
|
28
|
+
/** The library a quick stamp's `library:name` names: `standard` is the standard stamps. */
|
|
29
|
+
const STANDARD_LIBRARY_NAME = "standard";
|
|
30
|
+
/** The asset id a `plugins.stamp.quick` entry names: `'standard:Approved'`, or `'<library id>:<name>'`. */
|
|
31
|
+
function quickStampAssetIdOf(entry) {
|
|
32
|
+
const prefix = `${STANDARD_LIBRARY_NAME}:`;
|
|
33
|
+
return entry.startsWith(prefix) ? `${DEFAULT_LIBRARY_ID}:${entry.slice(prefix.length)}` : entry;
|
|
34
|
+
}
|
|
35
|
+
/** Which shipped locale to show for the chrome's locale + the browser's. */
|
|
36
|
+
function resolveStampsLocale(chromeLocale) {
|
|
37
|
+
return negotiateLocale(LOCALES, [chromeLocale, ...typeof navigator !== "undefined" ? navigator.languages ?? [] : []]) ?? "en";
|
|
38
|
+
}
|
|
39
|
+
const fetchBytes = async (url) => {
|
|
40
|
+
const response = await fetch(url);
|
|
41
|
+
if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);
|
|
42
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
43
|
+
};
|
|
44
|
+
/** The library bytes for `locale`: your URL when you gave one, otherwise the
|
|
45
|
+
* copy that shipped with the viewer. Never anything else. */
|
|
46
|
+
const defaultLibraryBytes = (locale, source) => source ? fetchBytes(source.replace("{locale}", locale)) : loadDefaultLibrary(locale);
|
|
47
|
+
/** Per workspace: the locale the default library was last brought to, and
|
|
48
|
+
* the in-flight work — the panel mounts and unmounts with the sidebar, so
|
|
49
|
+
* this cannot live in component state. */
|
|
50
|
+
const loads = /* @__PURE__ */ new WeakMap();
|
|
51
|
+
/**
|
|
52
|
+
* Bring the built-in library to `locale`. Idempotent per workspace and
|
|
53
|
+
* locale; a locale change swaps the loaded default (remove, then import);
|
|
54
|
+
* a default the user removed in this session stays removed — the locale is
|
|
55
|
+
* recorded, nothing is re-added. An embedder that seeded a library under
|
|
56
|
+
* the same id is left alone.
|
|
57
|
+
*/
|
|
58
|
+
function ensureDefaultLibrary(stamp, locale, source) {
|
|
59
|
+
if (source === false) return Promise.resolve();
|
|
60
|
+
const url = typeof source === "string" ? source : void 0;
|
|
61
|
+
const current = loads.get(stamp);
|
|
62
|
+
if (current?.locale === locale) return current.promise;
|
|
63
|
+
const run = async () => {
|
|
64
|
+
await current?.promise.catch(() => void 0);
|
|
65
|
+
const loaded = stamp.getLibrary(DEFAULT_LIBRARY_ID);
|
|
66
|
+
if (loaded?.locale === locale) return;
|
|
67
|
+
if (current && !loaded) return;
|
|
68
|
+
if (loaded) await stamp.deleteLibrary(DEFAULT_LIBRARY_ID);
|
|
69
|
+
await stamp.importLibrary(await defaultLibraryBytes(locale, url));
|
|
70
|
+
};
|
|
71
|
+
const promise = run();
|
|
72
|
+
loads.set(stamp, {
|
|
73
|
+
locale,
|
|
74
|
+
promise
|
|
75
|
+
});
|
|
76
|
+
return promise;
|
|
77
|
+
}
|
|
78
|
+
//#endregion
|
|
79
|
+
//#region src/ui/stamp-store.tsx
|
|
80
|
+
/**
|
|
81
|
+
* The user's stamp libraries live in the browser (IndexedDB): restored once
|
|
82
|
+
* per workspace, written on every change, for as long as the viewer lives —
|
|
83
|
+
* a signature made while the stamps sidebar is closed is persisted exactly
|
|
84
|
+
* like a stamp imported while it is open. The built-in library is never
|
|
85
|
+
* stored (it is fetched again next time, in the locale of that moment); the
|
|
86
|
+
* panels that need it bring it in lazily through `ensureDefaultLibrary`.
|
|
87
|
+
*
|
|
88
|
+
* Mounted once in the Shell, workspace-scoped (the stamp plugin is).
|
|
89
|
+
*/
|
|
90
|
+
const store = typeof indexedDB === "undefined" ? null : indexedDbByteStore("embedpdf-stamps", { storeName: "libraries" });
|
|
91
|
+
const restored = /* @__PURE__ */ new WeakMap();
|
|
92
|
+
/** Bring the stored libraries back — once per workspace, whoever asks first. */
|
|
93
|
+
const restoreStampLibrariesOnce = (stamp) => {
|
|
94
|
+
let pending = restored.get(stamp);
|
|
95
|
+
if (!pending) {
|
|
96
|
+
pending = store ? restoreStampLibraries(stamp, store) : Promise.resolve();
|
|
97
|
+
restored.set(stamp, pending);
|
|
98
|
+
}
|
|
99
|
+
return pending;
|
|
100
|
+
};
|
|
101
|
+
function StampLibraryStore() {
|
|
102
|
+
const stamp = useStamp();
|
|
103
|
+
useEffect(() => {
|
|
104
|
+
restoreStampLibrariesOnce(stamp);
|
|
105
|
+
return store ? persistStampLibraries(stamp, store, { except: [DEFAULT_LIBRARY_ID] }) : void 0;
|
|
106
|
+
}, [stamp]);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { quickStampAssetIdOf as a, ensureDefaultLibrary as i, restoreStampLibrariesOnce as n, resolveStampsLocale as o, DEFAULT_LIBRARY_ID as r, StampLibraryStore as t };
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=stamp-store-WuF-L3Jj.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamp-store-WuF-L3Jj.js","names":[],"sources":["../src/config/default-stamps.ts","../src/ui/stamp-store.tsx"],"sourcesContent":["/**\n * The viewer's built-in stamp library: `@embedpdf/default-stamps`, the\n * standard rubber stamps as one Acrobat-compatible PDF per locale.\n *\n * Delivery: the library ships inside whatever ships this viewer. The package\n * exposes each locale as a lazy ES module (`@embedpdf/default-stamps/library`),\n * so it travels through the module graph like any other code — every bundler\n * splits it into a chunk served from the app's own origin, the CDN snippet\n * carries it as a sibling chunk in its folder, and nothing is fetched from a\n * third party. `plugins.stamp.standard` overrides that:\n * - `false` → no built-in library, no request (air-gapped);\n * - a URL template → exactly that (`{locale}` slot), for self-hosted copies.\n *\n * Loading is lazy (first open of the stamps panel, never at boot) and\n * locale-aware: the chrome locale leads, the browser languages break ties\n * for the locales the chrome does not translate itself, and a locale change\n * while a default library is loaded swaps it for the new one. The file names\n * itself (`/Title`, `Id: embedpdf-standard`), so no overrides are passed.\n */\nimport { LOCALES, loadDefaultLibrary } from '@embedpdf/default-stamps/library';\nimport { negotiateLocale } from '@embedpdf/react/i18n';\nimport type { StampCapability } from '@embedpdf/react/stamp';\n\n/** The built-in library's PieceInfo id — excluded from persistence (it is\n * loaded again on every first open, in the locale of that moment). */\nexport const DEFAULT_LIBRARY_ID = 'embedpdf-standard';\n\n/** Where the standard stamps come from: `plugins.stamp.standard` (`true` or left out: the shipped copy). */\nexport type DefaultLibrarySource = boolean | string | undefined;\n\n/** The library a quick stamp's `library:name` names: `standard` is the standard stamps. */\nconst STANDARD_LIBRARY_NAME = 'standard';\n\n/** The asset id a `plugins.stamp.quick` entry names: `'standard:Approved'`, or `'<library id>:<name>'`. */\nexport function quickStampAssetIdOf(entry: string): string {\n const prefix = `${STANDARD_LIBRARY_NAME}:`;\n return entry.startsWith(prefix) ? `${DEFAULT_LIBRARY_ID}:${entry.slice(prefix.length)}` : entry;\n}\n\n/** Which shipped locale to show for the chrome's locale + the browser's. */\nexport function resolveStampsLocale(chromeLocale: string): string {\n const languages = typeof navigator !== 'undefined' ? (navigator.languages ?? []) : [];\n return negotiateLocale(LOCALES, [chromeLocale, ...languages]) ?? 'en';\n}\n\nconst fetchBytes = async (url: string): Promise<Uint8Array> => {\n const response = await fetch(url);\n if (!response.ok) throw new Error(`${url}: HTTP ${response.status}`);\n return new Uint8Array(await response.arrayBuffer());\n};\n\n/** The library bytes for `locale`: your URL when you gave one, otherwise the\n * copy that shipped with the viewer. Never anything else. */\nconst defaultLibraryBytes = (locale: string, source: string | undefined) =>\n source ? fetchBytes(source.replace('{locale}', locale)) : loadDefaultLibrary(locale);\n\n/** Per workspace: the locale the default library was last brought to, and\n * the in-flight work — the panel mounts and unmounts with the sidebar, so\n * this cannot live in component state. */\nconst loads = new WeakMap<StampCapability, { locale: string; promise: Promise<void> }>();\n\n/**\n * Bring the built-in library to `locale`. Idempotent per workspace and\n * locale; a locale change swaps the loaded default (remove, then import);\n * a default the user removed in this session stays removed — the locale is\n * recorded, nothing is re-added. An embedder that seeded a library under\n * the same id is left alone.\n */\nexport function ensureDefaultLibrary(\n stamp: StampCapability,\n locale: string,\n source: DefaultLibrarySource,\n): Promise<void> {\n if (source === false) return Promise.resolve();\n const url = typeof source === 'string' ? source : undefined;\n const current = loads.get(stamp);\n if (current?.locale === locale) return current.promise;\n const run = async () => {\n await current?.promise.catch(() => undefined);\n const loaded = stamp.getLibrary(DEFAULT_LIBRARY_ID);\n if (loaded?.locale === locale) return;\n if (current && !loaded) return; // removed by the user this session\n if (loaded) await stamp.deleteLibrary(DEFAULT_LIBRARY_ID);\n await stamp.importLibrary(await defaultLibraryBytes(locale, url));\n };\n const promise = run();\n loads.set(stamp, { locale, promise });\n return promise;\n}\n","/**\n * The user's stamp libraries live in the browser (IndexedDB): restored once\n * per workspace, written on every change, for as long as the viewer lives —\n * a signature made while the stamps sidebar is closed is persisted exactly\n * like a stamp imported while it is open. The built-in library is never\n * stored (it is fetched again next time, in the locale of that moment); the\n * panels that need it bring it in lazily through `ensureDefaultLibrary`.\n *\n * Mounted once in the Shell, workspace-scoped (the stamp plugin is).\n */\nimport { useEffect } from 'react';\nimport {\n indexedDbByteStore,\n persistStampLibraries,\n restoreStampLibraries,\n useStamp,\n type StampCapability,\n} from '@embedpdf/react/stamp';\nimport { DEFAULT_LIBRARY_ID } from '../config/default-stamps';\n\nconst store =\n typeof indexedDB === 'undefined'\n ? null\n : indexedDbByteStore('embedpdf-stamps', { storeName: 'libraries' });\n\nconst restored = new WeakMap<StampCapability, Promise<unknown>>();\n\n/** Bring the stored libraries back — once per workspace, whoever asks first. */\nexport const restoreStampLibrariesOnce = (stamp: StampCapability): Promise<unknown> => {\n let pending = restored.get(stamp);\n if (!pending) {\n pending = store ? restoreStampLibraries(stamp, store) : Promise.resolve();\n restored.set(stamp, pending);\n }\n return pending;\n};\n\nexport function StampLibraryStore() {\n const stamp = useStamp();\n useEffect(() => {\n void restoreStampLibrariesOnce(stamp);\n return store\n ? persistStampLibraries(stamp, store, { except: [DEFAULT_LIBRARY_ID] })\n : undefined;\n }, [stamp]);\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,MAAa,qBAAqB;;AAMlC,MAAM,wBAAwB;;AAG9B,SAAgB,oBAAoB,OAAuB;CACzD,MAAM,SAAS,GAAG,sBAAsB;CACxC,OAAO,MAAM,WAAW,MAAM,IAAI,GAAG,mBAAmB,GAAG,MAAM,MAAM,OAAO,MAAM,MAAM;AAC5F;;AAGA,SAAgB,oBAAoB,cAA8B;CAEhE,OAAO,gBAAgB,SAAS,CAAC,cAAc,GAD7B,OAAO,cAAc,cAAe,UAAU,aAAa,CAAC,IAAK,CAAC,CACzB,CAAC,KAAK;AACnE;AAEA,MAAM,aAAa,OAAO,QAAqC;CAC7D,MAAM,WAAW,MAAM,MAAM,GAAG;CAChC,IAAI,CAAC,SAAS,IAAI,MAAM,IAAI,MAAM,GAAG,IAAI,SAAS,SAAS,QAAQ;CACnE,OAAO,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC;AACpD;;;AAIA,MAAM,uBAAuB,QAAgB,WAC3C,SAAS,WAAW,OAAO,QAAQ,YAAY,MAAM,CAAC,IAAI,mBAAmB,MAAM;;;;AAKrF,MAAM,wBAAQ,IAAI,QAAqE;;;;;;;;AASvF,SAAgB,qBACd,OACA,QACA,QACe;CACf,IAAI,WAAW,OAAO,OAAO,QAAQ,QAAQ;CAC7C,MAAM,MAAM,OAAO,WAAW,WAAW,SAAS,KAAA;CAClD,MAAM,UAAU,MAAM,IAAI,KAAK;CAC/B,IAAI,SAAS,WAAW,QAAQ,OAAO,QAAQ;CAC/C,MAAM,MAAM,YAAY;EACtB,MAAM,SAAS,QAAQ,YAAY,KAAA,CAAS;EAC5C,MAAM,SAAS,MAAM,WAAW,kBAAkB;EAClD,IAAI,QAAQ,WAAW,QAAQ;EAC/B,IAAI,WAAW,CAAC,QAAQ;EACxB,IAAI,QAAQ,MAAM,MAAM,cAAc,kBAAkB;EACxD,MAAM,MAAM,cAAc,MAAM,oBAAoB,QAAQ,GAAG,CAAC;CAClE;CACA,MAAM,UAAU,IAAI;CACpB,MAAM,IAAI,OAAO;EAAE;EAAQ;CAAQ,CAAC;CACpC,OAAO;AACT;;;;;;;;;;;;;ACpEA,MAAM,QACJ,OAAO,cAAc,cACjB,OACA,mBAAmB,mBAAmB,EAAE,WAAW,YAAY,CAAC;AAEtE,MAAM,2BAAW,IAAI,QAA2C;;AAGhE,MAAa,6BAA6B,UAA6C;CACrF,IAAI,UAAU,SAAS,IAAI,KAAK;CAChC,IAAI,CAAC,SAAS;EACZ,UAAU,QAAQ,sBAAsB,OAAO,KAAK,IAAI,QAAQ,QAAQ;EACxE,SAAS,IAAI,OAAO,OAAO;CAC7B;CACA,OAAO;AACT;AAEA,SAAgB,oBAAoB;CAClC,MAAM,QAAQ,SAAS;CACvB,gBAAgB;EACd,0BAA+B,KAAK;EACpC,OAAO,QACH,sBAAsB,OAAO,OAAO,EAAE,QAAQ,CAAC,kBAAkB,EAAE,CAAC,IACpE,KAAA;CACN,GAAG,CAAC,KAAK,CAAC;CACV,OAAO;AACT"}
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
const require_icons = require("./icons-x0Tc05bs.cjs");
|
|
2
|
+
const require_stamp_store = require("./stamp-store-BHvRs-Do.cjs");
|
|
3
|
+
let react = require("react");
|
|
4
|
+
let _embedpdf_react_interaction = require("@embedpdf/react/interaction");
|
|
5
|
+
let _embedpdf_react_stamp = require("@embedpdf/react/stamp");
|
|
6
|
+
let _embedpdf_react_i18n = require("@embedpdf/react/i18n");
|
|
7
|
+
let _embedpdf_react_shell = require("@embedpdf/react/shell");
|
|
8
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
9
|
+
//#region src/ui/stamps-panel.tsx
|
|
10
|
+
/**
|
|
11
|
+
* The stamps sidebar (right panel). The stamp plugin owns the libraries, the
|
|
12
|
+
* asset bytes and the cached previews; this panel is the picker over them:
|
|
13
|
+
*
|
|
14
|
+
* a library <select> ("All stamps" + one per library, shown once there
|
|
15
|
+
* are two) → a two-column grid of thumbnails → click a stamp → `armAsset`
|
|
16
|
+
* arms the annotation plugin's stamp tool → hovering a page ghosts the
|
|
17
|
+
* exact placement → each click on a page places one.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately not a file dialog: picking arbitrary image bytes is the Image
|
|
20
|
+
* tool's job (a click-then-pick tool, `tool:image`). The stamp button
|
|
21
|
+
* opens a library; a library is a set of reusable, named assets — the two
|
|
22
|
+
* gestures are different, so they are different controls.
|
|
23
|
+
*
|
|
24
|
+
* The panel stays open while you place, so a stamp can be dropped on several
|
|
25
|
+
* pages and swapped without a round trip through the toolbar. A command can
|
|
26
|
+
* open it on a library (`annotation:stamp-from-selection` → "My stamps")
|
|
27
|
+
* through the surface's open props.
|
|
28
|
+
*/
|
|
29
|
+
const ALL = "all";
|
|
30
|
+
function StampsPanel() {
|
|
31
|
+
const t = (0, _embedpdf_react_i18n.useT)();
|
|
32
|
+
const stamp = (0, _embedpdf_react_stamp.useStamp)();
|
|
33
|
+
const { standard, sidebar } = require_icons.useStampSettings();
|
|
34
|
+
const libraries = (0, _embedpdf_react_stamp.useStampLibraries)({ kind: sidebar ?? ["stamps"] });
|
|
35
|
+
const assets = (0, _embedpdf_react_stamp.useStampAssets)();
|
|
36
|
+
const { activeToolId } = (0, _embedpdf_react_interaction.useInteractionState)();
|
|
37
|
+
const { locale } = (0, _embedpdf_react_i18n.useI18nState)();
|
|
38
|
+
const surface = (0, _embedpdf_react_shell.useSurface)("stamps");
|
|
39
|
+
const fileRef = (0, react.useRef)(null);
|
|
40
|
+
const [armedId, setArmedId] = (0, react.useState)(null);
|
|
41
|
+
const [busy, setBusy] = (0, react.useState)(null);
|
|
42
|
+
const [error, setError] = (0, react.useState)(null);
|
|
43
|
+
const [picked, setPicked] = (0, react.useState)(ALL);
|
|
44
|
+
const openedOn = surface.props?.libraryId;
|
|
45
|
+
(0, react.useEffect)(() => {
|
|
46
|
+
if (typeof openedOn === "string") setPicked(openedOn);
|
|
47
|
+
}, [openedOn]);
|
|
48
|
+
(0, react.useEffect)(() => {
|
|
49
|
+
let live = true;
|
|
50
|
+
setBusy("loading");
|
|
51
|
+
require_stamp_store.restoreStampLibrariesOnce(stamp).then(() => require_stamp_store.ensureDefaultLibrary(stamp, require_stamp_store.resolveStampsLocale(locale), standard)).catch((error) => {
|
|
52
|
+
console.error("[embedpdf] default stamps failed:", error);
|
|
53
|
+
if (live) setError(t("demo.stampsError"));
|
|
54
|
+
}).finally(() => live && setBusy(null));
|
|
55
|
+
return () => {
|
|
56
|
+
live = false;
|
|
57
|
+
};
|
|
58
|
+
}, [stamp, locale]);
|
|
59
|
+
(0, react.useEffect)(() => {
|
|
60
|
+
if (activeToolId !== "stamp") setArmedId(null);
|
|
61
|
+
}, [activeToolId]);
|
|
62
|
+
const arm = (asset) => {
|
|
63
|
+
setError(null);
|
|
64
|
+
setArmedId(asset.id);
|
|
65
|
+
stamp.armAsset(asset.id).catch((error) => {
|
|
66
|
+
console.error("[embedpdf] arm stamp failed:", error);
|
|
67
|
+
setArmedId(null);
|
|
68
|
+
setError(t("demo.stampsArmError"));
|
|
69
|
+
});
|
|
70
|
+
};
|
|
71
|
+
const importPdf = (file) => {
|
|
72
|
+
setError(null);
|
|
73
|
+
setBusy("importing");
|
|
74
|
+
stamp.importLibrary(file, { name: file.name.replace(/\.pdf$/i, "") }).then(({ library }) => setPicked(library.id)).catch((error) => {
|
|
75
|
+
console.error("[embedpdf] stamp library import failed:", error);
|
|
76
|
+
setError(t("demo.stampsImportError"));
|
|
77
|
+
}).finally(() => setBusy(null));
|
|
78
|
+
};
|
|
79
|
+
/** The library as the PDF it is — title, registry, artwork — for Acrobat
|
|
80
|
+
* or another viewer. */
|
|
81
|
+
const exportPdf = async (libraryId, name) => {
|
|
82
|
+
const bytes = await stamp.exportLibrary(libraryId).catch(() => null);
|
|
83
|
+
if (!bytes) return;
|
|
84
|
+
const blob = new Blob([bytes], { type: "application/pdf" });
|
|
85
|
+
const url = URL.createObjectURL(blob);
|
|
86
|
+
const anchor = document.createElement("a");
|
|
87
|
+
anchor.href = url;
|
|
88
|
+
anchor.download = `${name || "stamps"}.pdf`;
|
|
89
|
+
anchor.click();
|
|
90
|
+
URL.revokeObjectURL(url);
|
|
91
|
+
};
|
|
92
|
+
const selectedId = libraries.length === 1 ? libraries[0].id : libraries.some((library) => library.id === picked) ? picked : ALL;
|
|
93
|
+
const selected = selectedId === ALL ? null : libraries.find((library) => library.id === selectedId) ?? null;
|
|
94
|
+
const shown = selectedId === ALL ? assets : assets.filter((asset) => asset.libraryId === selectedId);
|
|
95
|
+
const isDefault = (libraryId) => libraryId === require_stamp_store.DEFAULT_LIBRARY_ID;
|
|
96
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
97
|
+
className: "flex min-h-0 flex-1 flex-col",
|
|
98
|
+
children: [
|
|
99
|
+
libraries.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
100
|
+
className: "border-border-subtle flex items-center gap-1 border-b p-2",
|
|
101
|
+
children: [libraries.length > 1 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", {
|
|
102
|
+
value: selectedId,
|
|
103
|
+
onChange: (event) => setPicked(event.target.value),
|
|
104
|
+
"aria-label": t("panels.stamps"),
|
|
105
|
+
className: "border-border bg-surface text-fg min-w-0 flex-1 rounded border px-2 py-1.5 text-sm",
|
|
106
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
107
|
+
value: ALL,
|
|
108
|
+
children: t("demo.stampsAll")
|
|
109
|
+
}), libraries.map((library) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", {
|
|
110
|
+
value: library.id,
|
|
111
|
+
children: library.name
|
|
112
|
+
}, library.id))]
|
|
113
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
114
|
+
className: "text-fg-muted min-w-0 flex-1 truncate px-1 text-xs font-semibold uppercase tracking-wide",
|
|
115
|
+
children: libraries[0].name
|
|
116
|
+
}), selected && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
117
|
+
type: "button",
|
|
118
|
+
onClick: () => exportPdf(selected.id, selected.name),
|
|
119
|
+
title: t("demo.stampsExportLibrary"),
|
|
120
|
+
className: "text-fg-muted hover:text-fg grid h-7 w-7 shrink-0 place-items-center rounded",
|
|
121
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons.Icon, {
|
|
122
|
+
name: "download",
|
|
123
|
+
size: 14
|
|
124
|
+
})
|
|
125
|
+
}), !isDefault(selected.id) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
126
|
+
type: "button",
|
|
127
|
+
onClick: () => void stamp.deleteLibrary(selected.id),
|
|
128
|
+
title: t("demo.stampsRemoveLibrary"),
|
|
129
|
+
className: "text-fg-muted hover:text-fg grid h-7 w-7 shrink-0 place-items-center rounded",
|
|
130
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons.Icon, {
|
|
131
|
+
name: "trash",
|
|
132
|
+
size: 14
|
|
133
|
+
})
|
|
134
|
+
})] })]
|
|
135
|
+
}),
|
|
136
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
137
|
+
className: "min-h-0 flex-1 overflow-y-auto p-3",
|
|
138
|
+
children: busy === "loading" && shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
139
|
+
className: "flex flex-col items-center gap-3 px-4 py-10 text-center",
|
|
140
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "border-border-subtle border-t-accent h-6 w-6 animate-spin rounded-full border-2" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
141
|
+
className: "text-fg-muted text-sm",
|
|
142
|
+
children: t("demo.stampsLoading")
|
|
143
|
+
})]
|
|
144
|
+
}) : shown.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
145
|
+
className: "flex flex-col items-center gap-3 px-4 py-10 text-center",
|
|
146
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons.Icon, {
|
|
147
|
+
name: "rubberStamp",
|
|
148
|
+
size: 32,
|
|
149
|
+
className: "text-fg-muted"
|
|
150
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
151
|
+
className: "text-fg-muted text-sm",
|
|
152
|
+
children: t("demo.stampsEmpty")
|
|
153
|
+
})]
|
|
154
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
155
|
+
className: "grid grid-cols-2 gap-3",
|
|
156
|
+
children: shown.map((asset) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
157
|
+
className: "group relative",
|
|
158
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(StampAssetCell, {
|
|
159
|
+
asset,
|
|
160
|
+
armed: armedId === asset.id,
|
|
161
|
+
onArm: () => arm(asset)
|
|
162
|
+
}), !isDefault(asset.libraryId) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
163
|
+
type: "button",
|
|
164
|
+
onClick: () => void stamp.deleteAsset(asset.id),
|
|
165
|
+
title: t("demo.stampsRemoveStamp"),
|
|
166
|
+
className: "bg-surface border-border text-fg-muted hover:text-fg absolute end-1 top-1 grid h-5 w-5 place-items-center rounded-full border opacity-0 shadow-sm focus:opacity-100 group-hover:opacity-100",
|
|
167
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons.Icon, {
|
|
168
|
+
name: "x",
|
|
169
|
+
size: 12
|
|
170
|
+
})
|
|
171
|
+
})]
|
|
172
|
+
}, asset.id))
|
|
173
|
+
})
|
|
174
|
+
}),
|
|
175
|
+
(error || armedId) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
176
|
+
className: `border-border-subtle shrink-0 border-t px-3 py-2 text-xs ${error ? "text-red-600" : "text-fg-muted"}`,
|
|
177
|
+
children: error ?? t("demo.stampsArmedHint")
|
|
178
|
+
}),
|
|
179
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
180
|
+
className: "border-border-subtle shrink-0 border-t p-3",
|
|
181
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
182
|
+
type: "button",
|
|
183
|
+
disabled: busy != null,
|
|
184
|
+
onClick: () => fileRef.current?.click(),
|
|
185
|
+
title: t("demo.stampsImportHint"),
|
|
186
|
+
className: "border-border text-fg hover:bg-hover flex w-full items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm disabled:opacity-40",
|
|
187
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_icons.Icon, {
|
|
188
|
+
name: "rubberStampPlus",
|
|
189
|
+
size: 16
|
|
190
|
+
}), busy === "importing" ? t("demo.stampsImporting") : t("demo.stampsImport")]
|
|
191
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
192
|
+
ref: fileRef,
|
|
193
|
+
type: "file",
|
|
194
|
+
accept: "application/pdf",
|
|
195
|
+
className: "hidden",
|
|
196
|
+
onChange: (event) => {
|
|
197
|
+
const file = event.currentTarget.files?.[0];
|
|
198
|
+
event.currentTarget.value = "";
|
|
199
|
+
if (file) importPdf(file);
|
|
200
|
+
}
|
|
201
|
+
})]
|
|
202
|
+
})
|
|
203
|
+
]
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/** One grid cell: the plugin's cached preview through the object-URL hook
|
|
207
|
+
* (the DOM resource's lifetime belongs to the framework layer, not the
|
|
208
|
+
* store). Image only — the artwork is the label; the label is the
|
|
209
|
+
* tooltip for the curious. */
|
|
210
|
+
function StampAssetCell({ asset, armed, onArm }) {
|
|
211
|
+
const url = (0, _embedpdf_react_stamp.useStampAssetPreviewUrl)(asset.id);
|
|
212
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
213
|
+
type: "button",
|
|
214
|
+
onClick: onArm,
|
|
215
|
+
title: asset.label,
|
|
216
|
+
"aria-pressed": armed,
|
|
217
|
+
className: `flex aspect-square w-full items-center justify-center rounded-md border p-2 ${armed ? "border-accent bg-accent-light ring-accent ring-2" : "border-border-subtle hover:border-border hover:bg-hover"}`,
|
|
218
|
+
children: url ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
219
|
+
src: url,
|
|
220
|
+
alt: asset.label,
|
|
221
|
+
className: "max-h-full max-w-full object-contain"
|
|
222
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
223
|
+
className: "text-fg truncate text-sm",
|
|
224
|
+
children: asset.label
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
//#endregion
|
|
229
|
+
exports.StampsPanel = StampsPanel;
|
|
230
|
+
|
|
231
|
+
//# sourceMappingURL=stamps-panel-B9NYGayn.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stamps-panel-B9NYGayn.cjs","names":["useStampSettings","ensureDefaultLibrary","resolveStampsLocale","DEFAULT_LIBRARY_ID","Icon"],"sources":["../src/ui/stamps-panel.tsx"],"sourcesContent":["/**\n * The stamps sidebar (right panel). The stamp plugin owns the libraries, the\n * asset bytes and the cached previews; this panel is the picker over them:\n *\n * a library <select> (\"All stamps\" + one per library, shown once there\n * are two) → a two-column grid of thumbnails → click a stamp → `armAsset`\n * arms the annotation plugin's stamp tool → hovering a page ghosts the\n * exact placement → each click on a page places one.\n *\n * Deliberately not a file dialog: picking arbitrary image bytes is the Image\n * tool's job (a click-then-pick tool, `tool:image`). The stamp button\n * opens a library; a library is a set of reusable, named assets — the two\n * gestures are different, so they are different controls.\n *\n * The panel stays open while you place, so a stamp can be dropped on several\n * pages and swapped without a round trip through the toolbar. A command can\n * open it on a library (`annotation:stamp-from-selection` → \"My stamps\")\n * through the surface's open props.\n */\nimport { useEffect, useRef, useState } from 'react';\nimport { useInteractionState } from '@embedpdf/react/interaction';\nimport {\n useStamp,\n useStampAssetPreviewUrl,\n useStampAssets,\n useStampLibraries,\n type StampAsset,\n} from '@embedpdf/react/stamp';\nimport { useSurface } from '@embedpdf/react/shell';\nimport { useI18nState, useT } from '@embedpdf/react/i18n';\nimport { useStampSettings } from '../config-context';\nimport {\n DEFAULT_LIBRARY_ID,\n ensureDefaultLibrary,\n resolveStampsLocale,\n} from '../config/default-stamps';\nimport { Icon } from './icons';\nimport { restoreStampLibrariesOnce } from './stamp-store';\n\nconst ALL = 'all';\n\nexport function StampsPanel() {\n const t = useT();\n const stamp = useStamp();\n const { standard, sidebar } = useStampSettings();\n // Which catalogs this sidebar shows (`plugins.stamp.sidebar`): the plain stamps by\n // default — never the people's marks, which have their own panel.\n const libraries = useStampLibraries({ kind: sidebar ?? ['stamps'] });\n const assets = useStampAssets();\n const { activeToolId } = useInteractionState();\n const { locale } = useI18nState();\n const surface = useSurface('stamps');\n const fileRef = useRef<HTMLInputElement>(null);\n const [armedId, setArmedId] = useState<string | null>(null);\n const [busy, setBusy] = useState<'loading' | 'importing' | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [picked, setPicked] = useState<string>(ALL);\n\n // A command opened the panel on a library (the surface's `libraryId` prop).\n const openedOn = surface.props?.libraryId;\n useEffect(() => {\n if (typeof openedOn === 'string') setPicked(openedOn);\n }, [openedOn]);\n\n // First open: the user's stored libraries (the workspace store restores\n // them; this only awaits it), then the built-in library for this locale —\n // never at viewer boot, so a viewer whose user never opens this panel pays\n // nothing for it. A locale change while open swaps the built-in library;\n // the user's own are untouched.\n useEffect(() => {\n let live = true;\n setBusy('loading');\n restoreStampLibrariesOnce(stamp)\n .then(() => ensureDefaultLibrary(stamp, resolveStampsLocale(locale), standard))\n .catch((error: unknown) => {\n console.error('[embedpdf] default stamps failed:', error);\n if (live) setError(t('demo.stampsError'));\n })\n .finally(() => live && setBusy(null));\n return () => {\n live = false;\n };\n // `t` and `standard` are init-stable; only the locale re-runs this.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [stamp, locale]);\n\n // The interaction hub owns tool state: leaving the stamp tool (picking\n // another tool, pressing Escape) un-highlights the gallery.\n useEffect(() => {\n if (activeToolId !== 'stamp') setArmedId(null);\n }, [activeToolId]);\n\n const arm = (asset: StampAsset) => {\n setError(null);\n setArmedId(asset.id);\n void stamp.armAsset(asset.id).catch((error) => {\n console.error('[embedpdf] arm stamp failed:', error);\n setArmedId(null);\n setError(t('demo.stampsArmError'));\n });\n };\n\n const importPdf = (file: File) => {\n setError(null);\n setBusy('importing');\n // The file name is only a fallback: an Acrobat-authored or previously\n // exported library names itself through its /Title.\n stamp\n .importLibrary(file, { name: file.name.replace(/\\.pdf$/i, '') })\n .then(({ library }) => setPicked(library.id))\n .catch((error) => {\n console.error('[embedpdf] stamp library import failed:', error);\n setError(t('demo.stampsImportError'));\n })\n .finally(() => setBusy(null));\n };\n\n /** The library as the PDF it is — title, registry, artwork — for Acrobat\n * or another viewer. */\n const exportPdf = async (libraryId: string, name: string) => {\n const bytes = await stamp.exportLibrary(libraryId).catch(() => null);\n if (!bytes) return;\n const blob = new Blob([bytes as BlobPart], { type: 'application/pdf' });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement('a');\n anchor.href = url;\n anchor.download = `${name || 'stamps'}.pdf`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n // A removed library falls back to \"All\"; the picker never names a ghost.\n // One library needs no picker: it is simply the selection (so its export\n // button is reachable).\n const selectedId =\n libraries.length === 1\n ? libraries[0].id\n : libraries.some((library) => library.id === picked)\n ? picked\n : ALL;\n const selected =\n selectedId === ALL ? null : (libraries.find((library) => library.id === selectedId) ?? null);\n const shown =\n selectedId === ALL ? assets : assets.filter((asset) => asset.libraryId === selectedId);\n const isDefault = (libraryId: string) => libraryId === DEFAULT_LIBRARY_ID;\n\n return (\n <div className=\"flex min-h-0 flex-1 flex-col\">\n {libraries.length > 0 && (\n <div className=\"border-border-subtle flex items-center gap-1 border-b p-2\">\n {libraries.length > 1 ? (\n <select\n value={selectedId}\n onChange={(event) => setPicked(event.target.value)}\n aria-label={t('panels.stamps')}\n className=\"border-border bg-surface text-fg min-w-0 flex-1 rounded border px-2 py-1.5 text-sm\"\n >\n <option value={ALL}>{t('demo.stampsAll')}</option>\n {libraries.map((library) => (\n <option key={library.id} value={library.id}>\n {library.name}\n </option>\n ))}\n </select>\n ) : (\n <span className=\"text-fg-muted min-w-0 flex-1 truncate px-1 text-xs font-semibold uppercase tracking-wide\">\n {libraries[0].name}\n </span>\n )}\n {selected && (\n <>\n <button\n type=\"button\"\n onClick={() => exportPdf(selected.id, selected.name)}\n title={t('demo.stampsExportLibrary')}\n className=\"text-fg-muted hover:text-fg grid h-7 w-7 shrink-0 place-items-center rounded\"\n >\n <Icon name=\"download\" size={14} />\n </button>\n {!isDefault(selected.id) && (\n <button\n type=\"button\"\n onClick={() => void stamp.deleteLibrary(selected.id)}\n title={t('demo.stampsRemoveLibrary')}\n className=\"text-fg-muted hover:text-fg grid h-7 w-7 shrink-0 place-items-center rounded\"\n >\n <Icon name=\"trash\" size={14} />\n </button>\n )}\n </>\n )}\n </div>\n )}\n\n <div className=\"min-h-0 flex-1 overflow-y-auto p-3\">\n {busy === 'loading' && shown.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 px-4 py-10 text-center\">\n <div className=\"border-border-subtle border-t-accent h-6 w-6 animate-spin rounded-full border-2\" />\n <p className=\"text-fg-muted text-sm\">{t('demo.stampsLoading')}</p>\n </div>\n ) : shown.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 px-4 py-10 text-center\">\n <Icon name=\"rubberStamp\" size={32} className=\"text-fg-muted\" />\n <p className=\"text-fg-muted text-sm\">{t('demo.stampsEmpty')}</p>\n </div>\n ) : (\n <ul className=\"grid grid-cols-2 gap-3\">\n {shown.map((asset) => (\n <li key={asset.id} className=\"group relative\">\n <StampAssetCell\n asset={asset}\n armed={armedId === asset.id}\n onArm={() => arm(asset)}\n />\n {!isDefault(asset.libraryId) && (\n <button\n type=\"button\"\n onClick={() => void stamp.deleteAsset(asset.id)}\n title={t('demo.stampsRemoveStamp')}\n className=\"bg-surface border-border text-fg-muted hover:text-fg absolute end-1 top-1 grid h-5 w-5 place-items-center rounded-full border opacity-0 shadow-sm focus:opacity-100 group-hover:opacity-100\"\n >\n <Icon name=\"x\" size={12} />\n </button>\n )}\n </li>\n ))}\n </ul>\n )}\n </div>\n\n {(error || armedId) && (\n <div\n className={`border-border-subtle shrink-0 border-t px-3 py-2 text-xs ${\n error ? 'text-red-600' : 'text-fg-muted'\n }`}\n >\n {error ?? t('demo.stampsArmedHint')}\n </div>\n )}\n\n <div className=\"border-border-subtle shrink-0 border-t p-3\">\n <button\n type=\"button\"\n disabled={busy != null}\n onClick={() => fileRef.current?.click()}\n title={t('demo.stampsImportHint')}\n className=\"border-border text-fg hover:bg-hover flex w-full items-center justify-center gap-2 rounded-md border px-3 py-1.5 text-sm disabled:opacity-40\"\n >\n <Icon name=\"rubberStampPlus\" size={16} />\n {busy === 'importing' ? t('demo.stampsImporting') : t('demo.stampsImport')}\n </button>\n <input\n ref={fileRef}\n type=\"file\"\n accept=\"application/pdf\"\n className=\"hidden\"\n onChange={(event) => {\n const file = event.currentTarget.files?.[0];\n event.currentTarget.value = ''; // allow re-picking the same file\n if (file) importPdf(file);\n }}\n />\n </div>\n </div>\n );\n}\n\n/** One grid cell: the plugin's cached preview through the object-URL hook\n * (the DOM resource's lifetime belongs to the framework layer, not the\n * store). Image only — the artwork is the label; the label is the\n * tooltip for the curious. */\nfunction StampAssetCell({\n asset,\n armed,\n onArm,\n}: {\n asset: StampAsset;\n armed: boolean;\n onArm: () => void;\n}) {\n const url = useStampAssetPreviewUrl(asset.id);\n return (\n <button\n type=\"button\"\n onClick={onArm}\n title={asset.label}\n aria-pressed={armed}\n className={`flex aspect-square w-full items-center justify-center rounded-md border p-2 ${\n armed\n ? 'border-accent bg-accent-light ring-accent ring-2'\n : 'border-border-subtle hover:border-border hover:bg-hover'\n }`}\n >\n {url ? (\n <img src={url} alt={asset.label} className=\"max-h-full max-w-full object-contain\" />\n ) : (\n <span className=\"text-fg truncate text-sm\">{asset.label}</span>\n )}\n </button>\n );\n}\n"],"mappings":"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"}
|