Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import { listItemIconClasses } from '@mui/material/ListItemIcon';
import Menu from '@mui/material/Menu';
import { useImageInfo } from '../../../hooks/useImageInfo';
import { svgIconClasses } from '@mui/material/SvgIcon';
import { ensureSingleSlash } from '../../../utils/string';
import { resolveMediaUrl } from '../../../utils/string';
import { useDispatch } from 'react-redux';
import Tooltip from '@mui/material/Tooltip';
import { downloadMedia, getImageRestrictionMessages, showImageCropDialog } from '../lib/controlHelpers';
Expand Down Expand Up @@ -68,9 +68,9 @@ export function ImagePicker(props: ImagePickerProps) {
// endregion

const value = nnou(valueProp) ? valueProp : (defaultValue ?? '');
const { imageInfo, isFetchingDimensions, isFetchingMetadata, errorDimensions, errorMetadata } = useImageInfo(
value ? ensureSingleSlash(`${guestBase}${value}`) : ''
);
const mediaUrl = value ? resolveMediaUrl(guestBase, value) : '';
const { imageInfo, isFetchingDimensions, isFetchingMetadata, errorDimensions, errorMetadata } =
useImageInfo(mediaUrl);
const hasValue = Boolean(value);
const actions = dataSources?.actions ?? [];
const dataSourcesLoading = dataSources?.status === 'loading';
Expand Down Expand Up @@ -150,12 +150,7 @@ export function ImagePicker(props: ImagePickerProps) {
<FormsEngineField field={field}>
{hasValue ? (
<Card sx={{ display: 'flex' }}>
<CardMedia
component="img"
sx={{ width: '40%' }}
image={`${guestBase}${value}`}
alt="Live from space album cover"
/>
<CardMedia component="img" sx={{ width: '40%' }} image={mediaUrl} alt="" />
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<CardContent sx={{ flex: '1 0 auto' }}>
<Typography component="div" variant="body1" marginBottom={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import Typography from '@mui/material/Typography';
import IconButton from '@mui/material/IconButton';
import { DeleteOutlined, DownloadOutlined, EditOutlined } from '@mui/icons-material';
import { svgIconClasses } from '@mui/material';
import { ensureSingleSlash } from '../../../utils/string';
import { resolveMediaUrl } from '../../../utils/string';
import useVideoInfo from '../../../hooks/useVideoInfo';
import Skeleton from '@mui/material/Skeleton';
import { downloadMedia } from '../lib/controlHelpers';
Expand All @@ -47,13 +47,11 @@ export interface VideoPickerProps extends ControlProps {
export function VideoPicker(props: VideoPickerProps) {
const { field, value, setValue, readonly: formReadonly, dataSources } = props;
const { guestBase } = useEnv();
// TODO: For testing, by using 3000 as the guestBase both the fetch in `useImageInfo` and the download functionality will work
// const guestBase = 'http://localhost:3000';
const hasValue = Boolean(value);
const mediaUrl = value ? resolveMediaUrl(guestBase, value) : '';
const { formatMessage } = useIntl();
const { videoInfo, isFetchingMetadata, isFetchingDimensions, errorDimensions, errorMetadata } = useVideoInfo(
value ? ensureSingleSlash(`${guestBase}${value}`) : ''
);
const { videoInfo, isFetchingMetadata, isFetchingDimensions, errorDimensions, errorMetadata } =
useVideoInfo(mediaUrl);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const [addMenuOpen, setAddMenuOpen] = useState(false);

Expand Down Expand Up @@ -99,7 +97,7 @@ export function VideoPicker(props: VideoPickerProps) {
<FormsEngineField field={field}>
{hasValue ? (
<Card sx={{ display: 'flex' }}>
<CardMedia component="video" sx={{ width: '40%' }} image={ensureSingleSlash(`${guestBase}${value}`)} />
<CardMedia component="video" sx={{ width: '40%' }} image={mediaUrl} />
<Box sx={{ display: 'flex', flexDirection: 'column' }}>
<CardContent sx={{ flex: '1 0 auto' }}>
<Typography component="div" variant="body1" marginBottom={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import ContentType from '../../../models/ContentType';
import FormsEngineField from '../components/FormsEngineField';
import { FormsEngineAtoms, ItemContext, ItemMetaContext, StableGlobalContext } from './formsEngineContext';
import { getFileNameFromPath } from '../../../utils/path';
import { ensureSingleSlash } from '../../../utils/string';
import { isExternalMediaUrl, resolveMediaUrl } from '../../../utils/string';
import { Dispatch as ReduxDispatch } from 'redux';
import { BrowseFilesDialogProps } from '../../BrowseFilesDialog';
import { nanoid } from 'nanoid';
Expand Down Expand Up @@ -208,7 +208,7 @@ export function renderFieldControl(
* */
export function downloadMedia(base: string, url: string) {
const link = document.createElement('a');
link.href = ensureSingleSlash(`${base}${url}`);
link.href = resolveMediaUrl(base, url);
link.download = getFileNameFromPath(url); // Extracts the file name from the URL
document.body.appendChild(link);
link.click();
Expand Down Expand Up @@ -362,6 +362,8 @@ export const showImageCropDialog = ({
onCrop: (blob: Blob, newPath?: string) => void;
}): void => {
const dialogId = nanoid();
// Remote/absolute URLs load as `src` in the editor; writing cropped content back requires a site path.
const canWriteContent = writeContent !== false && !isExternalMediaUrl(path);
dispatch(
pushDialog({
id: dialogId,
Expand All @@ -371,7 +373,7 @@ export const showImageCropDialog = ({
mimeType,
subtitle: restrictions ? <ImageRestrictionSubtitle restrictions={restrictions} /> : undefined,
restrictions,
writeContent,
writeContent: canWriteContent,
onCrop: (blob: Blob, newPath: string) => {
dispatch(popDialog({ id: dialogId }));
onCrop?.(blob, newPath);
Expand Down
21 changes: 21 additions & 0 deletions studio-ui/ui/app/src/utils/string.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,27 @@ export function ensureSingleSlash(url: string): string {
return /^(http|https):\/\//g.test(url) ? url.replace(/([^:]\/)\/+/g, '$1') : url.replace(/\/+/g, '/');
}

/**
* True when the value is already a loadable absolute/remote media URL (http(s), data, or blob).
* Site-relative paths like `/static-assets/...` return false.
*/
export function isExternalMediaUrl(url: string): boolean {
if (!url) return false;
return /^(https?:)?\/\//i.test(url) || /^(data|blob):/i.test(url);
}
Comment thread
jvega190 marked this conversation as resolved.

/**
* Resolves a media field value for preview / fetch / download.
* Absolute/remote URLs are returned as-is; site paths are prefixed with `guestBase` (FE1 image-picker parity).
*/
export function resolveMediaUrl(guestBase: string, value: string): string {
if (!value) return value;
if (isExternalMediaUrl(value)) {
return value;
}
return ensureSingleSlash(`${guestBase}${value}`);
}

export function getSimplifiedVersion(version: string, options: { minor?: boolean; patch?: boolean } = {}) {
if (!version) {
return version;
Expand Down