@strapi/upload 5.47.1 → 5.48.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/admin/components/EditAssetDialog/EditAssetContent.js +12 -2
  2. package/dist/admin/components/EditAssetDialog/EditAssetContent.js.map +1 -1
  3. package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs +12 -2
  4. package/dist/admin/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
  5. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js +1 -0
  6. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.js.map +1 -1
  7. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs +1 -0
  8. package/dist/admin/components/UploadAssetDialog/UploadAssetDialog.mjs.map +1 -1
  9. package/dist/admin/future/components/Drawer.js +7 -8
  10. package/dist/admin/future/components/Drawer.js.map +1 -1
  11. package/dist/admin/future/components/Drawer.mjs +7 -8
  12. package/dist/admin/future/components/Drawer.mjs.map +1 -1
  13. package/dist/admin/future/components/UploadProgressDialog.js +33 -29
  14. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  15. package/dist/admin/future/components/UploadProgressDialog.mjs +36 -32
  16. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  17. package/dist/admin/future/pages/Assets/AssetsPage.js +2 -2
  18. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  19. package/dist/admin/future/pages/Assets/AssetsPage.mjs +3 -3
  20. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  21. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js +626 -169
  22. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.js.map +1 -1
  23. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs +630 -175
  24. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.mjs.map +1 -1
  25. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js +25 -5
  26. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.js.map +1 -1
  27. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs +25 -5
  28. package/dist/admin/future/pages/Assets/components/AssetDetails/AssetPreview.mjs.map +1 -1
  29. package/dist/admin/future/services/api.js +124 -200
  30. package/dist/admin/future/services/api.js.map +1 -1
  31. package/dist/admin/future/services/api.mjs +124 -200
  32. package/dist/admin/future/services/api.mjs.map +1 -1
  33. package/dist/admin/future/services/assets.js +57 -1
  34. package/dist/admin/future/services/assets.js.map +1 -1
  35. package/dist/admin/future/services/assets.mjs +56 -2
  36. package/dist/admin/future/services/assets.mjs.map +1 -1
  37. package/dist/admin/future/services/settings.js +18 -0
  38. package/dist/admin/future/services/settings.js.map +1 -0
  39. package/dist/admin/future/services/settings.mjs +16 -0
  40. package/dist/admin/future/services/settings.mjs.map +1 -0
  41. package/dist/admin/future/services/uploadFileViaXHR.js +92 -0
  42. package/dist/admin/future/services/uploadFileViaXHR.js.map +1 -0
  43. package/dist/admin/future/services/uploadFileViaXHR.mjs +88 -0
  44. package/dist/admin/future/services/uploadFileViaXHR.mjs.map +1 -0
  45. package/dist/admin/future/store/uploadProgress.js +32 -26
  46. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  47. package/dist/admin/future/store/uploadProgress.mjs +32 -27
  48. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  49. package/dist/admin/future/utils/createRafBatcher.js +42 -0
  50. package/dist/admin/future/utils/createRafBatcher.js.map +1 -0
  51. package/dist/admin/future/utils/createRafBatcher.mjs +40 -0
  52. package/dist/admin/future/utils/createRafBatcher.mjs.map +1 -0
  53. package/dist/admin/future/utils/downloadFile.js +19 -0
  54. package/dist/admin/future/utils/downloadFile.js.map +1 -0
  55. package/dist/admin/future/utils/downloadFile.mjs +17 -0
  56. package/dist/admin/future/utils/downloadFile.mjs.map +1 -0
  57. package/dist/admin/hooks/useAssets.js +5 -3
  58. package/dist/admin/hooks/useAssets.js.map +1 -1
  59. package/dist/admin/hooks/useAssets.mjs +5 -3
  60. package/dist/admin/hooks/useAssets.mjs.map +1 -1
  61. package/dist/admin/src/components/EditAssetDialog/EditAssetContent.d.ts +2 -1
  62. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetDetailsDrawer.d.ts +15 -1
  63. package/dist/admin/src/future/pages/Assets/components/AssetDetails/AssetPreview.d.ts +4 -1
  64. package/dist/admin/src/future/services/api.d.ts +9 -8
  65. package/dist/admin/src/future/services/assets.d.ts +6 -1
  66. package/dist/admin/src/future/services/uploadFileViaXHR.d.ts +34 -0
  67. package/dist/admin/src/future/store/uploadProgress.d.ts +17 -4
  68. package/dist/admin/src/future/utils/createRafBatcher.d.ts +23 -0
  69. package/dist/admin/src/future/utils/downloadFile.d.ts +6 -0
  70. package/dist/admin/translations/en.json.js +21 -0
  71. package/dist/admin/translations/en.json.js.map +1 -1
  72. package/dist/admin/translations/en.json.mjs +21 -0
  73. package/dist/admin/translations/en.json.mjs.map +1 -1
  74. package/dist/server/controllers/admin-upload.js +69 -118
  75. package/dist/server/controllers/admin-upload.js.map +1 -1
  76. package/dist/server/controllers/admin-upload.mjs +69 -118
  77. package/dist/server/controllers/admin-upload.mjs.map +1 -1
  78. package/dist/server/routes/admin.js +2 -2
  79. package/dist/server/routes/admin.js.map +1 -1
  80. package/dist/server/routes/admin.mjs +2 -2
  81. package/dist/server/routes/admin.mjs.map +1 -1
  82. package/dist/server/services/image-manipulation.js +16 -8
  83. package/dist/server/services/image-manipulation.js.map +1 -1
  84. package/dist/server/services/image-manipulation.mjs +16 -8
  85. package/dist/server/services/image-manipulation.mjs.map +1 -1
  86. package/dist/server/services/upload.js +1 -1
  87. package/dist/server/services/upload.js.map +1 -1
  88. package/dist/server/services/upload.mjs +1 -1
  89. package/dist/server/services/upload.mjs.map +1 -1
  90. package/dist/server/src/controllers/admin-upload.d.ts +6 -8
  91. package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
  92. package/dist/server/src/controllers/index.d.ts +1 -1
  93. package/dist/server/src/index.d.ts +1 -1
  94. package/dist/server/src/services/image-manipulation.d.ts +5 -0
  95. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  96. package/dist/server/src/services/upload.d.ts.map +1 -1
  97. package/dist/server/src/types.d.ts +2 -2
  98. package/dist/server/src/types.d.ts.map +1 -1
  99. package/dist/shared/contracts/files.d.ts +19 -2
  100. package/dist/shared/contracts/files.d.ts.map +1 -1
  101. package/package.json +7 -7
@@ -99,12 +99,68 @@ const assetsApi = api.uploadApi.injectEndpoints({
99
99
  id: 'LIST'
100
100
  }
101
101
  ]
