Skip to content

Commit

Permalink
Mitigating responsive in web (#752)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
duehoa1211 and ElementalCrisis authored Jan 13, 2024
1 parent 7f17467 commit c260daa
Show file tree
Hide file tree
Showing 17 changed files with 378 additions and 119 deletions.
28 changes: 17 additions & 11 deletions src/components/Collection/Series/EpisodeDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const getDuration = (duration: string) => {

function EpisodeDetails({ episode }: { episode: EpisodeType }) {
return (
<div className="flex grow flex-col gap-y-4">
<div className="flex max-h-[13rem] grow flex-col gap-y-4 overflow-hidden">
<div className="flex justify-between font-semibold">
<div className="opacity-65">
{episode.AniDB?.Type.replace('Normal', 'Episode').replace('ThemeSong', 'Credit') ?? 'Episode'}
Expand All @@ -35,16 +35,22 @@ function EpisodeDetails({ episode }: { episode: EpisodeType }) {
{episode.Name}
</div>

<div className="flex items-center gap-x-2 text-sm font-semibold">
<Icon className="text-panel-icon" path={mdiCalendarMonthOutline} size={1} />
{dayjs(episode.AniDB?.AirDate).format('MMMM Do, YYYY')}
<Icon className="text-panel-icon" path={mdiClockOutline} size={1} />
{getDuration(episode.Duration)}
<Icon className="text-panel-icon" path={mdiStarHalfFull} size={1} />
{toNumber(episode.AniDB?.Rating.Value).toFixed(2)}
&nbsp;(
{episode.AniDB?.Rating.Votes}
&nbsp;Votes)
<div className="flex flex-wrap items-center gap-x-3 text-sm font-semibold">
<div className="flex flex-wrap gap-x-2">
<Icon className="text-panel-icon" path={mdiCalendarMonthOutline} size={1} />
{dayjs(episode.AniDB?.AirDate).format('MMMM Do, YYYY')}
</div>
<div className="flex flex-wrap gap-x-2">
<Icon className="text-panel-icon" path={mdiClockOutline} size={1} />
{getDuration(episode.Duration)}
</div>
<div className="flex flex-wrap gap-x-2">
<Icon className="text-panel-icon" path={mdiStarHalfFull} size={1} />
{toNumber(episode.AniDB?.Rating.Value).toFixed(2)}
&nbsp;(
{episode.AniDB?.Rating.Votes}
&nbsp;Votes)
</div>
</div>

<div className="line-clamp-3">
Expand Down
8 changes: 4 additions & 4 deletions src/components/Collection/Series/EpisodeFiles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ const EpisodeFiles = ({ animeId, episodeFiles, episodeId }: Props) => {
className="flex cursor-pointer items-center gap-x-2"
onClick={() => handleRescan(selectedFile.ID)}
>
<Icon className="text-panel-icon-action" path={mdiRefresh} size={1} />
<Icon className="hidden text-panel-icon-action lg:inline" path={mdiRefresh} size={1} />
Force Update File Info
</div>
<div className="flex items-center gap-x-2">
<Icon className="text-panel-icon-action" path={mdiEyeOutline} size={1} />
<Icon className="hidden text-panel-icon-action lg:inline" path={mdiEyeOutline} size={1} />
{selectedFile.IsVariation ? 'Unmark' : 'Mark'}
&nbsp;File as Variation
</div>
Expand Down Expand Up @@ -109,14 +109,14 @@ const EpisodeFiles = ({ animeId, episodeFiles, episodeId }: Props) => {
<div className="flex text-center">
<Button
buttonType="danger"
className="flex gap-x-2 px-4 py-3"
className="flex items-center gap-x-2 px-4 py-3"
onClick={() => {
setShowDeleteModal(true);
setSelectedFileToDelete(selectedFile);
}}
>
<Icon path={mdiTrashCanOutline} size={1} />
Delete File
<span className="hidden lg:inline">Delete File</span>
</Button>
</div>
</div>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Collection/SeriesSidePanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ const SeriesSidePanel = ({ series }: SeriesSidePanelProps) => {
const onClickHandler = useEventCallback(() => setShowEditSeriesModal(true));

return (
<div className="flex w-[28.125rem] flex-col gap-y-8 rounded-md border border-panel-border bg-panel-background-transparent p-8">
<div className="hidden w-full flex-col gap-y-8 rounded-md border border-panel-border bg-panel-background-transparent p-8 lg:order-last lg:flex lg:max-w-[35%] 2xl:max-w-[28.125rem]">
<BackgroundImagePlaceholderDiv
image={mainPoster}
className="h-[33.125rem] w-[24.063rem] rounded drop-shadow-md"
className="aspect-[5/6] rounded drop-shadow-md lg:aspect-[4/6] 2xl:h-[33.125rem] 2xl:w-[24.063rem]"
>
{(series.AniDB?.Restricted ?? false) && (
<div className="absolute bottom-0 left-0 flex w-full justify-center bg-panel-background-overlay py-1.5 text-sm font-semibold text-panel-text opacity-100 transition-opacity group-hover:opacity-0">
Expand Down
108 changes: 108 additions & 0 deletions src/components/Input/ButtonDropdown.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="relative inline-block" ref={containerRef}>
<button
type="button"
title={tooltip}
className={cx([
`${className} button rounded font-semibold transition ease-in-out focus:shadow-none focus:outline-none min-w-full px-4 py-3`,
buttonTypes !== undefined && `${buttonTypeClasses[buttonTypes ?? 'secondary']} border border-panel-border`,
loading && 'cursor-default',
disabled && 'cursor-default opacity-50',
])}
onClick={onClick}
disabled={disabled}
>
{loading && (
<div className="flex items-center justify-center">
<Icon path={mdiLoading} spin size={loadingSize ?? 1} />
</div>
)}

{!loading && (
<div className="flex flex-row items-center justify-between">
<span>{content}</span>
<Icon path={mdiChevronDown} size={1} />
</div>
)}
</button>
<div
className={cx([
'flex-col fixed z-10 origin-top-right text-right overflow-hidden justify-center w-fit-content p-3 gap-y-2',
open ? 'flex' : 'hidden',
buttonTypes !== undefined && `${buttonTypeClasses[buttonTypes]} border border-panel-border`,
])}
style={{ left: isOutOfBounds ? `${menuShift}px` : `${containerBounds.left}px` }}
ref={menuRef}
>
{children}
</div>
</div>
);
};

export default ButtonDropdown;
77 changes: 71 additions & 6 deletions src/components/Input/Input.tsx
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -20,6 +24,9 @@ type Props = {
endIcons?: { icon: string, className?: string, onClick?: React.MouseEventHandler<HTMLDivElement> }[];
startIcon?: string;
inline?: boolean;
isOverlay?: boolean;
overlayClassName?: string;
onToggleOverlay?: (show: boolean) => void;
};

function Input(props: Props) {
Expand All @@ -32,9 +39,12 @@ function Input(props: Props) {
id,
inline,
inputClassName,
isOverlay,
label,
onChange,
onKeyUp,
onToggleOverlay,
overlayClassName,
placeholder,
startIcon,
type,
Expand All @@ -43,16 +53,53 @@ function Input(props: Props) {

const bodyVisible = useContext(BodyVisibleContext);
const inputRef = useRef<HTMLInputElement>(null);
const [isShow, setIsShow] = React.useState(false);

useEffect(() => {
if (autoFocus && bodyVisible && inputRef.current) {
inputRef.current?.focus();
}
}, [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 (
<div className={className}>
<label htmlFor={id} className={cx({ 'flex flex-row justify-center': inline })}>
<div
className={cx({
className,
'flex-row gap-x-2 flex': isOverlay,
})}
>
<label
htmlFor={id}
className={cx(inputContainerClassName)}
>
{label && (
<div
className={cx('font-semibold text-base', {
Expand Down Expand Up @@ -85,21 +132,39 @@ function Input(props: Props) {
disabled={disabled}
ref={inputRef}
/>
{endIcons?.length && (
{(endIcons?.length ?? isOverlay) && (
<div className="absolute right-3 top-1/2 flex -translate-y-1/2 flex-row gap-x-2">
{endIcons.map(icon => (
{endIcons?.map(icon => (
<div
key={`input-${icon.icon}`}
onClick={icon.onClick}
className={cx('cursor-pointer text-panel-text', icon.className)}
>
<Icon path={icon.icon} size={1} />
</div>
), [] as React.ReactNode[])}
), [] as React.ReactNode[]) ?? []}
{isOverlay && isShow && (
<div
key="input-toggler"
onClick={handleOverlayClick}
className={cx('cursor-pointer text-panel-text 2xl:hidden')}
>
<Icon path={mdiCloseCircleOutline} size={1} />
</div>
)}
</div>
)}
</div>
</label>
{isOverlay && startIcon && !isShow && (
<Button
buttonType="secondary"
className="inline p-2.5 2xl:hidden"
onClick={handleOverlayClick}
>
<Icon path={startIcon} size={1} />
</Button>
)}
</div>
);
}
Expand Down
11 changes: 11 additions & 0 deletions src/components/Layout/TopNav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
mdiInformationOutline,
mdiLayersTripleOutline,
mdiLoading,
mdiLogout,
mdiServer,
mdiTabletDashboard,
mdiTextBoxOutline,
Expand All @@ -24,6 +25,7 @@ import { Icon } from '@mdi/react';
import cx from 'classnames';
import semver from 'semver';
import { siDiscord } from 'simple-icons';
import { useEventCallback } from 'usehooks-ts';

import DashboardSettingsModal from '@/components/Dashboard/DashboardSettingsModal';
import ActionsModal from '@/components/Dialogs/ActionsModal';
Expand All @@ -36,6 +38,7 @@ import { useSettingsQuery } from '@/core/react-query/settings/queries';
import { useCurrentUserQuery } from '@/core/react-query/user/queries';
import { useUpdateWebuiMutation } from '@/core/react-query/webui/mutations';
import { useWebuiUpdateCheckQuery } from '@/core/react-query/webui/queries';
import { unsetDetails } from '@/core/slices/apiSession';
import { setQueueModalOpen } from '@/core/slices/mainpage';
import { NetworkAvailability } from '@/core/types/signalr';

Expand Down Expand Up @@ -165,6 +168,11 @@ function TopNav() {
dispatch(setQueueModalOpen(false));
};

const handleLogout = useEventCallback(() => {
dispatch(unsetDetails());
navigate('/webui/login');
});

const handleWebUiUpdate = () => {
const renderToast = () => (
<div className="flex flex-col gap-y-3">
Expand Down Expand Up @@ -229,6 +237,9 @@ function TopNav() {
>
<Icon path={mdiCogOutline} size={0.8333} />
</NavLink>
<Button onClick={handleLogout} tooltip="Log out">
<Icon path={mdiLogout} size={0.8333} />
</Button>
</div>
</div>
<div className="bg-topnav-background text-topnav-text">
Expand Down
4 changes: 2 additions & 2 deletions src/components/Panels/ShokoPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const ShokoPanel = (
className,
)}
>
<div className="mb-8 flex items-center justify-between">
<span className="flex w-full text-xl font-semibold">{title}</span>
<div className="mb-8 flex flex-wrap items-center justify-between">
<span className="flex text-xl font-semibold">{title}</span>
<div
className="flex"
onMouseDown={event => event.stopPropagation()}
Expand Down
Loading

0 comments on commit c260daa

Please sign in to comment.