@anvilkit/plugin-asset-manager 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/adapters/data-url.cjs +78 -0
  4. package/dist/adapters/data-url.d.cts +6 -0
  5. package/dist/adapters/data-url.d.cts.map +1 -0
  6. package/dist/adapters/data-url.d.ts +6 -0
  7. package/dist/adapters/data-url.d.ts.map +1 -0
  8. package/dist/adapters/data-url.js +44 -0
  9. package/dist/adapters/extract-image-dimensions.cjs +69 -0
  10. package/dist/adapters/extract-image-dimensions.d.cts +21 -0
  11. package/dist/adapters/extract-image-dimensions.d.cts.map +1 -0
  12. package/dist/adapters/extract-image-dimensions.d.ts +21 -0
  13. package/dist/adapters/extract-image-dimensions.d.ts.map +1 -0
  14. package/dist/adapters/extract-image-dimensions.js +35 -0
  15. package/dist/adapters/in-memory.cjs +62 -0
  16. package/dist/adapters/in-memory.d.cts +3 -0
  17. package/dist/adapters/in-memory.d.cts.map +1 -0
  18. package/dist/adapters/in-memory.d.ts +3 -0
  19. package/dist/adapters/in-memory.d.ts.map +1 -0
  20. package/dist/adapters/in-memory.js +28 -0
  21. package/dist/adapters/s3-presigned.cjs +166 -0
  22. package/dist/adapters/s3-presigned.d.cts +59 -0
  23. package/dist/adapters/s3-presigned.d.cts.map +1 -0
  24. package/dist/adapters/s3-presigned.d.ts +59 -0
  25. package/dist/adapters/s3-presigned.d.ts.map +1 -0
  26. package/dist/adapters/s3-presigned.js +129 -0
  27. package/dist/asset-reference.cjs +38 -0
  28. package/dist/asset-reference.d.cts +10 -0
  29. package/dist/asset-reference.d.cts.map +1 -0
  30. package/dist/asset-reference.d.ts +10 -0
  31. package/dist/asset-reference.d.ts.map +1 -0
  32. package/dist/asset-reference.js +4 -0
  33. package/dist/csp.cjs +83 -0
  34. package/dist/csp.d.cts +45 -0
  35. package/dist/csp.d.cts.map +1 -0
  36. package/dist/csp.d.ts +45 -0
  37. package/dist/csp.d.ts.map +1 -0
  38. package/dist/csp.js +49 -0
  39. package/dist/errors.cjs +58 -0
  40. package/dist/errors.d.cts +15 -0
  41. package/dist/errors.d.cts.map +1 -0
  42. package/dist/errors.d.ts +15 -0
  43. package/dist/errors.d.ts.map +1 -0
  44. package/dist/errors.js +21 -0
  45. package/dist/header-action.cjs +44 -0
  46. package/dist/header-action.d.cts +3 -0
  47. package/dist/header-action.d.cts.map +1 -0
  48. package/dist/header-action.d.ts +3 -0
  49. package/dist/header-action.d.ts.map +1 -0
  50. package/dist/header-action.js +10 -0
  51. package/dist/index.cjs +90 -0
  52. package/dist/index.d.cts +18 -0
  53. package/dist/index.d.cts.map +1 -0
  54. package/dist/index.d.ts +18 -0
  55. package/dist/index.d.ts.map +1 -0
  56. package/dist/index.js +10 -0
  57. package/dist/infer-kind.cjs +45 -0
  58. package/dist/infer-kind.d.cts +8 -0
  59. package/dist/infer-kind.d.cts.map +1 -0
  60. package/dist/infer-kind.d.ts +8 -0
  61. package/dist/infer-kind.d.ts.map +1 -0
  62. package/dist/infer-kind.js +11 -0
  63. package/dist/plugin.cjs +258 -0
  64. package/dist/plugin.d.cts +9 -0
  65. package/dist/plugin.d.cts.map +1 -0
  66. package/dist/plugin.d.ts +9 -0
  67. package/dist/plugin.d.ts.map +1 -0
  68. package/dist/plugin.js +212 -0
  69. package/dist/registry.cjs +198 -0
  70. package/dist/registry.d.cts +3 -0
  71. package/dist/registry.d.cts.map +1 -0
  72. package/dist/registry.d.ts +3 -0
  73. package/dist/registry.d.ts.map +1 -0
  74. package/dist/registry.js +164 -0
  75. package/dist/resolver.cjs +185 -0
  76. package/dist/resolver.d.cts +10 -0
  77. package/dist/resolver.d.cts.map +1 -0
  78. package/dist/resolver.d.ts +10 -0
  79. package/dist/resolver.d.ts.map +1 -0
  80. package/dist/resolver.js +148 -0
  81. package/dist/retry.cjs +123 -0
  82. package/dist/retry.d.cts +71 -0
  83. package/dist/retry.d.cts.map +1 -0
  84. package/dist/retry.d.ts +71 -0
  85. package/dist/retry.d.ts.map +1 -0
  86. package/dist/retry.js +86 -0
  87. package/dist/studio-asset-source.cjs +211 -0
  88. package/dist/studio-asset-source.d.cts +52 -0
  89. package/dist/studio-asset-source.d.cts.map +1 -0
  90. package/dist/studio-asset-source.d.ts +52 -0
  91. package/dist/studio-asset-source.d.ts.map +1 -0
  92. package/dist/studio-asset-source.js +171 -0
  93. package/dist/testing/index.cjs +66 -0
  94. package/dist/testing/index.d.cts +24 -0
  95. package/dist/testing/index.d.cts.map +1 -0
  96. package/dist/testing/index.d.ts +24 -0
  97. package/dist/testing/index.d.ts.map +1 -0
  98. package/dist/testing/index.js +29 -0
  99. package/dist/types.cjs +18 -0
  100. package/dist/types.d.cts +132 -0
  101. package/dist/types.d.cts.map +1 -0
  102. package/dist/types.d.ts +132 -0
  103. package/dist/types.d.ts.map +1 -0
  104. package/dist/types.js +0 -0
  105. package/dist/ui/AssetBrowser.cjs +271 -0
  106. package/dist/ui/AssetBrowser.d.cts +45 -0
  107. package/dist/ui/AssetBrowser.d.cts.map +1 -0
  108. package/dist/ui/AssetBrowser.d.ts +45 -0
  109. package/dist/ui/AssetBrowser.d.ts.map +1 -0
  110. package/dist/ui/AssetBrowser.js +237 -0
  111. package/dist/ui/AssetCommandPalette.cjs +135 -0
  112. package/dist/ui/AssetCommandPalette.d.cts +21 -0
  113. package/dist/ui/AssetCommandPalette.d.cts.map +1 -0
  114. package/dist/ui/AssetCommandPalette.d.ts +21 -0
  115. package/dist/ui/AssetCommandPalette.d.ts.map +1 -0
  116. package/dist/ui/AssetCommandPalette.js +101 -0
  117. package/dist/ui/AssetManagerUI.cjs +169 -0
  118. package/dist/ui/AssetManagerUI.d.cts +15 -0
  119. package/dist/ui/AssetManagerUI.d.cts.map +1 -0
  120. package/dist/ui/AssetManagerUI.d.ts +15 -0
  121. package/dist/ui/AssetManagerUI.d.ts.map +1 -0
  122. package/dist/ui/AssetManagerUI.js +135 -0
  123. package/dist/ui/DeleteAssetDialog.cjs +70 -0
  124. package/dist/ui/DeleteAssetDialog.d.cts +22 -0
  125. package/dist/ui/DeleteAssetDialog.d.cts.map +1 -0
  126. package/dist/ui/DeleteAssetDialog.d.ts +22 -0
  127. package/dist/ui/DeleteAssetDialog.d.ts.map +1 -0
  128. package/dist/ui/DeleteAssetDialog.js +36 -0
  129. package/dist/ui/MetadataPanel.cjs +147 -0
  130. package/dist/ui/MetadataPanel.d.cts +21 -0
  131. package/dist/ui/MetadataPanel.d.cts.map +1 -0
  132. package/dist/ui/MetadataPanel.d.ts +21 -0
  133. package/dist/ui/MetadataPanel.d.ts.map +1 -0
  134. package/dist/ui/MetadataPanel.js +113 -0
  135. package/dist/ui/ReplaceAssetDialog.cjs +125 -0
  136. package/dist/ui/ReplaceAssetDialog.d.cts +14 -0
  137. package/dist/ui/ReplaceAssetDialog.d.cts.map +1 -0
  138. package/dist/ui/ReplaceAssetDialog.d.ts +14 -0
  139. package/dist/ui/ReplaceAssetDialog.d.ts.map +1 -0
  140. package/dist/ui/ReplaceAssetDialog.js +91 -0
  141. package/dist/ui/UploadButton.cjs +189 -0
  142. package/dist/ui/UploadButton.d.cts +17 -0
  143. package/dist/ui/UploadButton.d.cts.map +1 -0
  144. package/dist/ui/UploadButton.d.ts +17 -0
  145. package/dist/ui/UploadButton.d.ts.map +1 -0
  146. package/dist/ui/UploadButton.js +155 -0
  147. package/dist/ui/index.cjs +60 -0
  148. package/dist/ui/index.d.cts +15 -0
  149. package/dist/ui/index.d.cts.map +1 -0
  150. package/dist/ui/index.d.ts +15 -0
  151. package/dist/ui/index.d.ts.map +1 -0
  152. package/dist/ui/index.js +7 -0
  153. package/dist/validate-upload-result.cjs +149 -0
  154. package/dist/validate-upload-result.d.cts +9 -0
  155. package/dist/validate-upload-result.d.cts.map +1 -0
  156. package/dist/validate-upload-result.d.ts +9 -0
  157. package/dist/validate-upload-result.d.ts.map +1 -0
  158. package/dist/validate-upload-result.js +115 -0
  159. package/package.json +131 -0
