mirror of
https://github.com/Dictionarry-Hub/profilarr.git
synced 2026-01-22 19:01:02 +01:00
feat(profile-modal): Improvements - Show quality profile type in status page - Increase modal size, split things into columns - Filter formats - Bigger description size
This commit is contained in:
committed by
Sam Chau
parent
d9601eac0f
commit
1cee08bdbd
@@ -224,6 +224,8 @@ def determine_type(file_path):
|
||||
return 'Regex Pattern'
|
||||
elif 'custom_formats' in file_path:
|
||||
return 'Custom Format'
|
||||
elif 'profiles' in file_path:
|
||||
return 'Quality Profile'
|
||||
return 'Unknown'
|
||||
|
||||
def interpret_git_status(x, y):
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import FormatCard from "./FormatCard";
|
||||
import FormatModal from "./FormatModal";
|
||||
import AddNewCard from "../ui/AddNewCard";
|
||||
import { getFormats } from "../../api/api";
|
||||
import FilterMenu from "../ui/FilterMenu";
|
||||
import SortMenu from "../ui/SortMenu";
|
||||
import { Loader } from "lucide-react";
|
||||
|
||||
function FormatPage() {
|
||||
const [formats, setFormats] = useState([]);
|
||||
@@ -15,6 +16,15 @@ function FormatPage() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [isCloning, setIsCloning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const loadingMessages = [
|
||||
"Decoding the custom format matrix...",
|
||||
"Parsing the digital alphabet soup...",
|
||||
"Untangling the format spaghetti...",
|
||||
"Calibrating the format-o-meter...",
|
||||
"Indexing your media DNA...",
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
fetchFormats();
|
||||
@@ -30,6 +40,8 @@ function FormatPage() {
|
||||
setAllTags(tags);
|
||||
} catch (error) {
|
||||
console.error("Error fetching formats:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,6 +98,17 @@ function FormatPage() {
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<Loader size={48} className="animate-spin text-blue-500 mb-4" />
|
||||
<p className="text-lg font-medium text-gray-700 dark:text-gray-300">
|
||||
{loadingMessages[Math.floor(Math.random() * loadingMessages.length)]}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Manage Custom Formats</h2>
|
||||
|
||||
@@ -25,6 +25,7 @@ function ProfileModal({
|
||||
const [formatTags, setFormatTags] = useState([]);
|
||||
const [tagScores, setTagScores] = useState({});
|
||||
const [tagFilter, setTagFilter] = useState("");
|
||||
const [formatFilter, setFormatFilter] = useState("");
|
||||
const [error, setError] = useState("");
|
||||
const [isDeleting, setIsDeleting] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@@ -161,8 +162,12 @@ function ProfileModal({
|
||||
tag.toLowerCase().includes(tagFilter.toLowerCase())
|
||||
);
|
||||
|
||||
const filteredFormats = customFormats.filter((format) =>
|
||||
format.name.toLowerCase().includes(formatFilter.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={modalTitle}>
|
||||
<Modal isOpen={isOpen} onClose={onClose} title={modalTitle} size="7xl">
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center">
|
||||
<Loader size={24} className="animate-spin text-gray-300" />
|
||||
@@ -170,117 +175,134 @@ function ProfileModal({
|
||||
) : (
|
||||
<>
|
||||
{error && <div className="text-red-500 mb-4">{error}</div>}
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Profile Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Enter profile name"
|
||||
className="w-full p-2 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Enter description"
|
||||
className="w-full p-2 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
rows="3"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Tags
|
||||
</label>
|
||||
<div className="flex flex-wrap mb-2">
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="bg-blue-100 text-blue-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300"
|
||||
>
|
||||
{tag}
|
||||
<div className="grid grid-cols-3 gap-4">
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Profile Name
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
placeholder="Enter profile name"
|
||||
className="w-full p-2 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Description
|
||||
</label>
|
||||
<textarea
|
||||
value={description}
|
||||
onChange={(e) => setDescription(e.target.value)}
|
||||
placeholder="Enter description"
|
||||
className="w-full p-2 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600 resize-none"
|
||||
rows="5"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Tags
|
||||
</label>
|
||||
<div className="flex flex-wrap mb-2">
|
||||
{tags.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="bg-blue-100 text-blue-800 text-xs font-medium mr-2 px-2.5 py-0.5 rounded dark:bg-blue-900 dark:text-blue-300"
|
||||
>
|
||||
{tag}
|
||||
<button
|
||||
onClick={() => handleRemoveTag(tag)}
|
||||
className="ml-1 text-xs"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<input
|
||||
type="text"
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
placeholder="Add a tag"
|
||||
className="flex-grow p-2 border rounded-l dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
<button
|
||||
onClick={() => handleRemoveTag(tag)}
|
||||
className="ml-1 text-xs"
|
||||
onClick={handleAddTag}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded-r hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
×
|
||||
Add
|
||||
</button>
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex">
|
||||
<input
|
||||
type="text"
|
||||
value={newTag}
|
||||
onChange={(e) => setNewTag(e.target.value)}
|
||||
placeholder="Add a tag"
|
||||
className="flex-grow p-2 border rounded-l dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
<button
|
||||
onClick={handleAddTag}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded-r hover:bg-blue-600 transition-colors"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Custom Formats
|
||||
</label>
|
||||
<div className="max-h-60 overflow-y-auto">
|
||||
{customFormats.map((format) => (
|
||||
<div
|
||||
key={format.id}
|
||||
className="flex items-center space-x-2 mb-2"
|
||||
>
|
||||
<span className="flex-grow">{format.name}</span>
|
||||
<input
|
||||
type="number"
|
||||
value={format.score}
|
||||
onChange={(e) =>
|
||||
handleScoreChange(format.id, e.target.value)
|
||||
}
|
||||
className="w-20 p-1 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Tag-based Scoring
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tagFilter}
|
||||
onChange={(e) => setTagFilter(e.target.value)}
|
||||
placeholder="Filter tags"
|
||||
className="w-full p-2 border rounded mb-2 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
<div className="max-h-60 overflow-y-auto">
|
||||
{filteredTags.map((tag) => (
|
||||
<div key={tag} className="flex items-center space-x-2 mb-2">
|
||||
<span className="flex-grow">{tag}</span>
|
||||
<input
|
||||
type="number"
|
||||
value={tagScores[tag]}
|
||||
onChange={(e) => handleTagScoreChange(tag, e.target.value)}
|
||||
className="w-20 p-1 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
min="0"
|
||||
/>
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Custom Formats
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={formatFilter}
|
||||
onChange={(e) => setFormatFilter(e.target.value)}
|
||||
placeholder="Filter formats"
|
||||
className="w-full p-2 border rounded mb-2 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{filteredFormats.map((format) => (
|
||||
<div
|
||||
key={format.id}
|
||||
className="flex items-center space-x-2 mb-2"
|
||||
>
|
||||
<span className="flex-grow">{format.name}</span>
|
||||
<input
|
||||
type="number"
|
||||
value={format.score}
|
||||
onChange={(e) =>
|
||||
handleScoreChange(format.id, e.target.value)
|
||||
}
|
||||
className="w-20 p-1 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
||||
Tag-based Scoring
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={tagFilter}
|
||||
onChange={(e) => setTagFilter(e.target.value)}
|
||||
placeholder="Filter tags"
|
||||
className="w-full p-2 border rounded mb-2 dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
/>
|
||||
<div className="max-h-96 overflow-y-auto">
|
||||
{filteredTags.map((tag) => (
|
||||
<div key={tag} className="flex items-center space-x-2 mb-2">
|
||||
<span className="flex-grow">{tag}</span>
|
||||
<input
|
||||
type="number"
|
||||
value={tagScores[tag]}
|
||||
onChange={(e) =>
|
||||
handleTagScoreChange(tag, e.target.value)
|
||||
}
|
||||
className="w-20 p-1 border rounded dark:bg-gray-700 dark:text-gray-200 dark:border-gray-600"
|
||||
min="0"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-between mt-4">
|
||||
{initialProfile && (
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
|
||||
@@ -5,6 +5,7 @@ import AddNewCard from "../ui/AddNewCard";
|
||||
import { getProfiles, getFormats } from "../../api/api";
|
||||
import FilterMenu from "../ui/FilterMenu";
|
||||
import SortMenu from "../ui/SortMenu";
|
||||
import { Loader } from "lucide-react";
|
||||
|
||||
function ProfilePage() {
|
||||
const [profiles, setProfiles] = useState([]);
|
||||
@@ -16,6 +17,15 @@ function ProfilePage() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [isCloning, setIsCloning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const loadingMessages = [
|
||||
"Profiling your media collection...",
|
||||
"Organizing your digital hoard...",
|
||||
"Calibrating the flux capacitor...",
|
||||
"Synchronizing with the movie matrix...",
|
||||
"Optimizing your binge-watching potential...",
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
fetchProfiles();
|
||||
@@ -32,6 +42,8 @@ function ProfilePage() {
|
||||
setAllTags(tags);
|
||||
} catch (error) {
|
||||
console.error("Error fetching profiles:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -105,6 +117,17 @@ function ProfilePage() {
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<Loader size={48} className="animate-spin text-blue-500 mb-4" />
|
||||
<p className="text-lg font-medium text-gray-700 dark:text-gray-300">
|
||||
{loadingMessages[Math.floor(Math.random() * loadingMessages.length)]}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Manage Profiles</h2>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import RegexCard from "./RegexCard";
|
||||
import RegexModal from "./RegexModal";
|
||||
import AddNewCard from "../ui/AddNewCard";
|
||||
import { getRegexes } from "../../api/api";
|
||||
import FilterMenu from "../ui/FilterMenu";
|
||||
import SortMenu from "../ui/SortMenu";
|
||||
import { Loader } from "lucide-react";
|
||||
|
||||
function RegexPage() {
|
||||
const [regexes, setRegexes] = useState([]);
|
||||
@@ -15,6 +16,15 @@ function RegexPage() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
const [allTags, setAllTags] = useState([]);
|
||||
const [isCloning, setIsCloning] = useState(false);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const loadingMessages = [
|
||||
"Matching patterns in the digital universe...",
|
||||
"Capturing groups of binary brilliance...",
|
||||
"Escaping special characters in the wild...",
|
||||
"Quantifying the unquantifiable...",
|
||||
"Regex-ing the un-regex-able...",
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
fetchRegexes();
|
||||
@@ -30,6 +40,8 @@ function RegexPage() {
|
||||
setAllTags(tags);
|
||||
} catch (error) {
|
||||
console.error("Error fetching regexes:", error);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -87,6 +99,17 @@ function RegexPage() {
|
||||
return 0;
|
||||
});
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center h-screen">
|
||||
<Loader size={48} className="animate-spin text-blue-500 mb-4" />
|
||||
<p className="text-lg font-medium text-gray-700 dark:text-gray-300">
|
||||
{loadingMessages[Math.floor(Math.random() * loadingMessages.length)]}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold mb-4">Manage Regex Patterns</h2>
|
||||
|
||||
@@ -67,6 +67,8 @@ function Modal({
|
||||
"3xl": "max-w-3xl",
|
||||
"4xl": "max-w-4xl",
|
||||
"5xl": "max-w-5xl",
|
||||
"6xl": "max-w-6xl",
|
||||
"7xl": "max-w-7xl",
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -91,6 +93,8 @@ function Modal({
|
||||
style={{
|
||||
zIndex: 1001 + level * 10,
|
||||
minHeight: "300px",
|
||||
maxHeight: "90vh",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
@@ -130,7 +134,18 @@ Modal.propTypes = {
|
||||
level: PropTypes.number,
|
||||
disableCloseOnOutsideClick: PropTypes.bool,
|
||||
disableCloseOnEscape: PropTypes.bool,
|
||||
size: PropTypes.oneOf(["sm", "md", "lg", "xl", "2xl", "3xl", "4xl", "5xl"]),
|
||||
size: PropTypes.oneOf([
|
||||
"sm",
|
||||
"md",
|
||||
"lg",
|
||||
"xl",
|
||||
"2xl",
|
||||
"3xl",
|
||||
"4xl",
|
||||
"5xl",
|
||||
"6xl",
|
||||
"7xl",
|
||||
]),
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
|
||||
Reference in New Issue
Block a user