From 7bbe9e8e89033617a840a9a5a7bf1060604456fe Mon Sep 17 00:00:00 2001 From: Matthew Penner Date: Sun, 31 Jan 2021 15:59:37 -0700 Subject: [PATCH] ui(admin): start work on LocationSelect.tsx --- .../Locations/LocationController.php | 35 ++---- .../api/admin/locations/searchLocations.ts | 20 ++++ .../components/admin/nodes/LocationSelect.tsx | 111 ++++++++++++++++++ .../admin/nodes/NodeSettingsContainer.tsx | 20 ++++ resources/scripts/routers/AdminRouter.tsx | 2 +- 5 files changed, 160 insertions(+), 28 deletions(-) create mode 100644 resources/scripts/api/admin/locations/searchLocations.ts create mode 100644 resources/scripts/components/admin/nodes/LocationSelect.tsx diff --git a/app/Http/Controllers/Api/Application/Locations/LocationController.php b/app/Http/Controllers/Api/Application/Locations/LocationController.php index 43c17c164..cd91b6713 100644 --- a/app/Http/Controllers/Api/Application/Locations/LocationController.php +++ b/app/Http/Controllers/Api/Application/Locations/LocationController.php @@ -2,7 +2,6 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Locations; -use Illuminate\Http\Response; use Pterodactyl\Models\Location; use Illuminate\Http\JsonResponse; use Spatie\QueryBuilder\QueryBuilder; @@ -21,25 +20,10 @@ use Pterodactyl\Http\Requests\Api\Application\Locations\UpdateLocationRequest; class LocationController extends ApplicationApiController { - /** - * @var \Pterodactyl\Services\Locations\LocationCreationService - */ - private $creationService; - - /** - * @var \Pterodactyl\Services\Locations\LocationDeletionService - */ - private $deletionService; - - /** - * @var \Pterodactyl\Contracts\Repository\LocationRepositoryInterface - */ - private $repository; - - /** - * @var \Pterodactyl\Services\Locations\LocationUpdateService - */ - private $updateService; + private LocationCreationService $creationService; + private LocationDeletionService $deletionService; + private LocationUpdateService $updateService; + private LocationRepositoryInterface $repository; /** * LocationController constructor. @@ -47,15 +31,15 @@ class LocationController extends ApplicationApiController public function __construct( LocationCreationService $creationService, LocationDeletionService $deletionService, - LocationRepositoryInterface $repository, - LocationUpdateService $updateService + LocationUpdateService $updateService, + LocationRepositoryInterface $repository ) { parent::__construct(); $this->creationService = $creationService; $this->deletionService = $deletionService; - $this->repository = $repository; $this->updateService = $updateService; + $this->repository = $repository; } /** @@ -66,7 +50,7 @@ class LocationController extends ApplicationApiController $perPage = $request->query('per_page', 10); if ($perPage < 1) { $perPage = 10; - } else if ($perPage > 100) { + } elseif ($perPage > 100) { throw new BadRequestHttpException('"per_page" query parameter must be below 100.'); } @@ -94,9 +78,6 @@ class LocationController extends ApplicationApiController * Store a new location on the Panel and return a HTTP/201 response code with the * new location attached. * - * @param \Pterodactyl\Http\Requests\Api\Application\Locations\StoreLocationRequest $request - * @return \Illuminate\Http\JsonResponse - * * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ public function store(StoreLocationRequest $request): JsonResponse diff --git a/resources/scripts/api/admin/locations/searchLocations.ts b/resources/scripts/api/admin/locations/searchLocations.ts new file mode 100644 index 000000000..4d065f944 --- /dev/null +++ b/resources/scripts/api/admin/locations/searchLocations.ts @@ -0,0 +1,20 @@ +import http from '@/api/http'; +import { Location, rawDataToLocation } from '@/api/admin/locations/getLocations'; + +export default (filters?: Record): Promise => { + const params = {}; + if (filters !== undefined) { + Object.keys(filters).forEach(key => { + // @ts-ignore + params['filter[' + key + ']'] = filters[key]; + }); + } + + return new Promise((resolve, reject) => { + http.get('/api/application/locations', { params: { ...params } }) + .then(response => resolve( + (response.data.data || []).map(rawDataToLocation) + )) + .catch(reject); + }); +}; diff --git a/resources/scripts/components/admin/nodes/LocationSelect.tsx b/resources/scripts/components/admin/nodes/LocationSelect.tsx new file mode 100644 index 000000000..219a99b70 --- /dev/null +++ b/resources/scripts/components/admin/nodes/LocationSelect.tsx @@ -0,0 +1,111 @@ +import React, { useEffect, useState } from 'react'; +import styled from 'styled-components/macro'; +import tw from 'twin.macro'; +import Input from '@/components/elements/Input'; +import Label from '@/components/elements/Label'; +import { Location } from '@/api/admin/locations/getLocations'; +import searchLocations from '@/api/admin/locations/searchLocations'; +import InputSpinner from '@/components/elements/InputSpinner'; +import { debounce } from 'debounce'; + +const Dropdown = styled.div<{ expanded: boolean }>` + ${tw`absolute mt-1 w-full rounded-md bg-neutral-900 shadow-lg z-10`}; + ${props => !props.expanded && tw`hidden`}; +`; + +export default ({ defaultLocation }: { defaultLocation: Location }) => { + const [ loading, setLoading ] = useState(false); + const [ expanded, setExpanded ] = useState(false); + const [ location, setLocation ] = useState(defaultLocation); + const [ locations, setLocations ] = useState([]); + + const [ inputText, setInputText ] = useState(''); + + const onFocus = () => { + setInputText(''); + setLocations([]); + setExpanded(true); + }; + + const onBlur = () => { + // setInputText(location.short); + // setExpanded(false); + }; + + const search = debounce((query: string) => { + if (!expanded) { + return; + } + + if (query === '') { + setLocations([]); + return; + } + + setLoading(true); + searchLocations({ short: query }).then((locations) => { + console.log(locations); + setLocations(locations); + }).then(() => setLoading(false)); + }, 200); + + const selectLocation = (location: Location) => { + setLocation(location); + }; + + useEffect(() => { + setInputText(location.short); + setExpanded(false); + }, [ location ]); + + return ( +
+ + +
+ + { + setInputText(e.currentTarget.value); + search(e.currentTarget.value); + }} + /> + + +
+ +
+ + +
    + {locations.map(l => ( + l.id === location.id ? +
  • selectLocation(l)}> +
    + + {l.short} + +
    + + + + +
  • + : +
  • selectLocation(l)}> +
    + + {l.short} + +
    +
  • + ))} +
+
+
+
+ ); +}; diff --git a/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx index c0b52ff94..0ac16c096 100644 --- a/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx +++ b/resources/scripts/components/admin/nodes/NodeSettingsContainer.tsx @@ -1,3 +1,4 @@ +import LocationSelect from '@/components/admin/nodes/LocationSelect'; import React from 'react'; import AdminBox from '@/components/admin/AdminBox'; import tw from 'twin.macro'; @@ -12,8 +13,11 @@ import { ApplicationStore } from '@/state'; import { Actions, useStoreActions } from 'easy-peasy'; interface Values { + public: boolean; name: string; description: string; + locationId: number; + fqdn: string; } export default () => { @@ -44,8 +48,11 @@ export default () => { { /> +
+ +
+ +
+ +
+