102
+ }),
103
+ /**
104
+ * Replace the binary content of an existing asset.
105
+ * Hits `POST /upload?id=<id>` with a multipart body — the controller
106
+ * dispatches to `admin-upload.replaceFile` when a `files` part is present.
107
+ * Uses the standard axios baseQuery (no streaming) since we only ever
108
+ * replace one file at a time and don't need per-byte progress here.
109
+ */ replaceAsset: builder.mutation({
110
+ query: ({ id, file, fileInfo })=>{
111
+ const formData = new FormData();
112
+ formData.append('files', file);
113
+ if (fileInfo) {
114
+ formData.append('fileInfo', JSON.stringify(fileInfo));
115
+ }
116
+ return {
117
+ url: '/upload',
118
+ method: 'POST',
119
+ data: formData,
120
+ config: {
121
+ params: {
122
+ id
123
+ }
124
+ }
125
+ };
126
+ },
127
+ invalidatesTags: (_result, _error, { id })=>[
128
+ {
129
+ type: 'Asset',
130
+ id
131
+ },
132
+ {
133
+ type: 'Asset',
134
+ id: 'LIST'
135
+ }
136
+ ]
137
+ }),
138
+ /**
139
+ * Permanently delete an asset by id. Hits the same endpoint as the legacy
140
+ * `useRemoveAsset` hook so server behaviour is unchanged.
141
+ */ deleteAsset: builder.mutation({
142
+ query: (id)=>({
143
+ url: `/upload/files/${id}`,
144
+ method: 'DELETE'
145
+ }),
146
+ invalidatesTags: (_result, _error, id)=>[
147
+ {
148
+ type: 'Asset',
149
+ id
150
+ },
151
+ {
152
+ type: 'Asset',
153
+ id: 'LIST'
154
+ }
155
+ ]
102
156
  })
103
157
  })
104
158
  });
105
- const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation } = assetsApi;
159
+ const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation, useReplaceAssetMutation, useDeleteAssetMutation } = assetsApi;
106
160
 
161
+ exports.useDeleteAssetMutation = useDeleteAssetMutation;
107
162
  exports.useGetAssetQuery = useGetAssetQuery;
108
163
  exports.useGetAssetsQuery = useGetAssetsQuery;
164
+ exports.useReplaceAssetMutation = useReplaceAssetMutation;
109
165
  exports.useUpdateAssetMutation = useUpdateAssetMutation;
110
166
  //# sourceMappingURL=assets.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sources":["../../../../admin/src/future/services/assets.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n GetFiles,\n File,\n Pagination,\n UploadFileInfo,\n AssetWithPopulatedCreatedBy,\n} from '../../../../shared/contracts/files';\n\ninterface GetAssetsParams {\n page?: number;\n pageSize?: number;\n folder?: number | null;\n sort?: string;\n}\n\ninterface GetAssetsResponse {\n results: File[];\n pagination: Pagination;\n}\n\ninterface UpdateAssetArgs {\n id: number;\n fileInfo: Partial<UploadFileInfo>;\n}\n\nconst assetsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getAssets: builder.query<GetAssetsResponse, GetAssetsParams | void>({\n query: (params = {}) => {\n const { folder, ...rest } = params as GetAssetsParams;\n\n const queryParams: Record<string, unknown> = { ...rest };\n\n if (folder != null) {\n queryParams['filters'] = {\n $and: [{ folder: { id: folder } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ folder: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/files',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFiles.Response['data']) => response,\n providesTags: (result) =>\n result\n ? [\n ...result.results.map(({ id }) => ({ type: 'Asset' as const, id })),\n { type: 'Asset', id: 'LIST' },\n ]\n : [{ type: 'Asset', id: 'LIST' }],\n }),\n getAsset: builder.query<AssetWithPopulatedCreatedBy, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'GET',\n }),\n providesTags: (_result, _error, id) => [{ type: 'Asset' as const, id }],\n }),\n /**\n * Update the editable metadata of an existing asset.\n * Hits the legacy `POST /upload?id=<id>` endpoint which dispatches to\n * `admin-upload.updateFileInfo`.\n */\n updateAsset: builder.mutation<AssetWithPopulatedCreatedBy, UpdateAssetArgs>({\n query: ({ id, fileInfo }) => {\n const formData = new FormData();\n formData.append('fileInfo', JSON.stringify(fileInfo));\n\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n }),\n});\n\nexport const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation } = assetsApi;\n"],"names":["assetsApi","uploadApi","injectEndpoints","endpoints","builder","getAssets","query","params","folder","rest","queryParams","$and","id","$null","url","method","config","transformResponse","response","providesTags","result","results","map","type","getAsset","_result","_error","updateAsset","mutation","fileInfo","formData","FormData","append","JSON","stringify","data","invalidatesTags","useGetAssetsQuery","useGetAssetQuery","useUpdateAssetMutation"],"mappings":"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"}
