feat: add media lib virtualization (#3381)
This commit is contained in:
parent
9c0f618148
commit
92e76011e7
@ -1,6 +1,6 @@
|
|||||||
import GoTrue from 'gotrue-js';
|
import GoTrue from 'gotrue-js';
|
||||||
import jwtDecode from 'jwt-decode';
|
import jwtDecode from 'jwt-decode';
|
||||||
import { fromPairs, get, pick, intersection, unzip } from 'lodash';
|
import { get, pick, intersection } from 'lodash';
|
||||||
import ini from 'ini';
|
import ini from 'ini';
|
||||||
import {
|
import {
|
||||||
APIError,
|
APIError,
|
||||||
@ -21,9 +21,9 @@ import {
|
|||||||
UnpublishedEntryMediaFile,
|
UnpublishedEntryMediaFile,
|
||||||
parsePointerFile,
|
parsePointerFile,
|
||||||
getLargeMediaPatternsFromGitAttributesFile,
|
getLargeMediaPatternsFromGitAttributesFile,
|
||||||
PointerFile,
|
|
||||||
getPointerFileForMediaFileObj,
|
getPointerFileForMediaFileObj,
|
||||||
getLargeMediaFilteredMediaFiles,
|
getLargeMediaFilteredMediaFiles,
|
||||||
|
DisplayURLObject,
|
||||||
} from 'netlify-cms-lib-util';
|
} from 'netlify-cms-lib-util';
|
||||||
import { GitHubBackend } from 'netlify-cms-backend-github';
|
import { GitHubBackend } from 'netlify-cms-backend-github';
|
||||||
import { GitLabBackend } from 'netlify-cms-backend-gitlab';
|
import { GitLabBackend } from 'netlify-cms-backend-gitlab';
|
||||||
@ -75,12 +75,6 @@ interface NetlifyUser extends Credentials {
|
|||||||
user_metadata: { full_name: string; avatar_url: string };
|
user_metadata: { full_name: string; avatar_url: string };
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetMediaDisplayURLArgs {
|
|
||||||
path: string;
|
|
||||||
original: { id: string; path: string } | string;
|
|
||||||
largeMedia: PointerFile;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class GitGateway implements Implementation {
|
export default class GitGateway implements Implementation {
|
||||||
config: Config;
|
config: Config;
|
||||||
api?: GitHubAPI | GitLabAPI | BitBucketAPI;
|
api?: GitHubAPI | GitLabAPI | BitBucketAPI;
|
||||||
@ -278,12 +272,8 @@ export default class GitGateway implements Implementation {
|
|||||||
const mediaFiles = await Promise.all(
|
const mediaFiles = await Promise.all(
|
||||||
files.map(async file => {
|
files.map(async file => {
|
||||||
if (client.matchPath(file.path)) {
|
if (client.matchPath(file.path)) {
|
||||||
const { id, path } = file;
|
const { path, id } = file;
|
||||||
const largeMediaDisplayURLs = await this.getLargeMediaDisplayURLs(
|
const url = await this.getLargeMediaDisplayURL({ path, id }, branch);
|
||||||
[{ ...file, id }],
|
|
||||||
branch,
|
|
||||||
);
|
|
||||||
const url = await client.getDownloadURL(largeMediaDisplayURLs[id]);
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
name: basename(path),
|
name: basename(path),
|
||||||
@ -303,32 +293,7 @@ export default class GitGateway implements Implementation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getMedia(mediaFolder = this.mediaFolder) {
|
getMedia(mediaFolder = this.mediaFolder) {
|
||||||
return Promise.all([this.backend!.getMedia(mediaFolder), this.getLargeMediaClient()]).then(
|
return this.backend!.getMedia(mediaFolder);
|
||||||
async ([mediaFiles, largeMediaClient]) => {
|
|
||||||
if (!largeMediaClient.enabled) {
|
|
||||||
return mediaFiles.map(({ displayURL, ...rest }) => ({
|
|
||||||
...rest,
|
|
||||||
displayURL: { original: displayURL },
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
if (mediaFiles.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const largeMediaDisplayURLs = await this.getLargeMediaDisplayURLs(mediaFiles);
|
|
||||||
return mediaFiles.map(({ id, displayURL, path, ...rest }) => {
|
|
||||||
return {
|
|
||||||
...rest,
|
|
||||||
id,
|
|
||||||
path,
|
|
||||||
displayURL: {
|
|
||||||
path,
|
|
||||||
original: displayURL,
|
|
||||||
largeMedia: largeMediaDisplayURLs[id],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// this method memoizes this._getLargeMediaClient so that there can
|
// this method memoizes this._getLargeMediaClient so that there can
|
||||||
@ -382,79 +347,54 @@ export default class GitGateway implements Implementation {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
async getLargeMediaDisplayURLs(
|
async getLargeMediaDisplayURL(
|
||||||
mediaFiles: { path: string; id: string | null }[],
|
{ path, id }: { path: string; id: string | null },
|
||||||
branch = this.branch,
|
branch = this.branch,
|
||||||
) {
|
) {
|
||||||
const client = await this.getLargeMediaClient();
|
|
||||||
const readFile = (
|
const readFile = (
|
||||||
path: string,
|
path: string,
|
||||||
id: string | null | undefined,
|
id: string | null | undefined,
|
||||||
{ parseText }: { parseText: boolean },
|
{ parseText }: { parseText: boolean },
|
||||||
) => this.api!.readFile(path, id, { branch, parseText });
|
) => this.api!.readFile(path, id, { branch, parseText });
|
||||||
|
|
||||||
const filesPromise = entriesByFiles(mediaFiles, readFile, 'Git-Gateway');
|
const items = await entriesByFiles([{ path, id }], readFile, 'Git-Gateway');
|
||||||
|
const entry = items[0];
|
||||||
|
const pointerFile = parsePointerFile(entry.data);
|
||||||
|
if (!pointerFile.sha) {
|
||||||
|
console.warn(`Failed parsing pointer file ${path}`);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
return filesPromise
|
const client = await this.getLargeMediaClient();
|
||||||
.then(items =>
|
const url = await client.getDownloadURL(pointerFile);
|
||||||
items.map(({ file: { id, path }, data }) => {
|
return url;
|
||||||
const parsedPointerFile = parsePointerFile(data);
|
|
||||||
if (!parsedPointerFile.sha) {
|
|
||||||
console.warn(`Failed parsing pointer file ${path}`);
|
|
||||||
}
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
pointerId: id,
|
|
||||||
resourceId: parsedPointerFile.sha,
|
|
||||||
},
|
|
||||||
parsedPointerFile,
|
|
||||||
];
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.then(unzip)
|
|
||||||
.then(([idMaps, files]) =>
|
|
||||||
Promise.all([
|
|
||||||
idMaps as { pointerId: string; resourceId: string }[],
|
|
||||||
client.getResourceDownloadURLArgs(files as PointerFile[]).then(r => fromPairs(r)),
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
.then(([idMaps, resourceMap]) =>
|
|
||||||
idMaps.map(({ pointerId, resourceId }) => [pointerId, resourceMap[resourceId]]),
|
|
||||||
)
|
|
||||||
.then(fromPairs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getMediaDisplayURL(displayURL: DisplayURL) {
|
async getMediaDisplayURL(displayURL: DisplayURL) {
|
||||||
const {
|
const { path, id } = displayURL as DisplayURLObject;
|
||||||
path,
|
const client = await this.getLargeMediaClient();
|
||||||
original,
|
if (client.enabled && client.matchPath(path)) {
|
||||||
largeMedia: largeMediaDisplayURL,
|
return this.getLargeMediaDisplayURL({ path, id });
|
||||||
} = (displayURL as unknown) as GetMediaDisplayURLArgs;
|
}
|
||||||
return this.getLargeMediaClient().then(client => {
|
if (typeof displayURL === 'string') {
|
||||||
if (client.enabled && client.matchPath(path)) {
|
return displayURL;
|
||||||
return client.getDownloadURL(largeMediaDisplayURL);
|
}
|
||||||
}
|
if (this.backend!.getMediaDisplayURL) {
|
||||||
if (typeof original === 'string') {
|
return this.backend!.getMediaDisplayURL(displayURL);
|
||||||
return original;
|
}
|
||||||
}
|
const err = new Error(
|
||||||
if (this.backend!.getMediaDisplayURL) {
|
`getMediaDisplayURL is not implemented by the ${this.backendType} backend, but the backend returned a displayURL which was not a string!`,
|
||||||
return this.backend!.getMediaDisplayURL(original);
|
) as Error & {
|
||||||
}
|
displayURL: DisplayURL;
|
||||||
const err = new Error(
|
};
|
||||||
`getMediaDisplayURL is not implemented by the ${this.backendType} backend, but the backend returned a displayURL which was not a string!`,
|
err.displayURL = displayURL;
|
||||||
) as Error & {
|
return Promise.reject(err);
|
||||||
displayURL: DisplayURL;
|
|
||||||
};
|
|
||||||
err.displayURL = displayURL;
|
|
||||||
return Promise.reject(err);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMediaFile(path: string) {
|
async getMediaFile(path: string) {
|
||||||
const client = await this.getLargeMediaClient();
|
const client = await this.getLargeMediaClient();
|
||||||
if (client.enabled && client.matchPath(path)) {
|
if (client.enabled && client.matchPath(path)) {
|
||||||
const largeMediaDisplayURLs = await this.getLargeMediaDisplayURLs([{ path, id: null }]);
|
const url = await this.getLargeMediaDisplayURL({ path, id: null });
|
||||||
const url = await client.getDownloadURL(Object.values(largeMediaDisplayURLs)[0]);
|
|
||||||
return {
|
return {
|
||||||
id: url,
|
id: url,
|
||||||
name: basename(path),
|
name: basename(path),
|
||||||
|
@ -66,11 +66,6 @@ const getDownloadURL = (
|
|||||||
return Promise.resolve('');
|
return Promise.resolve('');
|
||||||
});
|
});
|
||||||
|
|
||||||
const getResourceDownloadURLArgs = (_clientConfig: ClientConfig, objects: PointerFile[]) => {
|
|
||||||
const result = objects.map(({ sha }) => [sha, { sha }]) as [string, { sha: string }][];
|
|
||||||
return Promise.resolve(result);
|
|
||||||
};
|
|
||||||
|
|
||||||
const uploadOperation = (objects: PointerFile[]) => ({
|
const uploadOperation = (objects: PointerFile[]) => ({
|
||||||
operation: 'upload',
|
operation: 'upload',
|
||||||
transfers: ['basic'],
|
transfers: ['basic'],
|
||||||
@ -129,7 +124,6 @@ const configureFn = (config: ClientConfig, fn: Function) => (...args: unknown[])
|
|||||||
const clientFns: Record<string, Function> = {
|
const clientFns: Record<string, Function> = {
|
||||||
resourceExists,
|
resourceExists,
|
||||||
getResourceUploadURLs,
|
getResourceUploadURLs,
|
||||||
getResourceDownloadURLArgs,
|
|
||||||
getDownloadURL,
|
getDownloadURL,
|
||||||
uploadResource,
|
uploadResource,
|
||||||
matchPath,
|
matchPath,
|
||||||
@ -138,7 +132,6 @@ const clientFns: Record<string, Function> = {
|
|||||||
export type Client = {
|
export type Client = {
|
||||||
resourceExists: (pointer: PointerFile) => Promise<boolean | undefined>;
|
resourceExists: (pointer: PointerFile) => Promise<boolean | undefined>;
|
||||||
getResourceUploadURLs: (objects: PointerFile[]) => Promise<string>;
|
getResourceUploadURLs: (objects: PointerFile[]) => Promise<string>;
|
||||||
getResourceDownloadURLArgs: (objects: PointerFile[]) => Promise<[string, { sha: string }][]>;
|
|
||||||
getDownloadURL: (pointer: PointerFile) => Promise<string>;
|
getDownloadURL: (pointer: PointerFile) => Promise<string>;
|
||||||
uploadResource: (pointer: PointerFile, blob: Blob) => Promise<string>;
|
uploadResource: (pointer: PointerFile, blob: Blob) => Promise<string>;
|
||||||
matchPath: (path: string) => boolean;
|
matchPath: (path: string) => boolean;
|
||||||
|
@ -56,7 +56,9 @@
|
|||||||
"react-sortable-hoc": "^1.0.0",
|
"react-sortable-hoc": "^1.0.0",
|
||||||
"react-split-pane": "^0.1.85",
|
"react-split-pane": "^0.1.85",
|
||||||
"react-topbar-progress-indicator": "^2.0.0",
|
"react-topbar-progress-indicator": "^2.0.0",
|
||||||
|
"react-virtualized-auto-sizer": "^1.0.2",
|
||||||
"react-waypoint": "^8.1.0",
|
"react-waypoint": "^8.1.0",
|
||||||
|
"react-window": "^1.8.5",
|
||||||
"redux": "^4.0.1",
|
"redux": "^4.0.1",
|
||||||
"redux-notifications": "^4.0.1",
|
"redux-notifications": "^4.0.1",
|
||||||
"redux-optimist": "^1.0.0",
|
"redux-optimist": "^1.0.0",
|
||||||
|
@ -8,7 +8,7 @@ const IMAGE_HEIGHT = 160;
|
|||||||
|
|
||||||
const Card = styled.div`
|
const Card = styled.div`
|
||||||
width: ${props => props.width};
|
width: ${props => props.width};
|
||||||
height: 240px;
|
height: ${props => props.height};
|
||||||
margin: ${props => props.margin};
|
margin: ${props => props.margin};
|
||||||
border: ${borders.textField};
|
border: ${borders.textField};
|
||||||
border-color: ${props => props.isSelected && colors.active};
|
border-color: ${props => props.isSelected && colors.active};
|
||||||
@ -71,6 +71,7 @@ class MediaLibraryCard extends React.Component {
|
|||||||
onClick,
|
onClick,
|
||||||
draftText,
|
draftText,
|
||||||
width,
|
width,
|
||||||
|
height,
|
||||||
margin,
|
margin,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
type,
|
type,
|
||||||
@ -83,6 +84,7 @@ class MediaLibraryCard extends React.Component {
|
|||||||
isSelected={isSelected}
|
isSelected={isSelected}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
width={width}
|
width={width}
|
||||||
|
height={height}
|
||||||
margin={margin}
|
margin={margin}
|
||||||
tabIndex="-1"
|
tabIndex="-1"
|
||||||
isPrivate={isPrivate}
|
isPrivate={isPrivate}
|
||||||
@ -114,6 +116,7 @@ MediaLibraryCard.propTypes = {
|
|||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
draftText: PropTypes.string.isRequired,
|
draftText: PropTypes.string.isRequired,
|
||||||
width: PropTypes.string.isRequired,
|
width: PropTypes.string.isRequired,
|
||||||
|
height: PropTypes.string.isRequired,
|
||||||
margin: PropTypes.string.isRequired,
|
margin: PropTypes.string.isRequired,
|
||||||
isPrivate: PropTypes.bool,
|
isPrivate: PropTypes.bool,
|
||||||
type: PropTypes.string,
|
type: PropTypes.string,
|
||||||
|
@ -5,6 +5,143 @@ import Waypoint from 'react-waypoint';
|
|||||||
import MediaLibraryCard from './MediaLibraryCard';
|
import MediaLibraryCard from './MediaLibraryCard';
|
||||||
import { Map } from 'immutable';
|
import { Map } from 'immutable';
|
||||||
import { colors } from 'netlify-cms-ui-default';
|
import { colors } from 'netlify-cms-ui-default';
|
||||||
|
import { FixedSizeGrid as Grid } from 'react-window';
|
||||||
|
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||||
|
|
||||||
|
const CardWrapper = props => {
|
||||||
|
const {
|
||||||
|
rowIndex,
|
||||||
|
columnIndex,
|
||||||
|
style,
|
||||||
|
data: {
|
||||||
|
mediaItems,
|
||||||
|
isSelectedFile,
|
||||||
|
onAssetClick,
|
||||||
|
cardDraftText,
|
||||||
|
cardWidth,
|
||||||
|
cardHeight,
|
||||||
|
isPrivate,
|
||||||
|
displayURLs,
|
||||||
|
loadDisplayURL,
|
||||||
|
columnCount,
|
||||||
|
gutter,
|
||||||
|
},
|
||||||
|
} = props;
|
||||||
|
const index = rowIndex * columnCount + columnIndex;
|
||||||
|
if (index >= mediaItems.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const file = mediaItems[index];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
...style,
|
||||||
|
left: style.left + gutter * columnIndex,
|
||||||
|
top: style.top + gutter,
|
||||||
|
width: style.width - gutter,
|
||||||
|
height: style.height - gutter,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<MediaLibraryCard
|
||||||
|
key={file.key}
|
||||||
|
isSelected={isSelectedFile(file)}
|
||||||
|
text={file.name}
|
||||||
|
onClick={() => onAssetClick(file)}
|
||||||
|
isDraft={file.draft}
|
||||||
|
draftText={cardDraftText}
|
||||||
|
width={cardWidth}
|
||||||
|
height={cardHeight}
|
||||||
|
margin={'0px'}
|
||||||
|
isPrivate={isPrivate}
|
||||||
|
displayURL={displayURLs.get(file.id, file.url ? Map({ url: file.url }) : Map())}
|
||||||
|
loadDisplayURL={() => loadDisplayURL(file)}
|
||||||
|
type={file.type}
|
||||||
|
isViewableImage={file.isViewableImage}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const VirtualizedGrid = props => {
|
||||||
|
const { mediaItems, setScrollContainerRef } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CardGridContainer ref={setScrollContainerRef}>
|
||||||
|
<AutoSizer>
|
||||||
|
{({ height, width }) => {
|
||||||
|
const cardWidth = parseInt(props.cardWidth, 10);
|
||||||
|
const cardHeight = parseInt(props.cardHeight, 10);
|
||||||
|
const gutter = parseInt(props.cardMargin, 10);
|
||||||
|
const columnWidth = cardWidth + gutter;
|
||||||
|
const rowHeight = cardHeight + gutter;
|
||||||
|
const columnCount = Math.floor(width / columnWidth);
|
||||||
|
const rowCount = Math.ceil(mediaItems.length / columnCount);
|
||||||
|
return (
|
||||||
|
<Grid
|
||||||
|
columnCount={columnCount}
|
||||||
|
columnWidth={columnWidth}
|
||||||
|
rowCount={rowCount}
|
||||||
|
rowHeight={rowHeight}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
itemData={{ ...props, gutter, columnCount }}
|
||||||
|
>
|
||||||
|
{CardWrapper}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
</AutoSizer>
|
||||||
|
</CardGridContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const PaginatedGrid = ({
|
||||||
|
setScrollContainerRef,
|
||||||
|
mediaItems,
|
||||||
|
isSelectedFile,
|
||||||
|
onAssetClick,
|
||||||
|
cardDraftText,
|
||||||
|
cardWidth,
|
||||||
|
cardHeight,
|
||||||
|
cardMargin,
|
||||||
|
isPrivate,
|
||||||
|
displayURLs,
|
||||||
|
loadDisplayURL,
|
||||||
|
canLoadMore,
|
||||||
|
onLoadMore,
|
||||||
|
isPaginating,
|
||||||
|
paginatingMessage,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<CardGridContainer ref={setScrollContainerRef}>
|
||||||
|
<CardGrid>
|
||||||
|
{mediaItems.map(file => (
|
||||||
|
<MediaLibraryCard
|
||||||
|
key={file.key}
|
||||||
|
isSelected={isSelectedFile(file)}
|
||||||
|
text={file.name}
|
||||||
|
onClick={() => onAssetClick(file)}
|
||||||
|
isDraft={file.draft}
|
||||||
|
draftText={cardDraftText}
|
||||||
|
width={cardWidth}
|
||||||
|
height={cardHeight}
|
||||||
|
margin={cardMargin}
|
||||||
|
isPrivate={isPrivate}
|
||||||
|
displayURL={displayURLs.get(file.id, file.url ? Map({ url: file.url }) : Map())}
|
||||||
|
loadDisplayURL={() => loadDisplayURL(file)}
|
||||||
|
type={file.type}
|
||||||
|
isViewableImage={file.isViewableImage}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{!canLoadMore ? null : <Waypoint onEnter={onLoadMore} />}
|
||||||
|
</CardGrid>
|
||||||
|
{!isPaginating ? null : (
|
||||||
|
<PaginatingMessage isPrivate={isPrivate}>{paginatingMessage}</PaginatingMessage>
|
||||||
|
)}
|
||||||
|
</CardGridContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const CardGridContainer = styled.div`
|
const CardGridContainer = styled.div`
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
@ -23,48 +160,13 @@ const PaginatingMessage = styled.h1`
|
|||||||
color: ${props => props.isPrivate && colors.textFieldBorder};
|
color: ${props => props.isPrivate && colors.textFieldBorder};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const MediaLibraryCardGrid = ({
|
const MediaLibraryCardGrid = props => {
|
||||||
setScrollContainerRef,
|
const { canLoadMore, isPaginating } = props;
|
||||||
mediaItems,
|
if (canLoadMore || isPaginating) {
|
||||||
isSelectedFile,
|
return <PaginatedGrid {...props} />;
|
||||||
onAssetClick,
|
}
|
||||||
canLoadMore,
|
return <VirtualizedGrid {...props} />;
|
||||||
onLoadMore,
|
};
|
||||||
isPaginating,
|
|
||||||
paginatingMessage,
|
|
||||||
cardDraftText,
|
|
||||||
cardWidth,
|
|
||||||
cardMargin,
|
|
||||||
isPrivate,
|
|
||||||
displayURLs,
|
|
||||||
loadDisplayURL,
|
|
||||||
}) => (
|
|
||||||
<CardGridContainer ref={setScrollContainerRef}>
|
|
||||||
<CardGrid>
|
|
||||||
{mediaItems.map(file => (
|
|
||||||
<MediaLibraryCard
|
|
||||||
key={file.key}
|
|
||||||
isSelected={isSelectedFile(file)}
|
|
||||||
text={file.name}
|
|
||||||
onClick={() => onAssetClick(file)}
|
|
||||||
isDraft={file.draft}
|
|
||||||
draftText={cardDraftText}
|
|
||||||
width={cardWidth}
|
|
||||||
margin={cardMargin}
|
|
||||||
isPrivate={isPrivate}
|
|
||||||
displayURL={displayURLs.get(file.id, file.url ? Map({ url: file.url }) : Map())}
|
|
||||||
loadDisplayURL={() => loadDisplayURL(file)}
|
|
||||||
type={file.type}
|
|
||||||
isViewableImage={file.isViewableImage}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
{!canLoadMore ? null : <Waypoint onEnter={onLoadMore} />}
|
|
||||||
</CardGrid>
|
|
||||||
{!isPaginating ? null : (
|
|
||||||
<PaginatingMessage isPrivate={isPrivate}>{paginatingMessage}</PaginatingMessage>
|
|
||||||
)}
|
|
||||||
</CardGridContainer>
|
|
||||||
);
|
|
||||||
|
|
||||||
MediaLibraryCardGrid.propTypes = {
|
MediaLibraryCardGrid.propTypes = {
|
||||||
setScrollContainerRef: PropTypes.func.isRequired,
|
setScrollContainerRef: PropTypes.func.isRequired,
|
||||||
|
@ -17,6 +17,7 @@ import { colors } from 'netlify-cms-ui-default';
|
|||||||
* widths per breakpoint.
|
* widths per breakpoint.
|
||||||
*/
|
*/
|
||||||
const cardWidth = `280px`;
|
const cardWidth = `280px`;
|
||||||
|
const cardHeight = `240px`;
|
||||||
const cardMargin = `10px`;
|
const cardMargin = `10px`;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -172,6 +173,7 @@ const MediaLibraryModal = ({
|
|||||||
paginatingMessage={t('mediaLibrary.mediaLibraryModal.loading')}
|
paginatingMessage={t('mediaLibrary.mediaLibraryModal.loading')}
|
||||||
cardDraftText={t('mediaLibrary.mediaLibraryCard.draft')}
|
cardDraftText={t('mediaLibrary.mediaLibraryCard.draft')}
|
||||||
cardWidth={cardWidth}
|
cardWidth={cardWidth}
|
||||||
|
cardHeight={cardHeight}
|
||||||
cardMargin={cardMargin}
|
cardMargin={cardMargin}
|
||||||
isPrivate={privateUpload}
|
isPrivate={privateUpload}
|
||||||
loadDisplayURL={loadDisplayURL}
|
loadDisplayURL={loadDisplayURL}
|
||||||
|
@ -10,6 +10,7 @@ describe('MediaLibraryCard', () => {
|
|||||||
onClick: jest.fn(),
|
onClick: jest.fn(),
|
||||||
draftText: 'Draft',
|
draftText: 'Draft',
|
||||||
width: '100px',
|
width: '100px',
|
||||||
|
height: '240px',
|
||||||
margin: '10px',
|
margin: '10px',
|
||||||
isViewableImage: true,
|
isViewableImage: true,
|
||||||
loadDisplayURL: jest.fn(),
|
loadDisplayURL: jest.fn(),
|
||||||
|
@ -52,6 +52,7 @@ exports[`MediaLibraryCard should match snapshot for draft image 1`] = `
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="emotion-8 emotion-9"
|
class="emotion-8 emotion-9"
|
||||||
|
height="240px"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
width="100px"
|
width="100px"
|
||||||
>
|
>
|
||||||
@ -122,6 +123,7 @@ exports[`MediaLibraryCard should match snapshot for non draft image 1`] = `
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="emotion-6 emotion-7"
|
class="emotion-6 emotion-7"
|
||||||
|
height="240px"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
width="100px"
|
width="100px"
|
||||||
>
|
>
|
||||||
@ -188,6 +190,7 @@ exports[`MediaLibraryCard should match snapshot for non viewable image 1`] = `
|
|||||||
|
|
||||||
<div
|
<div
|
||||||
class="emotion-6 emotion-7"
|
class="emotion-6 emotion-7"
|
||||||
|
height="240px"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
width="100px"
|
width="100px"
|
||||||
>
|
>
|
||||||
|
@ -4,10 +4,7 @@ import { AsyncLock } from './asyncLock';
|
|||||||
|
|
||||||
export type DisplayURLObject = { id: string; path: string };
|
export type DisplayURLObject = { id: string; path: string };
|
||||||
|
|
||||||
export type DisplayURL =
|
export type DisplayURL = DisplayURLObject | string;
|
||||||
| DisplayURLObject
|
|
||||||
| string
|
|
||||||
| { original: DisplayURL; path?: string; largeMedia?: string };
|
|
||||||
|
|
||||||
export interface ImplementationMediaFile {
|
export interface ImplementationMediaFile {
|
||||||
name: string;
|
name: string;
|
||||||
|
68
yarn.lock
68
yarn.lock
@ -6495,11 +6495,6 @@ detect-indent@^5.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
|
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"
|
||||||
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
|
integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50=
|
||||||
|
|
||||||
detect-libc@^1.0.2:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
|
|
||||||
integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
|
|
||||||
|
|
||||||
detect-newline@^2.1.0:
|
detect-newline@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
|
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"
|
||||||
@ -8089,7 +8084,6 @@ fsevents@^1.2.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
bindings "^1.5.0"
|
bindings "^1.5.0"
|
||||||
nan "^2.12.1"
|
nan "^2.12.1"
|
||||||
node-pre-gyp "*"
|
|
||||||
|
|
||||||
fsevents@~2.1.2:
|
fsevents@~2.1.2:
|
||||||
version "2.1.2"
|
version "2.1.2"
|
||||||
@ -9062,7 +9056,7 @@ husky@^3.0.9:
|
|||||||
run-node "^1.0.0"
|
run-node "^1.0.0"
|
||||||
slash "^3.0.0"
|
slash "^3.0.0"
|
||||||
|
|
||||||
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13:
|
iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@~0.4.13:
|
||||||
version "0.4.24"
|
version "0.4.24"
|
||||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||||
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
|
||||||
@ -11261,16 +11255,16 @@ mem@^4.0.0:
|
|||||||
mimic-fn "^2.0.0"
|
mimic-fn "^2.0.0"
|
||||||
p-is-promise "^2.0.0"
|
p-is-promise "^2.0.0"
|
||||||
|
|
||||||
|
"memoize-one@>=3.1.1 <6", memoize-one@^5.0.0:
|
||||||
|
version "5.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
||||||
|
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
||||||
|
|
||||||
memoize-one@^4.0.0:
|
memoize-one@^4.0.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906"
|
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906"
|
||||||
integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==
|
integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==
|
||||||
|
|
||||||
memoize-one@^5.0.0:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0"
|
|
||||||
integrity sha512-HKeeBpWvqiVJD57ZUAsJNm71eHTykffzcLZVYWiVfQeI1rJtuEaS7hQiEpWfVVk18donPwJEcFKIkCmPJNOhHA==
|
|
||||||
|
|
||||||
memoizerific@^1.11.3:
|
memoizerific@^1.11.3:
|
||||||
version "1.11.3"
|
version "1.11.3"
|
||||||
resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a"
|
resolved "https://registry.yarnpkg.com/memoizerific/-/memoizerific-1.11.3.tgz#7c87a4646444c32d75438570905f2dbd1b1a805a"
|
||||||
@ -11763,15 +11757,6 @@ ncp@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
|
||||||
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
|
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
|
||||||
|
|
||||||
needle@^2.2.1:
|
|
||||||
version "2.3.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/needle/-/needle-2.3.2.tgz#3342dea100b7160960a450dc8c22160ac712a528"
|
|
||||||
integrity sha512-DUzITvPVDUy6vczKKYTnWc/pBZ0EnjMJnQ3y+Jo5zfKFimJs7S3HFCxCRZYB9FUZcrzUQr3WsmvZgddMEIZv6w==
|
|
||||||
dependencies:
|
|
||||||
debug "^3.2.6"
|
|
||||||
iconv-lite "^0.4.4"
|
|
||||||
sax "^1.2.4"
|
|
||||||
|
|
||||||
negotiator@0.6.2:
|
negotiator@0.6.2:
|
||||||
version "0.6.2"
|
version "0.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
|
||||||
@ -11943,22 +11928,6 @@ node-polyglot@^2.3.0:
|
|||||||
string.prototype.trim "^1.1.2"
|
string.prototype.trim "^1.1.2"
|
||||||
warning "^4.0.3"
|
warning "^4.0.3"
|
||||||
|
|
||||||
node-pre-gyp@*:
|
|
||||||
version "0.14.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.14.0.tgz#9a0596533b877289bcad4e143982ca3d904ddc83"
|
|
||||||
integrity sha512-+CvDC7ZttU/sSt9rFjix/P05iS43qHCOOGzcr3Ry99bXG7VX953+vFyEuph/tfqoYu8dttBkE86JSKBO2OzcxA==
|
|
||||||
dependencies:
|
|
||||||
detect-libc "^1.0.2"
|
|
||||||
mkdirp "^0.5.1"
|
|
||||||
needle "^2.2.1"
|
|
||||||
nopt "^4.0.1"
|
|
||||||
npm-packlist "^1.1.6"
|
|
||||||
npmlog "^4.0.2"
|
|
||||||
rc "^1.2.7"
|
|
||||||
rimraf "^2.6.1"
|
|
||||||
semver "^5.3.0"
|
|
||||||
tar "^4.4.2"
|
|
||||||
|
|
||||||
node-releases@^1.1.29, node-releases@^1.1.50:
|
node-releases@^1.1.29, node-releases@^1.1.50:
|
||||||
version "1.1.50"
|
version "1.1.50"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.50.tgz#803c40d2c45db172d0410e4efec83aa8c6ad0592"
|
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.50.tgz#803c40d2c45db172d0410e4efec83aa8c6ad0592"
|
||||||
@ -12080,7 +12049,7 @@ npm-normalize-package-bin@^1.0.0, npm-normalize-package-bin@^1.0.1:
|
|||||||
semver "^5.6.0"
|
semver "^5.6.0"
|
||||||
validate-npm-package-name "^3.0.0"
|
validate-npm-package-name "^3.0.0"
|
||||||
|
|
||||||
npm-packlist@^1.1.6, npm-packlist@^1.4.4:
|
npm-packlist@^1.4.4:
|
||||||
version "1.4.8"
|
version "1.4.8"
|
||||||
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
|
resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.8.tgz#56ee6cc135b9f98ad3d51c1c95da22bbb9b2ef3e"
|
||||||
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
|
integrity sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==
|
||||||
@ -12127,7 +12096,7 @@ npm-run-path@^4.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
path-key "^3.0.0"
|
path-key "^3.0.0"
|
||||||
|
|
||||||
npmlog@^4.0.2, npmlog@^4.1.2:
|
npmlog@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
|
||||||
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
|
integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
|
||||||
@ -13622,7 +13591,7 @@ rbush@2.0.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
quickselect "^1.0.1"
|
quickselect "^1.0.1"
|
||||||
|
|
||||||
rc@^1.0.1, rc@^1.1.6, rc@^1.2.7:
|
rc@^1.0.1, rc@^1.1.6:
|
||||||
version "1.2.8"
|
version "1.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
|
||||||
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
|
||||||
@ -14079,6 +14048,11 @@ react-transition-group@^2.2.1, react-transition-group@^2.6.0:
|
|||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
react-lifecycles-compat "^3.0.4"
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
|
react-virtualized-auto-sizer@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.2.tgz#a61dd4f756458bbf63bd895a92379f9b70f803bd"
|
||||||
|
integrity sha512-MYXhTY1BZpdJFjUovvYHVBmkq79szK/k7V3MO+36gJkWGkrXKtyr4vCPtpphaTLRAdDNoYEYFZWE8LjN+PIHNg==
|
||||||
|
|
||||||
react-waypoint@^8.1.0:
|
react-waypoint@^8.1.0:
|
||||||
version "8.1.0"
|
version "8.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-8.1.0.tgz#91d926a2fd1be4cbd0351cb8c3d494fac0ef1699"
|
resolved "https://registry.yarnpkg.com/react-waypoint/-/react-waypoint-8.1.0.tgz#91d926a2fd1be4cbd0351cb8c3d494fac0ef1699"
|
||||||
@ -14088,6 +14062,14 @@ react-waypoint@^8.1.0:
|
|||||||
prop-types "^15.0.0"
|
prop-types "^15.0.0"
|
||||||
react-is "^16.6.3"
|
react-is "^16.6.3"
|
||||||
|
|
||||||
|
react-window@^1.8.5:
|
||||||
|
version "1.8.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.5.tgz#a56b39307e79979721021f5d06a67742ecca52d1"
|
||||||
|
integrity sha512-HeTwlNa37AFa8MDZFZOKcNEkuF2YflA0hpGPiTT9vR7OawEt+GZbfM6wqkBahD3D3pUjIabQYzsnY/BSJbgq6Q==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.0.0"
|
||||||
|
memoize-one ">=3.1.1 <6"
|
||||||
|
|
||||||
react@^16.12.0, react@^16.8.3, react@^16.8.4:
|
react@^16.12.0, react@^16.8.3, react@^16.8.4:
|
||||||
version "16.13.0"
|
version "16.13.0"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.13.0.tgz#d046eabcdf64e457bbeed1e792e235e1b9934cf7"
|
||||||
@ -14816,7 +14798,7 @@ rimraf@2.6.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
glob "^7.1.3"
|
glob "^7.1.3"
|
||||||
|
|
||||||
rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
|
rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.2, rimraf@^2.6.3, rimraf@^2.7.1:
|
||||||
version "2.7.1"
|
version "2.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
|
||||||
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
|
||||||
@ -15000,7 +14982,7 @@ semver-diff@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.0.3"
|
semver "^5.0.3"
|
||||||
|
|
||||||
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
"semver@2 || 3 || 4 || 5", "semver@2.x || 3.x || 4 || 5", semver@^5.0.3, semver@^5.1.0, semver@^5.4.1, semver@^5.5, semver@^5.5.0, semver@^5.5.1, semver@^5.6.0, semver@^5.7.0, semver@^5.7.1:
|
||||||
version "5.7.1"
|
version "5.7.1"
|
||||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||||
@ -16184,7 +16166,7 @@ tapable@^1.0.0, tapable@^1.1.3:
|
|||||||
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2"
|
||||||
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
integrity sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==
|
||||||
|
|
||||||
tar@^4.4.10, tar@^4.4.12, tar@^4.4.2, tar@^4.4.8:
|
tar@^4.4.10, tar@^4.4.12, tar@^4.4.8:
|
||||||
version "4.4.13"
|
version "4.4.13"
|
||||||
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
|
resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.13.tgz#43b364bc52888d555298637b10d60790254ab525"
|
||||||
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
|
integrity sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user