mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat(frontend): make services sortable
This commit is contained in:
@@ -11,6 +11,8 @@ import {
|
||||
VisualTag,
|
||||
AudioTag,
|
||||
Encode,
|
||||
ServiceDetail,
|
||||
ServiceCredential,
|
||||
} from '@aiostreams/types';
|
||||
import SortableCardList from '../../components/SortableCardList';
|
||||
import ServiceInput from '../../components/ServiceInput';
|
||||
@@ -686,7 +688,7 @@ export default function Configure() {
|
||||
Enable the services you have accounts with and enter your
|
||||
credentials.
|
||||
</p>
|
||||
{services.map((service) => (
|
||||
{services.map((service, index) => (
|
||||
<ServiceInput
|
||||
key={service.id}
|
||||
serviceName={service.name}
|
||||
@@ -701,8 +703,8 @@ export default function Configure() {
|
||||
}}
|
||||
fields={
|
||||
serviceDetails
|
||||
.find((detail) => detail.id === service.id)
|
||||
?.credentials.map((credential) => ({
|
||||
.find((detail: ServiceDetail) => detail.id === service.id)
|
||||
?.credentials.map((credential: ServiceCredential) => ({
|
||||
label: credential.label,
|
||||
link: credential.link,
|
||||
value: service.credentials[credential.id] || '',
|
||||
@@ -722,6 +724,21 @@ export default function Configure() {
|
||||
},
|
||||
})) || []
|
||||
}
|
||||
moveService={(direction) => {
|
||||
const newServices = [...services];
|
||||
const serviceIndex = newServices.findIndex(
|
||||
(s) => s.id === service.id
|
||||
);
|
||||
const [movedService] = newServices.splice(serviceIndex, 1);
|
||||
if (direction === 'up' && serviceIndex > 0) {
|
||||
newServices.splice(serviceIndex - 1, 0, movedService);
|
||||
} else if (direction === 'down' && serviceIndex < newServices.length) {
|
||||
newServices.splice(serviceIndex + 1, 0, movedService);
|
||||
}
|
||||
setServices(newServices);
|
||||
}}
|
||||
canMoveUp={index > 0}
|
||||
canMoveDown={index < services.length - 1}
|
||||
/>
|
||||
))}
|
||||
|
||||
|
||||
@@ -51,3 +51,29 @@
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.actionButton {
|
||||
background-color: transparent;
|
||||
border-radius: var(--borderRadius);
|
||||
display: flex;
|
||||
margin: 5px;
|
||||
font-size: 1.2em;
|
||||
align-items: center;
|
||||
border-width: 0;
|
||||
transition: transform 0.2s;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
.actionButton:hover {
|
||||
transform: scale(1.1);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.actionButton:active {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import styles from './ServiceInput.module.css';
|
||||
|
||||
interface Field {
|
||||
label: string;
|
||||
link: string;
|
||||
link?: string;
|
||||
value: string;
|
||||
setValue: (value: string) => void;
|
||||
}
|
||||
@@ -13,6 +13,9 @@ interface ServiceInputProps {
|
||||
enabled: boolean;
|
||||
setEnabled: (enabled: boolean) => void;
|
||||
fields: Field[];
|
||||
moveService: (direction: 'up' | 'down') => void;
|
||||
canMoveUp: boolean;
|
||||
canMoveDown: boolean;
|
||||
}
|
||||
|
||||
const ServiceInput: React.FC<ServiceInputProps> = ({
|
||||
@@ -20,6 +23,9 @@ const ServiceInput: React.FC<ServiceInputProps> = ({
|
||||
enabled,
|
||||
setEnabled,
|
||||
fields,
|
||||
moveService,
|
||||
canMoveUp,
|
||||
canMoveDown,
|
||||
}) => {
|
||||
return (
|
||||
<div
|
||||
@@ -27,12 +33,79 @@ const ServiceInput: React.FC<ServiceInputProps> = ({
|
||||
>
|
||||
<div className={styles.header}>
|
||||
<span className={styles.serviceName}>{serviceName}</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked)}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
<div className={styles.actions}>
|
||||
{canMoveUp && (
|
||||
<button
|
||||
className={styles.actionButton}
|
||||
onClick={() => moveService('up')}
|
||||
>
|
||||
<svg
|
||||
fill="#ffffff"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 96.154 96.154"
|
||||
xmlSpace="preserve"
|
||||
stroke="#ffffff"
|
||||
transform="matrix(1, 0, 0, -1, 0, 0)"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
{' '}
|
||||
<g>
|
||||
{' '}
|
||||
<path d="M0.561,20.971l45.951,57.605c0.76,0.951,2.367,0.951,3.127,0l45.956-57.609c0.547-0.689,0.709-1.716,0.414-2.61 c-0.061-0.187-0.129-0.33-0.186-0.437c-0.351-0.65-1.025-1.056-1.765-1.056H2.093c-0.736,0-1.414,0.405-1.762,1.056 c-0.059,0.109-0.127,0.253-0.184,0.426C-0.15,19.251,0.011,20.28,0.561,20.971z"></path>{' '}
|
||||
</g>{' '}
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
{canMoveDown && (
|
||||
<button
|
||||
className={styles.actionButton}
|
||||
onClick={() => moveService('down')}
|
||||
>
|
||||
<svg
|
||||
fill="#ffffff"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24px"
|
||||
height="24px"
|
||||
viewBox="0 0 96.154 96.154"
|
||||
stroke="#ffffff"
|
||||
>
|
||||
<g id="SVGRepo_bgCarrier" strokeWidth="0"></g>
|
||||
<g
|
||||
id="SVGRepo_tracerCarrier"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
></g>
|
||||
<g id="SVGRepo_iconCarrier">
|
||||
{' '}
|
||||
<g>
|
||||
{' '}
|
||||
<path d="M0.561,20.971l45.951,57.605c0.76,0.951,2.367,0.951,3.127,0l45.956-57.609c0.547-0.689,0.709-1.716,0.414-2.61 c-0.061-0.187-0.129-0.33-0.186-0.437c-0.351-0.65-1.025-1.056-1.765-1.056H2.093c-0.736,0-1.414,0.405-1.762,1.056 c-0.059,0.109-0.127,0.253-0.184,0.426C-0.15,19.251,0.011,20.28,0.561,20.971z"></path>{' '}
|
||||
</g>{' '}
|
||||
</g>
|
||||
</svg>
|
||||
</button>
|
||||
)}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={enabled}
|
||||
onChange={(e) => setEnabled(e.target.checked)}
|
||||
className={styles.checkbox}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{enabled && (
|
||||
@@ -40,15 +113,20 @@ const ServiceInput: React.FC<ServiceInputProps> = ({
|
||||
{fields.map((field, index) => (
|
||||
<div key={index} className={styles.field}>
|
||||
<label>
|
||||
{field.label}. Get it{' '}
|
||||
<a
|
||||
href={field.link}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style={{ textDecoration: 'underline' }}
|
||||
>
|
||||
here
|
||||
</a>
|
||||
{field.label}
|
||||
{field.link && (
|
||||
<>
|
||||
{'. Get it '}
|
||||
<a
|
||||
href={field.link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={styles.link}
|
||||
>
|
||||
here
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
|
||||
@@ -193,3 +193,17 @@ export interface AddonDetail {
|
||||
supportedServices: string[];
|
||||
options?: AddonOptionDetail[];
|
||||
}
|
||||
|
||||
export interface ServiceDetail {
|
||||
name: string;
|
||||
id: string;
|
||||
shortName: string;
|
||||
knownNames: string[];
|
||||
credentials: ServiceCredential[];
|
||||
}
|
||||
|
||||
export interface ServiceCredential {
|
||||
id: string;
|
||||
label: string;
|
||||
link?: string;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user