@@ -0,0 +1,21 @@
1
+ import type { UploadResult } from "../types.js";
2
+ export interface MetadataPanelProps {
3
+ /**
4
+ * Asset whose metadata is being edited. When `null`, the panel is
5
+ * closed. Setting this opens it; the host clears it on cancel or
6
+ * after `onConfirm` resolves.
7
+ */
8
+ readonly asset: UploadResult | null;
9
+ /**
10
+ * Save handler. Receives the original asset plus the new name and
11
+ * tag set. May return a Promise to keep the Save button busy until
12
+ * the underlying registry mutation settles.
13
+ */
14
+ readonly onConfirm: (asset: UploadResult, next: {
15
+ readonly name: string;
16
+ readonly tags: readonly string[];
17
+ }) => void | Promise<void>;
18
+ readonly onCancel: () => void;
19
+ }
20
+ export declare function MetadataPanel({ asset, onCancel, onConfirm, }: MetadataPanelProps): import("react/jsx-runtime").JSX.Element;
21
+ //# sourceMappingURL=MetadataPanel.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetadataPanel.d.cts","sourceRoot":"","sources":["../../src/ui/MetadataPanel.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CACnB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,KAC7D,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,wBAAgB,aAAa,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,SAAS,GACT,EAAE,kBAAkB,2CAuIpB"}
@@ -0,0 +1,21 @@
1
+ import type { UploadResult } from "../types.js";
2
+ export interface MetadataPanelProps {
3
+ /**
4
+ * Asset whose metadata is being edited. When `null`, the panel is
5
+ * closed. Setting this opens it; the host clears it on cancel or
6
+ * after `onConfirm` resolves.
7
+ */
8
+ readonly asset: UploadResult | null;
9
+ /**
10
+ * Save handler. Receives the original asset plus the new name and
11
+ * tag set. May return a Promise to keep the Save button busy until
12
+ * the underlying registry mutation settles.
13
+ */
14
+ readonly onConfirm: (asset: UploadResult, next: {
15
+ readonly name: string;
16
+ readonly tags: readonly string[];
17
+ }) => void | Promise<void>;
18
+ readonly onCancel: () => void;
19
+ }
20
+ export declare function MetadataPanel({ asset, onCancel, onConfirm, }: MetadataPanelProps): import("react/jsx-runtime").JSX.Element;
21
+ //# sourceMappingURL=MetadataPanel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetadataPanel.d.ts","sourceRoot":"","sources":["../../src/ui/MetadataPanel.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,WAAW,kBAAkB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CACnB,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,KAC7D,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,wBAAgB,aAAa,CAAC,EAC7B,KAAK,EACL,QAAQ,EACR,SAAS,GACT,EAAE,kBAAkB,2CAuIpB"}
@@ -0,0 +1,113 @@
1
+ import { Button } from "@anvilkit/ui/button";
2
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@anvilkit/ui/dialog";
3
+ import { Input } from "@anvilkit/ui/input";
4
+ import * as __rspack_external_react from "react";
5
+ function MetadataPanel({ asset, onCancel, onConfirm }) {
6
+ const [name, setName] = __rspack_external_react.useState("");
7
+ const [tagInput, setTagInput] = __rspack_external_react.useState("");
8
+ const [tags, setTags] = __rspack_external_react.useState([]);
9
+ const [busy, setBusy] = __rspack_external_react.useState(false);
10
+ __rspack_external_react.useEffect(()=>{
11
+ if (null === asset) return;
12
+ setName(asset.name ?? "");
13
+ setTags(asset.tags ?? []);
14
+ setTagInput("");
15
+ setBusy(false);
16
+ }, [
17
+ asset
18
+ ]);
19
+ function commitTagInput() {
20
+ const next = tagInput.trim().toLowerCase();
21
+ if ("" === next) return tags;
22
+ if (tags.includes(next)) {
23
+ setTagInput("");
24
+ return tags;
25
+ }
26
+ const merged = [
27
+ ...tags,
28
+ next
29
+ ];
30
+ setTags(merged);
31
+ setTagInput("");
32
+ return merged;
33
+ }
34
+ function removeTag(value) {
35
+ setTags((current)=>current.filter((entry)=>entry !== value));
36
+ }
37
+ function handleTagKeyDown(event) {
38
+ if ("Enter" === event.key || "," === event.key) {
39
+ event.preventDefault();
40
+ commitTagInput();
41
+ }
42
+ }
43
+ async function handleConfirm() {
44
+ if (null === asset || busy) return;
45
+ const finalTags = "" === tagInput.trim() ? tags : commitTagInput();
46
+ setBusy(true);
47
+ try {
48
+ await onConfirm(asset, {
49
+ name: name.trim(),
50
+ tags: finalTags
51
+ });
52
+ } finally{
53
+ setBusy(false);
54
+ }
55
+ }
56
+ function handleOpenChange(nextOpen) {
57
+ if (!nextOpen && !busy) onCancel();
58
+ }
59
+ const open = null !== asset;
60
+ const mimeType = asset?.meta?.mimeType;
61
+ return /*#__PURE__*/ __rspack_external_react.createElement(Dialog, {
62
+ open: open,
63
+ onOpenChange: handleOpenChange
64
+ }, /*#__PURE__*/ __rspack_external_react.createElement(DialogContent, null, /*#__PURE__*/ __rspack_external_react.createElement(DialogHeader, null, /*#__PURE__*/ __rspack_external_react.createElement(DialogTitle, null, "Edit asset"), /*#__PURE__*/ __rspack_external_react.createElement(DialogDescription, null, asset?.id, mimeType ? ` (${mimeType})` : "")), /*#__PURE__*/ __rspack_external_react.createElement("div", {
65
+ "data-asset-manager-metadata": true
66
+ }, /*#__PURE__*/ __rspack_external_react.createElement("label", {
67
+ htmlFor: "asset-metadata-name"
68
+ }, "Name"), /*#__PURE__*/ __rspack_external_react.createElement(Input, {
69
+ id: "asset-metadata-name",
70
+ value: name,
71
+ onChange: (event)=>{
72
+ setName(event.target.value);
73
+ },
74
+ placeholder: asset?.id ?? "",
75
+ disabled: busy
76
+ }), /*#__PURE__*/ __rspack_external_react.createElement("div", {
77
+ "data-asset-manager-tag-editor": true
78
+ }, /*#__PURE__*/ __rspack_external_react.createElement("label", {
79
+ htmlFor: "asset-metadata-tag-input"
80
+ }, "Tags"), /*#__PURE__*/ __rspack_external_react.createElement("ul", {
81
+ "aria-label": "Current tags",
82
+ role: "list"
83
+ }, tags.map((tag)=>/*#__PURE__*/ __rspack_external_react.createElement("li", {
84
+ key: tag
85
+ }, /*#__PURE__*/ __rspack_external_react.createElement("span", null, tag), /*#__PURE__*/ __rspack_external_react.createElement("button", {
86
+ "aria-label": `Remove tag ${tag}`,
87
+ "data-asset-action": "remove-tag",
88
+ disabled: busy,
89
+ onClick: ()=>{
90
+ removeTag(tag);
91
+ },
92
+ type: "button"
93
+ }, "\xd7")))), /*#__PURE__*/ __rspack_external_react.createElement(Input, {
94
+ id: "asset-metadata-tag-input",
95
+ value: tagInput,
96
+ onChange: (event)=>{
97
+ setTagInput(event.target.value);
98
+ },
99
+ onKeyDown: handleTagKeyDown,
100
+ placeholder: "Add a tag and press Enter",
101
+ disabled: busy
102
+ }))), /*#__PURE__*/ __rspack_external_react.createElement(DialogFooter, null, /*#__PURE__*/ __rspack_external_react.createElement(Button, {
103
+ disabled: busy,
104
+ onClick: onCancel,
105
+ type: "button",
106
+ variant: "outline"
107
+ }, "Cancel"), /*#__PURE__*/ __rspack_external_react.createElement(Button, {
108
+ disabled: busy || null === asset,
109
+ onClick: handleConfirm,
110
+ type: "button"
111
+ }, busy ? "Saving…" : "Save"))));
112
+ }
113
+ export { MetadataPanel };
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ ReplaceAssetDialog: ()=>ReplaceAssetDialog
28
+ });
29
+ const button_namespaceObject = require("@anvilkit/ui/button");
30
+ const dialog_namespaceObject = require("@anvilkit/ui/dialog");
31
+ const external_react_namespaceObject = require("react");
32
+ const external_plugin_cjs_namespaceObject = require("../plugin.cjs");
33
+ function ReplaceAssetDialog({ acceptedMimeTypes, asset, maxFileSize, onCancel, onConfirm }) {
34
+ const inputRef = external_react_namespaceObject.useRef(null);
35
+ const [selectedFile, setSelectedFile] = external_react_namespaceObject.useState(null);
36
+ const [error, setError] = external_react_namespaceObject.useState(null);
37
+ const [busy, setBusy] = external_react_namespaceObject.useState(false);
38
+ const acceptAttr = external_react_namespaceObject.useMemo(()=>acceptedMimeTypes?.join(","), [
39
+ acceptedMimeTypes
40
+ ]);
41
+ external_react_namespaceObject.useEffect(()=>{
42
+ if (null === asset) {
43
+ setSelectedFile(null);
44
+ setError(null);
45
+ setBusy(false);
46
+ }
47
+ }, [
48
+ asset
49
+ ]);
50
+ function handleFileChange(event) {
51
+ const file = event.target.files?.[0];
52
+ event.target.value = "";
53
+ if (!file) return;
54
+ try {
55
+ (0, external_plugin_cjs_namespaceObject.validateSelectedFile)(file, {
56
+ acceptedMimeTypes,
57
+ maxFileSize
58
+ });
59
+ setError(null);
60
+ setSelectedFile(file);
61
+ } catch (err) {
62
+ setError(err instanceof Error ? err.message : String(err));
63
+ setSelectedFile(null);
64
+ }
65
+ }
66
+ async function handleConfirm() {
67
+ if (null === asset || null === selectedFile || busy) return;
68
+ setBusy(true);
69
+ try {
70
+ await onConfirm(asset, selectedFile);
71
+ } catch (err) {
72
+ setError(err instanceof Error ? err.message : String(err));
73
+ } finally{
74
+ setBusy(false);
75
+ }
76
+ }
77
+ function handleOpenChange(nextOpen) {
78
+ if (!nextOpen && !busy) onCancel();
79
+ }
80
+ const open = null !== asset;
81
+ const label = asset?.name ?? asset?.id ?? "";
82
+ return /*#__PURE__*/ external_react_namespaceObject.createElement(dialog_namespaceObject.Dialog, {
83
+ open: open,
84
+ onOpenChange: handleOpenChange
85
+ }, /*#__PURE__*/ external_react_namespaceObject.createElement(dialog_namespaceObject.DialogContent, null, /*#__PURE__*/ external_react_namespaceObject.createElement(dialog_namespaceObject.DialogHeader, null, /*#__PURE__*/ external_react_namespaceObject.createElement(dialog_namespaceObject.DialogTitle, null, "Replace asset?"), /*#__PURE__*/ external_react_namespaceObject.createElement(dialog_namespaceObject.DialogDescription, null, label ? `Pick a file to replace ${label} with. Existing asset:// references will resolve to the new bytes.` : "Pick a file to replace this asset with.")), /*#__PURE__*/ external_react_namespaceObject.createElement("input", {
86
+ "aria-label": "Replacement file",
87
+ "data-testid": "replace-asset-file-input",
88
+ hidden: true,
89
+ onChange: handleFileChange,
90
+ ref: inputRef,
91
+ type: "file",
92
+ ...acceptAttr ? {
93
+ accept: acceptAttr
94
+ } : {}
95
+ }), /*#__PURE__*/ external_react_namespaceObject.createElement("div", {
96
+ "data-asset-manager-replace-state": true
97
+ }, /*#__PURE__*/ external_react_namespaceObject.createElement(button_namespaceObject.Button, {
98
+ type: "button",
99
+ variant: "outline",
100
+ onClick: ()=>inputRef.current?.click(),
101
+ disabled: busy
102
+ }, selectedFile ? "Choose a different file" : "Choose file"), /*#__PURE__*/ external_react_namespaceObject.createElement("p", {
103
+ "aria-live": "polite",
104
+ role: "status"
105
+ }, selectedFile ? `Selected: ${selectedFile.name}` : "No file selected."), error ? /*#__PURE__*/ external_react_namespaceObject.createElement("p", {
106
+ "data-asset-manager-replace-error": true,
107
+ role: "alert"
108
+ }, error) : null), /*#__PURE__*/ external_react_namespaceObject.createElement(dialog_namespaceObject.DialogFooter, null, /*#__PURE__*/ external_react_namespaceObject.createElement(button_namespaceObject.Button, {
109
+ type: "button",
110
+ variant: "outline",
111
+ onClick: onCancel,
112
+ disabled: busy
113
+ }, "Cancel"), /*#__PURE__*/ external_react_namespaceObject.createElement(button_namespaceObject.Button, {
114
+ type: "button",
115
+ onClick: handleConfirm,
116
+ disabled: busy || null === asset || null === selectedFile
117
+ }, busy ? "Replacing…" : "Replace"))));
118
+ }
119
+ exports.ReplaceAssetDialog = __webpack_exports__.ReplaceAssetDialog;
120
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
121
+ "ReplaceAssetDialog"
122
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
123
+ Object.defineProperty(exports, '__esModule', {
124
+ value: true
125
+ });
@@ -0,0 +1,14 @@
1
+ import type { AssetManagerOptions, UploadResult } from "../types.js";
2
+ export interface ReplaceAssetDialogProps extends Pick<AssetManagerOptions, "acceptedMimeTypes" | "maxFileSize"> {
3
+ /** Asset to replace. `null` closes the dialog. */
4
+ readonly asset: UploadResult | null;
5
+ /**
6
+ * Receives the selected file. Typically calls
7
+ * `studioAssetSource.replace(asset.id, file)` or
8
+ * `registry.replace(asset.id, await uploader(file))`.
9
+ */
10
+ readonly onConfirm: (asset: UploadResult, file: File) => void | Promise<void>;
11
+ readonly onCancel: () => void;
12
+ }
13
+ export declare function ReplaceAssetDialog({ acceptedMimeTypes, asset, maxFileSize, onCancel, onConfirm, }: ReplaceAssetDialogProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=ReplaceAssetDialog.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReplaceAssetDialog.d.cts","sourceRoot":"","sources":["../../src/ui/ReplaceAssetDialog.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,WAAW,uBAChB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,aAAa,CAAC;IACtE,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,wBAAgB,kBAAkB,CAAC,EAClC,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,GACT,EAAE,uBAAuB,2CAuHzB"}
@@ -0,0 +1,14 @@
1
+ import type { AssetManagerOptions, UploadResult } from "../types.js";
2
+ export interface ReplaceAssetDialogProps extends Pick<AssetManagerOptions, "acceptedMimeTypes" | "maxFileSize"> {
3
+ /** Asset to replace. `null` closes the dialog. */
4
+ readonly asset: UploadResult | null;
5
+ /**
6
+ * Receives the selected file. Typically calls
7
+ * `studioAssetSource.replace(asset.id, file)` or
8
+ * `registry.replace(asset.id, await uploader(file))`.
9
+ */
10
+ readonly onConfirm: (asset: UploadResult, file: File) => void | Promise<void>;
11
+ readonly onCancel: () => void;
12
+ }
13
+ export declare function ReplaceAssetDialog({ acceptedMimeTypes, asset, maxFileSize, onCancel, onConfirm, }: ReplaceAssetDialogProps): import("react/jsx-runtime").JSX.Element;
14
+ //# sourceMappingURL=ReplaceAssetDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReplaceAssetDialog.d.ts","sourceRoot":"","sources":["../../src/ui/ReplaceAssetDialog.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,WAAW,uBAChB,SAAQ,IAAI,CAAC,mBAAmB,EAAE,mBAAmB,GAAG,aAAa,CAAC;IACtE,kDAAkD;IAClD,QAAQ,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;IACpC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;CAC9B;AAED,wBAAgB,kBAAkB,CAAC,EAClC,iBAAiB,EACjB,KAAK,EACL,WAAW,EACX,QAAQ,EACR,SAAS,GACT,EAAE,uBAAuB,2CAuHzB"}
@@ -0,0 +1,91 @@
1
+ import { Button } from "@anvilkit/ui/button";
2
+ import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@anvilkit/ui/dialog";
3
+ import { validateSelectedFile } from "../plugin.js";
4
+ import * as __rspack_external_react from "react";
5
+ function ReplaceAssetDialog({ acceptedMimeTypes, asset, maxFileSize, onCancel, onConfirm }) {
6
+ const inputRef = __rspack_external_react.useRef(null);
7
+ const [selectedFile, setSelectedFile] = __rspack_external_react.useState(null);
8
+ const [error, setError] = __rspack_external_react.useState(null);
9
+ const [busy, setBusy] = __rspack_external_react.useState(false);
10
+ const acceptAttr = __rspack_external_react.useMemo(()=>acceptedMimeTypes?.join(","), [
11
+ acceptedMimeTypes
12
+ ]);
13
+ __rspack_external_react.useEffect(()=>{
14
+ if (null === asset) {
15
+ setSelectedFile(null);
16
+ setError(null);
17
+ setBusy(false);
18
+ }
19
+ }, [
20
+ asset
21
+ ]);
22
+ function handleFileChange(event) {
23
+ const file = event.target.files?.[0];
24
+ event.target.value = "";
25
+ if (!file) return;
26
+ try {
27
+ validateSelectedFile(file, {
28
+ acceptedMimeTypes,
29
+ maxFileSize
30
+ });
31
+ setError(null);
32
+ setSelectedFile(file);
33
+ } catch (err) {
34
+ setError(err instanceof Error ? err.message : String(err));
35
+ setSelectedFile(null);
36
+ }
37
+ }
38
+ async function handleConfirm() {
39
+ if (null === asset || null === selectedFile || busy) return;
40
+ setBusy(true);
41
+ try {
42
+ await onConfirm(asset, selectedFile);
43
+ } catch (err) {
44
+ setError(err instanceof Error ? err.message : String(err));
45
+ } finally{
46
+ setBusy(false);
47
+ }
48
+ }
49
+ function handleOpenChange(nextOpen) {
50
+ if (!nextOpen && !busy) onCancel();
51
+ }
52
+ const open = null !== asset;
53
+ const label = asset?.name ?? asset?.id ?? "";
54
+ return /*#__PURE__*/ __rspack_external_react.createElement(Dialog, {
55
+ open: open,
56
+ onOpenChange: handleOpenChange
57
+ }, /*#__PURE__*/ __rspack_external_react.createElement(DialogContent, null, /*#__PURE__*/ __rspack_external_react.createElement(DialogHeader, null, /*#__PURE__*/ __rspack_external_react.createElement(DialogTitle, null, "Replace asset?"), /*#__PURE__*/ __rspack_external_react.createElement(DialogDescription, null, label ? `Pick a file to replace ${label} with. Existing asset:// references will resolve to the new bytes.` : "Pick a file to replace this asset with.")), /*#__PURE__*/ __rspack_external_react.createElement("input", {
58
+ "aria-label": "Replacement file",
59
+ "data-testid": "replace-asset-file-input",
60
+ hidden: true,
61
+ onChange: handleFileChange,
62
+ ref: inputRef,
63
+ type: "file",
64
+ ...acceptAttr ? {
65
+ accept: acceptAttr
66
+ } : {}
67
+ }), /*#__PURE__*/ __rspack_external_react.createElement("div", {
68
+ "data-asset-manager-replace-state": true
69
+ }, /*#__PURE__*/ __rspack_external_react.createElement(Button, {
70
+ type: "button",
71
+ variant: "outline",
72
+ onClick: ()=>inputRef.current?.click(),
73
+ disabled: busy
74
+ }, selectedFile ? "Choose a different file" : "Choose file"), /*#__PURE__*/ __rspack_external_react.createElement("p", {
75
+ "aria-live": "polite",
76
+ role: "status"
77
+ }, selectedFile ? `Selected: ${selectedFile.name}` : "No file selected."), error ? /*#__PURE__*/ __rspack_external_react.createElement("p", {
78
+ "data-asset-manager-replace-error": true,
79
+ role: "alert"
80
+ }, error) : null), /*#__PURE__*/ __rspack_external_react.createElement(DialogFooter, null, /*#__PURE__*/ __rspack_external_react.createElement(Button, {
81
+ type: "button",
82
+ variant: "outline",
83
+ onClick: onCancel,
84
+ disabled: busy
85
+ }, "Cancel"), /*#__PURE__*/ __rspack_external_react.createElement(Button, {
86
+ type: "button",
87
+ onClick: handleConfirm,
88
+ disabled: busy || null === asset || null === selectedFile
89
+ }, busy ? "Replacing…" : "Replace"))));
90
+ }
91
+ export { ReplaceAssetDialog };
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.d = (exports1, definition)=>{
5
+ for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
6
+ enumerable: true,
7
+ get: definition[key]
8
+ });
9
+ };
10
+ })();
11
+ (()=>{
12
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
13
+ })();
14
+ (()=>{
15
+ __webpack_require__.r = (exports1)=>{
16
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
17
+ value: 'Module'
18
+ });
19
+ Object.defineProperty(exports1, '__esModule', {
20
+ value: true
21
+ });
22
+ };
23
+ })();
24
+ var __webpack_exports__ = {};
25
+ __webpack_require__.r(__webpack_exports__);
26
+ __webpack_require__.d(__webpack_exports__, {
27
+ UploadButton: ()=>UploadButton
28
+ });
29
+ const button_namespaceObject = require("@anvilkit/ui/button");
30
+ const external_react_namespaceObject = require("react");
31
+ const external_plugin_cjs_namespaceObject = require("../plugin.cjs");
32
+ const external_validate_upload_result_cjs_namespaceObject = require("../validate-upload-result.cjs");
33
+ function UploadButton({ acceptedMimeTypes, allowMixedScriptHostnames, dataUrlAllowlistOptIn, maxFileSize, onError, onProgress, onUploaded, uploader }) {
34
+ const inputRef = external_react_namespaceObject.useRef(null);
35
+ const [errorMessage, setErrorMessage] = external_react_namespaceObject.useState(null);
36
+ const [batch, setBatch] = external_react_namespaceObject.useState(null);
37
+ const [isDragOver, setIsDragOver] = external_react_namespaceObject.useState(false);
38
+ const isUploading = null !== batch;
39
+ const acceptAttr = external_react_namespaceObject.useMemo(()=>acceptedMimeTypes?.join(","), [
40
+ acceptedMimeTypes
41
+ ]);
42
+ async function processFiles(files) {
43
+ if (0 === files.length) return;
44
+ const total = files.length;
45
+ setErrorMessage(null);
46
+ const initial = {
47
+ completed: 0,
48
+ total
49
+ };
50
+ setBatch(initial);
51
+ onProgress?.(initial);
52
+ let lastError = null;
53
+ for(let index = 0; index < files.length; index += 1){
54
+ const file = files[index];
55
+ if (file) try {
56
+ (0, external_plugin_cjs_namespaceObject.validateSelectedFile)(file, {
57
+ acceptedMimeTypes,
58
+ maxFileSize
59
+ });
60
+ const uploaded = await uploader(file);
61
+ const validated = (0, external_validate_upload_result_cjs_namespaceObject.validateUploadResult)({
62
+ ...uploaded,
63
+ meta: {
64
+ size: file.size,
65
+ ...file.type ? {
66
+ mimeType: file.type
67
+ } : {},
68
+ ...uploaded.meta ?? {}
69
+ }
70
+ }, {
71
+ dataUrlAllowlistOptIn,
72
+ allowMixedScriptHostnames
73
+ });
74
+ onUploaded?.(validated);
75
+ } catch (error) {
76
+ lastError = error instanceof Error ? error.message : String(error);
77
+ onError?.(error);
78
+ } finally{
79
+ const next = {
80
+ completed: index + 1,
81
+ total
82
+ };
83
+ setBatch(next);
84
+ onProgress?.(next);
85
+ }
86
+ }
87
+ if (null !== lastError) setErrorMessage(lastError);
88
+ setBatch(null);
89
+ onProgress?.(null);
90
+ }
91
+ async function handleChange(event) {
92
+ const list = event.currentTarget.files;
93
+ const picked = list ? Array.from(list) : [];
94
+ try {
95
+ await processFiles(picked);
96
+ } finally{
97
+ if (inputRef.current) inputRef.current.value = "";
98
+ }
99
+ }
100
+ function handleDragEnter(event) {
101
+ event.preventDefault();
102
+ event.stopPropagation();
103
+ if (event.dataTransfer?.types?.includes("Files")) setIsDragOver(true);
104
+ }
105
+ function handleDragOver(event) {
106
+ event.preventDefault();
107
+ event.stopPropagation();
108
+ if (event.dataTransfer) event.dataTransfer.dropEffect = "copy";
109
+ }
110
+ function handleDragLeave(event) {
111
+ event.preventDefault();
112
+ event.stopPropagation();
113
+ setIsDragOver(false);
114
+ }
115
+ async function handleDrop(event) {
116
+ event.preventDefault();
117
+ event.stopPropagation();
118
+ setIsDragOver(false);
119
+ const files = event.dataTransfer?.files ? Array.from(event.dataTransfer.files) : [];
120
+ await processFiles(files);
121
+ }
122
+ const statusMessage = errorMessage ?? (null !== batch ? `Uploading ${Math.min(batch.completed + 1, batch.total)} of ${batch.total}…` : "Accepted files upload through the configured adapter.");
123
+ return /*#__PURE__*/ external_react_namespaceObject.createElement("div", {
124
+ "data-asset-manager-drop-zone": true,
125
+ "data-drag-over": isDragOver ? "true" : void 0,
126
+ onDragEnter: handleDragEnter,
127
+ onDragLeave: handleDragLeave,
128
+ onDragOver: handleDragOver,
129
+ onDrop: (event)=>{
130
+ handleDrop(event);
131
+ },
132
+ tabIndex: -1
133
+ }, /*#__PURE__*/ external_react_namespaceObject.createElement("input", {
134
+ accept: acceptAttr,
135
+ multiple: true,
136
+ onChange: (event)=>{
137
+ handleChange(event);
138
+ },
139
+ ref: inputRef,
140
+ style: {
141
+ display: "none"
142
+ },
143
+ type: "file"
144
+ }), /*#__PURE__*/ external_react_namespaceObject.createElement(button_namespaceObject.Button, {
145
+ "aria-label": "Upload asset file",
146
+ disabled: isUploading,
147
+ onClick: ()=>{
148
+ inputRef.current?.click();
149
+ },
150
+ type: "button",
151
+ variant: "outline"
152
+ }, isUploading ? /*#__PURE__*/ external_react_namespaceObject.createElement(UploadSpinner, null) : null, /*#__PURE__*/ external_react_namespaceObject.createElement("span", null, isUploading ? "Uploading…" : "Upload asset")), /*#__PURE__*/ external_react_namespaceObject.createElement("p", {
153
+ "aria-live": "polite",
154
+ role: "status"
155
+ }, statusMessage));
156
+ }
157
+ function UploadSpinner() {
158
+ return /*#__PURE__*/ external_react_namespaceObject.createElement("svg", {
159
+ "aria-hidden": true,
160
+ fill: "none",
161
+ height: 14,
162
+ style: {
163
+ display: "inline-block",
164
+ marginRight: 6,
165
+ animation: "spin 1s linear infinite"
166
+ },
167
+ viewBox: "0 0 24 24",
168
+ width: 14
169
+ }, /*#__PURE__*/ external_react_namespaceObject.createElement("circle", {
170
+ cx: "12",
171
+ cy: "12",
172
+ opacity: "0.25",
173
+ r: "10",
174
+ stroke: "currentColor",
175
+ strokeWidth: "4"
176
+ }), /*#__PURE__*/ external_react_namespaceObject.createElement("path", {
177
+ d: "M4 12a8 8 0 0 1 8-8",
178
+ stroke: "currentColor",
179
+ strokeLinecap: "round",
180
+ strokeWidth: "4"
181
+ }));
182
+ }
183
+ exports.UploadButton = __webpack_exports__.UploadButton;
184
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
185
+ "UploadButton"
186
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
187
+ Object.defineProperty(exports, '__esModule', {
188
+ value: true
189
+ });
@@ -0,0 +1,17 @@
1
+ import type { AssetManagerOptions, UploadResult } from "../types.js";
2
+ export interface UploadProgressSnapshot {
3
+ readonly completed: number;
4
+ readonly total: number;
5
+ }
6
+ export interface UploadButtonProps extends Pick<AssetManagerOptions, "acceptedMimeTypes" | "maxFileSize" | "uploader" | "dataUrlAllowlistOptIn" | "allowMixedScriptHostnames"> {
7
+ readonly onUploaded?: (asset: UploadResult) => void;
8
+ readonly onError?: (error: unknown) => void;
9
+ /**
10
+ * Fires whenever the in-flight batch advances. Emitted as
11
+ * `{ completed, total }` while a batch is running and `null` once the
12
+ * batch settles. Used by `AssetManagerUI` to surface an aggregate bar.
13
+ */
14
+ readonly onProgress?: (snapshot: UploadProgressSnapshot | null) => void;
15
+ }
16
+ export declare function UploadButton({ acceptedMimeTypes, allowMixedScriptHostnames, dataUrlAllowlistOptIn, maxFileSize, onError, onProgress, onUploaded, uploader, }: UploadButtonProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=UploadButton.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UploadButton.d.cts","sourceRoot":"","sources":["../../src/ui/UploadButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGrE,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAChB,SAAQ,IAAI,CACX,mBAAmB,EACjB,mBAAmB,GACnB,aAAa,GACb,UAAU,GACV,uBAAuB,GACvB,2BAA2B,CAC7B;IACD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,KAAK,IAAI,CAAC;CACxE;AAED,wBAAgB,YAAY,CAAC,EAC5B,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,EACrB,WAAW,EACX,OAAO,EACP,UAAU,EACV,UAAU,EACV,QAAQ,GACR,EAAE,iBAAiB,2CAwJnB"}
@@ -0,0 +1,17 @@
1
+ import type { AssetManagerOptions, UploadResult } from "../types.js";
2
+ export interface UploadProgressSnapshot {
3
+ readonly completed: number;
4
+ readonly total: number;
5
+ }
6
+ export interface UploadButtonProps extends Pick<AssetManagerOptions, "acceptedMimeTypes" | "maxFileSize" | "uploader" | "dataUrlAllowlistOptIn" | "allowMixedScriptHostnames"> {
7
+ readonly onUploaded?: (asset: UploadResult) => void;
8
+ readonly onError?: (error: unknown) => void;
9
+ /**
10
+ * Fires whenever the in-flight batch advances. Emitted as
11
+ * `{ completed, total }` while a batch is running and `null` once the
12
+ * batch settles. Used by `AssetManagerUI` to surface an aggregate bar.
13
+ */
14
+ readonly onProgress?: (snapshot: UploadProgressSnapshot | null) => void;
15
+ }
16
+ export declare function UploadButton({ acceptedMimeTypes, allowMixedScriptHostnames, dataUrlAllowlistOptIn, maxFileSize, onError, onProgress, onUploaded, uploader, }: UploadButtonProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=UploadButton.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UploadButton.d.ts","sourceRoot":"","sources":["../../src/ui/UploadButton.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAGrE,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAChB,SAAQ,IAAI,CACX,mBAAmB,EACjB,mBAAmB,GACnB,aAAa,GACb,UAAU,GACV,uBAAuB,GACvB,2BAA2B,CAC7B;IACD,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC;IACpD,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC5C;;;;OAIG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,KAAK,IAAI,CAAC;CACxE;AAED,wBAAgB,YAAY,CAAC,EAC5B,iBAAiB,EACjB,yBAAyB,EACzB,qBAAqB,EACrB,WAAW,EACX,OAAO,EACP,UAAU,EACV,UAAU,EACV,QAAQ,GACR,EAAE,iBAAiB,2CAwJnB"}