@@ -10,10 +10,12 @@ import {
1010 ActionIcon ,
1111 Tooltip ,
1212 Title ,
13+ Anchor ,
14+ Text ,
1315} from '@mantine/core' ;
1416import { useForm } from '@mantine/form' ;
1517import { useState } from 'react' ;
16- import { Apps } from 'tabler- icons-react ' ;
18+ import { IconApps as Apps } from '@ tabler/ icons' ;
1719import { v4 as uuidv4 } from 'uuid' ;
1820import { useConfig } from '../../tools/state' ;
1921import { ServiceTypeList } from '../../tools/types' ;
@@ -61,15 +63,48 @@ function MatchIcon(name: string, form: any) {
6163 return false ;
6264}
6365
66+ function MatchService ( name : string , form : any ) {
67+ const service = ServiceTypeList . find ( ( s ) => s === name ) ;
68+ if ( service ) {
69+ form . setFieldValue ( 'type' , service ) ;
70+ }
71+ }
72+
73+ function MatchPort ( name : string , form : any ) {
74+ const portmap = [
75+ { name : 'qBittorrent' , value : '8080' } ,
76+ { name : 'Sonarr' , value : '8989' } ,
77+ { name : 'Radarr' , value : '7878' } ,
78+ { name : 'Lidarr' , value : '8686' } ,
79+ { name : 'Readarr' , value : '8686' } ,
80+ { name : 'Deluge' , value : '8112' } ,
81+ { name : 'Transmission' , value : '9091' } ,
82+ ] ;
83+ // Match name with portmap key
84+ const port = portmap . find ( ( p ) => p . name === name ) ;
85+ if ( port ) {
86+ form . setFieldValue ( 'url' , `http://localhost:${ port . value } ` ) ;
87+ }
88+ }
89+
6490export function AddAppShelfItemForm ( props : { setOpened : ( b : boolean ) => void } & any ) {
6591 const { setOpened } = props ;
6692 const { config, setConfig } = useConfig ( ) ;
6793 const [ isLoading , setLoading ] = useState ( false ) ;
6894
95+ // Extract all the categories from the services in config
96+ const categoryList = config . services . reduce ( ( acc , cur ) => {
97+ if ( cur . category && ! acc . includes ( cur . category ) ) {
98+ acc . push ( cur . category ) ;
99+ }
100+ return acc ;
101+ } , [ ] as string [ ] ) ;
102+
69103 const form = useForm ( {
70104 initialValues : {
71105 id : props . id ?? uuidv4 ( ) ,
72106 type : props . type ?? 'Other' ,
107+ category : props . category ?? undefined ,
73108 name : props . name ?? '' ,
74109 icon : props . icon ?? '/favicon.svg' ,
75110 url : props . url ?? '' ,
@@ -99,6 +134,15 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
99134 } ,
100135 } ) ;
101136
137+ // Try to set const hostname to new URL(form.values.url).hostname)
138+ // If it fails, set it to the form.values.url
139+ let hostname = form . values . url ;
140+ try {
141+ hostname = new URL ( form . values . url ) . origin ;
142+ } catch ( e ) {
143+ // Do nothing
144+ }
145+
102146 return (
103147 < >
104148 < Center >
@@ -145,10 +189,9 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
145189 value = { form . values . name }
146190 onChange = { ( event ) => {
147191 form . setFieldValue ( 'name' , event . currentTarget . value ) ;
148- const match = MatchIcon ( event . currentTarget . value , form ) ;
149- if ( match ) {
150- form . setFieldValue ( 'icon' , match ) ;
151- }
192+ MatchIcon ( event . currentTarget . value , form ) ;
193+ MatchService ( event . currentTarget . value , form ) ;
194+ MatchPort ( event . currentTarget . value , form ) ;
152195 } }
153196 error = { form . errors . name && 'Invalid icon url' }
154197 />
@@ -166,29 +209,64 @@ export function AddAppShelfItemForm(props: { setOpened: (b: boolean) => void } &
166209 { ...form . getInputProps ( 'url' ) }
167210 />
168211 < Select
169- label = "Select the type of service (used for API calls) "
212+ label = "Service type"
170213 defaultValue = "Other"
171214 placeholder = "Pick one"
172215 required
173216 searchable
174217 data = { ServiceTypeList }
175218 { ...form . getInputProps ( 'type' ) }
176219 />
220+ < Select
221+ label = "Category"
222+ data = { categoryList }
223+ placeholder = "Select a category or create a new one"
224+ nothingFound = "Nothing found"
225+ searchable
226+ clearable
227+ creatable
228+ onClick = { ( e ) => {
229+ e . preventDefault ( ) ;
230+ } }
231+ getCreateLabel = { ( query ) => `+ Create "${ query } "` }
232+ onCreate = { ( query ) => { } }
233+ { ...form . getInputProps ( 'category' ) }
234+ />
177235 < LoadingOverlay visible = { isLoading } />
178236 { ( form . values . type === 'Sonarr' ||
179237 form . values . type === 'Radarr' ||
180238 form . values . type === 'Lidarr' ||
181239 form . values . type === 'Readarr' ) && (
182- < TextInput
183- required
184- label = "API key"
185- placeholder = "Your API key"
186- value = { form . values . apiKey }
187- onChange = { ( event ) => {
188- form . setFieldValue ( 'apiKey' , event . currentTarget . value ) ;
189- } }
190- error = { form . errors . apiKey && 'Invalid API key' }
191- />
240+ < >
241+ < TextInput
242+ required
243+ label = "API key"
244+ placeholder = "Your API key"
245+ value = { form . values . apiKey }
246+ onChange = { ( event ) => {
247+ form . setFieldValue ( 'apiKey' , event . currentTarget . value ) ;
248+ } }
249+ error = { form . errors . apiKey && 'Invalid API key' }
250+ />
251+ < Text
252+ style = { {
253+ alignSelf : 'center' ,
254+ fontSize : '0.75rem' ,
255+ textAlign : 'center' ,
256+ color : 'gray' ,
257+ } }
258+ >
259+ Tip: Get your API key{ ' ' }
260+ < Anchor
261+ target = "_blank"
262+ weight = "bold"
263+ style = { { fontStyle : 'inherit' , fontSize : 'inherit' } }
264+ href = { `${ hostname } /settings/general` }
265+ >
266+ here.
267+ </ Anchor >
268+ </ Text >
269+ </ >
192270 ) }
193271 { form . values . type === 'qBittorrent' && (
194272 < >
0 commit comments