Squashed 'sample-front-end/' changes from 0e20813..8ae211a

8ae211a feat: Introduce path mapping for sync

git-subtree-dir: sample-front-end
git-subtree-split: 8ae211a79c0d522050553e80674b82e2c9471e0f
This commit is contained in:
2025-11-30 02:58:04 +09:00
parent d1a4273fb2
commit f791798206
4 changed files with 418 additions and 174 deletions
+69 -13
View File
@@ -1,7 +1,5 @@
import React, { useEffect, useState, useCallback, useRef } from 'react';
import { Playlist, ServerType, SyncStrategy, PlexServerConnection, RegexReplacement, SyncState, ScheduleSettings, ScheduleMode } from './types';
import { Playlist, ServerType, SyncStrategy, PlexServerConnection, PathMappingConfig, PathMappingMode, SyncState, ScheduleSettings, ScheduleMode } from './types';
import { apiService } from './services/api';
import {
STRIPE_BASE_SPEED,
@@ -17,7 +15,7 @@ import { SYNC_BANNER_PADDING_X, SYNC_BANNER_PADDING_Y, SYNC_BANNER_MIN_WIDTH } f
import ServerPanel from './components/ServerPanel';
import StrategySelector from './components/StrategySelector';
import ConnectionModal from './components/ConnectionModal';
import { ArrowLeftRight, ShieldCheck, X, Server, ServerOff, Clock, Eye, EyeOff } from 'lucide-react';
import { ArrowLeftRight, ShieldCheck, X, Server, ServerOff, Clock, Eye, EyeOff, Type, Code2 } from 'lucide-react';
interface Toast {
id: number;
@@ -137,8 +135,17 @@ const App: React.FC = () => {
// Strategy State
const [currentStrategy, setCurrentStrategy] = useState<SyncStrategy>(SyncStrategy.LOCAL_OVERWRITE);
// Regex State
const [regexReplacements, setRegexReplacements] = useState<RegexReplacement[]>([]);
// Path Mapping State (Includes Simple and Regex Rules)
const [pathMappingConfig, setPathMappingConfig] = useState<PathMappingConfig>({
mode: PathMappingMode.SIMPLE,
simple: [],
regex: {
localPre: [],
localPost: [],
remotePre: [],
remotePost: []
}
});
// Schedule State
const [scheduleSettings, setScheduleSettings] = useState<ScheduleSettings>({
@@ -292,10 +299,10 @@ const App: React.FC = () => {
addToast(`Selected strategy "${label}" has been saved.`);
};
// Handle Regex Save
const handleSaveRegex = (replacements: RegexReplacement[]) => {
setRegexReplacements(replacements);
addToast('Regex preprocessing rules have been saved.');
// Handle Path Mapping Save
const handleSavePathMapping = (config: PathMappingConfig) => {
setPathMappingConfig(config);
addToast('Path mapping rules have been saved.');
};
// Handle Schedule Save
@@ -328,7 +335,7 @@ const App: React.FC = () => {
setSyncState(SyncState.SYNCING);
// Note: We deliberately do not clear playlists here to keep UI populated during sync
const result = await apiService.syncPlaylists(currentStrategy, regexReplacements);
const result = await apiService.syncPlaylists(currentStrategy, pathMappingConfig);
if (result.status === 'success') {
// Transition to Success state
@@ -478,6 +485,44 @@ const App: React.FC = () => {
const scheduleInfo = getScheduleDisplayInfo(scheduleSettings);
// Helper: Calculate Path Mapping Info
const getPathMappingDisplayInfo = (config: PathMappingConfig) => {
let count = 0;
let modeLabel = '';
let Icon = Type;
if (config.mode === PathMappingMode.SIMPLE) {
modeLabel = 'Simple';
count = config.simple.length;
Icon = Type;
} else {
modeLabel = 'Regex';
count = config.regex.localPre.length +
config.regex.localPost.length +
config.regex.remotePre.length +
config.regex.remotePost.length;
Icon = Code2;
}
if (count === 0) {
return {
label: 'Path Mapping',
value: 'Not Set',
active: false,
Icon: Icon
};
}
return {
label: 'Path Mapping',
value: `${modeLabel} (${count})`,
active: true,
Icon: Icon
};
};
const pathMappingInfo = getPathMappingDisplayInfo(pathMappingConfig);
return (
<div className="min-h-screen flex flex-col bg-gray-900 text-gray-100 font-sans overflow-hidden bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-gray-800 via-gray-900 to-black">
@@ -555,6 +600,17 @@ const App: React.FC = () => {
{/* Normal Toolbar Right */}
<div className="flex items-center gap-4">
{/* Path Mapping Info */}
<div className="flex flex-col items-end hidden md:flex border-r border-gray-700/50 pr-4 mr-1">
<span className="text-[10px] uppercase font-bold text-gray-500 tracking-wider">
{pathMappingInfo.label}
</span>
<div className={`text-xs font-mono flex items-center gap-1.5 ${pathMappingInfo.active ? 'text-plex-orange' : 'text-gray-600'}`}>
{pathMappingInfo.active && <pathMappingInfo.Icon size={12} />}
<span>{pathMappingInfo.value}</span>
</div>
</div>
{/* Schedule Info */}
<div className="flex flex-col items-end mr-2 md:mr-0 hidden md:flex">
<span className="text-[10px] uppercase font-bold text-gray-500 tracking-wider">
@@ -661,8 +717,8 @@ const App: React.FC = () => {
<StrategySelector
currentStrategy={currentStrategy}
onSelect={handleStrategyChange}
savedRegexReplacements={regexReplacements}
onSaveRegex={handleSaveRegex}
savedPathMapping={pathMappingConfig}
onSavePathMapping={handleSavePathMapping}
savedSchedule={scheduleSettings}
onSaveSchedule={handleSaveSchedule}
syncState={syncState}