Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mitigating responsive in web #752

Merged
merged 14 commits into from
Jan 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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