|
|
@@ -47,16 +47,55 @@ interface EntityAssetsProps {
|
|
|
onChange?: (change: EntityAssetValue) => void;
|
|
|
}
|
|
|
|
|
|
+// FeaturedAsset component
|
|
|
+interface FeaturedAssetProps {
|
|
|
+ featuredAsset?: Asset | null;
|
|
|
+ compact?: boolean;
|
|
|
+ onSelectAssets: () => void;
|
|
|
+ onPreviewAsset: (asset: Asset) => void;
|
|
|
+}
|
|
|
+
|
|
|
+function FeaturedAsset({
|
|
|
+ featuredAsset,
|
|
|
+ compact = false,
|
|
|
+ onSelectAssets,
|
|
|
+ onPreviewAsset,
|
|
|
+ }: FeaturedAssetProps) {
|
|
|
+ return (
|
|
|
+ <div
|
|
|
+ className={`flex items-center justify-center ${compact ? 'h-40' : 'h-64'} border border-dashed rounded-md`}
|
|
|
+ >
|
|
|
+ {featuredAsset ? (
|
|
|
+ <VendureImage
|
|
|
+ asset={featuredAsset}
|
|
|
+ mode="crop"
|
|
|
+ preset="small"
|
|
|
+ onClick={() => onPreviewAsset(featuredAsset)}
|
|
|
+ className="max-w-full max-h-full object-contain cursor-pointer"
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <div
|
|
|
+ className="flex flex-col items-center justify-center text-muted-foreground cursor-pointer"
|
|
|
+ onClick={onSelectAssets}
|
|
|
+ >
|
|
|
+ <ImageIcon className={compact ? 'h-10 w-10' : 'h-16 w-16'} />
|
|
|
+ {!compact && <div className="mt-2">No featured asset</div>}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
// Sortable asset item component
|
|
|
function SortableAsset({
|
|
|
- asset,
|
|
|
- compact,
|
|
|
- isFeatured,
|
|
|
- updatePermissions,
|
|
|
- onPreview,
|
|
|
- onSetAsFeatured,
|
|
|
- onRemove,
|
|
|
-}: {
|
|
|
+ asset,
|
|
|
+ compact,
|
|
|
+ isFeatured,
|
|
|
+ updatePermissions,
|
|
|
+ onPreview,
|
|
|
+ onSetAsFeatured,
|
|
|
+ onRemove,
|
|
|
+ }: {
|
|
|
asset: Asset;
|
|
|
compact: boolean;
|
|
|
isFeatured: boolean;
|
|
|
@@ -122,13 +161,13 @@ function SortableAsset({
|
|
|
}
|
|
|
|
|
|
export function EntityAssets({
|
|
|
- assets: initialAssets = [],
|
|
|
- featuredAsset: initialFeaturedAsset,
|
|
|
- compact = false,
|
|
|
- updatePermissions = true,
|
|
|
- multiSelect = true,
|
|
|
- onChange,
|
|
|
-}: EntityAssetsProps) {
|
|
|
+ assets: initialAssets = [],
|
|
|
+ featuredAsset: initialFeaturedAsset,
|
|
|
+ compact = false,
|
|
|
+ updatePermissions = true,
|
|
|
+ multiSelect = true,
|
|
|
+ onChange,
|
|
|
+ }: EntityAssetsProps) {
|
|
|
const [assets, setAssets] = useState<Asset[]>([...initialAssets]);
|
|
|
const [featuredAsset, setFeaturedAsset] = useState<Asset | undefined | null>(initialFeaturedAsset);
|
|
|
const [isPickerOpen, setIsPickerOpen] = useState(false);
|
|
|
@@ -234,57 +273,6 @@ export function EntityAssets({
|
|
|
},
|
|
|
[featuredAsset],
|
|
|
);
|
|
|
-
|
|
|
- const renderAssetList = () => (
|
|
|
- <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
|
|
- <div className={`${compact ? 'max-h-32' : ''} overflow-auto p-1`}>
|
|
|
- <SortableContext
|
|
|
- items={assets.map(asset => asset.id)}
|
|
|
- strategy={horizontalListSortingStrategy}
|
|
|
- >
|
|
|
- <div className="flex flex-wrap gap-2">
|
|
|
- {assets.map(asset => (
|
|
|
- <SortableAsset
|
|
|
- key={asset.id}
|
|
|
- asset={asset}
|
|
|
- compact={compact}
|
|
|
- isFeatured={isFeatured(asset)}
|
|
|
- updatePermissions={updatePermissions}
|
|
|
- onPreview={setPreviewAsset}
|
|
|
- onSetAsFeatured={handleSetAsFeatured}
|
|
|
- onRemove={handleRemoveAsset}
|
|
|
- />
|
|
|
- ))}
|
|
|
- </div>
|
|
|
- </SortableContext>
|
|
|
- </div>
|
|
|
- </DndContext>
|
|
|
- );
|
|
|
-
|
|
|
- const FeaturedAsset = () => (
|
|
|
- <div
|
|
|
- className={`flex items-center justify-center ${compact ? 'h-40' : 'h-64'} border border-dashed rounded-md`}
|
|
|
- >
|
|
|
- {featuredAsset ? (
|
|
|
- <VendureImage
|
|
|
- asset={featuredAsset}
|
|
|
- mode="crop"
|
|
|
- preset="small"
|
|
|
- onClick={() => setPreviewAsset(featuredAsset)}
|
|
|
- className="max-w-full max-h-full object-contain cursor-pointer"
|
|
|
- />
|
|
|
- ) : (
|
|
|
- <div
|
|
|
- className="flex flex-col items-center justify-center text-muted-foreground cursor-pointer"
|
|
|
- onClick={handleSelectAssets}
|
|
|
- >
|
|
|
- <ImageIcon className={compact ? 'h-10 w-10' : 'h-16 w-16'} />
|
|
|
- {!compact && <div className="mt-2">No featured asset</div>}
|
|
|
- </div>
|
|
|
- )}
|
|
|
- </div>
|
|
|
- );
|
|
|
-
|
|
|
// AddAssetButton component
|
|
|
const AddAssetButton = () =>
|
|
|
updatePermissions && (
|
|
|
@@ -303,15 +291,45 @@ export function EntityAssets({
|
|
|
<>
|
|
|
{compact ? (
|
|
|
<div className="flex flex-col gap-3">
|
|
|
- <FeaturedAsset />
|
|
|
- {renderAssetList()}
|
|
|
+ <FeaturedAsset
|
|
|
+ featuredAsset={featuredAsset}
|
|
|
+ compact={compact}
|
|
|
+ onSelectAssets={handleSelectAssets}
|
|
|
+ onPreviewAsset={setPreviewAsset}
|
|
|
+ />
|
|
|
+ <AssetList
|
|
|
+ assets={assets}
|
|
|
+ compact={compact}
|
|
|
+ sensors={sensors}
|
|
|
+ updatePermissions={updatePermissions}
|
|
|
+ isFeatured={isFeatured}
|
|
|
+ onPreview={setPreviewAsset}
|
|
|
+ onSetAsFeatured={handleSetAsFeatured}
|
|
|
+ onRemove={handleRemoveAsset}
|
|
|
+ onDragEnd={handleDragEnd}
|
|
|
+ />
|
|
|
<AddAssetButton />
|
|
|
</div>
|
|
|
) : (
|
|
|
<div className="grid grid-cols-1 md:grid-cols-[256px_1fr] gap-4">
|
|
|
- <FeaturedAsset />
|
|
|
+ <FeaturedAsset
|
|
|
+ featuredAsset={featuredAsset}
|
|
|
+ compact={compact}
|
|
|
+ onSelectAssets={handleSelectAssets}
|
|
|
+ onPreviewAsset={setPreviewAsset}
|
|
|
+ />
|
|
|
<div className="flex flex-col gap-4">
|
|
|
- {renderAssetList()}
|
|
|
+ <AssetList
|
|
|
+ assets={assets}
|
|
|
+ compact={compact}
|
|
|
+ sensors={sensors}
|
|
|
+ updatePermissions={updatePermissions}
|
|
|
+ isFeatured={isFeatured}
|
|
|
+ onPreview={setPreviewAsset}
|
|
|
+ onSetAsFeatured={handleSetAsFeatured}
|
|
|
+ onRemove={handleRemoveAsset}
|
|
|
+ onDragEnd={handleDragEnd}
|
|
|
+ />
|
|
|
<AddAssetButton />
|
|
|
</div>
|
|
|
</div>
|
|
|
@@ -338,3 +356,55 @@ export function EntityAssets({
|
|
|
</>
|
|
|
);
|
|
|
}
|
|
|
+
|
|
|
+// AssetList component
|
|
|
+interface AssetListProps {
|
|
|
+ assets: Asset[];
|
|
|
+ compact: boolean;
|
|
|
+ sensors: ReturnType<typeof useSensors>;
|
|
|
+ updatePermissions: boolean;
|
|
|
+ isFeatured: (asset: Asset) => boolean;
|
|
|
+ onPreview: (asset: Asset) => void;
|
|
|
+ onSetAsFeatured: (asset: Asset) => void;
|
|
|
+ onRemove: (asset: Asset) => void;
|
|
|
+ onDragEnd: (event: DragEndEvent) => void;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+function AssetList({
|
|
|
+ assets,
|
|
|
+ compact,
|
|
|
+ sensors,
|
|
|
+ updatePermissions,
|
|
|
+ isFeatured,
|
|
|
+ onPreview,
|
|
|
+ onSetAsFeatured,
|
|
|
+ onRemove,
|
|
|
+ onDragEnd,
|
|
|
+ }: AssetListProps) {
|
|
|
+ return (
|
|
|
+ <DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={onDragEnd}>
|
|
|
+ <div className={`${compact ? 'max-h-32' : ''} overflow-auto p-1`}>
|
|
|
+ <SortableContext
|
|
|
+ items={assets.map(asset => asset.id)}
|
|
|
+ strategy={horizontalListSortingStrategy}
|
|
|
+ >
|
|
|
+ <div className="flex flex-wrap gap-2">
|
|
|
+ {assets.map(asset => (
|
|
|
+ <SortableAsset
|
|
|
+ key={asset.id}
|
|
|
+ asset={asset}
|
|
|
+ compact={compact}
|
|
|
+ isFeatured={isFeatured(asset)}
|
|
|
+ updatePermissions={updatePermissions}
|
|
|
+ onPreview={onPreview}
|
|
|
+ onSetAsFeatured={onSetAsFeatured}
|
|
|
+ onRemove={onRemove}
|
|
|
+ />
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </SortableContext>
|
|
|
+ </div>
|
|
|
+ </DndContext>
|
|
|
+ );
|
|
|
+}
|