Squashed 'sample-front-end/' changes from 0881bf1..601ffe4
601ffe4 fix: Refine UI layout and visual elements 4689aaa feat: Add request timeout and cancellation to API calls git-subtree-dir: sample-front-end git-subtree-split: 601ffe468a78955839eef6c839314d9b96ea204d
This commit is contained in:
@@ -22,6 +22,10 @@ const App: React.FC = () => {
|
||||
const [loadingLocal, setLoadingLocal] = useState(false);
|
||||
const [loadingCloud, setLoadingCloud] = useState(false);
|
||||
|
||||
// Abort Controllers for Refresh Actions
|
||||
const localAbortRef = useRef<AbortController | null>(null);
|
||||
const cloudAbortRef = useRef<AbortController | null>(null);
|
||||
|
||||
// Connection Modal State
|
||||
const [isConnectionModalOpen, setIsConnectionModalOpen] = useState(false);
|
||||
|
||||
@@ -100,36 +104,71 @@ const App: React.FC = () => {
|
||||
|
||||
// Fetch Local Playlists
|
||||
const refreshLocal = useCallback(async () => {
|
||||
if (localAbortRef.current) localAbortRef.current.abort();
|
||||
const abortController = new AbortController();
|
||||
localAbortRef.current = abortController;
|
||||
|
||||
setLoadingLocal(true);
|
||||
const result = await apiService.getPlaylists(ServerType.LOCAL);
|
||||
const result = await apiService.getPlaylists(ServerType.LOCAL, abortController.signal);
|
||||
if (result.status === 'success') {
|
||||
setLocalPlaylists(result.data);
|
||||
}
|
||||
setLoadingLocal(false);
|
||||
localAbortRef.current = null;
|
||||
}, []);
|
||||
|
||||
const cancelLocalRefresh = () => {
|
||||
if (localAbortRef.current) {
|
||||
localAbortRef.current.abort();
|
||||
localAbortRef.current = null;
|
||||
setLoadingLocal(false);
|
||||
addToast("Local refresh cancelled.");
|
||||
}
|
||||
};
|
||||
|
||||
// Fetch Cloud Playlists and Info
|
||||
const refreshCloud = useCallback(async () => {
|
||||
if (cloudAbortRef.current) cloudAbortRef.current.abort();
|
||||
const abortController = new AbortController();
|
||||
cloudAbortRef.current = abortController;
|
||||
|
||||
setLoadingCloud(true);
|
||||
// Fetch playlists
|
||||
const playlistResult = await apiService.getPlaylists(ServerType.CLOUD);
|
||||
if (playlistResult.status === 'success') {
|
||||
setCloudPlaylists(playlistResult.data);
|
||||
const playlistResult = await apiService.getPlaylists(ServerType.CLOUD, abortController.signal);
|
||||
if (!abortController.signal.aborted) {
|
||||
if (playlistResult.status === 'success') {
|
||||
setCloudPlaylists(playlistResult.data);
|
||||
}
|
||||
|
||||
// Fetch server info
|
||||
const infoResult = await apiService.getServerStatus(abortController.signal);
|
||||
if (infoResult.status === 'success') {
|
||||
setCloudServerInfo(infoResult.data);
|
||||
}
|
||||
|
||||
setLoadingCloud(false);
|
||||
cloudAbortRef.current = null;
|
||||
}
|
||||
|
||||
// Fetch server info
|
||||
const infoResult = await apiService.getServerStatus();
|
||||
if (infoResult.status === 'success') {
|
||||
setCloudServerInfo(infoResult.data);
|
||||
}
|
||||
|
||||
setLoadingCloud(false);
|
||||
}, []);
|
||||
|
||||
const cancelCloudRefresh = () => {
|
||||
if (cloudAbortRef.current) {
|
||||
cloudAbortRef.current.abort();
|
||||
cloudAbortRef.current = null;
|
||||
setLoadingCloud(false);
|
||||
addToast("Cloud refresh cancelled.");
|
||||
}
|
||||
};
|
||||
|
||||
// Initial Load
|
||||
useEffect(() => {
|
||||
refreshLocal();
|
||||
refreshCloud();
|
||||
return () => {
|
||||
// Cleanup on unmount
|
||||
if (localAbortRef.current) localAbortRef.current.abort();
|
||||
if (cloudAbortRef.current) cloudAbortRef.current.abort();
|
||||
}
|
||||
}, [refreshLocal, refreshCloud]);
|
||||
|
||||
// Handle Strategy Change
|
||||
@@ -146,8 +185,8 @@ const App: React.FC = () => {
|
||||
|
||||
const handleConnectSuccess = (serverInfo: PlexServerConnection) => {
|
||||
setCloudServerInfo(serverInfo);
|
||||
// Removed implicit toast here to allow the caller (ConnectionModal) to handle specific messaging
|
||||
refreshCloud(); // Refresh playlists after new connection
|
||||
// Refresh playlists after new connection
|
||||
refreshCloud();
|
||||
};
|
||||
|
||||
const getToastStyles = (toast: Toast): React.CSSProperties => {
|
||||
@@ -221,7 +260,8 @@ const App: React.FC = () => {
|
||||
|
||||
{/* Main Content Area */}
|
||||
<main className="flex-1 overflow-hidden relative z-10">
|
||||
<div className="absolute inset-0 flex flex-col md:flex-row max-w-7xl mx-auto p-4 md:p-6 gap-3 md:gap-6">
|
||||
{/* Reduced gap from gap-3/gap-6 to gap-2/gap-3 for tighter layout */}
|
||||
<div className="absolute inset-0 flex flex-col md:flex-row max-w-7xl mx-auto p-4 md:p-6 gap-2 md:gap-3">
|
||||
|
||||
{/* Left Column - Local */}
|
||||
<div className="flex-1 min-h-0 h-full w-full">
|
||||
@@ -230,12 +270,11 @@ const App: React.FC = () => {
|
||||
playlists={localPlaylists}
|
||||
isLoading={loadingLocal}
|
||||
onRefresh={refreshLocal}
|
||||
onCancel={cancelLocalRefresh}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Strategy Selector - Positioned specifically between headers */}
|
||||
{/* Desktop: Centered Horizontally, Top aligned with Headers (Padding Top 24px + Header Half Height ~40px = ~64px) */}
|
||||
{/* Mobile: Centered Vertically, Right aligned with Headers (Padding Right 16px + Header Half Width ~36px = ~52px) */}
|
||||
<div className="absolute
|
||||
z-30
|
||||
/* Mobile Positioning: Center Vertically, Anchored Right */
|
||||
@@ -259,6 +298,7 @@ const App: React.FC = () => {
|
||||
playlists={cloudPlaylists}
|
||||
isLoading={loadingCloud}
|
||||
onRefresh={refreshCloud}
|
||||
onCancel={cancelCloudRefresh}
|
||||
serverInfo={cloudServerInfo}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user