Skip to content

Commit fd93f24

Browse files
authored
feat: support mp4s as doc preview image (#761)
1 parent 9b0c57d commit fd93f24

File tree

6 files changed

+111
-16
lines changed

6 files changed

+111
-16
lines changed

.changeset/shaggy-penguins-fold.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@blinkk/root-cms': patch
3+
---
4+
5+
feat: support mp4s as doc preview image (#761)

docs/collections/Sandbox.schema.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export default schema.collection({
77
url: '/sandbox/[...slug]',
88
preview: {
99
title: 'meta.title',
10-
image: 'meta.image',
10+
image: ['content.modules[0].file', 'meta.image'],
1111
defaultImage: {
1212
src: 'https://lh3.googleusercontent.com/c2ECbvhJtxf3xbPIjaXCSpmvAsJkkhzJwG98T9RPvWy4s30jZKClom8pvWTnupRYOnyI3qGhNXPOwqoN6sqljkDO62LIKRtR988',
1313
},

packages/root-cms/ui/components/DocPreviewCard/DocPreviewCard.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import './DocPreviewCard.css';
22

3-
import {Image, Loader} from '@mantine/core';
3+
import {Loader} from '@mantine/core';
44
import {useEffect, useState} from 'preact/hooks';
55
import {joinClassNames} from '../../utils/classes.js';
66
import {getDocFromCacheOrFetch} from '../../utils/doc-cache.js';
77
import {getDocServingUrl} from '../../utils/doc-urls.js';
88
import {notifyErrors} from '../../utils/notifications.js';
99
import {getNestedValue} from '../../utils/objects.js';
1010
import {DocStatusBadges} from '../DocStatusBadges/DocStatusBadges.js';
11+
import {FilePreview} from '../FilePreview/FilePreview.js';
1112

1213
export interface DocPreviewCardProps {
1314
className?: string;
@@ -87,8 +88,8 @@ export function DocPreviewCard(props: DocPreviewCardProps) {
8788
{...attrs}
8889
>
8990
<div className="DocPreviewCard__image">
90-
<Image
91-
src={previewImage?.src}
91+
<FilePreview
92+
file={previewImage}
9293
width={80}
9394
height={60}
9495
withPlaceholder={!previewImage?.src}

packages/root-cms/ui/components/DocSelectModal/DocSelectModal.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1-
import {Button, Image, Loader, Select} from '@mantine/core';
1+
import './DocSelectModal.css';
2+
3+
import {Button, Loader, Select} from '@mantine/core';
24
import {ContextModalProps, useModals} from '@mantine/modals';
35
import {useState} from 'preact/hooks';
46
import {useDocsList} from '../../hooks/useDocsList.js';
57
import {useModalTheme} from '../../hooks/useModalTheme.js';
68
import {getDocServingUrl} from '../../utils/doc-urls.js';
79
import {getNestedValue} from '../../utils/objects.js';
8-
import './DocSelectModal.css';
10+
import {FilePreview} from '../FilePreview/FilePreview.js';
911

1012
const MODAL_ID = 'DocSelectModal';
1113

@@ -69,8 +71,6 @@ export function DocSelectModal(
6971
}
7072
}
7173

72-
console.log('selected docs:', props.selectedDocIds);
73-
7474
return (
7575
<div className="DocSelectModal">
7676
{collections.length === 0 && (
@@ -172,8 +172,8 @@ DocSelectModal.DocCard = (props: {
172172
return (
173173
<div className="DocSelectModal__DocCard">
174174
<div className="DocSelectModal__DocCard__image">
175-
<Image
176-
src={previewImage?.src}
175+
<FilePreview
176+
file={previewImage}
177177
width={120}
178178
height={90}
179179
withPlaceholder={!previewImage?.src}
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {Image} from '@mantine/core';
2+
3+
export interface FilePreviewFile {
4+
src?: string;
5+
mimeType?: string;
6+
[key: string]: any;
7+
}
8+
9+
export interface FilePreviewProps {
10+
file?: FilePreviewFile | string | null;
11+
width: number;
12+
height: number;
13+
withPlaceholder?: boolean;
14+
className?: string;
15+
alt?: string;
16+
}
17+
18+
export function FilePreview(props: FilePreviewProps) {
19+
const {file, width, height, withPlaceholder, className, alt} = props;
20+
const src = typeof file === 'string' ? file : file?.src;
21+
22+
if (isMp4File(file)) {
23+
return (
24+
<VideoPreview
25+
className={className}
26+
src={src!}
27+
width={width}
28+
height={height}
29+
/>
30+
);
31+
}
32+
33+
return (
34+
<Image
35+
className={className}
36+
src={src}
37+
width={width}
38+
height={height}
39+
withPlaceholder={withPlaceholder}
40+
alt={alt}
41+
/>
42+
);
43+
}
44+
45+
function isMp4File(file?: FilePreviewFile | string | null) {
46+
if (!file) {
47+
return false;
48+
}
49+
50+
if (typeof file === 'string') {
51+
return /\.mp4($|\?)/i.test(file);
52+
}
53+
54+
if (typeof file.mimeType === 'string') {
55+
return file.mimeType.toLowerCase() === 'video/mp4';
56+
}
57+
58+
const src = file.src;
59+
if (typeof src !== 'string') {
60+
return false;
61+
}
62+
return /\.mp4($|\?)/i.test(src);
63+
}
64+
65+
function VideoPreview(props: {
66+
className?: string;
67+
src: string;
68+
width: number;
69+
height: number;
70+
}) {
71+
return (
72+
<video
73+
className={props.className}
74+
src={props.src}
75+
width={props.width}
76+
height={props.height}
77+
muted
78+
playsInline
79+
preload="metadata"
80+
style={{
81+
width: `${props.width}px`,
82+
height: `${props.height}px`,
83+
backgroundColor: '#f1f3f5',
84+
objectFit: 'cover',
85+
display: 'block',
86+
}}
87+
/>
88+
);
89+
}

packages/root-cms/ui/pages/CollectionPage/CollectionPage.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import {Button, Image, Loader, Select, Tabs} from '@mantine/core';
1+
import './CollectionPage.css';
2+
3+
import {Button, Loader, Select, Tabs} from '@mantine/core';
24
import {
35
IconArrowRoundaboutRight,
46
IconCirclePlus,
57
IconFolder,
68
} from '@tabler/icons-preact';
7-
89
import {useEffect, useState} from 'preact/hooks';
910
import {route} from 'preact-router';
10-
1111
import {DocActionsMenu} from '../../components/DocActionsMenu/DocActionsMenu.js';
1212
import {DocStatusBadges} from '../../components/DocStatusBadges/DocStatusBadges.js';
13+
import {FilePreview} from '../../components/FilePreview/FilePreview.js';
1314
import {Heading} from '../../components/Heading/Heading.js';
1415
import {Markdown} from '../../components/Markdown/Markdown.js';
1516
import {NewDocModal} from '../../components/NewDocModal/NewDocModal.js';
@@ -20,7 +21,6 @@ import {Layout} from '../../layout/Layout.js';
2021
import {joinClassNames} from '../../utils/classes.js';
2122
import {getDocServingUrl} from '../../utils/doc-urls.js';
2223
import {getNestedValue} from '../../utils/objects.js';
23-
import './CollectionPage.css';
2424

2525
interface CollectionPageProps {
2626
collection?: string;
@@ -299,8 +299,8 @@ CollectionPage.DocsList = (props: {
299299
>
300300
<div className="CollectionPage__collection__docsList__doc__image">
301301
<a href={cmsUrl}>
302-
<Image
303-
src={previewImage?.src}
302+
<FilePreview
303+
file={previewImage}
304304
width={120}
305305
height={90}
306306
withPlaceholder={!previewImage?.src}

0 commit comments

Comments
 (0)