From b060232090edd880ee3932fadd27eb64dc2882eb Mon Sep 17 00:00:00 2001 From: Genadijus Paleckis Date: Sat, 4 Nov 2023 09:41:24 +0000 Subject: [PATCH] more eslint fixed (#387) * enable react/no-unstable-nested-components rule * enable react/no-unused-class-component-methods rule * enable react/no-array-index-key rule * enable react/destructuring-assignment rule * enable react/sort-comp rule --- .eslintrc.js | 10 +- src/components/AlbumLocationMap.tsx | 14 +- src/components/LocationMap.tsx | 6 +- src/components/charts/FaceClusterGraph.tsx | 10 +- src/components/charts/SocialGraph.tsx | 4 +- src/components/charts/WordCloud.tsx | 12 +- src/components/locationLink.tsx | 4 +- src/components/photolist/FavoritedOverlay.tsx | 8 +- src/components/photolist/PhotoListView.tsx | 2 +- src/components/react-pig/index.jsx | 180 +++++++++--------- src/layouts/settings/Library.tsx | 38 ++-- 11 files changed, 146 insertions(+), 142 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 5be4cab4..5c660fe1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -16,15 +16,9 @@ module.exports = { "import/prefer-default-export": "off", "import/no-cycle": "warn", "react/prop-types": "warn", - "react/destructuring-assignment": "warn", - "react/sort-comp": "warn", - "react/no-access-state-in-setstate": "warn", - "react/jsx-props-no-spreading": "warn", - "react-hooks/exhaustive-deps": "warn", - "react/no-unused-class-component-methods": "warn", - "react/no-array-index-key": "warn", + "react/jsx-props-no-spreading": "off", // some Mantine components need to use spread operator + "react-hooks/exhaustive-deps": "off", // at this stage it is too risky to enable this "react/require-default-props": "warn", - "react/no-unstable-nested-components": "warn", }, parserOptions: { project: "./tsconfig.eslint.json", diff --git a/src/components/AlbumLocationMap.tsx b/src/components/AlbumLocationMap.tsx index ce4bd2c0..dcd849ab 100644 --- a/src/components/AlbumLocationMap.tsx +++ b/src/components/AlbumLocationMap.tsx @@ -5,14 +5,8 @@ type Props = { photos: any[]; }; -export function AlbumLocationMap(props: Readonly) { - const photosWithGPS = props.photos.filter(photo => { - if (photo.exif_gps_lon !== null && photo.exif_gps_lon) { - return true; - } - return false; - }); - +export function AlbumLocationMap({ photos }: Readonly) { + const photosWithGPS = photos.filter(photo => photo.exif_gps_lon !== null && photo.exif_gps_lon); let sumLat = 0; let sumLon = 0; for (const element of photosWithGPS) { @@ -22,8 +16,8 @@ export function AlbumLocationMap(props: Readonly) { const avgLat = sumLat / photosWithGPS.length; const avgLon = sumLon / photosWithGPS.length; - const markers = photosWithGPS.map((photo, idx) => ( - + const markers = photosWithGPS.map(photo => ( + )); if (photosWithGPS.length > 0) { return ( diff --git a/src/components/LocationMap.tsx b/src/components/LocationMap.tsx index 2b7dedb4..cbc045cd 100644 --- a/src/components/LocationMap.tsx +++ b/src/components/LocationMap.tsx @@ -6,16 +6,16 @@ type Props = { photos: any[]; }; -export function LocationMap(props: Props) { +export function LocationMap({ photos }: Props) { const mapRef = useRef(null); const height = "200px"; useEffect(() => { mapRef.current?.leafletElement.invalidateSize(); - }, [height, props]); + }, [height, photos]); - const photosWithGPS = props.photos.filter(photo => { + const photosWithGPS = photos.filter(photo => { if (photo.exif_gps_lon !== null && photo.exif_gps_lon) { return true; } diff --git a/src/components/charts/FaceClusterGraph.tsx b/src/components/charts/FaceClusterGraph.tsx index 043f850e..89ba6fb6 100644 --- a/src/components/charts/FaceClusterGraph.tsx +++ b/src/components/charts/FaceClusterGraph.tsx @@ -13,7 +13,7 @@ type Props = { height: number; }; -export function FaceClusterGraph(props: Props) { +export function FaceClusterGraph({ height }: Props) { const [hintValue, setHintValue] = useState({} as any); const { t } = useTranslation(); const { observe: observeChange, width } = useDimensions({ @@ -30,8 +30,8 @@ export function FaceClusterGraph(props: Props) { const personNames = [...new Set(facesVis.map((el: any) => el.person_name))]; - const mappedScatter = personNames.map((person_name, idx) => { - const thisPersonVis = facesVis.filter((el: any) => person_name === el.person_name); + const mappedScatter = personNames.map(name => { + const thisPersonVis = facesVis.filter((el: any) => name === el.person_name); const thisPersonData = thisPersonVis.map((el: any) => ({ x: el.value.x, y: el.value.y, @@ -44,7 +44,7 @@ export function FaceClusterGraph(props: Props) { return ( { setHintValue(d); @@ -61,7 +61,7 @@ export function FaceClusterGraph(props: Props) { {t("facecluster")} {t("faceclusterexplanation")} - + {mappedScatter} diff --git a/src/components/charts/SocialGraph.tsx b/src/components/charts/SocialGraph.tsx index 81a60d2f..29d77748 100644 --- a/src/components/charts/SocialGraph.tsx +++ b/src/components/charts/SocialGraph.tsx @@ -12,7 +12,7 @@ type Props = { height: number; }; -export function SocialGraph(props: Props) { +export function SocialGraph({ height }: Props) { const { colorScheme } = useMantineColorScheme(); const { observe: observeChange, width } = useDimensions({ onResize: ({ observe }) => { @@ -49,7 +49,7 @@ export function SocialGraph(props: Props) { highlightColor: "orange", color: "#12939A", }, - height: props.height, + height, width, }; let graph; diff --git a/src/components/charts/WordCloud.tsx b/src/components/charts/WordCloud.tsx index 0fb96e2f..b7e25a07 100644 --- a/src/components/charts/WordCloud.tsx +++ b/src/components/charts/WordCloud.tsx @@ -24,25 +24,27 @@ export function WordCloud(props: Props) { const { t } = useTranslation(); const title = () => { + const { type } = props; let result = t("people"); - if (props.type === "captions") { + if (type === "captions") { result = t("things"); } - if (props.type === "location") { + if (type === "location") { result = t("places"); } return result; }; const series = () => { + const { type } = props; if (fetchedWordCloud) { - if (props.type === "people") { + if (type === "people") { return [{ data: wordCloud.people }]; } - if (props.type === "captions") { + if (type === "captions") { return [{ data: wordCloud.captions }]; } - if (props.type === "location") { + if (type === "location") { return [{ data: wordCloud.locations }]; } } diff --git a/src/components/locationLink.tsx b/src/components/locationLink.tsx index acdd5145..05afa018 100644 --- a/src/components/locationLink.tsx +++ b/src/components/locationLink.tsx @@ -160,7 +160,7 @@ export function LocationLink(props: Props) { ); })} - {tree.descendants().map((node, key) => { + {tree.descendants().map(node => { const rectWidth = 120; const rectHeight = 30; @@ -179,7 +179,7 @@ export function LocationLink(props: Props) { } return ( - + {node.depth === 0 && ( store.user.userSelfDetails); - return item.item.rating >= rating && ; +export function FavoritedOverlay({ item }: { item: PigPhoto }) { + const { favorite_min_rating: favoriteMinRating } = useAppSelector(store => store.user.userSelfDetails); + const { rating } = item; + return rating >= favoriteMinRating && ; } diff --git a/src/components/photolist/PhotoListView.tsx b/src/components/photolist/PhotoListView.tsx index 79b4c955..cdfe9482 100644 --- a/src/components/photolist/PhotoListView.tsx +++ b/src/components/photolist/PhotoListView.tsx @@ -348,7 +348,7 @@ function PhotoListViewComponent(props: Props) { getUrl={getUrl} toprightoverlay={FavoritedOverlay} bottomleftoverlay={VideoOverlay} - numberOfItems={numberOfItems || idx2hashRef.current.length} + numberOfItems={numberOfItems ?? idx2hashRef.current.length} updateItems={updateItems ? throttledUpdateItems : () => {}} updateGroups={updateGroups ? throttledUpdateGroups : () => {}} bgColor="inherit" diff --git a/src/components/react-pig/index.jsx b/src/components/react-pig/index.jsx index f8a92006..f6c11c51 100644 --- a/src/components/react-pig/index.jsx +++ b/src/components/react-pig/index.jsx @@ -76,7 +76,6 @@ export default class Pig extends Component { this.totalHeight = 0; this.containerRef = React.createRef(); - this.titleRef = React.createRef(); this.minAspectRatio = null; this.latestYOffset = 0; this.previousYOffset = 0; @@ -101,6 +100,37 @@ export default class Pig extends Component { this.debouncedResize = debounce(this.onResize, 500); } + componentDidMount() { + this.container = this.containerRef.current; + this.containerOffsetTop = this.container.offsetTop; + this.containerWidth = this.container.offsetWidth; + + this.imageData = this.getUpdatedImageLayout(); + this.setRenderedItems(this.imageData); + + if (typeof window === "undefined") return; + window.addEventListener("scroll", this.throttledScroll); + window.addEventListener("resize", this.debouncedResize); + } + + componentDidUpdate(prevProps) { + if (this.props !== prevProps) { + const { imageData } = this.props; + this.imageData = imageData; + this.imageData = this.getUpdatedImageLayout(); + this.container.style.height = `${this.totalHeight}px`; // set the container height again based on new layout + this.containerWidth = this.container.offsetWidth; + this.containerOffsetTop = this.container.offsetTop; + this.windowHeight = window.innerHeight; + this.setRenderedItems(this.imageData); + } + } + + componentWillUnmount() { + window.removeEventListener("scroll", this.throttledScroll); + window.removeEventListener("resize", this.debouncedResize); + } + setRenderedItems(imageData) { // Set the container height, only need to do this once. if (!this.container.style.height) this.container.style.height = `${this.totalHeight}px`; @@ -135,7 +165,8 @@ export default class Pig extends Component { this.setState({ scrollSpeed }); // dismiss any active Tile - if (this.state.activeTileUrl) this.setState({ activeTileUrl: null }); + const { activeTileUrl } = this.state; + if (activeTileUrl) this.setState({ activeTileUrl: null }); }); }; @@ -148,30 +179,6 @@ export default class Pig extends Component { this.windowHeight = window.innerHeight; }; - defaultHandleSelection = item => { - console.log(item); - let newSelectedItems = this.state.selectedItems; - if (newSelectedItems.includes(item)) { - newSelectedItems = newSelectedItems.filter(value => value !== item); - } else { - newSelectedItems = newSelectedItems.concat(item); - } - this.setState({ selectedItems: newSelectedItems }); - }; - - defaultHandleClick = (event, item) => { - // if an image is already the width of the container, don't expand it on click - if (item.style.width >= this.containerWidth) { - this.setState({ activeTileUrl: null }); - return; - } - - this.setState({ - // if Tile is already active, deactivate it - activeTileUrl: item.url !== this.state.activeTileUrl ? item.url : null, - }); - }; - getUpdatedImageLayout() { const wrapperWidth = this.container.offsetWidth; @@ -200,76 +207,79 @@ export default class Pig extends Component { return imageData; } - componentDidUpdate(prevProps) { - if (this.props !== prevProps) { - this.imageData = this.props.imageData; - this.imageData = this.getUpdatedImageLayout(); - this.container.style.height = `${this.totalHeight}px`; // set the container height again based on new layout - this.containerWidth = this.container.offsetWidth; - this.containerOffsetTop = this.container.offsetTop; - this.windowHeight = window.innerHeight; - this.setRenderedItems(this.imageData); + defaultHandleSelection = item => { + console.log(item); + let { newSelectedItems } = this.state; + if (newSelectedItems.includes(item)) { + newSelectedItems = newSelectedItems.filter(value => value !== item); + } else { + newSelectedItems = newSelectedItems.concat(item); } - } - - componentDidMount() { - this.container = this.containerRef.current; - this.containerOffsetTop = this.container.offsetTop; - this.containerWidth = this.container.offsetWidth; + this.setState({ selectedItems: newSelectedItems }); + }; - this.imageData = this.getUpdatedImageLayout(); - this.setRenderedItems(this.imageData); + defaultHandleClick = (event, item) => { + // if an image is already the width of the container, don't expand it on click + if (item.style.width >= this.containerWidth) { + this.setState({ activeTileUrl: null }); + return; + } - if (typeof window === "undefined") return; - window.addEventListener("scroll", this.throttledScroll); - window.addEventListener("resize", this.debouncedResize); - } + const { activeTileUrl } = this.state; + this.setState({ + // if Tile is already active, deactivate it + activeTileUrl: item.url !== activeTileUrl ? item.url : null, + }); + }; - componentWillUnmount() { - window.removeEventListener("scroll", this.throttledScroll); - window.removeEventListener("resize", this.debouncedResize); - } + renderTile = item => { + const { useLqip, selectedItems, thumbnailSize, toprightoverlay, bottomleftoverlay } = this.props; + const { selectedItems: stateSelectedItems, activeTileUrl, scrollSpeed } = this.state; + return ( + selectedItem.id === item.id) >= 0 + : stateSelectedItems.includes(item) + } + activeTileUrl={activeTileUrl} + settings={this.settings} + thumbnailSize={thumbnailSize} + scrollSpeed={scrollSpeed} + toprightoverlay={toprightoverlay} + bottomleftoverlay={bottomleftoverlay} + /> + ); + }; - renderTile = item => ( - selectedItem.id === item.id) >= 0 - : this.state.selectedItems.includes(item) - } - activeTileUrl={this.state.activeTileUrl} - settings={this.settings} - thumbnailSize={this.props.thumbnailSize} - scrollSpeed={this.state.scrollSpeed} - toprightoverlay={this.props.toprightoverlay} - bottomleftoverlay={this.props.bottomleftoverlay} - /> - ); - - renderGroup = group => ( - - - {group.items.map(item => this.renderTile(item))} - - ); + renderGroup = group => { + const { activeTileUrl } = this.state; + return ( + + + {group.items.map(item => this.renderTile(item))} + + ); + }; renderFlat = item => this.renderTile(item); render() { + const { renderedItems } = this.state; return (
- {this.state.renderedItems.map(item => { + {renderedItems.map(item => { if (this.settings.groupByDate) { return this.renderGroup(item); } diff --git a/src/layouts/settings/Library.tsx b/src/layouts/settings/Library.tsx index d2555e7b..00ccbb49 100644 --- a/src/layouts/settings/Library.tsx +++ b/src/layouts/settings/Library.tsx @@ -54,6 +54,7 @@ import { ModalNextcloudScanDirectoryEdit } from "../../components/modals/ModalNe import { CountStats } from "../../components/statistics"; import i18n from "../../i18n"; import { useAppDispatch, useAppSelector } from "../../store/store"; +import { IUser } from "../../store/user/user.zod"; const useStyles = createStyles(theme => ({ button: { @@ -69,6 +70,20 @@ const useStyles = createStyles(theme => ({ }, })); +function BadgeIcon(details: IUser, isSuccess: boolean, isError: boolean, isFetching: boolean) { + const { nextcloud_server_address: server } = details; + if (isSuccess && server) { + return ; + } + if (isError) { + return ; + } + if (isFetching) { + return ; + } + return ; +} + export function Library() { const [isOpen, { open, close }] = useDisclosure(false); const [isOpenUpdateDialog, setIsOpenUpdateDialog] = useState(false); @@ -149,19 +164,6 @@ export function Library() { } } - function BadgeIcon() { - if (isNextcloudSuccess && userSelfDetails.nextcloud_server_address) { - return ; - } - if (isNextcloudError) { - return ; - } - if (isNextcloudFetching) { - return ; - } - return ; - } - return ( @@ -377,8 +379,8 @@ export function Library() { onClick={() => { dispatch(api.endpoints.trainFaces.initiate()); showNotification({ - message: i18n.t("toasts.trainingstarted"), - title: i18n.t("toasts.trainingstartedtitle"), + message: i18n.t("toasts.trainingstarted"), + title: i18n.t("toasts.trainingstartedtitle"), color: "teal", }); }} @@ -404,8 +406,8 @@ export function Library() { onClick={() => { dispatch(api.endpoints.rescanFaces.initiate()); showNotification({ - message: i18n.t("toasts.rescanfaces"), - title: i18n.t("toasts.rescanfacestitle"), + message: i18n.t("toasts.rescanfaces"), + title: i18n.t("toasts.rescanfacestitle"), color: "teal", }); }} @@ -428,7 +430,7 @@ export function Library() { size="xs" p={10} style={{ marginLeft: -20 }} - leftSection={BadgeIcon()} + leftSection={BadgeIcon(userSelfDetails, isNextcloudSuccess, isNextcloudError, isNextcloudFetching)} variant="outline" color={nextcloudStatusColor} >