1
+ {"version":3,"file":"assets.js","sources":["../../../../admin/src/future/services/assets.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n GetFiles,\n File,\n Pagination,\n UploadFileInfo,\n AssetWithPopulatedCreatedBy,\n} from '../../../../shared/contracts/files';\n\ninterface GetAssetsParams {\n page?: number;\n pageSize?: number;\n folder?: number | null;\n sort?: string;\n}\n\ninterface GetAssetsResponse {\n results: File[];\n pagination: Pagination;\n}\n\ninterface UpdateAssetArgs {\n id: number;\n fileInfo: Partial<UploadFileInfo>;\n}\n\ninterface ReplaceAssetArgs {\n id: number;\n // `File` is shadowed in this module by the asset-file contract type; the\n // global browser File is what we need for FormData.\n file: globalThis.File;\n fileInfo?: Partial<UploadFileInfo>;\n}\n\nconst assetsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getAssets: builder.query<GetAssetsResponse, GetAssetsParams | void>({\n query: (params = {}) => {\n const { folder, ...rest } = params as GetAssetsParams;\n\n const queryParams: Record<string, unknown> = { ...rest };\n\n if (folder != null) {\n queryParams['filters'] = {\n $and: [{ folder: { id: folder } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ folder: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/files',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFiles.Response['data']) => response,\n providesTags: (result) =>\n result\n ? [\n ...result.results.map(({ id }) => ({ type: 'Asset' as const, id })),\n { type: 'Asset', id: 'LIST' },\n ]\n : [{ type: 'Asset', id: 'LIST' }],\n }),\n getAsset: builder.query<AssetWithPopulatedCreatedBy, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'GET',\n }),\n providesTags: (_result, _error, id) => [{ type: 'Asset' as const, id }],\n }),\n /**\n * Update the editable metadata of an existing asset.\n * Hits the legacy `POST /upload?id=<id>` endpoint which dispatches to\n * `admin-upload.updateFileInfo`.\n */\n updateAsset: builder.mutation<AssetWithPopulatedCreatedBy, UpdateAssetArgs>({\n query: ({ id, fileInfo }) => {\n const formData = new FormData();\n formData.append('fileInfo', JSON.stringify(fileInfo));\n\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Replace the binary content of an existing asset.\n * Hits `POST /upload?id=<id>` with a multipart body — the controller\n * dispatches to `admin-upload.replaceFile` when a `files` part is present.\n * Uses the standard axios baseQuery (no streaming) since we only ever\n * replace one file at a time and don't need per-byte progress here.\n */\n replaceAsset: builder.mutation<AssetWithPopulatedCreatedBy, ReplaceAssetArgs>({\n query: ({ id, file, fileInfo }) => {\n const formData = new FormData();\n formData.append('files', file);\n if (fileInfo) {\n formData.append('fileInfo', JSON.stringify(fileInfo));\n }\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Permanently delete an asset by id. Hits the same endpoint as the legacy\n * `useRemoveAsset` hook so server behaviour is unchanged.\n */\n deleteAsset: builder.mutation<unknown, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'DELETE',\n }),\n invalidatesTags: (_result, _error, id) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n }),\n});\n\nexport const {\n useGetAssetsQuery,\n useGetAssetQuery,\n useUpdateAssetMutation,\n useReplaceAssetMutation,\n useDeleteAssetMutation,\n} = assetsApi;\n"],"names":["assetsApi","uploadApi","injectEndpoints","endpoints","builder","getAssets","query","params","folder","rest","queryParams","$and","id","$null","url","method","config","transformResponse","response","providesTags","result","results","map","type","getAsset","_result","_error","updateAsset","mutation","fileInfo","formData","FormData","append","JSON","stringify","data","invalidatesTags","replaceAsset","file","deleteAsset","useGetAssetsQuery","useGetAssetQuery","useUpdateAssetMutation","useReplaceAssetMutation","useDeleteAssetMutation"],"mappings":";;;;AAmCA,MAAMA,SAAAA,GAAYC,aAAAA,CAAUC,eAAe,CAAC;IAC1CC,SAAAA,EAAW,CAACC,WAAa;YACvBC,SAAAA,EAAWD,OAAAA,CAAQE,KAAK,CAA4C;gBAClEA,KAAAA,EAAO,CAACC,MAAAA,GAAS,EAAE,GAAA;AACjB,oBAAA,MAAM,EAAEC,MAAM,EAAE,GAAGC,MAAM,GAAGF,MAAAA;AAE5B,oBAAA,MAAMG,WAAAA,GAAuC;AAAE,wBAAA,GAAGD;AAAK,qBAAA;AAEvD,oBAAA,IAAID,UAAU,IAAA,EAAM;wBAClBE,WAAW,CAAC,UAAU,GAAG;4BACvBC,IAAAA,EAAM;AAAC,gCAAA;oCAAEH,MAAAA,EAAQ;wCAAEI,EAAAA,EAAIJ;AAAO;AAAE;AAAE;AACpC,yBAAA;oBACF,CAAA,MAAO;wBACLE,WAAW,CAAC,UAAU,GAAG;4BACvBC,IAAAA,EAAM;AAAC,gCAAA;oCAAEH,MAAAA,EAAQ;wCAAEI,EAAAA,EAAI;4CAAEC,KAAAA,EAAO;AAAK;AAAE;AAAE;AAAE;AAC7C,yBAAA;AACF,oBAAA;oBAEA,OAAO;wBACLC,GAAAA,EAAK,eAAA;wBACLC,MAAAA,EAAQ,KAAA;wBACRC,MAAAA,EAAQ;4BAAET,MAAAA,EAAQG;AAAY;AAChC,qBAAA;AACF,gBAAA,CAAA;AACAO,gBAAAA,iBAAAA,EAAmB,CAACC,QAAAA,GAAwCA,QAAAA;gBAC5DC,YAAAA,EAAc,CAACC,SACbA,MAAAA,GACI;2BACKA,MAAAA,CAAOC,OAAO,CAACC,GAAG,CAAC,CAAC,EAAEV,EAAE,EAAE,IAAM;gCAAEW,IAAAA,EAAM,OAAA;AAAkBX,gCAAAA;6BAAG,CAAA,CAAA;AAChE,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;4BAASX,EAAAA,EAAI;AAAO;qBAC7B,GACD;AAAC,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;4BAASX,EAAAA,EAAI;AAAO;AAAE;AACvC,aAAA,CAAA;YACAY,QAAAA,EAAUpB,OAAAA,CAAQE,KAAK,CAAsC;gBAC3DA,KAAAA,EAAO,CAACM,MAAQ;wBACdE,GAAAA,EAAK,CAAC,cAAc,EAAEF,EAAAA,CAAAA,CAAI;wBAC1BG,MAAAA,EAAQ;qBACV,CAAA;gBACAI,YAAAA,EAAc,CAACM,OAAAA,EAASC,MAAAA,EAAQd,EAAAA,GAAO;AAAC,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;AAAkBX,4BAAAA;AAAG;AAAE;AACzE,aAAA,CAAA;AACA;;;;QAKAe,WAAAA,EAAavB,OAAAA,CAAQwB,QAAQ,CAA+C;AAC1EtB,gBAAAA,KAAAA,EAAO,CAAC,EAAEM,EAAE,EAAEiB,QAAQ,EAAE,GAAA;AACtB,oBAAA,MAAMC,WAAW,IAAIC,QAAAA,EAAAA;AACrBD,oBAAAA,QAAAA,CAASE,MAAM,CAAC,UAAA,EAAYC,IAAAA,CAAKC,SAAS,CAACL,QAAAA,CAAAA,CAAAA;oBAE3C,OAAO;wBACLf,GAAAA,EAAK,SAAA;wBACLC,MAAAA,EAAQ,MAAA;wBACRoB,IAAAA,EAAML,QAAAA;wBACNd,MAAAA,EAAQ;4BAAET,MAAAA,EAAQ;AAAEK,gCAAAA;AAAG;AAAE;AAC3B,qBAAA;AACF,gBAAA,CAAA;AACAwB,gBAAAA,eAAAA,EAAiB,CAACX,OAAAA,EAASC,MAAAA,EAAQ,EAAEd,EAAE,EAAE,GAAK;AAC5C,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;AAAkBX,4BAAAA;AAAG,yBAAA;AAC7B,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;4BAAkBX,EAAAA,EAAI;AAAO;AACtC;AACH,aAAA,CAAA;AACA;;;;;;QAOAyB,YAAAA,EAAcjC,OAAAA,CAAQwB,QAAQ,CAAgD;AAC5EtB,gBAAAA,KAAAA,EAAO,CAAC,EAAEM,EAAE,EAAE0B,IAAI,EAAET,QAAQ,EAAE,GAAA;AAC5B,oBAAA,MAAMC,WAAW,IAAIC,QAAAA,EAAAA;oBACrBD,QAAAA,CAASE,MAAM,CAAC,OAAA,EAASM,IAAAA,CAAAA;AACzB,oBAAA,IAAIT,QAAAA,EAAU;AACZC,wBAAAA,QAAAA,CAASE,MAAM,CAAC,UAAA,EAAYC,IAAAA,CAAKC,SAAS,CAACL,QAAAA,CAAAA,CAAAA;AAC7C,oBAAA;oBACA,OAAO;wBACLf,GAAAA,EAAK,SAAA;wBACLC,MAAAA,EAAQ,MAAA;wBACRoB,IAAAA,EAAML,QAAAA;wBACNd,MAAAA,EAAQ;4BAAET,MAAAA,EAAQ;AAAEK,gCAAAA;AAAG;AAAE;AAC3B,qBAAA;AACF,gBAAA,CAAA;AACAwB,gBAAAA,eAAAA,EAAiB,CAACX,OAAAA,EAASC,MAAAA,EAAQ,EAAEd,EAAE,EAAE,GAAK;AAC5C,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;AAAkBX,4BAAAA;AAAG,yBAAA;AAC7B,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;4BAAkBX,EAAAA,EAAI;AAAO;AACtC;AACH,aAAA,CAAA;AACA;;;QAIA2B,WAAAA,EAAanC,OAAAA,CAAQwB,QAAQ,CAAkB;gBAC7CtB,KAAAA,EAAO,CAACM,MAAQ;wBACdE,GAAAA,EAAK,CAAC,cAAc,EAAEF,EAAAA,CAAAA,CAAI;wBAC1BG,MAAAA,EAAQ;qBACV,CAAA;gBACAqB,eAAAA,EAAiB,CAACX,OAAAA,EAASC,MAAAA,EAAQd,EAAAA,GAAO;AACxC,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;AAAkBX,4BAAAA;AAAG,yBAAA;AAC7B,wBAAA;4BAAEW,IAAAA,EAAM,OAAA;4BAAkBX,EAAAA,EAAI;AAAO;AACtC;AACH,aAAA;SACF;AACF,CAAA,CAAA;AAEO,MAAM,EACX4B,iBAAiB,EACjBC,gBAAgB,EAChBC,sBAAsB,EACtBC,uBAAuB,EACvBC,sBAAsB,EACvB,GAAG5C;;;;;;;;"}
@@ -97,10 +97,64 @@ const assetsApi = uploadApi.injectEndpoints({
97
97
  id: 'LIST'
98
98
  }
99
99
  ]
