diff --git a/public/css/admin.css b/public/css/admin.css index c78da08..a89bb63 100644 --- a/public/css/admin.css +++ b/public/css/admin.css @@ -7,18 +7,4 @@ border-radius: 0.75vw; font-weight: bold; color: lightgrey; -} - -.addButton { - position: absolute; - right: 2%; - top: calc(90vh - 3vw); - width: 3vw; -} - -.addButton button { - position: fixed; - height: 3vw; - width: 3vw; - font-size: 1.4vw; } \ No newline at end of file diff --git a/public/css/base.css b/public/css/base.css index a25db3e..101b52a 100644 --- a/public/css/base.css +++ b/public/css/base.css @@ -69,4 +69,18 @@ a.btn.disabled { color: dimgrey; pointer-events: none; cursor: pointer; +} + +.addButton { + position: absolute; + right: 2%; + top: calc(90vh - 3vw); + width: 3vw; +} + +.addButton button { + position: fixed; + height: 3vw; + width: 3vw; + font-size: 1.4vw; } \ No newline at end of file diff --git a/public/css/sets.css b/public/css/sets.css new file mode 100644 index 0000000..8a22e86 --- /dev/null +++ b/public/css/sets.css @@ -0,0 +1,21 @@ +.rename { + white-space: nowrap; +} + +.rename input[type=text] { + width: 20%; + display: inline-block; +} + +.rename button { + display: inline-block; +} + +.videos .list img { + height: 40px; +} + +.list { + max-height: 400px; + overflow-y: scroll; +} \ No newline at end of file diff --git a/public/js/search.js b/public/js/search.js new file mode 100644 index 0000000..6a316eb --- /dev/null +++ b/public/js/search.js @@ -0,0 +1,24 @@ +function search(input, elements, contents) { + for (let i = 0; i < contents.length; i++) { + console.log(contents[i]) + contents[i] = contents[i].toLowerCase(); + } + + input.onkeyup = function () { + let tokens = input.value; + tokens = tokens.toLowerCase(); + tokens = tokens.split(" "); + + for (let i = 0; i < contents.length; i++) { + let okay = true; + for (let j = 0; j < tokens.length; j++) { + if (contents[i].indexOf(tokens[j]) < 0) { + okay = false; + break; + } + } + + elements[i].style.display = okay ? "block" : "none"; + } + } +} \ No newline at end of file diff --git a/src/Controller/DashboardController.php b/src/Controller/DashboardController.php index 9f46d29..e56fb05 100644 --- a/src/Controller/DashboardController.php +++ b/src/Controller/DashboardController.php @@ -4,13 +4,16 @@ namespace App\Controller; +use App\Entity\Set; use App\Entity\User; use App\Entity\Video; use App\Entity\VideoLink; +use App\Form\SetType; use App\Form\VideoLinkType; use App\Form\VideoType; use App\Mapper\CustomUuidMapper; use App\Service\LoggingService; +use App\Service\SetService; use App\Service\UserService; use App\Service\VideoLinkService; use App\Service\VideoService; @@ -28,10 +31,13 @@ class DashboardController extends AbstractController { public const DELETE_VIDEO_CSRF_TOKEN_ID = "delete-video"; public const DELETE_LINK_CSRF_TOKEN_ID = "delete-link"; + public const DELETE_SET_CSRF_TOKEN_ID = "delete-set"; + public const EDIT_SET_CSRF_TOKEN_ID = "edit-set"; private $userService; private $videoService; private $videoLinkService; + private $setService; private $loggingService; private $uuidMapper; @@ -40,6 +46,7 @@ class DashboardController extends AbstractController UserService $userService, VideoService $videoService, VideoLinkService $videoLinkService, + SetService $setService, LoggingService $loggingService, CustomUuidMapper $uuidMapper ) @@ -47,6 +54,7 @@ class DashboardController extends AbstractController $this->userService = $userService; $this->videoService = $videoService; $this->videoLinkService = $videoLinkService; + $this->setService = $setService; $this->loggingService = $loggingService; $this->uuidMapper = $uuidMapper; } @@ -375,4 +383,154 @@ class DashboardController extends AbstractController "form" => $form->createView() ]); } + + /** + * @Route("/sets", name="app_sets") + */ + public function showSets(): Response + { + if (!$this->isGranted(User::ROLE_USER)) { + // not logged in + return $this->redirectToRoute("app_login"); + } + + $user = $this->userService->getLoggedInUser(); + $sets = $this->setService->getAll($user); + + foreach ($sets as $set) { + $set->setCustomId($this->uuidMapper->toString($set->getId())); + } + + return $this->render("dashboard/sets.html.twig", [ + "sets" => $sets + ]); + } + + /** + * @Route("/sets/create", name="app_create_set") + */ + public function createSet(Request $request): Response + { + if (!$this->isGranted(User::ROLE_USER)) { + // not logged in + return $this->redirectToRoute("app_login"); + } + + $set = new Set(); + $form = $this->createForm(SetType::class, $set); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $set = $form->getData(); + + $this->setService->add($set); + + return $this->redirectToRoute("app_sets"); + } + + return $this->render("dashboard/set-new.html.twig", [ + "form" => $form->createView() + ]); + } + + /** + * @Route("/sets/delete", name="app_delete_set", methods={"POST"}) + */ + public function deleteSet(Request $request): Response + { + $token = $request->request->get("csrfToken"); + $setId = $request->request->get("setId"); + + if (!$this->isCsrfTokenValid(self::DELETE_SET_CSRF_TOKEN_ID, $token)) { + throw new AccessDeniedHttpException(); + } + + if (!$setId) { + throw new BadRequestHttpException(); + } + + try { + $setId = $this->uuidMapper->fromString($setId); + } catch (ConversionException $e) { + throw new BadRequestHttpException(); + } + + $set = $this->setService->get($setId); + if ($set == null || $set->getCreator() != $this->userService->getLoggedInUser()) { + throw new AccessDeniedHttpException(); + } + + $this->setService->delete($set); + + return $this->redirectToRoute("app_sets"); + } + + /** + * @Route("/sets/{setId}", name="app_edit_set") + */ + public function editSet($setId, Request $request): Response + { + if (!$this->isGranted(User::ROLE_USER)) { + // not logged in + return $this->redirectToRoute("app_login"); + } + + try { + $setId = $this->uuidMapper->fromString($setId); + } catch (ConversionException $e) { + throw new BadRequestHttpException(); + } + + $set = $this->setService->get($setId); + + $form = $this->createForm(SetType::class, $set); + $form->handleRequest($request); + if ($form->isSubmitted() && $form->isValid()) { + $set = $form->getData(); + + $this->setService->update($set); + } + + $set->setCustomId($this->uuidMapper->toString($set->getId())); + + return $this->render("dashboard/set-edit.html.twig", [ + "form" => $form->createView(), + "set" => $set + ]); + } + + /** + * @Route("/sets/{setId}/add", name="app_edit_set_add") + */ + public function editSetAdd($setId, Request $request): Response + { + if (!$this->isGranted(User::ROLE_USER)) { + // not logged in + return $this->redirectToRoute("app_login"); + } + + try { + $setId = $this->uuidMapper->fromString($setId); + } catch (ConversionException $e) { + throw new BadRequestHttpException(); + } + + $set = $this->setService->get($setId); + + $user = $this->userService->getLoggedInUser(); + + $videos = $this->videoService->getVideos($user); + + $videos = array_udiff($videos, $set->getVideos()->getValues(), function ($a, $b) { + return $a->getId()->compareTo($b->getId()); + }); + + foreach ($videos as $video) { + $video->setCustomId($this->uuidMapper->toString($video->getId())); + } + + return $this->render("dashboard/set-edit-add.html.twig", [ + "set" => $set, + "videos" => $videos + ]); + } } \ No newline at end of file diff --git a/src/Entity/Set.php b/src/Entity/Set.php new file mode 100644 index 0000000..3fc2ac7 --- /dev/null +++ b/src/Entity/Set.php @@ -0,0 +1,135 @@ +videos = new ArrayCollection(); + } + + public function getId(): ?UuidInterface + { + return $this->id; + } + + /** + * @return Collection|Video[] + */ + public function getVideos(): Collection + { + return $this->videos; + } + + public function addVideo(Video $video): self + { + if (!$this->videos->contains($video)) { + $this->videos[] = $video; + } + + return $this; + } + + public function removeVideo(Video $video): self + { + $this->videos->removeElement($video); + + return $this; + } + + public function getName(): ?string + { + return $this->name; + } + + public function setName(string $name): self + { + $this->name = $name; + + return $this; + } + + public function getCreated(): ?DateTimeImmutable + { + return $this->created; + } + + public function setCreated(): self + { + $this->created = new DateTimeImmutable(); + + return $this; + } + + public function getCreator(): ?User + { + return $this->creator; + } + + public function setCreator(?User $creator): self + { + $this->creator = $creator; + + return $this; + } + + public function getCustomId(): string + { + return $this->customId; + } + + public function setCustomId($customId): self + { + $this->customId = $customId; + return $this; + } + + public function clearVideos() + { + $this->videos = []; + return $this; + } +} diff --git a/src/Entity/Video.php b/src/Entity/Video.php index 02c3eb1..800aa98 100644 --- a/src/Entity/Video.php +++ b/src/Entity/Video.php @@ -5,6 +5,7 @@ namespace App\Entity; use App\Repository\VideoRepository; use DateTimeImmutable; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; use Ramsey\Uuid\Doctrine\UuidGenerator; use Ramsey\Uuid\UuidInterface; @@ -73,9 +74,15 @@ class Video private $views = 0; + /** + * @ORM\ManyToMany(targetEntity=Set::class, mappedBy="videos") + */ + private $sets; + public function __construct() { $this->videoLinks = new ArrayCollection(); + $this->sets = new ArrayCollection(); } public function getId(): ?UuidInterface @@ -243,4 +250,31 @@ class Video $this->views = $views; return $this; } + + /** + * @return Collection|Set[] + */ + public function getSets(): Collection + { + return $this->sets; + } + + public function addSet(Set $set): self + { + if (!$this->sets->contains($set)) { + $this->sets[] = $set; + $set->addVideo($this); + } + + return $this; + } + + public function removeSet(Set $set): self + { + if ($this->sets->removeElement($set)) { + $set->removeVideo($this); + } + + return $this; + } } diff --git a/src/Entity/VideoLink.php b/src/Entity/VideoLink.php index 1b6f924..d58eb5d 100644 --- a/src/Entity/VideoLink.php +++ b/src/Entity/VideoLink.php @@ -64,6 +64,11 @@ class VideoLink */ private $comment; + /** + * @ORM\ManyToOne(targetEntity=Set::class) + */ + private $sets; + public function getId(): ?UuidInterface { return $this->id; @@ -207,4 +212,16 @@ class VideoLink return true; } + + public function getSets(): ?Set + { + return $this->sets; + } + + public function setSets(?Set $sets): self + { + $this->sets = $sets; + + return $this; + } } diff --git a/src/Form/SetType.php b/src/Form/SetType.php new file mode 100644 index 0000000..3437144 --- /dev/null +++ b/src/Form/SetType.php @@ -0,0 +1,32 @@ +add("name", TextType::class, [ + "required" => true + ]) + ->add("submit", SubmitType::class); + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + "data_class" => Set::class, + ]); + } + +} \ No newline at end of file diff --git a/src/Repository/SetRepository.php b/src/Repository/SetRepository.php new file mode 100644 index 0000000..05603dd --- /dev/null +++ b/src/Repository/SetRepository.php @@ -0,0 +1,67 @@ +createQueryBuilder('s') + ->andWhere('s.exampleField = :val') + ->setParameter('val', $value) + ->orderBy('s.id', 'ASC') + ->setMaxResults(10) + ->getQuery() + ->getResult() + ; + } + */ + + /* + public function findOneBySomeField($value): ?Set + { + return $this->createQueryBuilder('s') + ->andWhere('s.exampleField = :val') + ->setParameter('val', $value) + ->getQuery() + ->getOneOrNullResult() + ; + } + */ + + public function save(Set $set) + { + $this->_em->persist($set); + $this->_em->flush(); + } + + public function delete(Set $set) + { + $this->_em->remove($set); + $this->_em->flush(); + } + + public function update($set) + { + $this->_em->flush(); + } +} diff --git a/src/Service/SetService.php b/src/Service/SetService.php new file mode 100644 index 0000000..0ee4f41 --- /dev/null +++ b/src/Service/SetService.php @@ -0,0 +1,57 @@ +userService = $userService; + $this->setRepository = $setRepository; + } + + public function getAll(User $user): array + { + return $this->setRepository->findByCreator($user); + } + + public function add(Set $set) + { + $set->setCreated(); + $set->setCreator($this->userService->getLoggedInUser()); + $set->clearVideos(); + + $this->setRepository->save($set); + } + + public function get(UuidInterface $setId): ?Set + { + return $this->setRepository->findOneById($setId); + } + + public function delete(Set $set) + { + $this->setRepository->delete($set); + } + + public function update($set) + { + $this->setRepository->update($set); + } + +} \ No newline at end of file diff --git a/templates/base.html.twig b/templates/base.html.twig index 610ced6..3159bdd 100644 --- a/templates/base.html.twig +++ b/templates/base.html.twig @@ -48,6 +48,11 @@ aria-current="page" href="{{ path("app_links") }}">Links + {% endif %} {% if is_granted('ROLE_ADMIN') %}