Skip to content

Commit 6501763

Browse files
authored
feat: add download asset button (#5417)
Ref #5416 Downloading assets is forbidden in view mode and when user is on free plan. <img width="332" height="374" alt="Screenshot 2025-10-02 at 21 34 34" src="https://github.com/user-attachments/assets/8695b5e4-68b4-484d-8425-37a2de81203f" />
1 parent 7decfeb commit 6501763

File tree

4 files changed

+61
-3
lines changed

4 files changed

+61
-3
lines changed

apps/builder/app/builder/shared/image-manager/image-info.tsx

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
DialogTrigger,
1515
Flex,
1616
Grid,
17+
IconButton,
1718
InputErrorsTooltip,
1819
InputField,
1920
Label,
@@ -32,6 +33,7 @@ import {
3233
AspectRatioIcon,
3334
CloudIcon,
3435
DimensionsIcon,
36+
DownloadIcon,
3537
GearIcon,
3638
InfoCircleIcon,
3739
PageIcon,
@@ -48,6 +50,7 @@ import {
4850
$props,
4951
$styles,
5052
$styleSourceSelections,
53+
$userPlanFeatures,
5154
} from "~/shared/nano-states";
5255
import { $openProjectSettings } from "~/shared/nano-states/project-settings";
5356
import {
@@ -58,7 +61,7 @@ import {
5861
import { updateWebstudioData } from "~/shared/instance-utils";
5962
import { deleteAssets, $usagesByAssetId, type AssetUsage } from "../assets";
6063
import { $activeInspectorPanel, setActiveSidebarPanel } from "../nano-states";
61-
import { parseAssetName } from "../assets/asset-utils";
64+
import { formatAssetName, parseAssetName } from "../assets/asset-utils";
6265
import { getFormattedAspectRatio } from "./utils";
6366

6467
const buttonLinkClass = css({
@@ -257,6 +260,7 @@ const ImageInfoContent = ({
257260
asset: Asset;
258261
usages: AssetUsage[];
259262
}) => {
263+
const { hasProPlan } = useStore($userPlanFeatures);
260264
const { size, meta, id, name } = asset;
261265
const { basename, ext } = parseAssetName(name);
262266
const [filenameError, setFilenameError] = useState<string>();
@@ -303,6 +307,14 @@ const ImageInfoContent = ({
303307

304308
const authPermit = useStore($authPermit);
305309

310+
let downloadError: undefined | string;
311+
if (authPermit === "view") {
312+
downloadError =
313+
"Unavailable in View mode. Switch to Edit to download assets.";
314+
} else if (!hasProPlan) {
315+
downloadError = "Upgrade to Pro to download assets.";
316+
}
317+
306318
return (
307319
<>
308320
<Box css={{ padding: theme.panel.padding }}>
@@ -392,7 +404,7 @@ const ImageInfoContent = ({
392404
/>
393405
</Grid>
394406

395-
<Box css={{ padding: theme.panel.padding }}>
407+
<Flex justify="between" css={{ padding: theme.panel.padding }}>
396408
{authPermit === "view" ? (
397409
<Tooltip side="bottom" content="View mode. You can't delete assets.">
398410
<Button disabled color="destructive" prefix={<TrashIcon />}>
@@ -436,7 +448,25 @@ const ImageInfoContent = ({
436448
</DialogContent>
437449
</Dialog>
438450
)}
439-
</Box>
451+
452+
{downloadError ? (
453+
<Tooltip side="bottom" content={downloadError}>
454+
<IconButton disabled>
455+
<DownloadIcon />
456+
</IconButton>
457+
</Tooltip>
458+
) : (
459+
<Tooltip side="bottom" content="Download asset">
460+
<IconButton
461+
as="a"
462+
download={formatAssetName(asset)}
463+
href={`/cgi/image/${asset.name}?format=raw`}
464+
>
465+
<DownloadIcon />
466+
</IconButton>
467+
</Tooltip>
468+
)}
469+
</Flex>
440470
</>
441471
);
442472
};

packages/icons/icons/download.svg

Lines changed: 1 addition & 0 deletions
Loading

packages/icons/src/__generated__/components.tsx

Lines changed: 25 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/icons/src/__generated__/svg.ts

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)