100
+ }),
101
+ /**
102
+ * Replace the binary content of an existing asset.
103
+ * Hits `POST /upload?id=<id>` with a multipart body — the controller
104
+ * dispatches to `admin-upload.replaceFile` when a `files` part is present.
105
+ * Uses the standard axios baseQuery (no streaming) since we only ever
106
+ * replace one file at a time and don't need per-byte progress here.
107
+ */ replaceAsset: builder.mutation({
108
+ query: ({ id, file, fileInfo })=>{
109
+ const formData = new FormData();
110
+ formData.append('files', file);
111
+ if (fileInfo) {
112
+ formData.append('fileInfo', JSON.stringify(fileInfo));
113
+ }
114
+ return {
115
+ url: '/upload',
116
+ method: 'POST',
117
+ data: formData,
118
+ config: {
119
+ params: {
120
+ id
121
+ }
122
+ }
123
+ };
124
+ },
125
+ invalidatesTags: (_result, _error, { id })=>[
126
+ {
127
+ type: 'Asset',
128
+ id
129
+ },
130
+ {
131
+ type: 'Asset',
132
+ id: 'LIST'
133
+ }
134
+ ]
135
+ }),
136
+ /**
137
+ * Permanently delete an asset by id. Hits the same endpoint as the legacy
138
+ * `useRemoveAsset` hook so server behaviour is unchanged.
139
+ */ deleteAsset: builder.mutation({
140
+ query: (id)=>({
141
+ url: `/upload/files/${id}`,
142
+ method: 'DELETE'
143
+ }),
144
+ invalidatesTags: (_result, _error, id)=>[
145
+ {
146
+ type: 'Asset',
147
+ id
148
+ },
149
+ {
150
+ type: 'Asset',
151
+ id: 'LIST'
152
+ }
153
+ ]
100
154
  })
101
155
  })
102
156
  });
103
- const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation } = assetsApi;
157
+ const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation, useReplaceAssetMutation, useDeleteAssetMutation } = assetsApi;
104
158
 
105
- export { useGetAssetQuery, useGetAssetsQuery, useUpdateAssetMutation };
159
+ export { useDeleteAssetMutation, useGetAssetQuery, useGetAssetsQuery, useReplaceAssetMutation, useUpdateAssetMutation };
106
160
  //# sourceMappingURL=assets.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"assets.mjs","sources":["../../../../admin/src/future/services/assets.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n GetFiles,\n File,\n Pagination,\n UploadFileInfo,\n AssetWithPopulatedCreatedBy,\n} from '../../../../shared/contracts/files';\n\ninterface GetAssetsParams {\n page?: number;\n pageSize?: number;\n folder?: number | null;\n sort?: string;\n}\n\ninterface GetAssetsResponse {\n results: File[];\n pagination: Pagination;\n}\n\ninterface UpdateAssetArgs {\n id: number;\n fileInfo: Partial<UploadFileInfo>;\n}\n\nconst assetsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getAssets: builder.query<GetAssetsResponse, GetAssetsParams | void>({\n query: (params = {}) => {\n const { folder, ...rest } = params as GetAssetsParams;\n\n const queryParams: Record<string, unknown> = { ...rest };\n\n if (folder != null) {\n queryParams['filters'] = {\n $and: [{ folder: { id: folder } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ folder: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/files',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFiles.Response['data']) => response,\n providesTags: (result) =>\n result\n ? [\n ...result.results.map(({ id }) => ({ type: 'Asset' as const, id })),\n { type: 'Asset', id: 'LIST' },\n ]\n : [{ type: 'Asset', id: 'LIST' }],\n }),\n getAsset: builder.query<AssetWithPopulatedCreatedBy, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'GET',\n }),\n providesTags: (_result, _error, id) => [{ type: 'Asset' as const, id }],\n }),\n /**\n * Update the editable metadata of an existing asset.\n * Hits the legacy `POST /upload?id=<id>` endpoint which dispatches to\n * `admin-upload.updateFileInfo`.\n */\n updateAsset: builder.mutation<AssetWithPopulatedCreatedBy, UpdateAssetArgs>({\n query: ({ id, fileInfo }) => {\n const formData = new FormData();\n formData.append('fileInfo', JSON.stringify(fileInfo));\n\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n }),\n});\n\nexport const { useGetAssetsQuery, useGetAssetQuery, useUpdateAssetMutation } = assetsApi;\n"],"names":["assetsApi","uploadApi","injectEndpoints","endpoints","builder","getAssets","query","params","folder","rest","queryParams","$and","id","$null","url","method","config","transformResponse","response","providesTags","result","results","map","type","getAsset","_result","_error","updateAsset","mutation","fileInfo","formData","FormData","append","JSON","stringify","data","invalidatesTags","useGetAssetsQuery","useGetAssetQuery","useUpdateAssetMutation"],"mappings":"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"}
