From c260daa433c45e86ac54921ac633296805c2ca78 Mon Sep 17 00:00:00 2001 From: duehoa1211 Date: Sat, 13 Jan 2024 17:13:26 +0700 Subject: [PATCH] Mitigating responsive in web (#752) * Update styles for Collections views * Add button dropdown * Add logout button * update responsive for unregconized file tab * mitigating responsive for credits, episode, images, tags * fix reponsive bug in unregconized file tab * update change request * update button padding * fix linting and code conflict * Fix bugs reponsive for Unregconized Tabs and File Search * remove filcker for and fix bug reponsive for file search, update change request * Fix Unrecognized Files Toolbar. --------- Co-authored-by: ElementalCrisis <9443295+ElementalCrisis@users.noreply.github.com> --- .../Collection/Series/EpisodeDetails.tsx | 28 +++-- .../Collection/Series/EpisodeFiles.tsx | 8 +- src/components/Collection/SeriesSidePanel.tsx | 4 +- src/components/Input/ButtonDropdown.tsx | 108 ++++++++++++++++ src/components/Input/Input.tsx | 77 +++++++++++- src/components/Layout/TopNav.tsx | 11 ++ src/components/Panels/ShokoPanel.tsx | 4 +- src/components/Utilities/ItemCount.tsx | 29 +++-- src/pages/collection/Series.tsx | 7 +- src/pages/collection/series/SeriesCredits.tsx | 8 +- .../collection/series/SeriesEpisodes.tsx | 41 +++--- src/pages/collection/series/SeriesImages.tsx | 12 +- .../collection/series/SeriesOverview.tsx | 4 +- src/pages/collection/series/SeriesTags.tsx | 2 +- src/pages/utilities/FileSearch.tsx | 30 ++--- .../UnrecognizedTab.tsx | 119 +++++++++++++----- tailwind.config.js | 5 +- 17 files changed, 378 insertions(+), 119 deletions(-) create mode 100644 src/components/Input/ButtonDropdown.tsx diff --git a/src/components/Collection/Series/EpisodeDetails.tsx b/src/components/Collection/Series/EpisodeDetails.tsx index 056f4769f..341a8e2be 100644 --- a/src/components/Collection/Series/EpisodeDetails.tsx +++ b/src/components/Collection/Series/EpisodeDetails.tsx @@ -15,7 +15,7 @@ const getDuration = (duration: string) => { function EpisodeDetails({ episode }: { episode: EpisodeType }) { return ( -
+
{episode.AniDB?.Type.replace('Normal', 'Episode').replace('ThemeSong', 'Credit') ?? 'Episode'} @@ -35,16 +35,22 @@ function EpisodeDetails({ episode }: { episode: EpisodeType }) { {episode.Name}
-
- - {dayjs(episode.AniDB?.AirDate).format('MMMM Do, YYYY')} - - {getDuration(episode.Duration)} - - {toNumber(episode.AniDB?.Rating.Value).toFixed(2)} -  ( - {episode.AniDB?.Rating.Votes} -  Votes) +
+
+ + {dayjs(episode.AniDB?.AirDate).format('MMMM Do, YYYY')} +
+
+ + {getDuration(episode.Duration)} +
+
+ + {toNumber(episode.AniDB?.Rating.Value).toFixed(2)} +  ( + {episode.AniDB?.Rating.Votes} +  Votes) +
diff --git a/src/components/Collection/Series/EpisodeFiles.tsx b/src/components/Collection/Series/EpisodeFiles.tsx index 1b0ab2dbe..252432e4d 100644 --- a/src/components/Collection/Series/EpisodeFiles.tsx +++ b/src/components/Collection/Series/EpisodeFiles.tsx @@ -70,11 +70,11 @@ const EpisodeFiles = ({ animeId, episodeFiles, episodeId }: Props) => { className="flex cursor-pointer items-center gap-x-2" onClick={() => handleRescan(selectedFile.ID)} > - + Force Update File Info
- + {selectedFile.IsVariation ? 'Unmark' : 'Mark'}  File as Variation
@@ -109,14 +109,14 @@ const EpisodeFiles = ({ animeId, episodeFiles, episodeId }: Props) => {
diff --git a/src/components/Collection/SeriesSidePanel.tsx b/src/components/Collection/SeriesSidePanel.tsx index 1c70038ff..f396f3094 100644 --- a/src/components/Collection/SeriesSidePanel.tsx +++ b/src/components/Collection/SeriesSidePanel.tsx @@ -22,10 +22,10 @@ const SeriesSidePanel = ({ series }: SeriesSidePanelProps) => { const onClickHandler = useEventCallback(() => setShowEditSeriesModal(true)); return ( -
+
{(series.AniDB?.Restricted ?? false) && (
diff --git a/src/components/Input/ButtonDropdown.tsx b/src/components/Input/ButtonDropdown.tsx new file mode 100644 index 000000000..6b96fa6b7 --- /dev/null +++ b/src/components/Input/ButtonDropdown.tsx @@ -0,0 +1,108 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import useMeasure from 'react-use-measure'; +import { mdiChevronDown, mdiLoading } from '@mdi/js'; +import Icon from '@mdi/react'; +import cx from 'classnames'; +import { useEventCallback } from 'usehooks-ts'; + +type Props = { + buttonTypes?: 'primary' | 'secondary' | 'danger'; + className?: string; + children: React.ReactNode; + content: React.ReactNode | string; + disabled?: boolean; + loading?: boolean; + loadingSize?: number; + tooltip?: string; +}; + +const buttonTypeClasses = { + primary: + 'bg-button-primary text-button-primary-text border-2 !border-button-primary-border rounded-md hover:bg-button-primary-hover', + secondary: + 'bg-button-secondary text-button-secondary-text border-2 !border-button-secondary-border rounded-md hover:bg-button-secondary-hover', + danger: + 'bg-button-danger text-button-danger-text border-2 !border-button-danger-border rounded-md hover:bg-button-danger-hover', +}; + +const ButtonDropdown = (props: Props) => { + const { + buttonTypes, + children, + className, + content, + disabled, + loading, + loadingSize, + tooltip, + } = props; + + const [open, setOpen] = useState(false); + const onClick = useEventCallback(() => { + setOpen(prev => !prev); + }); + const [containerRef, containerBounds] = useMeasure(); + const [menuRef, menuBounds] = useMeasure(); + const [windowWidth, setWindowWidth] = useState(window.innerWidth); + const menuShift = useMemo(() => containerBounds.x - (menuBounds.width - (containerBounds.width)), [ + containerBounds.x, + containerBounds.width, + menuBounds.width, + ]); + + const isOutOfBounds = useMemo(() => containerBounds.right > windowWidth, [windowWidth, containerBounds.right]); + + useEffect(() => { + const resizeEvent = () => { + setOpen(_ => false); + setWindowWidth(_ => window.innerWidth); + }; + window.addEventListener('resize', resizeEvent); + return () => { + window.removeEventListener('resize', resizeEvent); + }; + }, [className]); + + return ( +
+ +
+ {children} +
+
+ ); +}; + +export default ButtonDropdown; diff --git a/src/components/Input/Input.tsx b/src/components/Input/Input.tsx index 3fa320ba7..dbf49e9c3 100644 --- a/src/components/Input/Input.tsx +++ b/src/components/Input/Input.tsx @@ -1,8 +1,12 @@ -import React, { useContext, useEffect, useRef } from 'react'; +import React, { useContext, useEffect, useMemo, useRef } from 'react'; +import { mdiCloseCircleOutline } from '@mdi/js'; import { Icon } from '@mdi/react'; import cx from 'classnames'; import { BodyVisibleContext } from '@/core/router'; +import useEventCallback from '@/hooks/useEventCallback'; + +import Button from './Button'; type Props = { id: string; @@ -20,6 +24,9 @@ type Props = { endIcons?: { icon: string, className?: string, onClick?: React.MouseEventHandler }[]; startIcon?: string; inline?: boolean; + isOverlay?: boolean; + overlayClassName?: string; + onToggleOverlay?: (show: boolean) => void; }; function Input(props: Props) { @@ -32,9 +39,12 @@ function Input(props: Props) { id, inline, inputClassName, + isOverlay, label, onChange, onKeyUp, + onToggleOverlay, + overlayClassName, placeholder, startIcon, type, @@ -43,6 +53,7 @@ function Input(props: Props) { const bodyVisible = useContext(BodyVisibleContext); const inputRef = useRef(null); + const [isShow, setIsShow] = React.useState(false); useEffect(() => { if (autoFocus && bodyVisible && inputRef.current) { @@ -50,9 +61,45 @@ function Input(props: Props) { } }, [autoFocus, bodyVisible]); + useEffect(() => { + if (isOverlay) return; + setIsShow(_ => false); + onToggleOverlay?.(false); + }, [isOverlay, onToggleOverlay]); + + const handleOverlayClick = useEventCallback(() => { + setIsShow(prev => !prev); + onToggleOverlay?.(!isShow); + }); + + const inputContainerClassName = useMemo(() => { + const combier = (input: string) => cx([overlayClassName, input]); + if (isOverlay && inline) { + if (!isShow) return combier('hidden 2xl:flex flex-row justify-center'); + return combier('flex flex-row justify-center'); + } + if (isOverlay && !inline) { + if (!isShow) return combier('hidden 2xl:inline'); + return combier(''); + } + if (!isOverlay && inline) { + return 'flex flex-row justify-center'; + } + + return ''; + }, [isShow, isOverlay, inline, overlayClassName]); + return ( -
-
diff --git a/src/components/Panels/ShokoPanel.tsx b/src/components/Panels/ShokoPanel.tsx index 8839b50c0..0e353c32b 100644 --- a/src/components/Panels/ShokoPanel.tsx +++ b/src/components/Panels/ShokoPanel.tsx @@ -39,8 +39,8 @@ const ShokoPanel = ( className, )} > -
- {title} +
+ {title}
event.stopPropagation()} diff --git a/src/components/Utilities/ItemCount.tsx b/src/components/Utilities/ItemCount.tsx index c015d4714..c29d6215b 100644 --- a/src/components/Utilities/ItemCount.tsx +++ b/src/components/Utilities/ItemCount.tsx @@ -1,13 +1,28 @@ import React from 'react'; -const ItemCount = ({ count, series = false }: { count: number, series?: boolean }) => ( -
- - {count} -   +const ItemCount = ({ count, selected, series = false }: { count: number, selected?: number, series?: boolean }) => ( +
+ + + {count} +   + + {series && 'Series'} + {!series && (count === 1 ? 'File' : 'Files')} - {series && 'Series'} - {!series && (count === 1 ? 'File' : 'Files')} + {(selected ?? 0) > 0 && ( + <> +  |  + + + {selected ?? 0} +   + + {series && 'Series'} + Selected + + + )}
); diff --git a/src/pages/collection/Series.tsx b/src/pages/collection/Series.tsx index b24d5013c..4263ba1c7 100644 --- a/src/pages/collection/Series.tsx +++ b/src/pages/collection/Series.tsx @@ -91,9 +91,8 @@ const Series = () => { } return ( -
- {/* I don't know why w-0 helps but it does, don't touch it. Without it, the container grows outside the screen */} -
+
+
@@ -137,7 +136,7 @@ const Series = () => {
-
+
diff --git a/src/pages/collection/series/SeriesCredits.tsx b/src/pages/collection/series/SeriesCredits.tsx index f65595273..537e75271 100644 --- a/src/pages/collection/series/SeriesCredits.tsx +++ b/src/pages/collection/series/SeriesCredits.tsx @@ -79,7 +79,7 @@ const SeriesCredits = () => {
-
+
{map( filter(cast, value => (mode === 'Character' ? isCharacter(value) : !isCharacter(value))), (item, idx) => ( @@ -91,15 +91,15 @@ const SeriesCredits = () => { {mode === 'Character' && ( )}
-
{item.Character?.Name}
+
{item.Character?.Name}
{item.Staff?.Name}
{item.RoleDetails}
diff --git a/src/pages/collection/series/SeriesEpisodes.tsx b/src/pages/collection/series/SeriesEpisodes.tsx index 951a8a146..74ca44331 100644 --- a/src/pages/collection/series/SeriesEpisodes.tsx +++ b/src/pages/collection/series/SeriesEpisodes.tsx @@ -57,10 +57,11 @@ const SeriesEpisodes = () => { const animeId = useMemo(() => seriesQuery.data?.IDs.AniDB ?? 0, [seriesQuery.data]); const { scrollRef } = useOutletContext<{ scrollRef: React.RefObject }>(); + const rowVirtualizer = useVirtualizer({ count: episodeCount, getScrollElement: () => scrollRef.current, - estimateSize: () => 332, // 332px is the minimum height of a loaded row + estimateSize: () => 345, // 332px is the minimum height of a loaded row overscan: 5, }); const virtualItems = rowVirtualizer.getVirtualItems(); @@ -95,12 +96,12 @@ const SeriesEpisodes = () => { { onChange={(event: React.ChangeEvent) => setSearch(event.target.value)} /> ( + <> + {selectedRows.length !== 0 + && ( + { + setSelectedRows([]); + invalidateQueries(['files', { include_only: ['Unrecognized'] }]); + }} + icon={mdiRefresh} + name="Refresh" + /> + )} + + + setSeriesSelectModal(true)} icon={mdiFileDocumentOutline} name="Add To AniDB" /> + + + setSelectedRows([])} + icon={mdiCloseCircleOutline} + name="Cancel Selection" + highlight + /> + + ), [ + ignoreFiles, + rehashFiles, + rescanFiles, + setSelectedRows, + setSeriesSelectModal, + showDeleteConfirmation, + selectedRows, + ]); + return ( -
- + <> +
{ setSelectedRows([]); @@ -158,26 +201,20 @@ const Menu = ( icon={mdiRefresh} name="Refresh" /> - - - - - setSeriesSelectModal(true)} icon={mdiFileDocumentOutline} name="Add To AniDB" /> - - - setSelectedRows([])} - icon={mdiCloseCircleOutline} - name="Cancel Selection" - highlight - /> - - - {selectedRows.length} -   - - {selectedRows.length === 1 ? 'File ' : 'Files '} - Selected + + {renderSelectedRowActions} + +
+ +
+ Options}> + {renderSelectedRowActions} + +
+ -
+ ); }; @@ -274,11 +311,22 @@ function UnrecognizedTab() { ), })); + const [tabContainerRef, bounds] = useMeasure(); + const isOverlay = bounds.width <= 1365 && bounds.width >= 1206 && selectedRows.length !== 0; + + const searchClassName = useMemo(() => { + if (bounds.width < 1547 && selectedRows.length !== 0) { + return '!w-[calc(100vw-624px)]'; + } + if (isOverlay) return '!w-[calc(100vw-38.4rem)]'; + return ''; + }, [selectedRows.length, bounds, isOverlay]); + return ( <> -
+
- } options={}> + } options={}>
setSearch(e.target.value)} - inputClassName="px-4 py-3" + inputClassName={cx('px-4 py-3', searchClassName)} + overlayClassName="grow 2xl:w-auto 2xl:grow-0" /> - +
- +
diff --git a/tailwind.config.js b/tailwind.config.js index 36173dbab..2b77e783f 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -107,7 +107,10 @@ module.exports = { }, borderWidth: { '16': '16px', - } + }, + screens: { + '3xl': '1630px', + }, }, }, variants: {