@bagelink/vue 1.16.0 → 1.17.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/assetTransport.d.ts +28 -0
- package/dist/assetTransport.d.ts.map +1 -0
- package/dist/assetTransport.test.d.ts +2 -0
- package/dist/assetTransport.test.d.ts.map +1 -0
- package/dist/assets.d.ts +65 -0
- package/dist/assets.d.ts.map +1 -0
- package/dist/assets.test.d.ts +2 -0
- package/dist/assets.test.d.ts.map +1 -0
- package/dist/components/AddressSearch.vue.d.ts +40 -36
- package/dist/components/AddressSearch.vue.d.ts.map +1 -1
- package/dist/components/ContextMenu.vue.d.ts +518 -0
- package/dist/components/ContextMenu.vue.d.ts.map +1 -0
- package/dist/components/Dropdown.types.d.ts +25 -0
- package/dist/components/Dropdown.types.d.ts.map +1 -0
- package/dist/components/Dropdown.vue.d.ts +20 -15
- package/dist/components/Dropdown.vue.d.ts.map +1 -1
- package/dist/components/FilterQuery.vue.d.ts +1 -1
- package/dist/components/FilterQuery.vue.d.ts.map +1 -1
- package/dist/components/Icon/Icon.vue.d.ts +0 -1
- package/dist/components/Icon/Icon.vue.d.ts.map +1 -1
- package/dist/components/Icon/constants.d.ts +0 -1
- package/dist/components/Icon/constants.d.ts.map +1 -1
- package/dist/components/Icon/types.d.ts +2 -3
- package/dist/components/Icon/types.d.ts.map +1 -1
- package/dist/components/Image.vue.d.ts +7 -1
- package/dist/components/Image.vue.d.ts.map +1 -1
- package/dist/components/Swiper.vue.d.ts +1 -1
- package/dist/components/draggable/useDraggable.d.ts.map +1 -1
- package/dist/components/form/inputs/IconPickerInput.vue.d.ts +48 -37
- package/dist/components/form/inputs/IconPickerInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/components/EditorToolbar.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useCommands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts +3 -0
- package/dist/components/form/inputs/RichText/composables/useEditor.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts +1 -1
- package/dist/components/form/inputs/RichText/composables/useEditorKeyboard.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/index.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/commands.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/selection.d.ts +2 -0
- package/dist/components/form/inputs/RichText/utils/selection.d.ts.map +1 -1
- package/dist/components/form/inputs/RichText/utils/table.d.ts +34 -8
- package/dist/components/form/inputs/RichText/utils/table.d.ts.map +1 -1
- package/dist/components/form/inputs/SelectInput.vue.d.ts +71 -63
- package/dist/components/form/inputs/SelectInput.vue.d.ts.map +1 -1
- package/dist/components/form/inputs/TelInput.vue.d.ts +2 -2
- package/dist/components/form/inputs/Upload/AssetUploadInput.vue.d.ts +11 -0
- package/dist/components/form/inputs/Upload/AssetUploadInput.vue.d.ts.map +1 -0
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts +2 -2
- package/dist/components/form/inputs/Upload/UploadInput.vue.d.ts.map +1 -1
- package/dist/components/index.d.ts +3 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/lightbox/AssetPreview.vue.d.ts +9 -0
- package/dist/components/lightbox/AssetPreview.vue.d.ts.map +1 -0
- package/dist/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/components/lightbox/LightboxImage.vue.d.ts +2 -1
- package/dist/components/lightbox/LightboxImage.vue.d.ts.map +1 -1
- package/dist/components/lightbox/index.d.ts.map +1 -1
- package/dist/components/lightbox/lightbox.types.d.ts +7 -0
- package/dist/components/lightbox/lightbox.types.d.ts.map +1 -1
- package/dist/composables/index.d.ts +1 -1
- package/dist/composables/index.d.ts.map +1 -1
- package/dist/composables/useBoundedRectDrag.d.ts.map +1 -1
- package/dist/composables/useImageSrc.d.ts +8 -4
- package/dist/composables/useImageSrc.d.ts.map +1 -1
- package/dist/composables/usePointerSession.d.ts +14 -3
- package/dist/composables/usePointerSession.d.ts.map +1 -1
- package/dist/core.css +3 -3
- package/dist/directives/vResize.d.ts.map +1 -1
- package/dist/form-flow/FormFlowPlayer.vue.d.ts +3 -3
- package/dist/form-flow/FormFlowPlayer.vue.d.ts.map +1 -1
- package/dist/form-flow/form-flow.d.ts +13 -6
- package/dist/form-flow/form-flow.d.ts.map +1 -1
- package/dist/form-flow/schema-fields.d.ts +6 -0
- package/dist/form-flow/schema-fields.d.ts.map +1 -1
- package/dist/index.cjs +240 -153
- package/dist/index.css +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +24167 -23417
- package/dist/style.css +3 -3
- package/dist/types/BagelForm.d.ts +5 -1
- package/dist/types/BagelForm.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/assetTransport.test.ts +75 -0
- package/src/assetTransport.ts +98 -0
- package/src/assets.test.ts +82 -0
- package/src/assets.ts +162 -0
- package/src/components/ContextMenu.vue +113 -0
- package/src/components/Dropdown.types.ts +27 -0
- package/src/components/Dropdown.vue +129 -64
- package/src/components/FilterQuery.vue +1 -1
- package/src/components/Icon/Icon.vue +8 -30
- package/src/components/Icon/constants.ts +0 -166
- package/src/components/Icon/types.ts +2 -3
- package/src/components/Image.vue +19 -3
- package/src/components/draggable/useDraggable.ts +67 -100
- package/src/components/form/inputs/IconPickerInput.vue +14 -9
- package/src/components/form/inputs/RichText/components/EditorToolbar.vue +43 -111
- package/src/components/form/inputs/RichText/composables/useCommands.ts +32 -54
- package/src/components/form/inputs/RichText/composables/useEditor.ts +45 -171
- package/src/components/form/inputs/RichText/composables/useEditorKeyboard.ts +90 -14
- package/src/components/form/inputs/RichText/index.vue +1195 -1210
- package/src/components/form/inputs/RichText/utils/commands.ts +689 -1028
- package/src/components/form/inputs/RichText/utils/selection.ts +66 -41
- package/src/components/form/inputs/RichText/utils/table.ts +798 -136
- package/src/components/form/inputs/SelectInput.vue +1 -1
- package/src/components/form/inputs/Upload/AssetUploadInput.vue +67 -0
- package/src/components/form/inputs/Upload/UploadInput.vue +5 -1
- package/src/components/index.ts +3 -1
- package/src/components/lightbox/AssetPreview.vue +16 -0
- package/src/components/lightbox/Lightbox.vue +8 -3
- package/src/components/lightbox/LightboxImage.vue +4 -3
- package/src/components/lightbox/index.ts +29 -4
- package/src/components/lightbox/lightbox.types.ts +5 -1
- package/src/composables/index.ts +1 -1
- package/src/composables/useBoundedRectDrag.ts +9 -12
- package/src/composables/useImageSrc.ts +47 -37
- package/src/composables/usePointerSession.ts +57 -12
- package/src/directives/vResize.ts +6 -7
- package/src/form-flow/form-flow.ts +31 -7
- package/src/form-flow/schema-fields.ts +28 -1
- package/src/index.ts +3 -0
- package/src/styles/core.css +1 -18
- package/src/types/BagelForm.ts +5 -1
- package/dist/components/form/inputs/RichText/utils/formatting.d.ts +0 -10
- package/dist/components/form/inputs/RichText/utils/formatting.d.ts.map +0 -1
- package/src/components/form/inputs/RichText/components/gridBox.vue +0 -58
- package/src/components/form/inputs/RichText/utils/formatting.ts +0 -331
|
@@ -98,6 +98,10 @@ export interface UploadInputValueObject {
|
|
|
98
98
|
src?: string;
|
|
99
99
|
}
|
|
100
100
|
export interface UploadInputProps extends BagelInputShellProps {
|
|
101
|
+
/** Opt-in stable reference output; defaults to legacy path-key behavior. */
|
|
102
|
+
assetMode?: boolean;
|
|
103
|
+
/** Asset delivery policy. Defaults to protected; website/media fields use public. */
|
|
104
|
+
assetVisibility?: import('../assets').AssetVisibility;
|
|
101
105
|
id?: string;
|
|
102
106
|
name?: string;
|
|
103
107
|
label?: string;
|
|
@@ -106,7 +110,7 @@ export interface UploadInputProps extends BagelInputShellProps {
|
|
|
106
110
|
* Path-key string(s). Legacy/migrated records may also provide an image object
|
|
107
111
|
* (e.g. `{ imageURL, imageName, extension, altText }`); it is normalized internally.
|
|
108
112
|
*/
|
|
109
|
-
modelValue?: string | string[] | UploadInputValueObject | (string | UploadInputValueObject)[];
|
|
113
|
+
modelValue?: string | string[] | UploadInputValueObject | import('../assets').AssetReference | (string | UploadInputValueObject | import('../assets').AssetReference)[];
|
|
110
114
|
/** Optional upload handler. When omitted, the shared file-storage uploader is used. */
|
|
111
115
|
upload?: (file: File) => Promise<string>;
|
|
112
116
|
width?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"BagelForm.d.ts","sourceRoot":"","sources":["../../src/types/BagelForm.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"BagelForm.d.ts","sourceRoot":"","sources":["../../src/types/BagelForm.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAA;AAC/D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AACzD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAA;AACxE,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC/E,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,KAAK,CAAA;AAIhC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAA;AAErF,UAAU,MAAM;IAAG,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,GAAG,CAAA;CAAE;AAI9C,MAAM,MAAM,KAAK,CAChB,CAAC,EACD,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAC1C,QAAQ,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AAG9C,KAAK,mBAAmB,CAAC,CAAC,IAAI;KAC5B,CAAC,IAAI,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,GAChD,GAAG,CAAC,GAAG,MAAM,IAAI,MAAM,EAAE,GACzB,KAAK;CACR,CAAC,MAAM,CAAC,CAAC,CAAA;AAGV,MAAM,MAAM,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAC9D,QAAQ,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,KAAK,CAAC,GAAG,CAAC,GAC3C,kBAAkB,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAChC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,GAAG,MAAM,CAAC,GAAG,MAAM,cAAc,MAAM,EAAE,CAAA;AAErF,uFAAuF;AACvF,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IACtC,OAAO,SAAS,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAElD,sEAAsE;AACtE,MAAM,MAAM,aAAa,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAC3C,CAAC,SAAS,MAAM,GACf,CAAC,SAAS,MAAM,CAAC,GAChB,CAAC,CAAC,CAAC,CAAC,GACJ,GAAG,GACJ,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAIlB,MAAM,MAAM,cAAc,GACrB,MAAM,GACP,MAAM,GACN,OAAO,GACP,SAAS,GACT;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CAAA;AAE1B,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,CAC/C,KAAK,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAC1B,GAAG,CAAC,EAAE,CAAC,KACH,cAAc,CAAA;AAEnB,MAAM,WAAW,UAAU,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CACjD;AAED,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IACxC,MAAM,GACP,CACC;IACD,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;CACtB,GACC,MAAM,GACN,MAAM,GACN,OAAO,GACP;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;CAAE,CACxB,EAAE,GACD,CAAC,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,GAC/F,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAA;AAElF,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IACnC,MAAM,GACP,OAAO,GACP,CAAC,CAAC,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,OAAO,CAAC,CAAA;AAEzD,MAAM,MAAM,YAAY,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,CAChD,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,CAAC,KACP,MAAM,GAAG,SAAS,CAAA;AAEvB,MAAM,MAAM,WAAW,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,CAC/C,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,CAAC,KACP,GAAG,CAAA;AAER,MAAM,MAAM,QAAQ,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,CAC5C,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,EACzB,OAAO,CAAC,EAAE,CAAC,KACP,OAAO,CAAA;AAIZ,MAAM,MAAM,OAAO,CAAC,CAAC,EAAE,CAAC,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE;IACnD,GAAG,CAAC,EAAE,CAAC,CAAA;IACP,KAAK,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CAC3B,KAAK,KAAK,CAAA;AAEX,MAAM,WAAW,YAAY,CAC5B,CAAC,EACD,EAAE,SAAS,YAAY,GAAG,mBAAmB;IAE7C,GAAG,EAAE,GAAG,CAAA;IACR,EAAE,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,CAAA;IACzB,KAAK,CAAC,EAAE,GAAG,CAAA;IACX,GAAG,CAAC,EAAE,GAAG,CAAA;IACT,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC3B,OAAO,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,IAAI,CAAA;IAC1C,SAAS,CAAC,EAAE,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,CAAC,KAAK,GAAG,CAAA;IAC3C,QAAQ,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;CAChC;AAGD,KAAK,mBAAmB,CAAC,CAAC,EAAE,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAAI;KAC3E,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;CAC5C,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;AAEd,MAAM,MAAM,WAAW,CACtB,CAAC,EACD,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EACrB,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAC1C,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,MAAM,GAAG,mBAAmB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAEpG,MAAM,WAAW,cAAc,CAC9B,CAAC,EACD,CAAC,SAAS,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EACrB,EAAE,SAAS,YAAY,GAAG,mBAAmB;IAE7C,KAAK,CAAC,EAAE,MAAM,GAAG,GAAG,CAAA;IACpB,IAAI,CAAC,EAAE,CAAC,CAAA;IACR,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,cAAc,GAAG,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC5C,OAAO,CAAC,EAAE,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC9B,UAAU,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;IAC9C,OAAO,CAAC,EAAE;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,EAAE,CAAA;KAAE,CAAA;IAC9D,cAAc,CAAC,EAAE,GAAG,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IACtB,WAAW,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC/B,UAAU,CAAC,EAAE,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;IAC3B,UAAU,CAAC,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;CAC/B;AAED,MAAM,MAAM,KAAK,CAChB,CAAC,EACD,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAC1C,mBAAmB,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAE9B,MAAM,MAAM,WAAW,CACtB,CAAC,EACD,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAC1C,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;AAEtC,MAAM,MAAM,cAAc,CACzB,CAAC,EACD,EAAE,SAAS,YAAY,GAAG,mBAAmB,IAC1C,CAAC,WAAW,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,cAAc,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,CAAA;AAI/D,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,EAAE,YAAY,CAAC;QAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;KAAE,EAAE,MAAM,CAAC,CAAA;IAC3D,WAAW,CAAC,EAAE,MAAM,GAAG,CAAA;CACvB;AAED,qFAAqF;AACrF,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;CACZ;AAED,MAAM,WAAW,gBAAiB,SAAQ,oBAAoB;IAC7D,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,qFAAqF;IACrF,eAAe,CAAC,EAAE,OAAO,WAAW,EAAE,eAAe,CAAA;IACrD,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,sBAAsB,GAAG,OAAO,WAAW,EAAE,cAAc,GAAG,CAAC,MAAM,GAAG,sBAAsB,GAAG,OAAO,WAAW,EAAE,cAAc,CAAC,EAAE,CAAA;IACvK,uFAAuF;IACvF,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACxB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,+DAA+D;IAC/D,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB,IAAI,CAAC,EAAE,OAAO,CAAA;IACd,KAAK,CAAC,EAAE,UAAU,GAAG,OAAO,CAAA;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,aAAa,CAAA;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,0FAA0F;IAC1F,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CAChC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
2
|
+
import { configureAssetClient, createAssetHttpTransport, type AssetFetch } from './assetTransport'
|
|
3
|
+
import { createAssetClient } from './assets'
|
|
4
|
+
import type { ApiTransport } from '@bagelink/sdk'
|
|
5
|
+
import { createApp, ref } from 'vue'
|
|
6
|
+
|
|
7
|
+
const session = { asset_id: 'a', upload_id: 'u', status: 'ready' }
|
|
8
|
+
const json = (value: unknown, status = 200) => new Response(JSON.stringify(value), { status })
|
|
9
|
+
const signal = () => new AbortController().signal
|
|
10
|
+
|
|
11
|
+
describe('asset REST transport', () => {
|
|
12
|
+
it('starts a session and sends unchanged raw bytes, returning the public id', async () => {
|
|
13
|
+
const fetcher = vi.fn<AssetFetch>().mockResolvedValueOnce(json(session)).mockResolvedValueOnce(json(session))
|
|
14
|
+
const transport = createAssetHttpTransport({ baseURL: 'https://api.example/api', fetch: fetcher })
|
|
15
|
+
const file = new File(['original'], 'test.heic')
|
|
16
|
+
expect(await transport.upload(file, signal())).toEqual({ asset_id: 'a' })
|
|
17
|
+
expect(String(fetcher.mock.calls[0]![0])).toBe('https://api.example/api/assets/uploads')
|
|
18
|
+
expect(fetcher.mock.calls[0]![1]?.body).toBe('{"name":"test.heic"}')
|
|
19
|
+
expect(fetcher.mock.calls[1]![1]?.body).toBe(file)
|
|
20
|
+
expect(new Headers(fetcher.mock.calls[1]![1]?.headers).get('Content-Type')).toBe('application/octet-stream')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('fails closed for rejected completion and HTTP errors', async () => {
|
|
24
|
+
const fetcher = vi.fn<AssetFetch>().mockResolvedValueOnce(json(session))
|
|
25
|
+
.mockResolvedValueOnce(json({ ...session, status: 'rejected' })).mockResolvedValueOnce(json({}, 403))
|
|
26
|
+
const transport = createAssetHttpTransport({ baseURL: 'https://api.example', fetch: fetcher })
|
|
27
|
+
await expect(transport.upload(new File(['x'], 'x'), signal())).rejects.toThrow('not ready')
|
|
28
|
+
await expect(transport.upload(new File(['x'], 'x'), signal())).rejects.toThrow('(403)')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('does not PUT if session creation fails', async () => {
|
|
32
|
+
const fetcher = vi.fn<AssetFetch>().mockResolvedValueOnce(json({}, 503))
|
|
33
|
+
const transport = createAssetHttpTransport({ baseURL: 'https://api.example', fetch: fetcher })
|
|
34
|
+
await expect(transport.upload(new File(['x'], 'x'), signal())).rejects.toThrow('(503)')
|
|
35
|
+
expect(fetcher).toHaveBeenCalledTimes(1)
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
it('configureAssetClient provides a client that builds public URLs', () => {
|
|
39
|
+
const transport = { axios: { defaults: { baseURL: 'https://api.example/api' }, request: vi.fn() } } as unknown as ApiTransport
|
|
40
|
+
const assets = configureAssetClient({ transport, deliveryBaseUrl: 'https://cdn.bgl.dev/auctentic' })
|
|
41
|
+
expect(assets.client.assetUrl({ asset_id: 'a' })).toBe('https://cdn.bgl.dev/auctentic/a')
|
|
42
|
+
expect(assets.client.assetUrl({ asset_id: 'a' }, 'website')).toBe('https://cdn.bgl.dev/auctentic/a?fmt=webp')
|
|
43
|
+
expect(assets.client.assetUrl({ asset_id: 'a' }, 'social')).toBe('https://cdn.bgl.dev/auctentic/a?w=1200&h=630&fmt=jpeg&fit=cover')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('clears in-flight uploads on session change', async () => {
|
|
47
|
+
let requestSignal: AbortSignal | null | undefined
|
|
48
|
+
const fetcher = vi.fn<AssetFetch>((config: any) => {
|
|
49
|
+
requestSignal = config?.signal
|
|
50
|
+
return new Promise((_resolve, reject) => requestSignal?.addEventListener('abort', () => reject(requestSignal?.reason))
|
|
51
|
+
)
|
|
52
|
+
})
|
|
53
|
+
const transport = { axios: { defaults: { baseURL: 'https://api.example' }, request: fetcher } } as unknown as ApiTransport
|
|
54
|
+
const sessionKey = ref('a')
|
|
55
|
+
const assets = configureAssetClient({ transport, deliveryBaseUrl: 'https://cdn.bgl.dev/auctentic', sessionKey })
|
|
56
|
+
createApp({}).use(assets)
|
|
57
|
+
const promise = assets.client.upload(new File(['x'], 'x'))
|
|
58
|
+
sessionKey.value = 'b'
|
|
59
|
+
await expect(promise).rejects.toMatchObject({ name: 'AbortError' })
|
|
60
|
+
expect(requestSignal?.aborted).toBe(true)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
describe('asset client upload cancellation', () => {
|
|
65
|
+
it('does not complete after clearSession', async () => {
|
|
66
|
+
let finish!: (response: Response) => void
|
|
67
|
+
const fetcher = vi.fn<AssetFetch>().mockImplementationOnce(() => new Promise(resolve => { finish = resolve }))
|
|
68
|
+
const client = createAssetClient(createAssetHttpTransport({ baseURL: 'https://api.example', fetch: fetcher }), 'https://cdn.bgl.dev/auctentic')
|
|
69
|
+
const promise = client.upload(new File(['x'], 'x'))
|
|
70
|
+
client.clearSession()
|
|
71
|
+
finish(json(session))
|
|
72
|
+
await expect(promise).rejects.toMatchObject({ name: 'AbortError' })
|
|
73
|
+
expect(fetcher).toHaveBeenCalledTimes(1)
|
|
74
|
+
})
|
|
75
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { createAssetClient, ASSET_CLIENT_KEY, type AssetTransport } from './assets'
|
|
2
|
+
import type { ApiTransport } from '@bagelink/sdk'
|
|
3
|
+
import { watch, toValue, type App, type MaybeRefOrGetter } from 'vue'
|
|
4
|
+
|
|
5
|
+
export type AssetFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>
|
|
6
|
+
|
|
7
|
+
export interface AssetHttpOptions {
|
|
8
|
+
/** Absolute API base, optionally with a path prefix. */
|
|
9
|
+
baseURL: string
|
|
10
|
+
headers?: () => HeadersInit
|
|
11
|
+
fetch?: AssetFetch
|
|
12
|
+
credentials?: RequestCredentials
|
|
13
|
+
/** Reuse the app's authenticated axios instance, including tenant interceptors. */
|
|
14
|
+
apiTransport?: ApiTransport
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface UploadResponse {
|
|
18
|
+
asset_id: string
|
|
19
|
+
upload_id: string
|
|
20
|
+
status: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Concrete REST adapter. Public-by-default: upload only; display is a client-built URL. */
|
|
24
|
+
export function createAssetHttpTransport(options: AssetHttpOptions): AssetTransport {
|
|
25
|
+
const base = new URL(options.baseURL.endsWith('/') ? options.baseURL : `${options.baseURL}/`)
|
|
26
|
+
if (!['http:', 'https:'].includes(base.protocol) || base.username || base.password) throw new Error('Invalid API base')
|
|
27
|
+
const request = options.fetch ?? globalThis.fetch
|
|
28
|
+
async function send<T>(path: string, init: RequestInit, signal: AbortSignal): Promise<T> {
|
|
29
|
+
signal.throwIfAborted()
|
|
30
|
+
const headers = new Headers(options.headers?.())
|
|
31
|
+
new Headers(init.headers).forEach((value, key) => headers.set(key, value))
|
|
32
|
+
if (options.apiTransport) {
|
|
33
|
+
const requestHeaders: Record<string, string> = {}
|
|
34
|
+
headers.forEach((value, key) => { requestHeaders[key] = value })
|
|
35
|
+
const response = await options.apiTransport.axios.request<T>({
|
|
36
|
+
url: `assets/${path}`, method: init.method, data: init.body,
|
|
37
|
+
headers: requestHeaders, signal,
|
|
38
|
+
})
|
|
39
|
+
signal.throwIfAborted()
|
|
40
|
+
return response.data
|
|
41
|
+
}
|
|
42
|
+
const response = await request(new URL(`assets/${path}`, base), {
|
|
43
|
+
...init, headers, signal, credentials: options.credentials ?? 'include', cache: 'no-store', redirect: 'error',
|
|
44
|
+
})
|
|
45
|
+
signal.throwIfAborted()
|
|
46
|
+
if (!response.ok) throw new Error(`Asset request failed (${response.status})`)
|
|
47
|
+
return await response.json() as T
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
async upload(file, signal, uploadOptions) {
|
|
51
|
+
const started = await send<UploadResponse>('uploads', {
|
|
52
|
+
method: 'POST',
|
|
53
|
+
headers: { 'Content-Type': 'application/json' },
|
|
54
|
+
body: JSON.stringify({
|
|
55
|
+
name: file.name,
|
|
56
|
+
visibility: uploadOptions?.visibility ?? 'protected',
|
|
57
|
+
}),
|
|
58
|
+
}, signal)
|
|
59
|
+
if (!started.upload_id || !started.asset_id) throw new Error('Invalid upload session')
|
|
60
|
+
const completed = await send<UploadResponse>(`uploads/${encodeURIComponent(started.upload_id)}`, {
|
|
61
|
+
method: 'PUT', headers: { 'Content-Type': 'application/octet-stream' }, body: file,
|
|
62
|
+
}, signal)
|
|
63
|
+
if (completed.status !== 'ready' || completed.asset_id !== started.asset_id) {
|
|
64
|
+
throw new Error('Asset upload is not ready')
|
|
65
|
+
}
|
|
66
|
+
return { asset_id: completed.asset_id }
|
|
67
|
+
},
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface ConfigureAssetClientOptions {
|
|
72
|
+
transport: ApiTransport
|
|
73
|
+
/** Public CDN base, e.g. `https://cdn.bgl.dev/auctentic`. Used to build asset URLs. */
|
|
74
|
+
deliveryBaseUrl: string
|
|
75
|
+
/** Identity + tenant key; must change on logout and account/tenant switch. */
|
|
76
|
+
sessionKey?: MaybeRefOrGetter<string | undefined>
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Vue plugin; provides the asset client and clears uploads on session change. */
|
|
80
|
+
export function configureAssetClient(options: ConfigureAssetClientOptions) {
|
|
81
|
+
const baseURL = options.transport.axios.defaults.baseURL
|
|
82
|
+
if (!baseURL) throw new Error('Asset transport needs an API baseURL')
|
|
83
|
+
const transport = createAssetHttpTransport({
|
|
84
|
+
baseURL: new URL(baseURL, typeof location === 'undefined' ? undefined : location.origin).href,
|
|
85
|
+
apiTransport: options.transport,
|
|
86
|
+
})
|
|
87
|
+
const client = createAssetClient(transport, options.deliveryBaseUrl)
|
|
88
|
+
return {
|
|
89
|
+
client,
|
|
90
|
+
install(app: App) {
|
|
91
|
+
app.provide(ASSET_CLIENT_KEY, client)
|
|
92
|
+
const stop = options.sessionKey
|
|
93
|
+
? watch(() => toValue(options.sessionKey), () => client.clearSession(), { flush: 'sync' })
|
|
94
|
+
: undefined
|
|
95
|
+
app.onUnmount(() => { stop?.(); client.clearSession() })
|
|
96
|
+
},
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
+
import { createAssetClient, ASSET_CLIENT_KEY, useAssetDelivery, useAssetUpload, type AssetTransport } from './assets'
|
|
4
|
+
import { mount } from '@vue/test-utils'
|
|
5
|
+
import { defineComponent, nextTick } from 'vue'
|
|
6
|
+
|
|
7
|
+
const BASE = 'https://cdn.bgl.dev/auctentic'
|
|
8
|
+
|
|
9
|
+
function makeClient(upload?: AssetTransport['upload']) {
|
|
10
|
+
const transport = { upload: upload ?? (async () => ({ asset_id: 'a' })) }
|
|
11
|
+
return createAssetClient(transport, BASE)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
describe('asset client', () => {
|
|
15
|
+
it('builds deterministic public URLs per purpose', () => {
|
|
16
|
+
const client = makeClient()
|
|
17
|
+
expect(client.assetUrl({ asset_id: 'a' })).toBe(`${BASE}/a`)
|
|
18
|
+
expect(client.assetUrl({ asset_id: 'a' }, 'original')).toBe(`${BASE}/a`)
|
|
19
|
+
expect(client.assetUrl({ asset_id: 'a' }, 'website')).toBe(`${BASE}/a?fmt=webp`)
|
|
20
|
+
expect(client.assetUrl({ asset_id: 'a' }, 'social')).toBe(`${BASE}/a?w=1200&h=630&fmt=jpeg&fit=cover`)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('upload returns only the public id (version is ignored)', async () => {
|
|
24
|
+
const upload = vi.fn<AssetTransport['upload']>(async () => ({ asset_id: 'a', version_id: 'v1' }))
|
|
25
|
+
const client = makeClient(upload)
|
|
26
|
+
await expect(client.upload(new File(['x'], 'x'))).resolves.toEqual({ asset_id: 'a' })
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('clearSession aborts in-flight uploads and bumps revision', async () => {
|
|
30
|
+
const upload = vi.fn<AssetTransport['upload']>((_f, signal) => new Promise((_resolve, reject) => {
|
|
31
|
+
signal.addEventListener('abort', () => reject(new DOMException('x', 'AbortError')))
|
|
32
|
+
}))
|
|
33
|
+
const client = makeClient(upload)
|
|
34
|
+
const before = client.revision
|
|
35
|
+
const promise = client.upload(new File(['x'], 'x'))
|
|
36
|
+
client.clearSession()
|
|
37
|
+
await expect(promise).rejects.toMatchObject({ name: 'AbortError' })
|
|
38
|
+
expect(client.revision).toBe(before + 1)
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('useAssetDelivery (public URL resolution)', () => {
|
|
43
|
+
it('resolves an asset reference to its public URL without a network call', async () => {
|
|
44
|
+
const client = makeClient()
|
|
45
|
+
let state!: ReturnType<typeof useAssetDelivery>
|
|
46
|
+
mount(defineComponent({
|
|
47
|
+
setup() { state = useAssetDelivery(() => ({ asset_id: 'a', representation: 'preview' })); return () => null },
|
|
48
|
+
}), { global: { provide: { [ASSET_CLIENT_KEY as symbol]: client } } })
|
|
49
|
+
await nextTick()
|
|
50
|
+
expect(state.url.value).toBe(`${BASE}/a?fmt=webp`)
|
|
51
|
+
expect(state.error.value).toBe(false)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
it('sets error when no client is provided', async () => {
|
|
55
|
+
let state!: ReturnType<typeof useAssetDelivery>
|
|
56
|
+
mount(defineComponent({
|
|
57
|
+
setup() { state = useAssetDelivery(() => ({ asset_id: 'a' })); return () => null },
|
|
58
|
+
}))
|
|
59
|
+
await nextTick()
|
|
60
|
+
expect(state.url.value).toBeUndefined()
|
|
61
|
+
expect(state.error.value).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
describe('useAssetUpload', () => {
|
|
66
|
+
it('rejects when no client is injected', async () => {
|
|
67
|
+
let upload!: ReturnType<typeof useAssetUpload>['upload']
|
|
68
|
+
mount(defineComponent({
|
|
69
|
+
setup() { upload = useAssetUpload().upload; return () => null },
|
|
70
|
+
}))
|
|
71
|
+
await expect(upload(new File(['x'], 'x'))).rejects.toThrow('Asset client is not configured')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
it('uploads and returns the public reference', async () => {
|
|
75
|
+
const client = makeClient()
|
|
76
|
+
let upload!: ReturnType<typeof useAssetUpload>['upload']
|
|
77
|
+
mount(defineComponent({
|
|
78
|
+
setup() { upload = useAssetUpload().upload; return () => null },
|
|
79
|
+
}), { global: { provide: { [ASSET_CLIENT_KEY as symbol]: client } } })
|
|
80
|
+
await expect(upload(new File(['x'], 'x'))).resolves.toEqual({ asset_id: 'a' })
|
|
81
|
+
})
|
|
82
|
+
})
|
package/src/assets.ts
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { inject, onScopeDispose, ref, toValue, watch, type InjectionKey, type MaybeRefOrGetter } from 'vue'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Persist this reference, never a delivery URL. Assets are public-by-default;
|
|
5
|
+
* the id alone addresses the bytes. ``version_id`` is accepted for backward
|
|
6
|
+
* compatibility with callers that still emit it, but it is ignored: it no
|
|
7
|
+
* longer selects a version and never appears in a URL.
|
|
8
|
+
*/
|
|
9
|
+
export interface AssetReference {
|
|
10
|
+
asset_id: string
|
|
11
|
+
version_id?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type AssetVisibility = 'protected' | 'public'
|
|
15
|
+
|
|
16
|
+
export interface AssetUploadOptions {
|
|
17
|
+
/** Protected is the safe default. Website/media callers must opt into public. */
|
|
18
|
+
visibility?: AssetVisibility
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Display purpose → on-demand transform query params (mirrors common/blox). */
|
|
22
|
+
export type AssetPurpose = 'original' | 'preview' | 'thumbnail' | 'website' | 'social'
|
|
23
|
+
|
|
24
|
+
const PURPOSE_PARAMS: Record<AssetPurpose, string> = {
|
|
25
|
+
original: '',
|
|
26
|
+
preview: 'fmt=webp',
|
|
27
|
+
thumbnail: 'fmt=webp&w=256',
|
|
28
|
+
website: 'fmt=webp',
|
|
29
|
+
social: 'w=1200&h=630&fmt=jpeg&fit=cover',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** A display intent: a pinned asset plus an optional purpose/preset. */
|
|
33
|
+
export interface AssetDeliveryRequest extends AssetReference {
|
|
34
|
+
representation?: AssetPurpose
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Separate facade: generated SDK namespaces remain unchanged. */
|
|
38
|
+
export interface AssetTransport {
|
|
39
|
+
upload(file: File, signal: AbortSignal, options?: AssetUploadOptions): Promise<AssetReference>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface AssetClient {
|
|
43
|
+
/** Upload original bytes; protected unless the caller explicitly opts into public. */
|
|
44
|
+
upload(file: File, signal?: AbortSignal, options?: AssetUploadOptions): Promise<AssetReference>
|
|
45
|
+
/** Build the public CDN URL for a reference + optional purpose. */
|
|
46
|
+
assetUrl(reference: AssetReference, purpose?: AssetPurpose): string
|
|
47
|
+
/** Abort in-flight uploads (logout / tenant / account switch). */
|
|
48
|
+
clearSession(): void
|
|
49
|
+
/** Monotonic counter bumped on clearSession; watchers can subscribe. */
|
|
50
|
+
revision: number
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const abortError = () => new DOMException('Asset request cancelled', 'AbortError')
|
|
54
|
+
|
|
55
|
+
/** App/session scoped; no persistent storage or delivery cache (URLs are public). */
|
|
56
|
+
export function createAssetClient(
|
|
57
|
+
transport: AssetTransport,
|
|
58
|
+
deliveryBaseUrl: string,
|
|
59
|
+
): AssetClient {
|
|
60
|
+
const controllers = new Set<AbortController>()
|
|
61
|
+
let revision = 0
|
|
62
|
+
|
|
63
|
+
function assetUrl(reference: AssetReference, purpose: AssetPurpose = 'original'): string {
|
|
64
|
+
const query = PURPOSE_PARAMS[purpose] ?? ''
|
|
65
|
+
const base = deliveryBaseUrl.replace(/\/+$/, '')
|
|
66
|
+
return query ? `${base}/${reference.asset_id}?${query}` : `${base}/${reference.asset_id}`
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
get revision() { return revision },
|
|
71
|
+
assetUrl,
|
|
72
|
+
async upload(file: File, signal?: AbortSignal, options?: AssetUploadOptions): Promise<AssetReference> {
|
|
73
|
+
const controller = new AbortController()
|
|
74
|
+
const abort = () => controller.abort()
|
|
75
|
+
signal?.addEventListener('abort', abort, { once: true })
|
|
76
|
+
controllers.add(controller)
|
|
77
|
+
try {
|
|
78
|
+
if (signal?.aborted) controller.abort()
|
|
79
|
+
if (controller.signal.aborted) throw abortError()
|
|
80
|
+
const reference = await transport.upload(file, controller.signal, options)
|
|
81
|
+
if (controller.signal.aborted) throw abortError()
|
|
82
|
+
return { asset_id: reference.asset_id }
|
|
83
|
+
} finally {
|
|
84
|
+
controllers.delete(controller)
|
|
85
|
+
signal?.removeEventListener('abort', abort)
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
clearSession() {
|
|
89
|
+
for (const controller of controllers) controller.abort()
|
|
90
|
+
controllers.clear()
|
|
91
|
+
revision++
|
|
92
|
+
},
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const ASSET_CLIENT_KEY: InjectionKey<AssetClient> = Symbol('bagel:assets')
|
|
97
|
+
|
|
98
|
+
/** Opt-in upload state; original bytes are handed unchanged to the transport. */
|
|
99
|
+
export function useAssetUpload() {
|
|
100
|
+
const client = inject(ASSET_CLIENT_KEY, undefined)
|
|
101
|
+
const uploading = ref(false)
|
|
102
|
+
const reference = ref<AssetReference>()
|
|
103
|
+
let controller: AbortController | undefined
|
|
104
|
+
let generation = 0
|
|
105
|
+
function cancel() {
|
|
106
|
+
generation++
|
|
107
|
+
controller?.abort()
|
|
108
|
+
uploading.value = false
|
|
109
|
+
reference.value = undefined
|
|
110
|
+
}
|
|
111
|
+
async function upload(file: File, options?: AssetUploadOptions): Promise<AssetReference> {
|
|
112
|
+
cancel()
|
|
113
|
+
if (!client) throw new Error('Asset client is not configured')
|
|
114
|
+
const current = generation
|
|
115
|
+
controller = new AbortController()
|
|
116
|
+
uploading.value = true
|
|
117
|
+
try {
|
|
118
|
+
const result = await client.upload(file, controller.signal, options)
|
|
119
|
+
if (current !== generation) throw abortError()
|
|
120
|
+
reference.value = result
|
|
121
|
+
return result
|
|
122
|
+
} finally {
|
|
123
|
+
if (current === generation) uploading.value = false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const stop = client ? watch(() => client.revision, cancel, { flush: 'sync' }) : undefined
|
|
127
|
+
onScopeDispose(() => { cancel(); stop?.() })
|
|
128
|
+
return { upload, cancel, uploading, reference }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Resolve an asset reference to a public display URL. Public-by-default means
|
|
133
|
+
* no server round-trip and no expiry: the URL is deterministic from the asset id.
|
|
134
|
+
*/
|
|
135
|
+
export function useAssetUrl(source: MaybeRefOrGetter<AssetDeliveryRequest | undefined>) {
|
|
136
|
+
const client = inject(ASSET_CLIENT_KEY, undefined)
|
|
137
|
+
const url = ref<string>()
|
|
138
|
+
const error = ref(false)
|
|
139
|
+
|
|
140
|
+
watch(() => {
|
|
141
|
+
const reference = toValue(source)
|
|
142
|
+
return reference ? `${reference.asset_id}:${reference.representation ?? 'original'}` : ''
|
|
143
|
+
}, () => {
|
|
144
|
+
const reference = toValue(source)
|
|
145
|
+
if (!reference) { url.value = undefined; error.value = false; return }
|
|
146
|
+
if (!client) { url.value = undefined; error.value = true; return }
|
|
147
|
+
url.value = client.assetUrl(reference, reference.representation)
|
|
148
|
+
error.value = false
|
|
149
|
+
}, { immediate: true })
|
|
150
|
+
|
|
151
|
+
return { url, error }
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @deprecated Backward-compatible alias for ``useAssetUrl``. Public assets have
|
|
156
|
+
* no expiry, so the returned URL never needs refreshing; ``onError`` is a no-op
|
|
157
|
+
* retry hook kept for callers that expected the old grant-refresh contract.
|
|
158
|
+
*/
|
|
159
|
+
export function useAssetDelivery(source: MaybeRefOrGetter<AssetDeliveryRequest | undefined>) {
|
|
160
|
+
const { url, error } = useAssetUrl(source)
|
|
161
|
+
return { url, error, onError: () => { error.value = true } }
|
|
162
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { VirtualElement } from '@floating-ui/dom'
|
|
3
|
+
import type { AlignedPlacement, AnchorPoint, DropdownItem } from './Dropdown.types'
|
|
4
|
+
import { computed, ref, useSlots } from 'vue'
|
|
5
|
+
import Dropdown from './Dropdown.vue'
|
|
6
|
+
|
|
7
|
+
defineOptions({ inheritAttrs: false, name: 'BglContextMenu' })
|
|
8
|
+
|
|
9
|
+
const props = withDefaults(defineProps<{
|
|
10
|
+
/** Declarative menu items. */
|
|
11
|
+
items?: DropdownItem[]
|
|
12
|
+
/** Optional header title shown at the top of the context menu. */
|
|
13
|
+
title?: string
|
|
14
|
+
/** Explicit viewport anchor `{ x, y, width?, height? }` for controlled mode. */
|
|
15
|
+
anchorPoint?: AnchorPoint | null
|
|
16
|
+
/** External reference element or VirtualElement. */
|
|
17
|
+
referenceEl?: Element | VirtualElement | null
|
|
18
|
+
placement?: AlignedPlacement
|
|
19
|
+
popupClass?: string
|
|
20
|
+
autoHide?: boolean
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
}>(), {
|
|
23
|
+
placement: 'bottom-start',
|
|
24
|
+
autoHide: true,
|
|
25
|
+
disabled: false,
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
const emit = defineEmits<{
|
|
29
|
+
show: []
|
|
30
|
+
hide: []
|
|
31
|
+
select: [value: string | number | DropdownItem, item: DropdownItem]
|
|
32
|
+
}>()
|
|
33
|
+
|
|
34
|
+
const shown = defineModel('shown', { type: Boolean, default: false })
|
|
35
|
+
const slots = useSlots()
|
|
36
|
+
const dropdownRef = ref<InstanceType<typeof Dropdown> | null>(null)
|
|
37
|
+
const internalPoint = ref<AnchorPoint>({ x: 0, y: 0 })
|
|
38
|
+
|
|
39
|
+
const hasTargetSlot = computed(() => Boolean(slots.target))
|
|
40
|
+
const effectiveAnchorPoint = computed<AnchorPoint | null>(() => {
|
|
41
|
+
if (props.anchorPoint) return props.anchorPoint
|
|
42
|
+
if (props.referenceEl) return null
|
|
43
|
+
return internalPoint.value
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const effectiveItems = computed<DropdownItem[]>(() => {
|
|
47
|
+
if (!props.items?.length) return []
|
|
48
|
+
if (props.title) {
|
|
49
|
+
return [{ header: props.title }, ...props.items]
|
|
50
|
+
}
|
|
51
|
+
return props.items
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
function openAt(target: AnchorPoint | MouseEvent) {
|
|
55
|
+
if (props.disabled) return
|
|
56
|
+
if ('clientX' in target && 'clientY' in target) {
|
|
57
|
+
internalPoint.value = { x: target.clientX, y: target.clientY, width: 0, height: 0 }
|
|
58
|
+
} else {
|
|
59
|
+
internalPoint.value = { ...target }
|
|
60
|
+
}
|
|
61
|
+
dropdownRef.value?.openAt(internalPoint.value)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function onTargetContextMenu(e: MouseEvent) {
|
|
65
|
+
if (props.disabled) return
|
|
66
|
+
e.preventDefault()
|
|
67
|
+
openAt(e)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function show() {
|
|
71
|
+
dropdownRef.value?.show()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function hide() {
|
|
75
|
+
dropdownRef.value?.hide()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
defineExpose({ openAt, show, hide, shown })
|
|
79
|
+
</script>
|
|
80
|
+
|
|
81
|
+
<template>
|
|
82
|
+
<div
|
|
83
|
+
v-if="hasTargetSlot"
|
|
84
|
+
v-bind="$attrs"
|
|
85
|
+
style="display: contents"
|
|
86
|
+
@contextmenu="onTargetContextMenu"
|
|
87
|
+
>
|
|
88
|
+
<slot name="target" :open-at="openAt" :hide="hide" :shown="shown" />
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
<Dropdown
|
|
92
|
+
ref="dropdownRef"
|
|
93
|
+
v-model:shown="shown"
|
|
94
|
+
:anchor-point="effectiveAnchorPoint"
|
|
95
|
+
:reference-el="referenceEl"
|
|
96
|
+
:placement="placement"
|
|
97
|
+
:items="effectiveItems"
|
|
98
|
+
:popup-class="popupClass"
|
|
99
|
+
:auto-hide="autoHide"
|
|
100
|
+
:disabled="disabled"
|
|
101
|
+
:triggers="[]"
|
|
102
|
+
@show="emit('show')"
|
|
103
|
+
@hide="emit('hide')"
|
|
104
|
+
@select="(val, item) => emit('select', val, item)"
|
|
105
|
+
>
|
|
106
|
+
<template #default="slotProps">
|
|
107
|
+
<div v-if="title && !effectiveItems.length" class="px-05 py-025 txt11 semi opacity-5 uppercase">
|
|
108
|
+
{{ title }}
|
|
109
|
+
</div>
|
|
110
|
+
<slot v-bind="slotProps" />
|
|
111
|
+
</template>
|
|
112
|
+
</Dropdown>
|
|
113
|
+
</template>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IconType, ThemeType } from '../types'
|
|
2
|
+
|
|
3
|
+
export type Side = 'top' | 'right' | 'bottom' | 'left'
|
|
4
|
+
export type Alignment = 'start' | 'end'
|
|
5
|
+
export type AlignedPlacement = 'auto' | Side | `${Side}-${Alignment}`
|
|
6
|
+
export type TriggerEvent = 'click' | 'hover'
|
|
7
|
+
|
|
8
|
+
export interface AnchorPoint {
|
|
9
|
+
x: number
|
|
10
|
+
y: number
|
|
11
|
+
width?: number
|
|
12
|
+
height?: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface DropdownItem {
|
|
16
|
+
label?: string
|
|
17
|
+
value?: string | number
|
|
18
|
+
icon?: IconType
|
|
19
|
+
color?: ThemeType
|
|
20
|
+
danger?: boolean
|
|
21
|
+
disabled?: boolean
|
|
22
|
+
active?: boolean
|
|
23
|
+
divider?: boolean
|
|
24
|
+
header?: string
|
|
25
|
+
class?: string
|
|
26
|
+
action?: () => void
|
|
27
|
+
}
|