mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add reusable component
This commit is contained in:
parent
c3b5d50313
commit
cc87ba3859
16 changed files with 512 additions and 5 deletions
74
package-lock.json
generated
74
package-lock.json
generated
|
@ -25,6 +25,7 @@
|
||||||
"@react-hook/resize-observer": "1.2.6",
|
"@react-hook/resize-observer": "1.2.6",
|
||||||
"@tanstack/react-query": "4.36.1",
|
"@tanstack/react-query": "4.36.1",
|
||||||
"@tanstack/react-query-devtools": "4.36.1",
|
"@tanstack/react-query-devtools": "4.36.1",
|
||||||
|
"@types/react-lazy-load-image-component": "1.6.3",
|
||||||
"abortcontroller-polyfill": "1.7.5",
|
"abortcontroller-polyfill": "1.7.5",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||||
|
@ -52,7 +53,9 @@
|
||||||
"native-promise-only": "0.8.1",
|
"native-promise-only": "0.8.1",
|
||||||
"pdfjs-dist": "3.11.174",
|
"pdfjs-dist": "3.11.174",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
"react-blurhash": "0.3.0",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-lazy-load-image-component": "1.6.0",
|
||||||
"react-router-dom": "6.21.3",
|
"react-router-dom": "6.21.3",
|
||||||
"resize-observer-polyfill": "1.5.1",
|
"resize-observer-polyfill": "1.5.1",
|
||||||
"screenfull": "6.0.2",
|
"screenfull": "6.0.2",
|
||||||
|
@ -4705,6 +4708,15 @@
|
||||||
"@types/react": "^17"
|
"@types/react": "^17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-lazy-load-image-component": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-HsIsYz7yWWTh/bftdzGnijKD26JyofLRqM/RM80sxs7Gk13G83ew8R/ra2XzXuiZfjNEjAq/Va+NBHFF9ciwxA==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/react-transition-group": {
|
"node_modules/@types/react-transition-group": {
|
||||||
"version": "4.4.10",
|
"version": "4.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||||
|
@ -12671,8 +12683,7 @@
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/lodash.memoize": {
|
"node_modules/lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
|
@ -12686,6 +12697,11 @@
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash.throttle": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
|
||||||
|
},
|
||||||
"node_modules/lodash.truncate": {
|
"node_modules/lodash.truncate": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
||||||
|
@ -16202,6 +16218,15 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-blurhash": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"blurhash": "^2.0.3",
|
||||||
|
"react": ">=15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||||
|
@ -16220,6 +16245,19 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-lazy-load-image-component": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8KFkDTgjh+0+PVbH+cx0AgxLGbdTsxWMnxXzU5HEUztqewk9ufQAu8cstjZhyvtMIPsdMcPZfA0WAa7HtjQbBQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lodash.throttle": "^4.1.1"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x",
|
||||||
|
"react-dom": "^15.x.x || ^16.x.x || ^17.x.x || ^18.x.x"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "6.21.3",
|
"version": "6.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz",
|
||||||
|
@ -25943,6 +25981,15 @@
|
||||||
"@types/react": "^17"
|
"@types/react": "^17"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-lazy-load-image-component": {
|
||||||
|
"version": "1.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.3.tgz",
|
||||||
|
"integrity": "sha512-HsIsYz7yWWTh/bftdzGnijKD26JyofLRqM/RM80sxs7Gk13G83ew8R/ra2XzXuiZfjNEjAq/Va+NBHFF9ciwxA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"csstype": "^3.0.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/react-transition-group": {
|
"@types/react-transition-group": {
|
||||||
"version": "4.4.10",
|
"version": "4.4.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
|
||||||
|
@ -31866,8 +31913,7 @@
|
||||||
"lodash.debounce": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
|
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"lodash.memoize": {
|
"lodash.memoize": {
|
||||||
"version": "4.1.2",
|
"version": "4.1.2",
|
||||||
|
@ -31881,6 +31927,11 @@
|
||||||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"lodash.throttle": {
|
||||||
|
"version": "4.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
|
||||||
|
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ=="
|
||||||
|
},
|
||||||
"lodash.truncate": {
|
"lodash.truncate": {
|
||||||
"version": "4.4.2",
|
"version": "4.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
|
||||||
|
@ -34295,6 +34346,12 @@
|
||||||
"object-assign": "^4.1.1"
|
"object-assign": "^4.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"react-blurhash": {
|
||||||
|
"version": "0.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-blurhash/-/react-blurhash-0.3.0.tgz",
|
||||||
|
"integrity": "sha512-XlKr4Ns1iYFRnk6DkAblNbAwN/bTJvxTVoxMvmTcURdc5oLoXZwqAF9N3LZUh/HT+QFlq5n6IS6VsDGsviYAiQ==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"react-dom": {
|
"react-dom": {
|
||||||
"version": "17.0.2",
|
"version": "17.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",
|
||||||
|
@ -34310,6 +34367,15 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||||
},
|
},
|
||||||
|
"react-lazy-load-image-component": {
|
||||||
|
"version": "1.6.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-lazy-load-image-component/-/react-lazy-load-image-component-1.6.0.tgz",
|
||||||
|
"integrity": "sha512-8KFkDTgjh+0+PVbH+cx0AgxLGbdTsxWMnxXzU5HEUztqewk9ufQAu8cstjZhyvtMIPsdMcPZfA0WAa7HtjQbBQ==",
|
||||||
|
"requires": {
|
||||||
|
"lodash.debounce": "^4.0.8",
|
||||||
|
"lodash.throttle": "^4.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-router": {
|
"react-router": {
|
||||||
"version": "6.21.3",
|
"version": "6.21.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.21.3.tgz",
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
"@react-hook/resize-observer": "1.2.6",
|
"@react-hook/resize-observer": "1.2.6",
|
||||||
"@tanstack/react-query": "4.36.1",
|
"@tanstack/react-query": "4.36.1",
|
||||||
"@tanstack/react-query-devtools": "4.36.1",
|
"@tanstack/react-query-devtools": "4.36.1",
|
||||||
|
"@types/react-lazy-load-image-component": "1.6.3",
|
||||||
"abortcontroller-polyfill": "1.7.5",
|
"abortcontroller-polyfill": "1.7.5",
|
||||||
"blurhash": "2.0.5",
|
"blurhash": "2.0.5",
|
||||||
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
"classlist.js": "https://github.com/eligrey/classList.js/archive/1.2.20180112.tar.gz",
|
||||||
|
@ -113,7 +114,9 @@
|
||||||
"native-promise-only": "0.8.1",
|
"native-promise-only": "0.8.1",
|
||||||
"pdfjs-dist": "3.11.174",
|
"pdfjs-dist": "3.11.174",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
|
"react-blurhash": "0.3.0",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"react-lazy-load-image-component": "1.6.0",
|
||||||
"react-router-dom": "6.21.3",
|
"react-router-dom": "6.21.3",
|
||||||
"resize-observer-polyfill": "1.5.1",
|
"resize-observer-polyfill": "1.5.1",
|
||||||
"screenfull": "6.0.2",
|
"screenfull": "6.0.2",
|
||||||
|
|
56
src/components/common/DefaultIconText.tsx
Normal file
56
src/components/common/DefaultIconText.tsx
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import Icon from '@mui/material/Icon';
|
||||||
|
import imageHelper from 'utils/image';
|
||||||
|
import DefaultName from './DefaultName';
|
||||||
|
import type { ItemDto } from 'types/itemDto';
|
||||||
|
|
||||||
|
interface DefaultIconTextProps {
|
||||||
|
item: ItemDto;
|
||||||
|
defaultCardImageIcon?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultIconText: FC<DefaultIconTextProps> = ({
|
||||||
|
item,
|
||||||
|
defaultCardImageIcon
|
||||||
|
}) => {
|
||||||
|
if (item.CollectionType) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
className='cardImageIcon'
|
||||||
|
sx={{ color: 'inherit', fontSize: '5em' }}
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
|
{imageHelper.getLibraryIcon(item.CollectionType)}
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.Type && !(item.Type === BaseItemKind.TvChannel || item.Type === BaseItemKind.Studio )) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
className='cardImageIcon'
|
||||||
|
sx={{ color: 'inherit', fontSize: '5em' }}
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
|
{imageHelper.getItemTypeIcon(item.Type)}
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (defaultCardImageIcon) {
|
||||||
|
return (
|
||||||
|
<Icon
|
||||||
|
className='cardImageIcon'
|
||||||
|
sx={{ color: 'inherit', fontSize: '5em' }}
|
||||||
|
aria-hidden='true'
|
||||||
|
>
|
||||||
|
{defaultCardImageIcon}
|
||||||
|
</Icon>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <DefaultName item={item} />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultIconText;
|
23
src/components/common/DefaultName.tsx
Normal file
23
src/components/common/DefaultName.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import escapeHTML from 'escape-html';
|
||||||
|
import itemHelper from 'components/itemHelper';
|
||||||
|
import { isUsingLiveTvNaming } from '../cardbuilder/cardBuilderUtils';
|
||||||
|
import type { ItemDto } from 'types/itemDto';
|
||||||
|
|
||||||
|
interface DefaultNameProps {
|
||||||
|
item: ItemDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DefaultName: FC<DefaultNameProps> = ({ item }) => {
|
||||||
|
const defaultName = isUsingLiveTvNaming(item.Type) ?
|
||||||
|
item.Name :
|
||||||
|
itemHelper.getDisplayName(item);
|
||||||
|
return (
|
||||||
|
<Box className='cardText cardDefaultText'>
|
||||||
|
{escapeHTML(defaultName)}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DefaultName;
|
67
src/components/common/Image.tsx
Normal file
67
src/components/common/Image.tsx
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
import React, { FC, useCallback, useState } from 'react';
|
||||||
|
import { BlurhashCanvas } from 'react-blurhash';
|
||||||
|
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||||
|
|
||||||
|
const imageStyle: React.CSSProperties = {
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
width: '100%',
|
||||||
|
height: '100%',
|
||||||
|
zIndex: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ImageProps {
|
||||||
|
imgUrl: string;
|
||||||
|
blurhash?: string;
|
||||||
|
containImage: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Image: FC<ImageProps> = ({
|
||||||
|
imgUrl,
|
||||||
|
blurhash,
|
||||||
|
containImage
|
||||||
|
}) => {
|
||||||
|
const [isLoaded, setIsLoaded] = useState(false);
|
||||||
|
const [isLoadStarted, setIsLoadStarted] = useState(false);
|
||||||
|
const handleLoad = useCallback(() => {
|
||||||
|
setIsLoaded(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleLoadStarted = useCallback(() => {
|
||||||
|
setIsLoadStarted(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{!isLoaded && isLoadStarted && blurhash && (
|
||||||
|
<BlurhashCanvas
|
||||||
|
hash={blurhash}
|
||||||
|
width= {20}
|
||||||
|
height={20}
|
||||||
|
punch={1}
|
||||||
|
style={{
|
||||||
|
...imageStyle,
|
||||||
|
borderRadius: '0.2em',
|
||||||
|
pointerEvents: 'none'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<LazyLoadImage
|
||||||
|
key={imgUrl}
|
||||||
|
src={imgUrl}
|
||||||
|
style={{
|
||||||
|
...imageStyle,
|
||||||
|
objectFit: containImage ? 'contain' : 'cover'
|
||||||
|
}}
|
||||||
|
onLoad={handleLoad}
|
||||||
|
beforeLoad={handleLoadStarted}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Image;
|
22
src/components/common/InfoIconButton.tsx
Normal file
22
src/components/common/InfoIconButton.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import InfoIcon from '@mui/icons-material/Info';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
interface InfoIconButtonProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const InfoIconButton: FC<InfoIconButtonProps> = ({ className }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={className}
|
||||||
|
data-action='link'
|
||||||
|
title={globalize.translate('ButtonInfo')}
|
||||||
|
>
|
||||||
|
<InfoIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InfoIconButton;
|
36
src/components/common/Media.tsx
Normal file
36
src/components/common/Media.tsx
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client';
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import Image from './Image';
|
||||||
|
import DefaultIconText from './DefaultIconText';
|
||||||
|
import type { ItemDto } from 'types/itemDto';
|
||||||
|
|
||||||
|
interface MediaProps {
|
||||||
|
item: ItemDto;
|
||||||
|
imgUrl: string | undefined;
|
||||||
|
blurhash: string | undefined;
|
||||||
|
imageType?: ImageType
|
||||||
|
defaultCardImageIcon?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Media: FC<MediaProps> = ({
|
||||||
|
item,
|
||||||
|
imgUrl,
|
||||||
|
blurhash,
|
||||||
|
imageType,
|
||||||
|
defaultCardImageIcon
|
||||||
|
}) => {
|
||||||
|
return imgUrl ? (
|
||||||
|
<Image
|
||||||
|
imgUrl={imgUrl}
|
||||||
|
blurhash={blurhash}
|
||||||
|
containImage={item.Type === BaseItemKind.TvChannel || imageType === ImageType.Logo}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<DefaultIconText
|
||||||
|
item={item}
|
||||||
|
defaultCardImageIcon={defaultCardImageIcon}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Media;
|
23
src/components/common/MoreVertIconButton.tsx
Normal file
23
src/components/common/MoreVertIconButton.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
interface MoreVertIconButtonProps {
|
||||||
|
className?: string;
|
||||||
|
iconClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MoreVertIconButton: FC<MoreVertIconButtonProps> = ({ className, iconClassName }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={className}
|
||||||
|
data-action='menu'
|
||||||
|
title={globalize.translate('ButtonMore')}
|
||||||
|
>
|
||||||
|
<MoreVertIcon className={iconClassName} />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MoreVertIconButton;
|
25
src/components/common/NoItemsMessage.tsx
Normal file
25
src/components/common/NoItemsMessage.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
interface NoItemsMessageProps {
|
||||||
|
noItemsMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NoItemsMessage: FC<NoItemsMessageProps> = ({
|
||||||
|
noItemsMessage = 'MessageNoItemsAvailable'
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<Box className='noItemsMessage centerMessage'>
|
||||||
|
<Typography variant='h2'>
|
||||||
|
{globalize.translate('MessageNothingHere')}
|
||||||
|
</Typography>
|
||||||
|
<Typography paragraph variant='h2'>
|
||||||
|
{globalize.translate(noItemsMessage)}
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NoItemsMessage;
|
25
src/components/common/PlayArrowIconButton.tsx
Normal file
25
src/components/common/PlayArrowIconButton.tsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
interface PlayArrowIconButtonProps {
|
||||||
|
className: string;
|
||||||
|
action: string;
|
||||||
|
title: string;
|
||||||
|
iconClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlayArrowIconButton: FC<PlayArrowIconButtonProps> = ({ className, action, title, iconClassName }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={className}
|
||||||
|
data-action={action}
|
||||||
|
title={globalize.translate(title)}
|
||||||
|
>
|
||||||
|
<PlayArrowIcon className={iconClassName} />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlayArrowIconButton;
|
22
src/components/common/PlaylistAddIconButton.tsx
Normal file
22
src/components/common/PlaylistAddIconButton.tsx
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||||
|
import globalize from 'scripts/globalize';
|
||||||
|
|
||||||
|
interface PlaylistAddIconButtonProps {
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PlaylistAddIconButton: FC<PlaylistAddIconButtonProps> = ({ className }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={className}
|
||||||
|
data-action='addtoplaylist'
|
||||||
|
title={globalize.translate('AddToPlaylist')}
|
||||||
|
>
|
||||||
|
<PlaylistAddIcon />
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PlaylistAddIconButton;
|
24
src/components/common/RightIconButtons.tsx
Normal file
24
src/components/common/RightIconButtons.tsx
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import IconButton from '@mui/material/IconButton';
|
||||||
|
|
||||||
|
interface RightIconButtonsProps {
|
||||||
|
className?: string;
|
||||||
|
id: string;
|
||||||
|
icon: string;
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const RightIconButtons: FC<RightIconButtonsProps> = ({ className, id, title, icon }) => {
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
className={className}
|
||||||
|
data-action='custom'
|
||||||
|
data-customaction={id}
|
||||||
|
title={title}
|
||||||
|
>
|
||||||
|
{icon}
|
||||||
|
</IconButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RightIconButtons;
|
48
src/types/dataAttributes.ts
Normal file
48
src/types/dataAttributes.ts
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import type { CollectionType, UserItemDataDto } from '@jellyfin/sdk/lib/generated-client';
|
||||||
|
import type { NullableBoolean, NullableNumber, NullableString } from './itemDto';
|
||||||
|
|
||||||
|
export type AttributesOpts = {
|
||||||
|
context?: CollectionType | undefined,
|
||||||
|
parentId?: NullableString,
|
||||||
|
collectionId?: NullableString,
|
||||||
|
playlistId?: NullableString,
|
||||||
|
prefix?: NullableString,
|
||||||
|
action?: NullableString,
|
||||||
|
itemServerId?: NullableString,
|
||||||
|
itemId?: NullableString,
|
||||||
|
itemTimerId?: NullableString,
|
||||||
|
itemSeriesTimerId?: NullableString,
|
||||||
|
itemChannelId?: NullableString,
|
||||||
|
itemPlaylistItemId?: NullableString,
|
||||||
|
itemType?: NullableString,
|
||||||
|
itemMediaType?: NullableString,
|
||||||
|
itemCollectionType?: NullableString,
|
||||||
|
itemIsFolder?: NullableBoolean,
|
||||||
|
itemPath?: NullableString,
|
||||||
|
itemStartDate?: NullableString,
|
||||||
|
itemEndDate?: NullableString,
|
||||||
|
itemUserData?: UserItemDataDto
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DataAttributes = {
|
||||||
|
'data-playlistitemid'?: NullableString;
|
||||||
|
'data-timerid'?: NullableString;
|
||||||
|
'data-seriestimerid'?: NullableString;
|
||||||
|
'data-serverid'?: NullableString;
|
||||||
|
'data-id'?: NullableString;
|
||||||
|
'data-type'?: NullableString;
|
||||||
|
'data-collectionid'?: NullableString;
|
||||||
|
'data-playlistid'?: NullableString;
|
||||||
|
'data-mediatype'?: NullableString;
|
||||||
|
'data-channelid'?: NullableString;
|
||||||
|
'data-path'?: NullableString;
|
||||||
|
'data-collectiontype'?: NullableString;
|
||||||
|
'data-context'?: NullableString;
|
||||||
|
'data-parentid'?: NullableString;
|
||||||
|
'data-startdate'?: NullableString;
|
||||||
|
'data-enddate'?: NullableString;
|
||||||
|
'data-prefix'?: NullableString;
|
||||||
|
'data-action'?: NullableString;
|
||||||
|
'data-positionticks'?: NullableNumber;
|
||||||
|
'data-isfolder'?: NullableBoolean;
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
|
||||||
import type { DeviceInfo } from '@jellyfin/sdk/lib/generated-client/models/device-info';
|
import type { DeviceInfo } from '@jellyfin/sdk/lib/generated-client/models/device-info';
|
||||||
import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info';
|
import type { SessionInfo } from '@jellyfin/sdk/lib/generated-client/models/session-info';
|
||||||
|
|
||||||
|
@ -103,7 +104,41 @@ export function getLibraryIcon(library: string | null | undefined) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getItemTypeIcon(itemType: BaseItemKind | string) {
|
||||||
|
switch (itemType) {
|
||||||
|
case BaseItemKind.MusicAlbum:
|
||||||
|
return 'album';
|
||||||
|
case BaseItemKind.MusicArtist:
|
||||||
|
case BaseItemKind.Person:
|
||||||
|
return 'person';
|
||||||
|
case BaseItemKind.Audio:
|
||||||
|
return 'audiotrack';
|
||||||
|
case BaseItemKind.Movie:
|
||||||
|
return 'movie';
|
||||||
|
case BaseItemKind.Episode:
|
||||||
|
case BaseItemKind.Series:
|
||||||
|
return 'tv';
|
||||||
|
case BaseItemKind.Program:
|
||||||
|
return 'live_tv';
|
||||||
|
case BaseItemKind.Book:
|
||||||
|
return 'book';
|
||||||
|
case BaseItemKind.Folder:
|
||||||
|
return 'folder';
|
||||||
|
case BaseItemKind.BoxSet:
|
||||||
|
return 'collections';
|
||||||
|
case BaseItemKind.Playlist:
|
||||||
|
return 'view_list';
|
||||||
|
case BaseItemKind.Photo:
|
||||||
|
return 'photo';
|
||||||
|
case BaseItemKind.PhotoAlbum:
|
||||||
|
return 'photo_album';
|
||||||
|
default:
|
||||||
|
return 'folder';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
getDeviceIcon,
|
getDeviceIcon,
|
||||||
getLibraryIcon
|
getLibraryIcon,
|
||||||
|
getItemTypeIcon
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,8 +3,10 @@ import { ImageType } from '@jellyfin/sdk/lib/generated-client/models/image-type'
|
||||||
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
||||||
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
|
import { SortOrder } from '@jellyfin/sdk/lib/generated-client/models/sort-order';
|
||||||
import * as userSettings from 'scripts/settings/userSettings';
|
import * as userSettings from 'scripts/settings/userSettings';
|
||||||
|
import layoutManager from 'components/layoutManager';
|
||||||
import { EpisodeFilter, FeatureFilters, LibraryViewSettings, ParentId, VideoBasicFilter, ViewMode } from '../types/library';
|
import { EpisodeFilter, FeatureFilters, LibraryViewSettings, ParentId, VideoBasicFilter, ViewMode } from '../types/library';
|
||||||
import { LibraryTab } from 'types/libraryTab';
|
import { LibraryTab } from 'types/libraryTab';
|
||||||
|
import type { AttributesOpts, DataAttributes } from 'types/dataAttributes';
|
||||||
|
|
||||||
export const getVideoBasicFilter = (libraryViewSettings: LibraryViewSettings) => {
|
export const getVideoBasicFilter = (libraryViewSettings: LibraryViewSettings) => {
|
||||||
let isHd;
|
let isHd;
|
||||||
|
@ -164,3 +166,31 @@ export const getDefaultLibraryViewSettings = (viewType: LibraryTab): LibraryView
|
||||||
StartIndex: 0
|
StartIndex: 0
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function getDataAttributes(
|
||||||
|
opts: AttributesOpts
|
||||||
|
): DataAttributes {
|
||||||
|
return {
|
||||||
|
'data-context': opts.context,
|
||||||
|
'data-collectionid': opts.collectionId,
|
||||||
|
'data-playlistid': opts.playlistId,
|
||||||
|
'data-parentid': opts.parentId,
|
||||||
|
'data-playlistitemid': opts.itemPlaylistItemId,
|
||||||
|
'data-action': layoutManager.tv ? opts.action : null,
|
||||||
|
'data-serverid': opts.itemServerId,
|
||||||
|
'data-id': opts.itemId,
|
||||||
|
'data-timerid': opts.itemTimerId,
|
||||||
|
'data-seriestimerid': opts.itemSeriesTimerId,
|
||||||
|
'data-channelid': opts.itemChannelId,
|
||||||
|
'data-type': opts.itemType,
|
||||||
|
'data-mediatype': opts.itemMediaType,
|
||||||
|
'data-collectiontype': opts.itemCollectionType,
|
||||||
|
'data-isfolder': opts.itemIsFolder,
|
||||||
|
'data-path': opts.itemPath,
|
||||||
|
'data-prefix': opts.prefix,
|
||||||
|
'data-positionticks': opts.itemUserData?.PlaybackPositionTicks,
|
||||||
|
'data-startdate': opts.itemStartDate?.toString(),
|
||||||
|
'data-enddate': opts.itemEndDate?.toString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -200,6 +200,8 @@ const config = {
|
||||||
path.resolve(__dirname, 'node_modules/markdown-it'),
|
path.resolve(__dirname, 'node_modules/markdown-it'),
|
||||||
path.resolve(__dirname, 'node_modules/mdurl'),
|
path.resolve(__dirname, 'node_modules/mdurl'),
|
||||||
path.resolve(__dirname, 'node_modules/punycode'),
|
path.resolve(__dirname, 'node_modules/punycode'),
|
||||||
|
path.resolve(__dirname, 'node_modules/react-blurhash'),
|
||||||
|
path.resolve(__dirname, 'node_modules/react-lazy-load-image-component'),
|
||||||
path.resolve(__dirname, 'node_modules/react-router'),
|
path.resolve(__dirname, 'node_modules/react-router'),
|
||||||
path.resolve(__dirname, 'node_modules/screenfull'),
|
path.resolve(__dirname, 'node_modules/screenfull'),
|
||||||
path.resolve(__dirname, 'node_modules/ssr-window'),
|
path.resolve(__dirname, 'node_modules/ssr-window'),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue