🌊 qBittorrent Settings Scaffold (#90)

* 🌊 qBittorrent settings scaffold

* 🔧 Added scaffold for the qBittorrent connection form

* 🔧 Some refactoring

* 🔧 Cleaned up folder structure

* 🔧 Fixed broken paths

* 🔧 Cleaned up Search and Import component hierarchy

* 🔧 More path fixes

* 🔧 Tooling changes

* 📝 Qbittorrent form scaffold

* ⬆️ Bumped @dnd-kit deps

* 🧑🏼‍🔧 Fixed the hostname regex

* 🏗️ Adding fields to the settings form

* 🔧 Formatting and more layout changes

* 🔧 Added Prowlarr settings items in JSON

* 📝 Purified Card Component

* 📝 Abstracted connection form into a component

* 🏗️ Reorganized tabs

* Migrating from Redux to RTK-query

* ⬇️ Fetched qBittorrent settings

* 🏗️ Trying out react-query

* 🧩 Added react-query query to qBittorrentSettings page

* 📝 qbittorrent form RU actions first draft

* 🏗️ Added loading state check

* 🏗 Added error check state

* 🏗️ Refactored AirDCPP context using react-query

* 🏗️ Refactoring AirDCPP Settings Form with react-query

* 🔧 Removed context

* 🔧 Removing context from AirDCPP settings page

* 🔧 Fixed early init error on the store

* 🐛 Debugging AirDCPP Settings Form page

* 🧸 Zustand-ified AirDCPP Form

*  AirDCPP code cleaned up from App.tsx

*  Re-added yarn.lock
This commit was merged in pull request #90.
This commit is contained in:
2023-11-07 12:46:08 -05:00
committed by GitHub
parent 1bd3d611e4
commit 8bebffd95e
52 changed files with 2715 additions and 5428 deletions

View File

@@ -0,0 +1,25 @@
import React, { forwardRef } from "react";
export const Cover = forwardRef(
({ url, index, faded, style, ...props }, ref) => {
const inlineStyles = {
opacity: faded ? "0.2" : "1",
transformOrigin: "0 0",
minHeight: index === 0 ? 300 : 300,
maxWidth: 200,
gridRowStart: index === 0 ? "span" : null,
gridColumnStart: index === 0 ? "span" : null,
backgroundImage: `url("${url}")`,
backgroundSize: "cover",
backgroundPosition: "center",
backgroundColor: "grey",
border: "1px solid #CCC",
borderRadius: "10px",
...style,
};
return <div ref={ref} style={inlineStyles} {...props}></div>;
},
);
Cover.displayName = "Cover";

View File

@@ -0,0 +1,94 @@
import React, { ReactElement, useState } from "react";
// https://codesandbox.io/s/dndkit-sortable-image-grid-py6ve?file=/src/Grid.jsx
import {
DndContext,
closestCenter,
MouseSensor,
TouchSensor,
DragOverlay,
useSensor,
useSensors,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
rectSortingStrategy,
} from "@dnd-kit/sortable";
import { Grid } from "./Grid";
import { SortableCover } from "./SortableCover";
import { Cover } from "./Cover";
import { map } from "lodash";
export const DnD = (data) => {
const [items, setItems] = useState(data.data);
const [activeId, setActiveId] = useState(null);
const sensors = useSensors(useSensor(MouseSensor), useSensor(TouchSensor));
function handleDragStart(event) {
setActiveId(event.active.id);
}
function handleDragEnd(event) {
const { active, over } = event;
if (active.id !== over.id) {
setItems((items) => {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
return arrayMove(items, oldIndex, newIndex);
});
}
setActiveId(null);
}
function handleDragCancel() {
setActiveId(null);
}
return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragStart={handleDragStart}
onDragEnd={handleDragEnd}
onDragCancel={handleDragCancel}
>
<SortableContext items={items} strategy={rectSortingStrategy}>
<Grid columns={4}>
{map(items, (url, index) => {
return (
<div>
<SortableCover key={url} url={url} index={index} />
<div
className="mt-2 mb-2"
onClick={(e) => data.onClickHandler(url)}
>
<div className="box p-2 pl-3 control-palette">
<span className="tag is-warning mr-2">{index}</span>
<span className="icon is-small mr-2">
<i className="fa-solid fa-vial"></i>
</span>
<span className="icon is-small mr-2">
<i className="fa-solid fa-bullseye"></i>
</span>
<span className="icon is-small mr-2">
<i className="fa-regular fa-trash-can"></i>
</span>
</div>
</div>
</div>
);
})}
</Grid>
</SortableContext>
<DragOverlay adjustScale={true}>
{activeId ? (
<Cover url={activeId} index={items.indexOf(activeId)} />
) : null}
</DragOverlay>
</DndContext>
);
};
export default DnD;

View File

@@ -0,0 +1,17 @@
import React from "react";
export function Grid({ children, columns }) {
return (
<div
style={{
display: "grid",
gridTemplateColumns: `repeat(${columns}, 200px)`,
columnGap: 1,
gridGap: 10,
padding: 10,
}}
>
{children}
</div>
);
}

View File

@@ -0,0 +1,32 @@
import React from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { Cover } from "./Cover";
export const SortableCover = (props) => {
const sortable = useSortable({ id: props.url });
const {
attributes,
listeners,
isDragging,
setNodeRef,
transform,
transition,
} = sortable;
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<Cover
ref={setNodeRef}
style={style}
{...props}
{...attributes}
{...listeners}
/>
);
};