@alpaca-editor/sharedien-dam 1.0.4143
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/eslint.config.mjs +4 -0
- package/package.json +35 -0
- package/src/DamSelector.tsx +524 -0
- package/src/DamSelectorButton.tsx +53 -0
- package/src/index.ts +36 -0
- package/src/types.ts +15 -0
- package/styles.css +1 -0
- package/tsconfig.json +9 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@alpaca-editor/sharedien-dam",
|
|
3
|
+
"version": "1.0.4143",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts",
|
|
10
|
+
"./styles.css": "./styles.css"
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"build": "tsc -p tsconfig.build.json",
|
|
14
|
+
"lint": "eslint . --max-warnings 0",
|
|
15
|
+
"generate:component": "turbo gen react-component",
|
|
16
|
+
"check-types": "tsc --noEmit"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@repo/eslint-config": "*",
|
|
20
|
+
"@repo/typescript-config": "*",
|
|
21
|
+
"@turbo/gen": "^2.4.4",
|
|
22
|
+
"@types/node": "^22.13.9",
|
|
23
|
+
"@types/react": "19.0.10",
|
|
24
|
+
"@types/react-dom": "19.0.4",
|
|
25
|
+
"eslint": "^9.22.0",
|
|
26
|
+
"typescript": "5.8.2"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@tailwindcss/postcss": "^4.0.14",
|
|
30
|
+
"postcss": "^8.5.3",
|
|
31
|
+
"react": "^19.0.0",
|
|
32
|
+
"react-dom": "^19.0.0",
|
|
33
|
+
"tailwindcss": "^4.0.14"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,524 @@
|
|
|
1
|
+
import { Button, DialogProps, Input, Select, TabView, TabPanel, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, useEditContext } from '@alpaca-editor/core';
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { DamImageValue, DamSelectorProps, SharedienDamExtension } from './types';
|
|
4
|
+
import React from 'react';
|
|
5
|
+
import { ExternalLink, Search, Check, X } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
// Update interfaces for the API response
|
|
8
|
+
interface Derivation {
|
|
9
|
+
AssetTypeIdentifier: string;
|
|
10
|
+
FileTypeIdentifier: string;
|
|
11
|
+
FileMimeType: string;
|
|
12
|
+
DerivationTypeIdentifier: string;
|
|
13
|
+
FileName: string;
|
|
14
|
+
FileEnding: string;
|
|
15
|
+
FileSize: number;
|
|
16
|
+
Uri: string;
|
|
17
|
+
DownloadUri: string;
|
|
18
|
+
Status: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface DerivationItem {
|
|
22
|
+
Derivation: Derivation;
|
|
23
|
+
IsPreferred: boolean;
|
|
24
|
+
IsPreview: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface AssetProperty {
|
|
28
|
+
FieldName: string;
|
|
29
|
+
Value: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
interface AssetInfo {
|
|
33
|
+
MeetsRestrictions: boolean;
|
|
34
|
+
Message: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface AssetWarning {
|
|
38
|
+
ShouldWarn: boolean;
|
|
39
|
+
Message: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface AssetResponse {
|
|
43
|
+
Derivations: DerivationItem[];
|
|
44
|
+
PreviewDerivationSrc: string;
|
|
45
|
+
AssetInfo: AssetInfo;
|
|
46
|
+
AssetWarning: AssetWarning;
|
|
47
|
+
AssetProperties: AssetProperty[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
interface Response {
|
|
51
|
+
Success: boolean
|
|
52
|
+
Message: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add this interface to declare the global sharedienAssetBrowser object
|
|
56
|
+
declare global {
|
|
57
|
+
interface Window {
|
|
58
|
+
sharedienAssetBrowser?: {
|
|
59
|
+
show: (url: string, config: any, callback: (content: any, isCancelled: boolean) => void, selector: string) => void;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Utility function to format file sizes
|
|
65
|
+
function formatFileSize(bytes: number): string {
|
|
66
|
+
if (bytes === 0) return '0 Bytes';
|
|
67
|
+
|
|
68
|
+
const k = 1024;
|
|
69
|
+
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
|
70
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
71
|
+
|
|
72
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function DamSelector({
|
|
76
|
+
onClose,
|
|
77
|
+
language = 'en',
|
|
78
|
+
}: DialogProps<DamImageValue> & DamSelectorProps) {
|
|
79
|
+
const [assetId, setAssetId] = useState('');
|
|
80
|
+
const [selectedDerivative, setSelectedDerivative] = useState<Derivation | null>(null);
|
|
81
|
+
const [assetData, setAssetData] = useState<AssetResponse | null>(null);
|
|
82
|
+
const [loading, setLoading] = useState(false);
|
|
83
|
+
const [error, setError] = useState<string | null>(null);
|
|
84
|
+
const [activeIndex, setActiveIndex] = useState(0);
|
|
85
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
86
|
+
const [fromWidgetSelection, setFromWidgetSelection] = useState(false);
|
|
87
|
+
|
|
88
|
+
const editContext = useEditContext();
|
|
89
|
+
const sharedienDamConfig = editContext?.configuration?.extensions?.sharedienDam as SharedienDamExtension;
|
|
90
|
+
const damUrl = sharedienDamConfig?.damUrl ?? '';
|
|
91
|
+
const damScriptUrl = sharedienDamConfig?.damScriptUrl ?? '';
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const timer = setTimeout(() => {
|
|
95
|
+
setIsVisible(true);
|
|
96
|
+
}, 200);
|
|
97
|
+
|
|
98
|
+
return () => clearTimeout(timer);
|
|
99
|
+
}, []);
|
|
100
|
+
|
|
101
|
+
// Load the DAM widget script
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
// Load the external script for the DAM widget
|
|
104
|
+
const script = document.createElement('script');
|
|
105
|
+
script.src = damScriptUrl;
|
|
106
|
+
script.async = true;
|
|
107
|
+
document.body.appendChild(script);
|
|
108
|
+
|
|
109
|
+
return () => {
|
|
110
|
+
// Clean up script when component unmounts
|
|
111
|
+
if (document.body.contains(script)) {
|
|
112
|
+
document.body.removeChild(script);
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}, [damScriptUrl]);
|
|
116
|
+
|
|
117
|
+
// Initialize asset browser when switching to the second tab
|
|
118
|
+
useEffect(() => {
|
|
119
|
+
if (activeIndex === 1) {
|
|
120
|
+
initiateAssetBrowser();
|
|
121
|
+
}
|
|
122
|
+
}, [activeIndex]);
|
|
123
|
+
|
|
124
|
+
// Automatically search when assetId changes from widget selection
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
if (assetId && assetId.trim() && fromWidgetSelection) {
|
|
127
|
+
searchAsset();
|
|
128
|
+
setFromWidgetSelection(false); // Reset the flag
|
|
129
|
+
}
|
|
130
|
+
}, [assetId, fromWidgetSelection]);
|
|
131
|
+
|
|
132
|
+
// Function to initialize the asset browser (converted from your C# code)
|
|
133
|
+
const initiateAssetBrowser = () => {
|
|
134
|
+
if (!window.sharedienAssetBrowser) {
|
|
135
|
+
console.error('DAM Asset Browser widget not loaded');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const configuration = {
|
|
140
|
+
allowMultipleSelection: false,
|
|
141
|
+
canBeClosedByUser: false,
|
|
142
|
+
showMainNavigationBranding: false,
|
|
143
|
+
searchQuery: {
|
|
144
|
+
// Add your filters here if needed
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const callback = (content: any, isCancelled: boolean) => {
|
|
149
|
+
if (isCancelled) {
|
|
150
|
+
console.log('Component has been cancelled');
|
|
151
|
+
} else {
|
|
152
|
+
console.log('Message received', content['selectedAssets'][0]);
|
|
153
|
+
// Set the asset ID and switch back to the first tab
|
|
154
|
+
if (content['selectedAssets'] && content['selectedAssets'][0]) {
|
|
155
|
+
setFromWidgetSelection(true); // Mark that this selection is from widget
|
|
156
|
+
setAssetId(content['selectedAssets'][0].identifier);
|
|
157
|
+
setActiveIndex(0); // Switch back to first tab
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
window.sharedienAssetBrowser.show(
|
|
163
|
+
damUrl,
|
|
164
|
+
configuration,
|
|
165
|
+
callback,
|
|
166
|
+
'#damBrowserContainer'
|
|
167
|
+
);
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
// Function to search for an asset
|
|
171
|
+
async function searchAsset(): Promise<void> {
|
|
172
|
+
if (!assetId.trim()) {
|
|
173
|
+
setError('Please enter an Asset ID');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
setLoading(true);
|
|
178
|
+
setError(null);
|
|
179
|
+
|
|
180
|
+
try {
|
|
181
|
+
// Call the API with assetId and language using POST
|
|
182
|
+
const response = await fetch('/alpaca/editor/sharedien', {
|
|
183
|
+
method: 'POST',
|
|
184
|
+
headers: {
|
|
185
|
+
'Content-Type': 'application/json',
|
|
186
|
+
},
|
|
187
|
+
body: JSON.stringify({
|
|
188
|
+
assetId: assetId,
|
|
189
|
+
language: language
|
|
190
|
+
})
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
throw new Error(`Error fetching asset: ${response.statusText}`);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const data: AssetResponse & Response = await response.json();
|
|
198
|
+
|
|
199
|
+
if (data.Success === false) {
|
|
200
|
+
// Show the error message
|
|
201
|
+
setError(data.Message || 'An error occurred during insert');
|
|
202
|
+
return; // Don't close the dialog
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
setAssetData(data);
|
|
206
|
+
|
|
207
|
+
// Set the preferred derivation as selected by default
|
|
208
|
+
const preferredItem = data.Derivations.find(item => item.IsPreferred);
|
|
209
|
+
if (preferredItem) {
|
|
210
|
+
setSelectedDerivative(preferredItem.Derivation);
|
|
211
|
+
}
|
|
212
|
+
} catch (err) {
|
|
213
|
+
setError(err instanceof Error ? err.message : 'An unknown error occurred');
|
|
214
|
+
console.error('Error fetching asset:', err);
|
|
215
|
+
} finally {
|
|
216
|
+
setLoading(false);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
////
|
|
221
|
+
// Create select options from derivatives data
|
|
222
|
+
const getSelectOptions = () => {
|
|
223
|
+
if (!assetData?.Derivations) return [];
|
|
224
|
+
|
|
225
|
+
return assetData.Derivations.map(item => ({
|
|
226
|
+
value: item.Derivation.FileName,
|
|
227
|
+
label: `${item.Derivation.DerivationTypeIdentifier} (${item.Derivation.FileTypeIdentifier}) - ${formatFileSize(item.Derivation.FileSize)}${item.IsPreferred ? ' [recommended version for import]' : ''}`,
|
|
228
|
+
disabled: false
|
|
229
|
+
}));
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Handle select change
|
|
233
|
+
const handleSelectChange = (value: string) => {
|
|
234
|
+
const selectedItem = assetData?.Derivations.find(item => item.Derivation.FileName === value);
|
|
235
|
+
if (selectedItem) {
|
|
236
|
+
setSelectedDerivative(selectedItem.Derivation);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const handleInsert = async () => {
|
|
241
|
+
// Only proceed if a derivative is selected
|
|
242
|
+
if (!selectedDerivative) {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
try {
|
|
247
|
+
// Set loading state if needed
|
|
248
|
+
setLoading(true);
|
|
249
|
+
|
|
250
|
+
// Send POST request to insert endpoint
|
|
251
|
+
const response = await fetch('/alpaca/editor/sharedien/insert', {
|
|
252
|
+
method: 'POST',
|
|
253
|
+
headers: {
|
|
254
|
+
'Content-Type': 'application/json',
|
|
255
|
+
},
|
|
256
|
+
body: JSON.stringify({
|
|
257
|
+
assetId: assetId,
|
|
258
|
+
language: language,
|
|
259
|
+
derivation: {
|
|
260
|
+
assetTypeIdentifier: selectedDerivative.AssetTypeIdentifier,
|
|
261
|
+
fileTypeIdentifier: selectedDerivative.FileTypeIdentifier,
|
|
262
|
+
fileMimeType: selectedDerivative.FileMimeType,
|
|
263
|
+
derivationTypeIdentifier: selectedDerivative.DerivationTypeIdentifier,
|
|
264
|
+
fileName: selectedDerivative.FileName,
|
|
265
|
+
fileEnding: selectedDerivative.FileEnding,
|
|
266
|
+
fileSize: selectedDerivative.FileSize,
|
|
267
|
+
uri: selectedDerivative.Uri,
|
|
268
|
+
downloadUri: selectedDerivative.DownloadUri,
|
|
269
|
+
status: selectedDerivative.Status
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
if (!response.ok) {
|
|
275
|
+
throw new Error(`Error inserting asset: ${response.statusText}`);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Parse the response
|
|
279
|
+
const result = await response.json();
|
|
280
|
+
|
|
281
|
+
// Check if the response indicates failure
|
|
282
|
+
if (result.Success === false) {
|
|
283
|
+
// Show the error message
|
|
284
|
+
setError(result.Message || 'An error occurred during insert');
|
|
285
|
+
return; // Don't close the dialog
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check if this is a video asset by looking at Asset Type property
|
|
289
|
+
const assetTypeProperty = assetData?.AssetProperties?.find(prop => prop.FieldName === "Asset Type");
|
|
290
|
+
const isVideo = assetTypeProperty?.Value === "Movie";
|
|
291
|
+
|
|
292
|
+
const mediaItemId = result?.length > 0 ? result[0] : undefined;
|
|
293
|
+
|
|
294
|
+
// Close the dialog and return the selected derivative info
|
|
295
|
+
onClose({
|
|
296
|
+
mediaId: isVideo ? undefined : mediaItemId,
|
|
297
|
+
videoId: isVideo ? mediaItemId : undefined,
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
} catch (err) {
|
|
301
|
+
// Handle errors
|
|
302
|
+
setError(err instanceof Error ? err.message : 'An unknown error occurred during insert');
|
|
303
|
+
console.error('Error inserting asset:', err);
|
|
304
|
+
} finally {
|
|
305
|
+
// Reset loading state
|
|
306
|
+
setLoading(false);
|
|
307
|
+
}
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
const footer = (
|
|
311
|
+
<div className="flex gap-3 p-4">
|
|
312
|
+
<span title={!selectedDerivative
|
|
313
|
+
? "Please select a derivative first"
|
|
314
|
+
: loading
|
|
315
|
+
? "Processing..."
|
|
316
|
+
: assetData?.AssetInfo?.MeetsRestrictions === false
|
|
317
|
+
? "Cannot insert: " + assetData.AssetInfo.Message
|
|
318
|
+
: !assetData
|
|
319
|
+
? "Please search for an asset first"
|
|
320
|
+
: undefined}>
|
|
321
|
+
<Button
|
|
322
|
+
size="sm"
|
|
323
|
+
onClick={handleInsert}
|
|
324
|
+
disabled={!selectedDerivative || loading || (assetData?.AssetInfo?.MeetsRestrictions === false) || !assetData}
|
|
325
|
+
>
|
|
326
|
+
{loading ? (
|
|
327
|
+
<i className="pi pi-spin pi-spinner w-4 h-4" />
|
|
328
|
+
) : (
|
|
329
|
+
<Check className="w-4 h-4" />
|
|
330
|
+
)}
|
|
331
|
+
Insert
|
|
332
|
+
</Button>
|
|
333
|
+
</span>
|
|
334
|
+
<Button
|
|
335
|
+
size="sm"
|
|
336
|
+
onClick={() => onClose(null)}
|
|
337
|
+
>
|
|
338
|
+
<X className="w-4 h-4" />
|
|
339
|
+
Cancel
|
|
340
|
+
</Button>
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
return (
|
|
345
|
+
<Dialog open={true} onOpenChange={() => onClose(null)}>
|
|
346
|
+
<DialogContent
|
|
347
|
+
className="select-medidialog flex flex-col"
|
|
348
|
+
style={{ width: '90vw', height: '90vh', maxWidth: '90vw', maxHeight: '90vh' }}
|
|
349
|
+
>
|
|
350
|
+
<DialogHeader className="flex-shrink-0">
|
|
351
|
+
<DialogTitle>Get Asset from DAM</DialogTitle>
|
|
352
|
+
</DialogHeader>
|
|
353
|
+
|
|
354
|
+
<div className="flex-1 overflow-hidden flex flex-col">
|
|
355
|
+
<div className="p-3 flex-shrink-0">
|
|
356
|
+
<p className="p3 mb-4">
|
|
357
|
+
Browse for your asset in the DAM and copy the Asset ID. Paste the Asset ID below and click the Insert button to apply the image.
|
|
358
|
+
If available, you can also use the Asset Browser tab. In the widget, enter the Asset ID and click Confirm
|
|
359
|
+
</p>
|
|
360
|
+
</div>
|
|
361
|
+
|
|
362
|
+
{isVisible && (
|
|
363
|
+
<div className="flex-1 overflow-hidden">
|
|
364
|
+
<TabView
|
|
365
|
+
activeIndex={activeIndex}
|
|
366
|
+
onTabChange={(e) => setActiveIndex(e.index)}
|
|
367
|
+
className="dam-selector-tabs h-full"
|
|
368
|
+
>
|
|
369
|
+
<TabPanel header="SELECT BY ASSET ID">
|
|
370
|
+
<div className="h-full overflow-auto">
|
|
371
|
+
<div className="p-3 bg-blue-50 mb-4 flex align-items-center gap-2">
|
|
372
|
+
<i className="p-2 pi pi-info-circle" style={{ fontSize: '1.2rem' }} />
|
|
373
|
+
<span className="p-1 flex align-items-center">For testing you can open DAM Browser in a new Tab</span>
|
|
374
|
+
<Button
|
|
375
|
+
size="sm"
|
|
376
|
+
onClick={() => window.open(damUrl, '_blank')}>
|
|
377
|
+
<ExternalLink className="w-4 h-4" />
|
|
378
|
+
Open DAM
|
|
379
|
+
</Button>
|
|
380
|
+
</div>
|
|
381
|
+
|
|
382
|
+
<div className="grid grid-cols-2">
|
|
383
|
+
<div className="col-6 pr-4">
|
|
384
|
+
<div className="p-field mb-3">
|
|
385
|
+
<label htmlFor="assetId" className="block mb-2">Asset ID</label>
|
|
386
|
+
<Input
|
|
387
|
+
type="text"
|
|
388
|
+
id="assetId"
|
|
389
|
+
value={assetId}
|
|
390
|
+
onChange={(e) => setAssetId(e.target.value)}
|
|
391
|
+
className="w-full h-12 px-4"
|
|
392
|
+
placeholder="Enter Asset ID"
|
|
393
|
+
/>
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
<Button
|
|
397
|
+
size="sm"
|
|
398
|
+
onClick={searchAsset}
|
|
399
|
+
disabled={loading}
|
|
400
|
+
>
|
|
401
|
+
<Search className="w-4 h-4" />
|
|
402
|
+
Search Asset
|
|
403
|
+
</Button>
|
|
404
|
+
|
|
405
|
+
{error && (
|
|
406
|
+
<div className="p-3 bg-red-50 text-red-700 mb-4 border-round">
|
|
407
|
+
<i className="pi pi-exclamation-circle mr-2" />
|
|
408
|
+
{error}
|
|
409
|
+
</div>
|
|
410
|
+
)}
|
|
411
|
+
|
|
412
|
+
{assetData && (
|
|
413
|
+
<div className="p-field p-card1 p-31">
|
|
414
|
+
<label htmlFor='derivative' className="block mb-2">Select Derivatives</label>
|
|
415
|
+
<div className="mb-4">
|
|
416
|
+
<Select
|
|
417
|
+
id="derivative"
|
|
418
|
+
value={selectedDerivative?.FileName || ''}
|
|
419
|
+
onValueChange={handleSelectChange}
|
|
420
|
+
options={getSelectOptions()}
|
|
421
|
+
placeholder="Choose a derivative..."
|
|
422
|
+
className="w-full h-12 px-4"
|
|
423
|
+
disabled={false}
|
|
424
|
+
/>
|
|
425
|
+
</div>
|
|
426
|
+
</div>
|
|
427
|
+
)}
|
|
428
|
+
</div>
|
|
429
|
+
|
|
430
|
+
<div className="col-6 pl-4">
|
|
431
|
+
<div className="p-card p-3 flex flex-column grid grid-cols-1 grid-rows-2" style={{ minHeight: '300px' }}>
|
|
432
|
+
<div className="flex justify-content-center align-items-center row-1" style={{ flex: '1' }}>
|
|
433
|
+
{loading ? (
|
|
434
|
+
<i className="pi pi-spin pi-spinner text-6xl text-primary" />
|
|
435
|
+
) : assetData?.PreviewDerivationSrc ? (
|
|
436
|
+
<img
|
|
437
|
+
src={assetData.PreviewDerivationSrc}
|
|
438
|
+
alt="Asset preview"
|
|
439
|
+
className="w-full h-auto max-h-64 object-contain border border-gray-200 rounded"
|
|
440
|
+
style={{ maxWidth: '100%' }}
|
|
441
|
+
/>
|
|
442
|
+
) : (
|
|
443
|
+
<div className="flex flex-column align-items-center justify-content-center h-full">
|
|
444
|
+
<i className="pi pi-image text-6xl text-gray-300 mb-3 p-1" />
|
|
445
|
+
<p className="text-gray-500 text-center">Asset preview will appear here</p>
|
|
446
|
+
</div>
|
|
447
|
+
)}
|
|
448
|
+
</div>
|
|
449
|
+
|
|
450
|
+
{assetData?.AssetInfo?.Message && (
|
|
451
|
+
<div className="mt-3 p-3 bg-red-50 border-round">
|
|
452
|
+
<i className="pi pi-info-circle mr-2 text-red-500"></i>
|
|
453
|
+
<span>{assetData.AssetInfo.Message}</span>
|
|
454
|
+
</div>
|
|
455
|
+
)}
|
|
456
|
+
|
|
457
|
+
{!assetData?.AssetInfo?.Message && assetData?.AssetWarning?.ShouldWarn && assetData.AssetWarning.Message && (
|
|
458
|
+
<div className="mt-3 p-3 bg-yellow-50 border-round">
|
|
459
|
+
<i className="pi pi-exclamation-triangle mr-2 text-yellow-500"></i>
|
|
460
|
+
<span>{assetData.AssetWarning.Message}</span>
|
|
461
|
+
</div>
|
|
462
|
+
)}
|
|
463
|
+
|
|
464
|
+
{assetData?.AssetProperties && assetData.AssetProperties.length > 0 && (
|
|
465
|
+
<div className="mt-3 p-3 bg-gray-50 border-round">
|
|
466
|
+
<div className="grid">
|
|
467
|
+
{assetData.AssetProperties.map((prop, index) => (
|
|
468
|
+
<React.Fragment key={index}>
|
|
469
|
+
<div className="col-4 font-bold">{prop.FieldName}:</div>
|
|
470
|
+
<div className="col-8">
|
|
471
|
+
{prop.FieldName === "File Size"
|
|
472
|
+
? formatFileSize(parseInt(prop.Value))
|
|
473
|
+
: prop.FieldName === "Expiry Date"
|
|
474
|
+
? new Date(prop.Value).toLocaleDateString()
|
|
475
|
+
: prop.Value}
|
|
476
|
+
</div>
|
|
477
|
+
</React.Fragment>
|
|
478
|
+
))}
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
)}
|
|
482
|
+
</div>
|
|
483
|
+
</div>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
</TabPanel>
|
|
487
|
+
|
|
488
|
+
<TabPanel header="BROWSE IN DAM">
|
|
489
|
+
<div className="h-full overflow-hidden">
|
|
490
|
+
<div className="h-full flex flex-col">
|
|
491
|
+
{/* Container for the DAM browser widget */}
|
|
492
|
+
<div id="damBrowserContainer" className="flex-1" style={{ minHeight: 0 }}>
|
|
493
|
+
{/* The widget will create its own iframe here */}
|
|
494
|
+
</div>
|
|
495
|
+
|
|
496
|
+
{/* Fallback iframe if the widget doesn't load */}
|
|
497
|
+
{!window.sharedienAssetBrowser && (
|
|
498
|
+
<iframe
|
|
499
|
+
src={damUrl}
|
|
500
|
+
className="w-full h-full border border-gray-300"
|
|
501
|
+
title="DAM Browser"
|
|
502
|
+
sandbox="allow-same-origin allow-scripts allow-popups allow-forms"
|
|
503
|
+
/>
|
|
504
|
+
)}
|
|
505
|
+
|
|
506
|
+
<div className="mt-3 p-3 bg-yellow-50 border-round flex-shrink-0">
|
|
507
|
+
<i className="pi pi-exclamation-triangle mr-2 text-yellow-500"></i>
|
|
508
|
+
<span>Note: If the DAM widget doesn't load, you'll need to manually copy the Asset ID from the DAM and paste it in the Asset ID field on the first tab.</span>
|
|
509
|
+
</div>
|
|
510
|
+
</div>
|
|
511
|
+
</div>
|
|
512
|
+
</TabPanel>
|
|
513
|
+
</TabView>
|
|
514
|
+
</div>
|
|
515
|
+
)}
|
|
516
|
+
</div>
|
|
517
|
+
|
|
518
|
+
<DialogFooter className="flex-shrink-0 border-t bg-white relative z-10">
|
|
519
|
+
{footer}
|
|
520
|
+
</DialogFooter>
|
|
521
|
+
</DialogContent>
|
|
522
|
+
</Dialog>
|
|
523
|
+
);
|
|
524
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ClientFieldButton, PictureRawValue } from "@alpaca-editor/core";
|
|
2
|
+
import { DamSelector } from "./DamSelector";
|
|
3
|
+
import { DamImageValue, DamSelectorProps } from "./types";
|
|
4
|
+
|
|
5
|
+
export const DamSelectorButton: ClientFieldButton = {
|
|
6
|
+
label: "Open sharedien DAM",
|
|
7
|
+
icon: "pi pi-external-link",
|
|
8
|
+
clientAction: async ({ editContext, field }) => {
|
|
9
|
+
const data = await editContext.openDialog<DamImageValue, DamSelectorProps>(DamSelector, {
|
|
10
|
+
language: field.descriptor.item.language
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
if (data?.mediaId || data?.videoId) {
|
|
14
|
+
const raw = (() => {
|
|
15
|
+
try {
|
|
16
|
+
return field?.rawValue && field?.rawValue[0] === "{"
|
|
17
|
+
? (JSON.parse(field?.rawValue) as PictureRawValue)
|
|
18
|
+
: ({ Variants: [] } as PictureRawValue);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
console.warn("Failed to parse picture field raw value:", error);
|
|
21
|
+
return { Variants: [] } as PictureRawValue;
|
|
22
|
+
}
|
|
23
|
+
})();
|
|
24
|
+
|
|
25
|
+
if (raw.Variants && raw.Variants.length > 0 && raw.Variants[0]?.Name) {
|
|
26
|
+
const variantName = raw.Variants[0].Name;
|
|
27
|
+
const selected = raw.Variants.find((x: any) => x.Name == variantName);
|
|
28
|
+
if (selected) {
|
|
29
|
+
selected.MediaId = data.mediaId;
|
|
30
|
+
selected.VideoId = data.videoId;
|
|
31
|
+
} else {
|
|
32
|
+
raw.Variants.push({
|
|
33
|
+
Name: variantName,
|
|
34
|
+
MediaId: data.mediaId,
|
|
35
|
+
VideoId: data.videoId,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
console.warn("No existing variants with valid names found in picture field. Cannot determine variant name for DAM selection.");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
editContext!.operations.editField({
|
|
44
|
+
field: field.descriptor,
|
|
45
|
+
rawValue: JSON.stringify(raw),
|
|
46
|
+
refresh: "immediate",
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
isGenerator: false,
|
|
51
|
+
id: "open-sharedien-dam",
|
|
52
|
+
description: "Opens the sharedien DAM to select an image",
|
|
53
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { EditorConfiguration } from "@alpaca-editor/core";
|
|
2
|
+
import { SharedienDamExtension, DamImageValue, DamSelectorProps } from "./types";
|
|
3
|
+
import { DamSelectorButton } from "./DamSelectorButton";
|
|
4
|
+
import { DamSelector } from "./DamSelector";
|
|
5
|
+
|
|
6
|
+
export function configureSharedienDam(
|
|
7
|
+
configuration: EditorConfiguration,
|
|
8
|
+
sharedienDam: SharedienDamExtension,
|
|
9
|
+
) {
|
|
10
|
+
configuration.extensions = {
|
|
11
|
+
...configuration.extensions,
|
|
12
|
+
sharedienDam: {
|
|
13
|
+
enableDamSelector: sharedienDam.enableDamSelector ?? false,
|
|
14
|
+
damUrl: sharedienDam.damUrl,
|
|
15
|
+
damScriptUrl: sharedienDam.damScriptUrl,
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
if (!configuration.fieldTypes.picture) {
|
|
20
|
+
configuration.fieldTypes.picture = {
|
|
21
|
+
editor: () => null,
|
|
22
|
+
buttons: []
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (!configuration.fieldTypes.picture.buttons) {
|
|
27
|
+
configuration.fieldTypes.picture.buttons = [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
//configuration.fieldTypes.picture.buttons.push(DamSelectorButton);
|
|
31
|
+
|
|
32
|
+
return configuration;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { DamSelector, DamSelectorButton };
|
|
36
|
+
export type { DamImageValue, DamSelectorProps, SharedienDamExtension };
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type DamSelectorProps = {
|
|
2
|
+
language?: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export type DamImageValue = {
|
|
6
|
+
mediaId: string | undefined;
|
|
7
|
+
videoId: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type SharedienDamExtension = {
|
|
11
|
+
enableDamSelector?: boolean;
|
|
12
|
+
damUrl?: string;
|
|
13
|
+
damScriptUrl?: string;
|
|
14
|
+
}
|
|
15
|
+
|
package/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@source "./";
|