1
+ {"version":3,"file":"assets.mjs","sources":["../../../../admin/src/future/services/assets.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type {\n GetFiles,\n File,\n Pagination,\n UploadFileInfo,\n AssetWithPopulatedCreatedBy,\n} from '../../../../shared/contracts/files';\n\ninterface GetAssetsParams {\n page?: number;\n pageSize?: number;\n folder?: number | null;\n sort?: string;\n}\n\ninterface GetAssetsResponse {\n results: File[];\n pagination: Pagination;\n}\n\ninterface UpdateAssetArgs {\n id: number;\n fileInfo: Partial<UploadFileInfo>;\n}\n\ninterface ReplaceAssetArgs {\n id: number;\n // `File` is shadowed in this module by the asset-file contract type; the\n // global browser File is what we need for FormData.\n file: globalThis.File;\n fileInfo?: Partial<UploadFileInfo>;\n}\n\nconst assetsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getAssets: builder.query<GetAssetsResponse, GetAssetsParams | void>({\n query: (params = {}) => {\n const { folder, ...rest } = params as GetAssetsParams;\n\n const queryParams: Record<string, unknown> = { ...rest };\n\n if (folder != null) {\n queryParams['filters'] = {\n $and: [{ folder: { id: folder } }],\n };\n } else {\n queryParams['filters'] = {\n $and: [{ folder: { id: { $null: true } } }],\n };\n }\n\n return {\n url: '/upload/files',\n method: 'GET',\n config: { params: queryParams },\n };\n },\n transformResponse: (response: GetFiles.Response['data']) => response,\n providesTags: (result) =>\n result\n ? [\n ...result.results.map(({ id }) => ({ type: 'Asset' as const, id })),\n { type: 'Asset', id: 'LIST' },\n ]\n : [{ type: 'Asset', id: 'LIST' }],\n }),\n getAsset: builder.query<AssetWithPopulatedCreatedBy, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'GET',\n }),\n providesTags: (_result, _error, id) => [{ type: 'Asset' as const, id }],\n }),\n /**\n * Update the editable metadata of an existing asset.\n * Hits the legacy `POST /upload?id=<id>` endpoint which dispatches to\n * `admin-upload.updateFileInfo`.\n */\n updateAsset: builder.mutation<AssetWithPopulatedCreatedBy, UpdateAssetArgs>({\n query: ({ id, fileInfo }) => {\n const formData = new FormData();\n formData.append('fileInfo', JSON.stringify(fileInfo));\n\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Replace the binary content of an existing asset.\n * Hits `POST /upload?id=<id>` with a multipart body — the controller\n * dispatches to `admin-upload.replaceFile` when a `files` part is present.\n * Uses the standard axios baseQuery (no streaming) since we only ever\n * replace one file at a time and don't need per-byte progress here.\n */\n replaceAsset: builder.mutation<AssetWithPopulatedCreatedBy, ReplaceAssetArgs>({\n query: ({ id, file, fileInfo }) => {\n const formData = new FormData();\n formData.append('files', file);\n if (fileInfo) {\n formData.append('fileInfo', JSON.stringify(fileInfo));\n }\n return {\n url: '/upload',\n method: 'POST',\n data: formData,\n config: { params: { id } },\n };\n },\n invalidatesTags: (_result, _error, { id }) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n /**\n * Permanently delete an asset by id. Hits the same endpoint as the legacy\n * `useRemoveAsset` hook so server behaviour is unchanged.\n */\n deleteAsset: builder.mutation<unknown, number>({\n query: (id) => ({\n url: `/upload/files/${id}`,\n method: 'DELETE',\n }),\n invalidatesTags: (_result, _error, id) => [\n { type: 'Asset' as const, id },\n { type: 'Asset' as const, id: 'LIST' },\n ],\n }),\n }),\n});\n\nexport const {\n useGetAssetsQuery,\n useGetAssetQuery,\n useUpdateAssetMutation,\n useReplaceAssetMutation,\n useDeleteAssetMutation,\n} = assetsApi;\n"],"names":["assetsApi","uploadApi","injectEndpoints","endpoints","builder","getAssets","query","params","folder","rest","queryParams","$and","id","$null","url","method","config","transformResponse","response","providesTags","result","results","map","type","getAsset","_result","_error","updateAsset","mutation","fileInfo","formData","FormData","append","JSON","stringify","data","invalidatesTags","replaceAsset","file","deleteAsset","useGetAssetsQuery","useGetAssetQuery","useUpdateAssetMutation","useReplaceAssetMutation","useDeleteAssetMutation"],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ var api = require('./api.js');
4
+
5
+ const settingsApi = api.uploadApi.injectEndpoints({
6
+ endpoints: (builder)=>({
7
+ getSettings: builder.query({
8
+ query: ()=>({
9
+ url: '/upload/settings',
10
+ method: 'GET'
11
+ })
12
+ })
13
+ })
14
+ });
15
+ const { useGetSettingsQuery } = settingsApi;
16
+
17
+ exports.useGetSettingsQuery = useGetSettingsQuery;
18
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sources":["../../../../admin/src/future/services/settings.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type { GetSettings } from '../../../../shared/contracts/settings';\n\nconst settingsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getSettings: builder.query<GetSettings.Response['data'], void>({\n query: () => ({\n url: '/upload/settings',\n method: 'GET',\n }),\n }),\n }),\n});\n\nconst { useGetSettingsQuery } = settingsApi;\n\nexport { useGetSettingsQuery };\n"],"names":["settingsApi","uploadApi","injectEndpoints","endpoints","builder","getSettings","query","url","method","useGetSettingsQuery"],"mappings":";;;;AAIA,MAAMA,WAAAA,GAAcC,aAAAA,CAAUC,eAAe,CAAC;IAC5CC,SAAAA,EAAW,CAACC,WAAa;YACvBC,WAAAA,EAAaD,OAAAA,CAAQE,KAAK,CAAqC;AAC7DA,gBAAAA,KAAAA,EAAO,KAAO;wBACZC,GAAAA,EAAK,kBAAA;wBACLC,MAAAA,EAAQ;qBACV;AACF,aAAA;SACF;AACF,CAAA,CAAA;AAEA,MAAM,EAAEC,mBAAmB,EAAE,GAAGT;;;;"}
@@ -0,0 +1,16 @@
1
+ import { uploadApi } from './api.mjs';
2
+
3
+ const settingsApi = uploadApi.injectEndpoints({
4
+ endpoints: (builder)=>({
5
+ getSettings: builder.query({
6
+ query: ()=>({
7
+ url: '/upload/settings',
8
+ method: 'GET'
9
+ })
10
+ })
11
+ })
12
+ });
13
+ const { useGetSettingsQuery } = settingsApi;
14
+
15
+ export { useGetSettingsQuery };
16
+ //# sourceMappingURL=settings.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.mjs","sources":["../../../../admin/src/future/services/settings.ts"],"sourcesContent":["import { uploadApi } from './api';\n\nimport type { GetSettings } from '../../../../shared/contracts/settings';\n\nconst settingsApi = uploadApi.injectEndpoints({\n endpoints: (builder) => ({\n getSettings: builder.query<GetSettings.Response['data'], void>({\n query: () => ({\n url: '/upload/settings',\n method: 'GET',\n }),\n }),\n }),\n});\n\nconst { useGetSettingsQuery } = settingsApi;\n\nexport { useGetSettingsQuery };\n"],"names":["settingsApi","uploadApi","injectEndpoints","endpoints","builder","getSettings","query","url","method","useGetSettingsQuery"],"mappings":";;AAIA,MAAMA,WAAAA,GAAcC,SAAAA,CAAUC,eAAe,CAAC;IAC5CC,SAAAA,EAAW,CAACC,WAAa;YACvBC,WAAAA,EAAaD,OAAAA,CAAQE,KAAK,CAAqC;AAC7DA,gBAAAA,KAAAA,EAAO,KAAO;wBACZC,GAAAA,EAAK,kBAAA;wBACLC,MAAAA,EAAQ;qBACV;AACF,aAAA;SACF;AACF,CAAA,CAAA;AAEA,MAAM,EAAEC,mBAAmB,EAAE,GAAGT;;;;"}
@@ -0,0 +1,92 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Thrown when an upload is aborted via its `AbortSignal`.
5
+ * Distinct from {@link UploadFileError} so callers can tell cancellation apart
6
+ * from a genuine failure.
7
+ */ class UploadAbortedError extends Error {
8
+ constructor(message = 'Upload aborted'){
9
+ super(message);
10
+ this.name = 'UploadAbortedError';
11
+ }
12
+ }
13
+ /**
14
+ * Thrown when an upload fails (non-2xx response, network error, or unparseable body).
15
+ */ class UploadFileError extends Error {
16
+ constructor(message, status){
17
+ super(message);
18
+ this.name = 'UploadFileError';
19
+ this.status = status;
20
+ }
21
+ }
22
+ /**
23
+ * Uploads a single file via `XMLHttpRequest`, exposing real byte-level upload
24
+ * progress through {@link XMLHttpRequest.upload}'s `progress` event.
25
+ *
26
+ * This is the only place raw XHR lives. `fetch()` is intentionally avoided here
27
+ * because it does not surface upload progress.
28
+ *
29
+ * @param url - The full endpoint URL to POST to.
30
+ * @param token - Admin auth token; the `Authorization` header is only set when present.
31
+ * @param formData - Prebuilt multipart body containing the single file and its `fileInfo`.
32
+ * @param signal - Aborts the in-flight request when triggered.
33
+ * @param onProgress - Called with `(loaded, total)` bytes as the upload progresses.
34
+ * @returns The parsed, signed `File` on a 2xx response.
35
+ * @throws {UploadAbortedError} When the signal aborts.
36
+ * @throws {UploadFileError} On a non-2xx response or network error.
37
+ */ const uploadFileViaXHR = (url, token, formData, signal, onProgress)=>{
38
+ return new Promise((resolve, reject)=>{
39
+ if (signal.aborted) {
40
+ reject(new UploadAbortedError());
41
+ return;
42
+ }
43
+ const xhr = new XMLHttpRequest();
44
+ xhr.open('POST', url);
45
+ if (token) {
46
+ xhr.setRequestHeader('Authorization', `Bearer ${token}`);
47
+ }
48
+ const handleAbort = ()=>xhr.abort();
49
+ signal.addEventListener('abort', handleAbort);
50
+ const cleanup = ()=>signal.removeEventListener('abort', handleAbort);
51
+ if (onProgress) {
52
+ xhr.upload.onprogress = (event)=>{
53
+ if (event.lengthComputable) {
54
+ onProgress(event.loaded, event.total);
55
+ }
56
+ };
57
+ }
58
+ xhr.onload = ()=>{
59
+ cleanup();
60
+ if (xhr.status >= 200 && xhr.status < 300) {
61
+ try {
62
+ resolve(JSON.parse(xhr.responseText));
63
+ } catch {
64
+ reject(new UploadFileError('Failed to parse upload response'));
65
+ }
66
+ return;
67
+ }
68
+ let message = `Upload failed with status ${xhr.status}`;
69
+ try {
70
+ const parsed = JSON.parse(xhr.responseText);
71
+ message = parsed?.error?.message || parsed?.message || message;
72
+ } catch {
73
+ // Keep the default status-based message.
74
+ }
75
+ reject(new UploadFileError(message, xhr.status));
76
+ };
77
+ xhr.onerror = ()=>{
78
+ cleanup();
79
+ reject(new UploadFileError('Network error occurred'));
80
+ };
81
+ xhr.onabort = ()=>{
82
+ cleanup();
83
+ reject(new UploadAbortedError());
84
+ };
85
+ xhr.send(formData);
86
+ });
87
+ };
88
+
89
+ exports.UploadAbortedError = UploadAbortedError;
90
+ exports.UploadFileError = UploadFileError;
91
+ exports.uploadFileViaXHR = uploadFileViaXHR;
92
+ //# sourceMappingURL=uploadFileViaXHR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadFileViaXHR.js","sources":["../../../../admin/src/future/services/uploadFileViaXHR.ts"],"sourcesContent":["import type { File } from '../../../../shared/contracts/files';\n\n/**\n * Thrown when an upload is aborted via its `AbortSignal`.\n * Distinct from {@link UploadFileError} so callers can tell cancellation apart\n * from a genuine failure.\n */\nexport class UploadAbortedError extends Error {\n constructor(message = 'Upload aborted') {\n super(message);\n this.name = 'UploadAbortedError';\n }\n}\n\n/**\n * Thrown when an upload fails (non-2xx response, network error, or unparseable body).\n */\nexport class UploadFileError extends Error {\n status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.name = 'UploadFileError';\n this.status = status;\n }\n}\n\nexport type UploadProgressCallback = (bytes: number, total: number) => void;\n\n/**\n * Uploads a single file via `XMLHttpRequest`, exposing real byte-level upload\n * progress through {@link XMLHttpRequest.upload}'s `progress` event.\n *\n * This is the only place raw XHR lives. `fetch()` is intentionally avoided here\n * because it does not surface upload progress.\n *\n * @param url - The full endpoint URL to POST to.\n * @param token - Admin auth token; the `Authorization` header is only set when present.\n * @param formData - Prebuilt multipart body containing the single file and its `fileInfo`.\n * @param signal - Aborts the in-flight request when triggered.\n * @param onProgress - Called with `(loaded, total)` bytes as the upload progresses.\n * @returns The parsed, signed `File` on a 2xx response.\n * @throws {UploadAbortedError} When the signal aborts.\n * @throws {UploadFileError} On a non-2xx response or network error.\n */\nexport const uploadFileViaXHR = (\n url: string,\n token: string | null | undefined,\n formData: FormData,\n signal: AbortSignal,\n onProgress?: UploadProgressCallback\n): Promise<File> => {\n return new Promise<File>((resolve, reject) => {\n if (signal.aborted) {\n reject(new UploadAbortedError());\n return;\n }\n\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n\n if (token) {\n xhr.setRequestHeader('Authorization', `Bearer ${token}`);\n }\n\n const handleAbort = () => xhr.abort();\n signal.addEventListener('abort', handleAbort);\n\n const cleanup = () => signal.removeEventListener('abort', handleAbort);\n\n if (onProgress) {\n xhr.upload.onprogress = (event: ProgressEvent) => {\n if (event.lengthComputable) {\n onProgress(event.loaded, event.total);\n }\n };\n }\n\n xhr.onload = () => {\n cleanup();\n\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n resolve(JSON.parse(xhr.responseText) as File);\n } catch {\n reject(new UploadFileError('Failed to parse upload response'));\n }\n return;\n }\n\n let message = `Upload failed with status ${xhr.status}`;\n try {\n const parsed = JSON.parse(xhr.responseText);\n message = parsed?.error?.message || parsed?.message || message;\n } catch {\n // Keep the default status-based message.\n }\n reject(new UploadFileError(message, xhr.status));\n };\n\n xhr.onerror = () => {\n cleanup();\n reject(new UploadFileError('Network error occurred'));\n };\n\n xhr.onabort = () => {\n cleanup();\n reject(new UploadAbortedError());\n };\n\n xhr.send(formData);\n });\n};\n"],"names":["UploadAbortedError","Error","message","name","UploadFileError","status","uploadFileViaXHR","url","token","formData","signal","onProgress","Promise","resolve","reject","aborted","xhr","XMLHttpRequest","open","setRequestHeader","handleAbort","abort","addEventListener","cleanup","removeEventListener","upload","onprogress","event","lengthComputable","loaded","total","onload","JSON","parse","responseText","parsed","error","onerror","onabort","send"],"mappings":"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"}
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Thrown when an upload is aborted via its `AbortSignal`.
3
+ * Distinct from {@link UploadFileError} so callers can tell cancellation apart
4
+ * from a genuine failure.
5
+ */ class UploadAbortedError extends Error {
6
+ constructor(message = 'Upload aborted'){
7
+ super(message);
8
+ this.name = 'UploadAbortedError';
9
+ }
10
+ }
11
+ /**
12
+ * Thrown when an upload fails (non-2xx response, network error, or unparseable body).
13
+ */ class UploadFileError extends Error {
14
+ constructor(message, status){
15
+ super(message);
16
+ this.name = 'UploadFileError';
17
+ this.status = status;
18
+ }
19
+ }
20
+ /**
21
+ * Uploads a single file via `XMLHttpRequest`, exposing real byte-level upload
22
+ * progress through {@link XMLHttpRequest.upload}'s `progress` event.
23
+ *
24
+ * This is the only place raw XHR lives. `fetch()` is intentionally avoided here
25
+ * because it does not surface upload progress.
26
+ *
27
+ * @param url - The full endpoint URL to POST to.
28
+ * @param token - Admin auth token; the `Authorization` header is only set when present.
29
+ * @param formData - Prebuilt multipart body containing the single file and its `fileInfo`.
30
+ * @param signal - Aborts the in-flight request when triggered.
31
+ * @param onProgress - Called with `(loaded, total)` bytes as the upload progresses.
32
+ * @returns The parsed, signed `File` on a 2xx response.
33
+ * @throws {UploadAbortedError} When the signal aborts.
34
+ * @throws {UploadFileError} On a non-2xx response or network error.
35
+ */ const uploadFileViaXHR = (url, token, formData, signal, onProgress)=>{
36
+ return new Promise((resolve, reject)=>{
37
+ if (signal.aborted) {
38
+ reject(new UploadAbortedError());
39
+ return;
40
+ }
41
+ const xhr = new XMLHttpRequest();
42
+ xhr.open('POST', url);
43
+ if (token) {
44
+ xhr.setRequestHeader('Authorization', `Bearer ${token}`);
45
+ }
46
+ const handleAbort = ()=>xhr.abort();
47
+ signal.addEventListener('abort', handleAbort);
48
+ const cleanup = ()=>signal.removeEventListener('abort', handleAbort);
49
+ if (onProgress) {
50
+ xhr.upload.onprogress = (event)=>{
51
+ if (event.lengthComputable) {
52
+ onProgress(event.loaded, event.total);
53
+ }
54
+ };
55
+ }
56
+ xhr.onload = ()=>{
57
+ cleanup();
58
+ if (xhr.status >= 200 && xhr.status < 300) {
59
+ try {
60
+ resolve(JSON.parse(xhr.responseText));
61
+ } catch {
62
+ reject(new UploadFileError('Failed to parse upload response'));
63
+ }
64
+ return;
65
+ }
66
+ let message = `Upload failed with status ${xhr.status}`;
67
+ try {
68
+ const parsed = JSON.parse(xhr.responseText);
69
+ message = parsed?.error?.message || parsed?.message || message;
70
+ } catch {
71
+ // Keep the default status-based message.
72
+ }
73
+ reject(new UploadFileError(message, xhr.status));
74
+ };
75
+ xhr.onerror = ()=>{
76
+ cleanup();
77
+ reject(new UploadFileError('Network error occurred'));
78
+ };
79
+ xhr.onabort = ()=>{
80
+ cleanup();
81
+ reject(new UploadAbortedError());
82
+ };
83
+ xhr.send(formData);
84
+ });
85
+ };
86
+
87
+ export { UploadAbortedError, UploadFileError, uploadFileViaXHR };
88
+ //# sourceMappingURL=uploadFileViaXHR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uploadFileViaXHR.mjs","sources":["../../../../admin/src/future/services/uploadFileViaXHR.ts"],"sourcesContent":["import type { File } from '../../../../shared/contracts/files';\n\n/**\n * Thrown when an upload is aborted via its `AbortSignal`.\n * Distinct from {@link UploadFileError} so callers can tell cancellation apart\n * from a genuine failure.\n */\nexport class UploadAbortedError extends Error {\n constructor(message = 'Upload aborted') {\n super(message);\n this.name = 'UploadAbortedError';\n }\n}\n\n/**\n * Thrown when an upload fails (non-2xx response, network error, or unparseable body).\n */\nexport class UploadFileError extends Error {\n status?: number;\n\n constructor(message: string, status?: number) {\n super(message);\n this.name = 'UploadFileError';\n this.status = status;\n }\n}\n\nexport type UploadProgressCallback = (bytes: number, total: number) => void;\n\n/**\n * Uploads a single file via `XMLHttpRequest`, exposing real byte-level upload\n * progress through {@link XMLHttpRequest.upload}'s `progress` event.\n *\n * This is the only place raw XHR lives. `fetch()` is intentionally avoided here\n * because it does not surface upload progress.\n *\n * @param url - The full endpoint URL to POST to.\n * @param token - Admin auth token; the `Authorization` header is only set when present.\n * @param formData - Prebuilt multipart body containing the single file and its `fileInfo`.\n * @param signal - Aborts the in-flight request when triggered.\n * @param onProgress - Called with `(loaded, total)` bytes as the upload progresses.\n * @returns The parsed, signed `File` on a 2xx response.\n * @throws {UploadAbortedError} When the signal aborts.\n * @throws {UploadFileError} On a non-2xx response or network error.\n */\nexport const uploadFileViaXHR = (\n url: string,\n token: string | null | undefined,\n formData: FormData,\n signal: AbortSignal,\n onProgress?: UploadProgressCallback\n): Promise<File> => {\n return new Promise<File>((resolve, reject) => {\n if (signal.aborted) {\n reject(new UploadAbortedError());\n return;\n }\n\n const xhr = new XMLHttpRequest();\n xhr.open('POST', url);\n\n if (token) {\n xhr.setRequestHeader('Authorization', `Bearer ${token}`);\n }\n\n const handleAbort = () => xhr.abort();\n signal.addEventListener('abort', handleAbort);\n\n const cleanup = () => signal.removeEventListener('abort', handleAbort);\n\n if (onProgress) {\n xhr.upload.onprogress = (event: ProgressEvent) => {\n if (event.lengthComputable) {\n onProgress(event.loaded, event.total);\n }\n };\n }\n\n xhr.onload = () => {\n cleanup();\n\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n resolve(JSON.parse(xhr.responseText) as File);\n } catch {\n reject(new UploadFileError('Failed to parse upload response'));\n }\n return;\n }\n\n let message = `Upload failed with status ${xhr.status}`;\n try {\n const parsed = JSON.parse(xhr.responseText);\n message = parsed?.error?.message || parsed?.message || message;\n } catch {\n // Keep the default status-based message.\n }\n reject(new UploadFileError(message, xhr.status));\n };\n\n xhr.onerror = () => {\n cleanup();\n reject(new UploadFileError('Network error occurred'));\n };\n\n xhr.onabort = () => {\n cleanup();\n reject(new UploadAbortedError());\n };\n\n xhr.send(formData);\n });\n};\n"],"names":["UploadAbortedError","Error","message","name","UploadFileError","status","uploadFileViaXHR","url","token","formData","signal","onProgress","Promise","resolve","reject","aborted","xhr","XMLHttpRequest","open","setRequestHeader","handleAbort","abort","addEventListener","cleanup","removeEventListener","upload","onprogress","event","lengthComputable","loaded","total","onload","JSON","parse","responseText","parsed","error","onerror","onabort","send"],"mappings":"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"}
@@ -5,23 +5,11 @@ var toolkit = require('@reduxjs/toolkit');
5
5
  const initialState = {
6
6
  isVisible: false,
7
7
  isMinimized: false,
8
- progress: 0,
9
8
  totalFiles: 0,
10
9
  files: [],
11
10
  errors: [],
12
11
  uploadId: 0
13
12
  };
