mirror of
https://github.com/morpheus65535/bazarr
synced 2025-02-21 21:47:15 +00:00
Add a new notification center to the UI
This commit is contained in:
parent
56729e0dbb
commit
d7533bac57
15 changed files with 295 additions and 247 deletions
|
@ -85,12 +85,6 @@ def sync_episodes(series_id=None, send_event=True):
|
|||
episodes_to_add.append(episodeParser(episode))
|
||||
|
||||
if send_event:
|
||||
show_progress(id='episodes_progress',
|
||||
header='Syncing episodes...',
|
||||
name='Completed successfully',
|
||||
value=series_count,
|
||||
count=series_count)
|
||||
|
||||
hide_progress(id='episodes_progress')
|
||||
|
||||
# Remove old episodes from DB
|
||||
|
|
|
@ -88,14 +88,8 @@ def update_movies(send_event=True):
|
|||
tags_dict=tagsDict,
|
||||
movie_default_profile=movie_default_profile,
|
||||
audio_profiles=audio_profiles))
|
||||
|
||||
|
||||
if send_event:
|
||||
show_progress(id='movies_progress',
|
||||
header='Syncing movies...',
|
||||
name='Completed successfully',
|
||||
value=movies_count,
|
||||
count=movies_count)
|
||||
|
||||
hide_progress(id='movies_progress')
|
||||
|
||||
# Remove old movies from DB
|
||||
|
|
|
@ -72,12 +72,6 @@ def update_series(send_event=True):
|
|||
audio_profiles=audio_profiles))
|
||||
|
||||
if send_event:
|
||||
show_progress(id='series_progress',
|
||||
header='Syncing series...',
|
||||
name='Completed successfully',
|
||||
value=series_count,
|
||||
count=series_count)
|
||||
|
||||
hide_progress(id='series_progress')
|
||||
|
||||
# Remove old series from DB
|
||||
|
|
|
@ -796,13 +796,6 @@ def series_download_subtitles(no):
|
|||
logging.info("BAZARR All providers are throttled")
|
||||
break
|
||||
|
||||
if count_episodes_details:
|
||||
show_progress(id='series_search_progress_{}'.format(no),
|
||||
header='Searching missing subtitles...',
|
||||
name='Completed successfully',
|
||||
value=count_episodes_details,
|
||||
count=count_episodes_details)
|
||||
|
||||
hide_progress(id='series_search_progress_{}'.format(no))
|
||||
|
||||
|
||||
|
@ -975,13 +968,6 @@ def movies_download_subtitles(no):
|
|||
logging.info("BAZARR All providers are throttled")
|
||||
break
|
||||
|
||||
if count_movie:
|
||||
show_progress(id='movie_search_progress_{}'.format(no),
|
||||
header='Searching missing subtitles...',
|
||||
name='Completed successfully',
|
||||
value=count_movie,
|
||||
count=count_movie)
|
||||
|
||||
hide_progress(id='movie_search_progress_{}'.format(no))
|
||||
|
||||
|
||||
|
@ -1189,12 +1175,6 @@ def wanted_search_missing_subtitles_series():
|
|||
logging.info("BAZARR All providers are throttled")
|
||||
return
|
||||
|
||||
show_progress(id='wanted_episodes_progress',
|
||||
header='Searching subtitles...',
|
||||
name='Completed successfully',
|
||||
value=count_episodes,
|
||||
count=count_episodes)
|
||||
|
||||
hide_progress(id='wanted_episodes_progress')
|
||||
|
||||
logging.info('BAZARR Finished searching for missing Series Subtitles. Check History for more information.')
|
||||
|
@ -1226,12 +1206,6 @@ def wanted_search_missing_subtitles_movies():
|
|||
logging.info("BAZARR All providers are throttled")
|
||||
return
|
||||
|
||||
show_progress(id='wanted_movies_progress',
|
||||
header='Searching subtitles...',
|
||||
name='Completed successfully',
|
||||
value=count_movies,
|
||||
count=count_movies)
|
||||
|
||||
hide_progress(id='wanted_movies_progress')
|
||||
|
||||
logging.info('BAZARR Finished searching for missing Movies Subtitles. Check History for more information.')
|
||||
|
@ -1559,12 +1533,6 @@ def upgrade_subtitles():
|
|||
language_code, provider, score, subs_id, subs_path)
|
||||
send_notifications(episode['sonarrSeriesId'], episode['sonarrEpisodeId'], message)
|
||||
|
||||
show_progress(id='upgrade_episodes_progress',
|
||||
header='Upgrading episodes subtitles...',
|
||||
name='Completed successfully',
|
||||
value=count_episode_to_upgrade,
|
||||
count=count_episode_to_upgrade)
|
||||
|
||||
hide_progress(id='upgrade_episodes_progress')
|
||||
|
||||
if settings.general.getboolean('use_radarr'):
|
||||
|
@ -1632,12 +1600,6 @@ def upgrade_subtitles():
|
|||
history_log_movie(3, movie['radarrId'], message, path, language_code, provider, score, subs_id, subs_path)
|
||||
send_notifications_movie(movie['radarrId'], message)
|
||||
|
||||
show_progress(id='upgrade_movies_progress',
|
||||
header='Upgrading movies subtitles...',
|
||||
name='Completed successfully',
|
||||
value=count_movie_to_upgrade,
|
||||
count=count_movie_to_upgrade)
|
||||
|
||||
hide_progress(id='upgrade_movies_progress')
|
||||
|
||||
logging.info('BAZARR Finished searching for Subtitles to upgrade. Check History for more information.')
|
||||
|
|
|
@ -458,12 +458,6 @@ def series_full_scan_subtitles():
|
|||
count=count_episodes)
|
||||
store_subtitles(episode['path'], path_mappings.path_replace(episode['path']))
|
||||
|
||||
show_progress(id='episodes_disk_scan',
|
||||
header='Full disk scan...',
|
||||
name='Completed successfully',
|
||||
value=count_episodes,
|
||||
count=count_episodes)
|
||||
|
||||
hide_progress(id='episodes_disk_scan')
|
||||
|
||||
gc.collect()
|
||||
|
@ -482,12 +476,6 @@ def movies_full_scan_subtitles():
|
|||
count=count_movies)
|
||||
store_subtitles_movie(movie['path'], path_mappings.path_replace_movie(movie['path']))
|
||||
|
||||
show_progress(id='movies_disk_scan',
|
||||
header='Full disk scan...',
|
||||
name='Completed successfully',
|
||||
value=count_movies,
|
||||
count=count_movies)
|
||||
|
||||
hide_progress(id='movies_disk_scan')
|
||||
|
||||
gc.collect()
|
||||
|
|
|
@ -26,11 +26,12 @@ export const siteRemoveNotifications = createAction<string>(
|
|||
"site/notifications/remove"
|
||||
);
|
||||
|
||||
export const siteAddProgress = createAction<Server.Progress[]>(
|
||||
"site/progress/add"
|
||||
);
|
||||
export const siteAddProgress =
|
||||
createAction<Server.Progress[]>("site/progress/add");
|
||||
|
||||
export const siteRemoveProgress = createAction<string>("site/progress/remove");
|
||||
export const siteRemoveProgress = createAction<string[]>(
|
||||
"site/progress/remove"
|
||||
);
|
||||
|
||||
export const siteChangeSidebar = createAction<string>("site/sidebar/update");
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { createReducer } from "@reduxjs/toolkit";
|
||||
import { remove, uniqBy } from "lodash";
|
||||
import { pullAllWith, remove, uniqBy } from "lodash";
|
||||
import apis from "../../apis";
|
||||
import {
|
||||
siteAddNotifications,
|
||||
|
@ -73,7 +73,7 @@ const reducer = createReducer(defaultSite, (builder) => {
|
|||
);
|
||||
})
|
||||
.addCase(siteRemoveProgress, (state, action) => {
|
||||
remove(state.progress, (n) => n.id === action.payload);
|
||||
pullAllWith(state.progress, action.payload, (l, r) => l.id === r);
|
||||
})
|
||||
.addCase(siteChangeSidebar, (state, action) => {
|
||||
state.sidebar = action.payload;
|
||||
|
|
|
@ -87,9 +87,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
|
|||
update: bindReduxAction(siteAddProgress),
|
||||
delete: (ids) => {
|
||||
setTimeout(() => {
|
||||
ids.forEach((id) => {
|
||||
reduxStore.dispatch(siteRemoveProgress(id));
|
||||
});
|
||||
reduxStore.dispatch(siteRemoveProgress(ids));
|
||||
}, 3 * 1000);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ import { SystemApi } from "../apis";
|
|||
import { ActionButton, SearchBar, SearchResult } from "../components";
|
||||
import { useGotoHomepage } from "../utilites";
|
||||
import "./header.scss";
|
||||
import NotificationCenter from "./Notification";
|
||||
|
||||
async function SearchItem(text: string) {
|
||||
const results = await SystemApi.search(text);
|
||||
|
@ -58,7 +59,7 @@ const Header: FunctionComponent<Props> = () => {
|
|||
|
||||
const offline = useIsOffline();
|
||||
|
||||
const dropdown = useMemo(
|
||||
const serverActions = useMemo(
|
||||
() => (
|
||||
<Dropdown alignRight>
|
||||
<Dropdown.Toggle className="dropdown-hidden" as={Button}>
|
||||
|
@ -117,6 +118,7 @@ const Header: FunctionComponent<Props> = () => {
|
|||
<SearchBar onSearch={SearchItem}></SearchBar>
|
||||
</Col>
|
||||
<Col className="d-flex flex-row align-items-center justify-content-end pr-2">
|
||||
<NotificationCenter></NotificationCenter>
|
||||
<Button
|
||||
href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XHHRWXT9YB7WE&source=url"
|
||||
target="_blank"
|
||||
|
@ -134,7 +136,7 @@ const Header: FunctionComponent<Props> = () => {
|
|||
Connecting...
|
||||
</ActionButton>
|
||||
) : (
|
||||
dropdown
|
||||
serverActions
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
225
frontend/src/App/Notification.tsx
Normal file
225
frontend/src/App/Notification.tsx
Normal file
|
@ -0,0 +1,225 @@
|
|||
import {
|
||||
faBug,
|
||||
faCircleNotch,
|
||||
faExclamationTriangle,
|
||||
faInfoCircle,
|
||||
faStream,
|
||||
IconDefinition,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import {
|
||||
FontAwesomeIcon,
|
||||
FontAwesomeIconProps,
|
||||
} from "@fortawesome/react-fontawesome";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Overlay,
|
||||
ProgressBar,
|
||||
Tooltip,
|
||||
} from "react-bootstrap";
|
||||
import { useDidUpdate } from "rooks";
|
||||
import { useReduxStore } from "../@redux/hooks/base";
|
||||
import { BuildKey, useIsArrayExtended } from "../utilites";
|
||||
import "./notification.scss";
|
||||
|
||||
enum State {
|
||||
Idle,
|
||||
Working,
|
||||
Failed,
|
||||
}
|
||||
|
||||
function useTotalProgress(progress: Server.Progress[]) {
|
||||
return useMemo(() => {
|
||||
const { value, count } = progress.reduce(
|
||||
(prev, { value, count }) => {
|
||||
prev.value += value;
|
||||
prev.count += count;
|
||||
return prev;
|
||||
},
|
||||
{ value: 0, count: 0 }
|
||||
);
|
||||
|
||||
if (count === 0) {
|
||||
return 0;
|
||||
} else {
|
||||
return value / count;
|
||||
}
|
||||
}, [progress]);
|
||||
}
|
||||
|
||||
function useHasErrorNotification(notifications: Server.Notification[]) {
|
||||
return useMemo(
|
||||
() => notifications.find((v) => v.type !== "info") !== undefined,
|
||||
[notifications]
|
||||
);
|
||||
}
|
||||
|
||||
const NotificationCenter: FunctionComponent = () => {
|
||||
const { progress, notifications } = useReduxStore((s) => s.site);
|
||||
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
const [hasNew, setHasNew] = useState(false);
|
||||
|
||||
const hasNewProgress = useIsArrayExtended(progress);
|
||||
const hasNewNotifications = useIsArrayExtended(notifications);
|
||||
useDidUpdate(() => {
|
||||
if (hasNewNotifications || hasNewProgress) {
|
||||
setHasNew(true);
|
||||
}
|
||||
}, [hasNewProgress, hasNewNotifications]);
|
||||
|
||||
const [btnState, setBtnState] = useState(State.Idle);
|
||||
|
||||
const totalProgress = useTotalProgress(progress);
|
||||
const hasError = useHasErrorNotification(notifications);
|
||||
|
||||
useEffect(() => {
|
||||
if (hasError) {
|
||||
setBtnState(State.Failed);
|
||||
} else if (totalProgress > 0) {
|
||||
setBtnState(State.Working);
|
||||
} else if (totalProgress <= 0) {
|
||||
setBtnState(State.Idle);
|
||||
}
|
||||
}, [totalProgress, hasError]);
|
||||
|
||||
const iconProps = useMemo<FontAwesomeIconProps>(() => {
|
||||
switch (btnState) {
|
||||
case State.Idle:
|
||||
return {
|
||||
icon: faStream,
|
||||
};
|
||||
case State.Working:
|
||||
return {
|
||||
icon: faCircleNotch,
|
||||
spin: true,
|
||||
};
|
||||
default:
|
||||
return {
|
||||
icon: faExclamationTriangle,
|
||||
};
|
||||
}
|
||||
}, [btnState]);
|
||||
|
||||
const content = useMemo<React.ReactNode>(() => {
|
||||
const nodes: JSX.Element[] = [];
|
||||
|
||||
nodes.push(
|
||||
<Dropdown.Header key="notifications-header">
|
||||
{notifications.length > 0 ? "Notifications" : "No Notifications"}
|
||||
</Dropdown.Header>
|
||||
);
|
||||
nodes.push(
|
||||
...notifications.map((v, idx) => (
|
||||
<Dropdown.Item disabled key={BuildKey(idx, v.id, "notification")}>
|
||||
<Notification {...v}></Notification>
|
||||
</Dropdown.Item>
|
||||
))
|
||||
);
|
||||
|
||||
nodes.push(<Dropdown.Divider key="dropdown-divider"></Dropdown.Divider>);
|
||||
|
||||
nodes.push(
|
||||
<Dropdown.Header key="background-task-header">
|
||||
{progress.length > 0 ? "Background Tasks" : "No Background Tasks"}
|
||||
</Dropdown.Header>
|
||||
);
|
||||
nodes.push(
|
||||
...progress.map((v, idx) => (
|
||||
<Dropdown.Item disabled key={BuildKey(idx, v.id, "progress")}>
|
||||
<Progress {...v}></Progress>
|
||||
</Dropdown.Item>
|
||||
))
|
||||
);
|
||||
|
||||
return nodes;
|
||||
}, [progress, notifications]);
|
||||
|
||||
const onToggleClick = useCallback(() => {
|
||||
setHasNew(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<React.Fragment>
|
||||
<Dropdown
|
||||
onClick={onToggleClick}
|
||||
className={`notification-btn ${hasNew ? "new-item" : ""}`}
|
||||
ref={dropdownRef}
|
||||
alignRight
|
||||
>
|
||||
<Dropdown.Toggle as={Button} className="dropdown-hidden">
|
||||
<FontAwesomeIcon {...iconProps}></FontAwesomeIcon>
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="pb-3">{content}</Dropdown.Menu>
|
||||
</Dropdown>
|
||||
{/* Handle this later */}
|
||||
<Overlay target={dropdownRef} show={false} placement="bottom">
|
||||
{(props) => {
|
||||
return (
|
||||
<Tooltip id="new-notification-tip" {...props}>
|
||||
New Notifications
|
||||
</Tooltip>
|
||||
);
|
||||
}}
|
||||
</Overlay>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
const Notification: FunctionComponent<Server.Notification> = ({
|
||||
type,
|
||||
message,
|
||||
}) => {
|
||||
const icon = useMemo<IconDefinition>(() => {
|
||||
switch (type) {
|
||||
case "info":
|
||||
return faInfoCircle;
|
||||
case "warning":
|
||||
return faExclamationTriangle;
|
||||
default:
|
||||
return faBug;
|
||||
}
|
||||
}, [type]);
|
||||
return (
|
||||
<div className="notification-center-notification d-flex flex-nowrap align-items-center justify-content-start my-1">
|
||||
<FontAwesomeIcon className="mr-2 text-dark" icon={icon}></FontAwesomeIcon>
|
||||
<span className="text-dark small">{message}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Progress: FunctionComponent<Server.Progress> = ({
|
||||
name,
|
||||
value,
|
||||
count,
|
||||
header,
|
||||
}) => {
|
||||
const isCompleted = value / count >= 1;
|
||||
return (
|
||||
<div className="notification-center-progress d-flex flex-column">
|
||||
<p className="progress-header m-0 h-6 text-dark font-weight-bold">
|
||||
{header}
|
||||
</p>
|
||||
<p className="progress-name m-0 small text-secondary">
|
||||
{isCompleted ? "Completed successfully" : name}
|
||||
</p>
|
||||
<ProgressBar
|
||||
className="mt-2"
|
||||
animated={!isCompleted}
|
||||
now={value / count}
|
||||
max={1}
|
||||
label={`${value}/${count}`}
|
||||
></ProgressBar>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationCenter;
|
|
@ -22,7 +22,6 @@ import LaunchError from "../special-pages/LaunchError";
|
|||
import UIError from "../special-pages/UIError";
|
||||
import { useBaseUrl, useHasUpdateInject } from "../utilites";
|
||||
import Header from "./Header";
|
||||
import NotificationContainer from "./notifications";
|
||||
import Router from "./Router";
|
||||
|
||||
// Sidebar Toggle
|
||||
|
@ -75,7 +74,6 @@ const App: FunctionComponent<Props> = () => {
|
|||
<Router className="d-flex flex-row flex-grow-1 main-router"></Router>
|
||||
</ModalProvider>
|
||||
</Row>
|
||||
<NotificationContainer></NotificationContainer>
|
||||
</SidebarToggleContext.Provider>
|
||||
);
|
||||
} catch (e) {
|
||||
|
|
43
frontend/src/App/notification.scss
Normal file
43
frontend/src/App/notification.scss
Normal file
|
@ -0,0 +1,43 @@
|
|||
@function theme-color($key: "primary") {
|
||||
@return map-get($theme-colors, $key);
|
||||
}
|
||||
|
||||
.notification-btn {
|
||||
&.new-item {
|
||||
&::after {
|
||||
position: absolute;
|
||||
background-color: red;
|
||||
content: "";
|
||||
border-radius: 50%;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
right: 10%;
|
||||
top: 10%;
|
||||
}
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
max-width: 20rem;
|
||||
max-height: 40rem;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
$content-width: 16rem;
|
||||
|
||||
.notification-center-progress {
|
||||
width: $content-width;
|
||||
max-width: $content-width;
|
||||
|
||||
.progress-name {
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-center-notification {
|
||||
word-wrap: break-word;
|
||||
white-space: normal;
|
||||
width: $content-width;
|
||||
max-width: $content-width;
|
||||
}
|
||||
}
|
|
@ -1,118 +0,0 @@
|
|||
import {
|
||||
faExclamationTriangle,
|
||||
faPaperPlane,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { capitalize } from "lodash";
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
} from "react";
|
||||
import { ProgressBar, Toast } from "react-bootstrap";
|
||||
import {
|
||||
siteRemoveNotifications,
|
||||
siteRemoveProgress,
|
||||
} from "../../@redux/actions";
|
||||
import { useReduxAction, useReduxStore } from "../../@redux/hooks/base";
|
||||
import "./style.scss";
|
||||
|
||||
export interface NotificationContainerProps {}
|
||||
|
||||
const NotificationContainer: FunctionComponent<NotificationContainerProps> =
|
||||
() => {
|
||||
const { progress, notifications } = useReduxStore((s) => s.site);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const progressItems = progress.map((v) => (
|
||||
<ProgressToast key={v.id} {...v}></ProgressToast>
|
||||
));
|
||||
|
||||
const notificationItems = notifications.map((v) => (
|
||||
<NotificationToast key={v.id} {...v}></NotificationToast>
|
||||
));
|
||||
|
||||
return [...progressItems, ...notificationItems];
|
||||
}, [notifications, progress]);
|
||||
return (
|
||||
<div className="alert-container">
|
||||
<div className="toast-container">{items}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
type MessageHolderProps = Server.Notification & {};
|
||||
|
||||
const NotificationToast: FunctionComponent<MessageHolderProps> = (props) => {
|
||||
const { message, type, id, timeout } = props;
|
||||
const removeNotification = useReduxAction(siteRemoveNotifications);
|
||||
|
||||
const remove = useCallback(
|
||||
() => removeNotification(id),
|
||||
[removeNotification, id]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handle = setTimeout(remove, timeout);
|
||||
return () => {
|
||||
clearTimeout(handle);
|
||||
};
|
||||
}, [props, remove, timeout]);
|
||||
|
||||
return (
|
||||
<Toast onClose={remove} animation={false}>
|
||||
<Toast.Header>
|
||||
<FontAwesomeIcon
|
||||
className="mr-1"
|
||||
icon={faExclamationTriangle}
|
||||
></FontAwesomeIcon>
|
||||
<strong className="mr-auto">{capitalize(type)}</strong>
|
||||
</Toast.Header>
|
||||
<Toast.Body>{message}</Toast.Body>
|
||||
</Toast>
|
||||
);
|
||||
};
|
||||
|
||||
type ProgressHolderProps = Server.Progress & {};
|
||||
|
||||
const ProgressToast: FunctionComponent<ProgressHolderProps> = ({
|
||||
id,
|
||||
header,
|
||||
name,
|
||||
value,
|
||||
count,
|
||||
}) => {
|
||||
const removeProgress = useReduxAction(siteRemoveProgress);
|
||||
const remove = useCallback(() => removeProgress(id), [removeProgress, id]);
|
||||
|
||||
useEffect(() => {
|
||||
const handle = setTimeout(remove, 10 * 1000);
|
||||
return () => {
|
||||
clearTimeout(handle);
|
||||
};
|
||||
}, [value, remove]);
|
||||
|
||||
const incomplete = value / count < 1;
|
||||
|
||||
return (
|
||||
<Toast onClose={remove}>
|
||||
<Toast.Header closeButton={false}>
|
||||
<FontAwesomeIcon className="mr-2" icon={faPaperPlane}></FontAwesomeIcon>
|
||||
<span className="mr-auto">{header}</span>
|
||||
</Toast.Header>
|
||||
<Toast.Body>
|
||||
<span>{name}</span>
|
||||
<ProgressBar
|
||||
className="my-1"
|
||||
animated={incomplete}
|
||||
now={value / count}
|
||||
max={1}
|
||||
label={`${value}/${count}`}
|
||||
></ProgressBar>
|
||||
</Toast.Body>
|
||||
</Toast>
|
||||
);
|
||||
};
|
||||
|
||||
export default NotificationContainer;
|
|
@ -1,46 +0,0 @@
|
|||
@import "../../@scss/variable.scss";
|
||||
@import "../../@scss/bazarr.scss";
|
||||
|
||||
@function theme-color($key: "primary") {
|
||||
@return map-get($theme-colors, $key);
|
||||
}
|
||||
|
||||
.alert-container {
|
||||
position: fixed;
|
||||
display: block;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin-top: $header-height;
|
||||
|
||||
z-index: 9999;
|
||||
|
||||
.toast-container {
|
||||
padding: 1rem;
|
||||
|
||||
.toast {
|
||||
width: 16rem;
|
||||
|
||||
backdrop-filter: blur(6px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
|
||||
.toast-body {
|
||||
span {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
}
|
||||
.progress {
|
||||
.progress-bar {
|
||||
text-shadow: -2px -2px 5px theme-color("primary"),
|
||||
2px -2px 5px theme-color("primary"),
|
||||
-2px 2px 5px theme-color("primary"),
|
||||
2px 2px 5px theme-color("primary");
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
import { useCallback, useMemo } from "react";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import { useHistory } from "react-router";
|
||||
import { useDidUpdate } from "rooks";
|
||||
import { getBaseUrl } from ".";
|
||||
|
||||
export function useBaseUrl(slash: boolean = false) {
|
||||
|
@ -26,3 +27,15 @@ export function useHasUpdateInject() {
|
|||
return window.Bazarr.hasUpdate;
|
||||
}
|
||||
}
|
||||
|
||||
export function useIsArrayExtended(arr: any[]) {
|
||||
const [size, setSize] = useState(arr.length);
|
||||
const [isExtended, setExtended] = useState(arr.length !== 0);
|
||||
|
||||
useDidUpdate(() => {
|
||||
setExtended(arr.length > size);
|
||||
setSize(arr.length);
|
||||
}, [arr.length]);
|
||||
|
||||
return isExtended;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue