From 0882091bf24e26c7a810c6cc7ae619f6e8214903 Mon Sep 17 00:00:00 2001 From: ildyria Date: Wed, 13 Nov 2024 12:57:35 +0100 Subject: [PATCH] squash --- app/Actions/Photo/Timeline.php | 44 ++++ .../Gallery/TimelineController.php | 40 +++ .../Requests/Photo/GetTimelineRequest.php | 22 ++ app/Http/Resources/Timeline/InitResource.php | 22 ++ .../Resources/Timeline/TimelineResource.php | 77 ++++++ resources/js/lychee.d.ts | 14 ++ resources/js/router/routes.ts | 6 + resources/js/services/timeline-service.ts | 14 ++ .../js/views/gallery-panels/Timeline.vue | 229 ++++++++++++++++++ routes/api_v2.php | 5 +- routes/web_v2.php | 2 + 11 files changed, 474 insertions(+), 1 deletion(-) create mode 100644 app/Actions/Photo/Timeline.php create mode 100644 app/Http/Controllers/Gallery/TimelineController.php create mode 100644 app/Http/Requests/Photo/GetTimelineRequest.php create mode 100644 app/Http/Resources/Timeline/InitResource.php create mode 100644 app/Http/Resources/Timeline/TimelineResource.php create mode 100644 resources/js/services/timeline-service.ts create mode 100644 resources/js/views/gallery-panels/Timeline.vue diff --git a/app/Actions/Photo/Timeline.php b/app/Actions/Photo/Timeline.php new file mode 100644 index 00000000000..240e0bc10f5 --- /dev/null +++ b/app/Actions/Photo/Timeline.php @@ -0,0 +1,44 @@ +photoQueryPolicy = $photoQueryPolicy; + } + + /** + * Create the query manually. + * + * @return FixedQueryBuilder + */ + public function do(): Builder + { + $order = Configs::getValueAsEnum('timeline_photos_order', ColumnSortingPhotoType::class); + + // Safe default (should not be needed). + // @codeCoverageIgnoreStart + if (!in_array($order, [ColumnSortingPhotoType::CREATED_AT, ColumnSortingPhotoType::TAKEN_AT], true)) { + $order = ColumnSortingPhotoType::TAKEN_AT; + } + // @codeCoverageIgnoreEnd + + return $this->photoQueryPolicy->applySearchabilityFilter( + query: Photo::query()->with(['album', 'size_variants', 'size_variants.sym_links']), + origin: null, + include_nsfw: !Configs::getValueAsBool('hide_nsfw_in_timeline') + )->orderBy($order->value, OrderSortingType::DESC->value); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Gallery/TimelineController.php b/app/Http/Controllers/Gallery/TimelineController.php new file mode 100644 index 00000000000..d12d2484bd1 --- /dev/null +++ b/app/Http/Controllers/Gallery/TimelineController.php @@ -0,0 +1,40 @@ + $photoResults */ + /** @disregard P1013 Undefined method withQueryString() (stupid intelephense) */ + $photoResults = $timeline->do()->paginate(Configs::getValueAsInt('timeline_photos_pagination_limit')); + + return TimelineResource::fromData($photoResults); + } + + /** + * Return init Search. + * + * @param GetTimelineRequest $request + * + * @return InitResource + */ + public function init(GetTimelineRequest $request): Data + { + return new InitResource(); + } +} \ No newline at end of file diff --git a/app/Http/Requests/Photo/GetTimelineRequest.php b/app/Http/Requests/Photo/GetTimelineRequest.php new file mode 100644 index 00000000000..ebbd486536e --- /dev/null +++ b/app/Http/Requests/Photo/GetTimelineRequest.php @@ -0,0 +1,22 @@ +photo_layout = Configs::getValueAsEnum('timeline_photos_layout', PhotoLayoutType::class); + } +} \ No newline at end of file diff --git a/app/Http/Resources/Timeline/TimelineResource.php b/app/Http/Resources/Timeline/TimelineResource.php new file mode 100644 index 00000000000..c0b403920c3 --- /dev/null +++ b/app/Http/Resources/Timeline/TimelineResource.php @@ -0,0 +1,77 @@ + */ + #[LiteralTypeScriptType('App.Http.Resources.Models.PhotoResource[]')] + public Collection $photos; + + public int $current_page; + public int $from; + public int $last_page; + public int $per_page; + public int $to; + public int $total; + + /** + * @param LengthAwarePaginator&Paginator $photos + * + * @return void + */ + public function __construct( + LengthAwarePaginator $photos, + ) { + $this->photos = collect($photos->items()); + $this->current_page = $photos->currentPage(); + $this->from = $photos->firstItem() ?? 0; + $this->last_page = $photos->lastPage(); + $this->per_page = $photos->perPage(); + $this->to = $photos->lastItem() ?? 0; + $this->total = $photos->total(); + + // We do it manually this time. + $previous_photo = null; + $this->photos->each(function (PhotoResource &$photo) use (&$previous_photo) { + if ($previous_photo !== null) { + $previous_photo->next_photo_id = $photo->id; + } + $photo->previous_photo_id = $previous_photo?->id; + $previous_photo = $photo; + }); + $photo_granularity = Configs::getValueAsEnum('timeline_photos_granularity', TimelinePhotoGranularity::class); + $this->photos = TimelineData::setTimeLineDataForPhotos($this->photos, $photo_granularity); + } + + /** + * @param LengthAwarePaginator $photos + * + * @return TimelineResource + */ + public static function fromData(LengthAwarePaginator $photos): self + { + return new self( + photos: PhotoResource::collect($photos), // @phpstan-ignore-line + ); + } +} \ No newline at end of file diff --git a/resources/js/lychee.d.ts b/resources/js/lychee.d.ts index 0c12d52b63b..d4fe36f8898 100644 --- a/resources/js/lychee.d.ts +++ b/resources/js/lychee.d.ts @@ -628,3 +628,17 @@ declare namespace App.Http.Resources.Statistics { size: number; }; } +declare namespace App.Http.Resources.Timeline { + export type InitResource = { + photo_layout: App.Enum.PhotoLayoutType; + }; + export type TimelineResource = { + photos: App.Http.Resources.Models.PhotoResource[] | Array; + current_page: number; + from: number; + last_page: number; + per_page: number; + to: number; + total: number; + }; +} diff --git a/resources/js/router/routes.ts b/resources/js/router/routes.ts index b67a32d06f1..50e09f05e85 100644 --- a/resources/js/router/routes.ts +++ b/resources/js/router/routes.ts @@ -3,6 +3,7 @@ import Albums from "@/views/gallery-panels/Albums.vue"; import Photo from "@/views/gallery-panels/Photo.vue"; const Landing = () => import("@/views/Landing.vue"); +const Timeline = () => import("@/views/gallery-panels/Timeline.vue"); const Frame = () => import("@/views/gallery-panels/Frame.vue"); const Search = () => import("@/views/gallery-panels/Search.vue"); const MapView = () => import("@/views/gallery-panels/Map.vue"); @@ -45,6 +46,11 @@ const routes_ = [ component: Frame, props: true, }, + { + name: "timeline", + path: "/timeline", + component: Timeline, + }, { name: "frame", path: "/frame", diff --git a/resources/js/services/timeline-service.ts b/resources/js/services/timeline-service.ts new file mode 100644 index 00000000000..dc778702878 --- /dev/null +++ b/resources/js/services/timeline-service.ts @@ -0,0 +1,14 @@ +import axios, { type AxiosResponse } from "axios"; +import Constants from "./constants"; + +const TimelineService = { + timeline(page: number = 1): Promise> { + return axios.get(`${Constants.getApiUrl()}Timeline`, { params: { page: page }, data: {} }); + }, + + init(): Promise> { + return axios.get(`${Constants.getApiUrl()}Timeline::init`, { data: {} }); + }, +}; + +export default TimelineService; diff --git a/resources/js/views/gallery-panels/Timeline.vue b/resources/js/views/gallery-panels/Timeline.vue new file mode 100644 index 00000000000..fa165304fa3 --- /dev/null +++ b/resources/js/views/gallery-panels/Timeline.vue @@ -0,0 +1,229 @@ + + + diff --git a/routes/api_v2.php b/routes/api_v2.php index fb1ad1917af..999747f153d 100644 --- a/routes/api_v2.php +++ b/routes/api_v2.php @@ -16,7 +16,7 @@ | */ -Route::get('/LandingPage', [LandingPageController::class, '__invoke']); +Route::get('/LandingPage', [LandingPageController::class, '__invoke'])->middleware(['cache_control']); Route::get('/Frame', [Gallery\FrameController::class, 'get']); Route::get('/Gallery::Init', [Gallery\ConfigController::class, 'getInit']); @@ -24,6 +24,9 @@ Route::get('/Gallery::getLayout', [Gallery\ConfigController::class, 'getGalleryLayout'])->middleware(['cache_control']); Route::get('/Gallery::getUploadLimits', [Gallery\ConfigController::class, 'getUploadCOnfig'])->middleware(['cache_control']); +Route::get('/Timeline', [Gallery\TimelineController::class, '__invoke'])->middleware(['cache_control']); +Route::get('/Timeline::init', [Gallery\TimelineController::class, 'init'])->middleware(['cache_control']); + /** * ALBUMS. */ diff --git a/routes/web_v2.php b/routes/web_v2.php index 7630d99daae..553cb87ba46 100644 --- a/routes/web_v2.php +++ b/routes/web_v2.php @@ -27,6 +27,8 @@ Route::get('/frame', [VueController::class, 'view'])->name('frame')->middleware(['migration:complete']); Route::get('/frame/{albumId}', [VueController::class, 'view'])->name('frame')->middleware(['migration:complete']); +Route::get('/timeline', [VueController::class, 'view'])->name('map')->middleware(['migration:complete']); + Route::get('/map', [VueController::class, 'view'])->name('map')->middleware(['migration:complete']); Route::get('/map/{albumId}', [VueController::class, 'view'])->name('map')->middleware(['migration:complete']);