14
- const computeProgress = (files)=>{
15
- if (files.length === 0) return 0;
16
- const totalSize = files.reduce((sum, f)=>sum + f.size, 0);
17
- if (totalSize === 0) {
18
- // Fallback to count-based if sizes are unknown
19
- const completed = files.filter((f)=>f.status === 'complete' || f.status === 'error' || f.status === 'cancelled').length;
20
- return Math.round(completed / files.length * 100);
21
- }
22
- const completedSize = files.filter((f)=>f.status === 'complete' || f.status === 'error' || f.status === 'cancelled').reduce((sum, f)=>sum + f.size, 0);
23
- return Math.round(completedSize / totalSize * 100);
24
- };
25
13
  const uploadProgressSlice = toolkit.createSlice({
26
14
  name: 'uploadProgress',
27
15
  initialState,
@@ -29,13 +17,13 @@ const uploadProgressSlice = toolkit.createSlice({
29
17
  openUploadProgress (state, action) {
30
18
  state.isVisible = true;
31
19
  state.isMinimized = false;
32
- state.progress = 0;
33
20
  // Create pending files for upload
34
21
  const pendingFiles = action.payload.fileNames.map((name, index)=>({
35
22
  name,
36
23
  index,
37
24
  status: 'pending',
38
- size: action.payload.fileSizes?.[index] ?? 0
25
+ size: action.payload.fileSizes?.[index] ?? 0,
26
+ uploadedBytes: 0
39
27
  }));
40
28
  state.files = pendingFiles;
41
29
  state.totalFiles = action.payload.totalFiles;
@@ -49,13 +37,22 @@ const uploadProgressSlice = toolkit.createSlice({
49
37
  state.files[index].size = size;
50
38
  }
51
39
  },
40
+ setFileProgress (state, action) {
41
+ const { index, bytes } = action.payload;
42
+ const file = state.files[index];
43
+ if (file) {
44
+ // Clamp to the known file size so the aggregate can never exceed 100%.
45
+ file.uploadedBytes = Math.min(bytes, file.size);
46
+ }
47
+ },
52
48
  setFileComplete (state, action) {
53
49
  const { index, file } = action.payload;
54
50
  if (state.files[index]) {
55
51
  state.files[index].status = 'complete';
56
52
  state.files[index].file = file;
53
+ // Reflect completion in the aggregate even if the final progress event was throttled.
54
+ state.files[index].uploadedBytes = state.files[index].size;
57
55
  }
58
- state.progress = computeProgress(state.files);
59
56
  },
60
57
  setFileError (state, action) {
61
58
  const { index, name, message } = action.payload;
@@ -70,10 +67,6 @@ const uploadProgressSlice = toolkit.createSlice({
70
67
  message
71
68
  }
72
69
  ];
73
- state.progress = computeProgress(state.files);
74
- },
75
- updateProgress (state, action) {
76
- state.progress = action.payload;
77
70
  },
78
71
  addUploadErrors (state, action) {
79
72
  state.errors = [
@@ -84,7 +77,6 @@ const uploadProgressSlice = toolkit.createSlice({
84
77
  closeUploadProgress (state) {
85
78
  state.isVisible = false;
86
79
  state.isMinimized = false;
87
- state.progress = 0;
88
80
  state.totalFiles = 0;
89
81
  state.files = [];
90
82
  state.errors = [];
@@ -103,7 +95,6 @@ const uploadProgressSlice = toolkit.createSlice({
103
95
  }
104
96
  return file;
105
97
  });
106
- state.progress = computeProgress(state.files);
107
98
  },
108
99
  setUploadFailed (state, action) {
109
100
  // Mark all pending and uploading files as errored when a catastrophic failure occurs
@@ -117,7 +108,6 @@ const uploadProgressSlice = toolkit.createSlice({
117
108
  }
118
109
  return file;
119
110
  });
120
- state.progress = 100;
121
111
  state.errors = [
122
112
  ...state.errors,
123
113
  {
@@ -132,16 +122,31 @@ const uploadProgressSlice = toolkit.createSlice({
132
122
  if (file.status === 'cancelled') {
133
123
  return {
134
124
  ...file,
135
- status: 'pending'
125
+ status: 'pending',
126
+ uploadedBytes: 0
136
127
  };
137
128
  }
138
129
  return file;
139
130
  });
140
- state.progress = computeProgress(state.files);
141
131
  }
142
132
  }
143
133
  });
144
- const { openUploadProgress, setFileUploading, setFileComplete, setFileError, updateProgress, addUploadErrors, closeUploadProgress, toggleMinimize, cancelUpload, setUploadFailed, retryCancelledFiles } = uploadProgressSlice.actions;
134
+ /**
135
+ * Byte-weighted aggregate progress across the whole batch: `sum(uploadedBytes) / sum(size)`.
136
+ *
137
+ * Falls back to count-based progress (settled files / total files) when all sizes are
138
+ * zero — e.g. URL-flow rows where the file size is unknown up front.
139
+ */ const selectAggregateProgress = toolkit.createSelector((state)=>state.uploadProgress.files, (files)=>{
140
+ if (files.length === 0) return 0;
141
+ const totalSize = files.reduce((sum, f)=>sum + f.size, 0);
142
+ if (totalSize === 0) {
143
+ const settled = files.filter((f)=>f.status === 'complete' || f.status === 'error' || f.status === 'cancelled').length;
144
+ return Math.round(settled / files.length * 100);
145
+ }
146
+ const uploadedBytes = files.reduce((sum, f)=>sum + f.uploadedBytes, 0);
147
+ return Math.round(uploadedBytes / totalSize * 100);
148
+ });
149
+ const { openUploadProgress, setFileUploading, setFileProgress, setFileComplete, setFileError, addUploadErrors, closeUploadProgress, toggleMinimize, cancelUpload, setUploadFailed, retryCancelledFiles } = uploadProgressSlice.actions;
145
150
  const uploadProgressReducer = uploadProgressSlice.reducer;
146
151
 
147
152
  exports.addUploadErrors = addUploadErrors;
@@ -149,11 +154,12 @@ exports.cancelUpload = cancelUpload;
149
154
  exports.closeUploadProgress = closeUploadProgress;
150
155
  exports.openUploadProgress = openUploadProgress;
151
156
  exports.retryCancelledFiles = retryCancelledFiles;
157
+ exports.selectAggregateProgress = selectAggregateProgress;
152
158
  exports.setFileComplete = setFileComplete;
153
159
  exports.setFileError = setFileError;
160
+ exports.setFileProgress = setFileProgress;
154
161
  exports.setFileUploading = setFileUploading;
155
162
  exports.setUploadFailed = setUploadFailed;
156
163
  exports.toggleMinimize = toggleMinimize;
157
- exports.updateProgress = updateProgress;
158
164
  exports.uploadProgressReducer = uploadProgressReducer;
159
165
  //# sourceMappingURL=uploadProgress.js.map