Skip to content

Commit

Permalink
Merge branch 'release/1.1.12'
Browse files Browse the repository at this point in the history
  • Loading branch information
D3strukt0r committed Dec 30, 2024
2 parents 9670779 + 1f7fa05 commit 8a7c0bd
Show file tree
Hide file tree
Showing 15 changed files with 126 additions and 35 deletions.
2 changes: 2 additions & 0 deletions .docker/rootfs-local/minio/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
use OpenApi\Attributes as OA;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Routing\Attribute\Route;
Expand Down Expand Up @@ -87,24 +87,24 @@ public function __invoke(Request $request): Response
if ($this->defaultStorage->fileExists($location)) {
$contentStream = $this->defaultStorage->readStream($location);

$zipFile = FileHelper::createTempFile('gallery.zip', 'application/zip');
file_put_contents($zipFile->getPathname(), $contentStream);

return $this->file($zipFile, $zipFile->getClientOriginalName());
// $zipFile = FileHelper::createTempFile('gallery.zip', 'application/zip');
// file_put_contents($zipFile->getPathname(), $contentStream);
//
// return $this->file($zipFile, $zipFile->getClientOriginalName());

// https://dev.to/rubenrubiob/serve-a-file-stream-in-symfony-3ei3
// return new StreamedResponse(
// static function () use ($contentStream): void {
// fpassthru($contentStream);
// },
// Response::HTTP_OK,
// [
// 'Content-Transfer-Encoding', 'binary',
// 'Content-Type' => 'application/zip',
// 'Content-Disposition' => ResponseHeaderBag::DISPOSITION_ATTACHMENT.'; filename="gallery.zip"',
// 'Content-Length' => $this->defaultStorage->fileSize($location),
// ]
// );
return new StreamedResponse(
static function () use ($contentStream): void {
fpassthru($contentStream);
},
Response::HTTP_OK,
[
'Content-Transfer-Encoding', 'binary',
'Content-Disposition' => HeaderUtils::makeDisposition(HeaderUtils::DISPOSITION_ATTACHMENT, 'gallery.zip'),
'Content-Type' => 'application/zip',
'Content-Length' => $this->defaultStorage->fileSize($location),
],
);
}

if (\count($files) > $this->appDownloadSyncMax) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@
use OpenApi\Attributes as OA;
use Symfony\Bridge\Doctrine\Attribute\MapEntity;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\HeaderUtils;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Attribute\Route;
use Symfony\Component\Security\Http\Attribute\IsGranted;

Expand All @@ -32,14 +33,32 @@ public function __construct(
#[OA\Tag('Invited/Gallery')]
public function __invoke(#[MapEntity(id: 'file_id')] File $file): Response
{
$content = $this->defaultStorage->read($file->getPath());
$location = $file->getPath();

$response = new Response();
$disposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_INLINE, $file->getOriginalFilename());
$response->headers->set('Content-Disposition', $disposition);
$response->headers->set('Content-Type', $file->getMimeType());
$response->setContent($content);
// $content = $this->defaultStorage->read($location);

return $response;
// $response = new Response();
// $disposition = $response->headers->makeDisposition(HeaderUtils::DISPOSITION_INLINE, $file->getOriginalFilename());
// $response->headers->set('Content-Disposition', $disposition);
// $response->headers->set('Content-Type', $file->getMimeType());
// $response->setContent($content);

// return $response;

$contentStream = $this->defaultStorage->readStream($location);

// https://dev.to/rubenrubiob/serve-a-file-stream-in-symfony-3ei3
return new StreamedResponse(
static function () use ($contentStream): void {
fpassthru($contentStream);
},
Response::HTTP_OK,
[
'Content-Transfer-Encoding', 'binary',
'Content-Disposition' => HeaderUtils::makeDisposition(HeaderUtils::DISPOSITION_INLINE, $file->getOriginalFilename()),
'Content-Type' => $file->getMimeType(),
'Content-Length' => $this->defaultStorage->fileSize($location),
],
);
}
}
5 changes: 5 additions & 0 deletions api/src/Entity/GalleryDownload.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ public function setStateDownloading(int $countDone = 0): void
$this->stateContext = ['countDone' => $countDone];
}

public function setStateSaveZip(): void
{
$this->state = GalleryDownloadState::SAVE_ZIP;
}

public function setStateCaching(): void
{
$this->state = GalleryDownloadState::CACHING;
Expand Down
1 change: 1 addition & 0 deletions api/src/Entity/GalleryDownloadState.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ enum GalleryDownloadState: string
case PENDING = 'pending';
case CREATE_ZIP = 'create_zip';
case DOWNLOADING = 'downloading'; // with extra info on how many of how many
case SAVE_ZIP = 'save_zip'; // save zip to disk (will take a while)
case CACHING = 'caching'; // uploading to remote location
// case COMPLETED = 'completed'; // entity is deleted when completed
}
5 changes: 5 additions & 0 deletions api/src/MessageHandler/PrepareHugeGalleryDownloadHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ public function __invoke(PrepareHugeGalleryDownload $message): void
]);
}

$galleryDownload->setStateSaveZip();
$this->em->flush();

// Closing actually saves the additions, this can take a while for many files
// TODO: Do this in a batch job
$zip->close();
unset($zip);

Expand Down
1 change: 1 addition & 0 deletions pwa/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
"browserslist": "^4.24.3",
"browserslist-to-esbuild": "^2.1.1",
"clsx": "^2.1.1",
"dayjs": "^1.11.13",
"eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.1.0",
Expand Down
8 changes: 8 additions & 0 deletions pwa/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions pwa/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ function App() {
<RouterProvider
router={router}
fallbackElement={<BigSpinner />}
future={{
// https://reactrouter.com/6.28.1/upgrading/future#v7_relativesplatpath
v7_relativeSplatPath: true,
v7_startTransition: true,
v7_fetcherPersist: true,
v7_normalizeFormMethod: true,
v7_partialHydration: true,
v7_skipActionErrorRevalidation: true,
}}
/>
</Suspense>
</AuthenticationProvider>
Expand Down
2 changes: 1 addition & 1 deletion pwa/src/api/invited/gallery/useDownloadState.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useQuery, UseQueryOptions } from '@tanstack/react-query';
import axios from 'axios';

type GalleryDownloadState = 'pending' | 'create_zip' | 'downloading' | 'caching';
type GalleryDownloadState = 'pending' | 'create_zip' | 'downloading' | 'save_zip' | 'caching';

export interface DownloadCheckAsyncProcess {
message: string;
Expand Down
10 changes: 10 additions & 0 deletions pwa/src/assets/logo-gdrive.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion pwa/src/assets/logo-polarsteps.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 28 additions & 8 deletions pwa/src/components/homepage/Gallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ImageLazyLoad, { aspectRatio, ImageLazyLoadProps } from '#/components/com
import blurHashMap from '#/img/blurhash-map.json';
import image from '#/img/Fotos.jpg';
import LogoPolarsteps from '#/assets/logo-polarsteps.svg?react';
import LogoGDrive from '#/assets/logo-gdrive.svg?react';
import { GalleryImage as GalleryImageType } from '#/components/types.ts';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useAuthenticationContext } from '#/utils/authentication.tsx';
Expand All @@ -26,8 +27,10 @@ import Checkbox from '#/components/common/Checkbox.tsx';
import useDownloadGalleryImages from '#/api/invited/gallery/useDownloadGalleryImages.ts';
import { downloadBlob } from '#/utils/download.ts';
import { faInstagram } from '@fortawesome/free-brands-svg-icons';
import { AxiosProgressEvent } from 'axios';
import axios, { AxiosProgressEvent } from 'axios';
import useDownloadState, { DownloadCheckAsyncProcess } from '#/api/invited/gallery/useDownloadState.ts';
import * as dayjs from 'dayjs';
import { Markup } from 'interweave';

interface Props {
id?: string;
Expand Down Expand Up @@ -58,23 +61,26 @@ export default function Gallery({ id, isLast }: Props) {
<h2 className="uppercase text-title mb-6 font-philosopher">
{t('homepage.gallery.title')}
</h2>
<p className="whitespace-pre-line text-normal font-noto-sans md:max-w-prose">
<p className="whitespace-pre-line text-normal font-noto-sans md:max-w-prose mb-4">
{t('homepage.gallery.text')}
</p>
<div className="flex flex-wrap items-start gap-2 mt-4">
<div className="flex flex-wrap items-start gap-2 mb-4">
<a href="https://www.instagram.com/travel.za.world/" target="_blank"
className="rounded-md bg-[#c13584] px-3.5 py-2.5 text-sm font-semibold !text-white !no-underline !not-italic shadow-sm hover:bg-[#c13584]/75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#c13584]/50">
<FontAwesomeIcon icon={faInstagram} /> Instagram (@travel.za.world)
</a>
</div>
<div className="flex flex-wrap items-start gap-2 mt-4">
<div className="flex flex-wrap items-start gap-2 mb-4">
<a href="https://www.polarsteps.com/RubySu" className="rounded-md bg-[#cc3e55] px-3.5 py-2.5 text-sm font-semibold !text-white !no-underline !not-italic shadow-sm hover:bg-[#cc3e55]/75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#cc3e55]/50">
<LogoPolarsteps className="inline-block h-[1em] box-content align-[-0.125em]" /> Polarsteps (Robine)
</a>
<a href="https://www.polarsteps.com/D3strukt0r" className="rounded-md bg-[#cc3e55] px-3.5 py-2.5 text-sm font-semibold !text-white !no-underline !not-italic shadow-sm hover:bg-[#cc3e55]/75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#cc3e55]/50">
<LogoPolarsteps className="inline-block h-[1em] box-content align-[-0.125em]" /> Polarsteps (Manuele)
</a>
</div>
<p className="whitespace-pre-line text-normal font-noto-sans md:max-w-prose">
{t('homepage.gallery.downloadGDriveInstead')}
</p>
{authentication ? (
<div className="mt-8">
<MyGalleryUploadLoader />
Expand Down Expand Up @@ -355,6 +361,10 @@ function GalleryAndDownload({ files }: GalleryAndDownloadProps) {
return `${Number.parseFloat((bytes / (baseLog ** magnitude)).toString()).toFixed(decimalPlaces)} ${sizes[magnitude]}`;
}, []);

const downloadParams = new URLSearchParams({
fileIds: Array.isArray(lastRequestedFileIds) ? lastRequestedFileIds.join(',') : lastRequestedFileIds,
});

return (
<>
<form
Expand All @@ -367,6 +377,9 @@ function GalleryAndDownload({ files }: GalleryAndDownloadProps) {
})}
>
<div className="flex flex-wrap justify-end gap-1 mb-2">
<a href="https://drive.google.com/drive/folders/1ELFdf_4LDhU6AZqHfrNAw7xUk4Y8fOEW?usp=sharing" target="_blank" className="rounded-md bg-[#414141] px-3.5 py-2.5 text-sm !text-white !no-underline !not-italic shadow-sm hover:bg-[#414141]/75 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[#414141]/50">
<LogoGDrive className="inline-block h-[1.5em] box-content" /> Google Drive
</a>
<Button type="button" layout="app-primary" disabled={isPending || isPendingAsync} onClick={selectAll}>{t('homepage.gallery.selectAll')}</Button>
<Button type="button" layout="app-primary" disabled={isPending || isPendingAsync} onClick={deselectAll}>{t('homepage.gallery.deselectAll')}</Button>
<Button type="submit" layout="app-primary" loading={isPending || isPendingAsync}>{t('homepage.gallery.downloadSelected')}</Button>
Expand All @@ -390,10 +403,13 @@ function GalleryAndDownload({ files }: GalleryAndDownloadProps) {
<p>
{[
`${bytesToHumanReadableFileSize(progress.loaded)}${progress.total !== undefined ? ` / ${bytesToHumanReadableFileSize(progress.total)}` : ''}`,
progress.estimated !== undefined ? `${Math.ceil( progress.estimated)} sekunden` : undefined,
progress.estimated !== undefined ? `${dayjs.duration(progress.estimated, 'seconds').humanize()} übrig` : undefined,
progress.rate !== undefined ? `${bytesToHumanReadableFileSize(progress.rate)}/s` : undefined,
].filter(Boolean).join(' | ')}
</p>
<p>
<Markup noWrap content={t('homepage.gallery.downloadAlternative', { link: `${axios.defaults.baseURL}/invited/api/gallery/download?${downloadParams.toString()}` })} />
</p>
</div>
)}
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
Expand Down Expand Up @@ -452,6 +468,8 @@ function AsyncDownload({ hash, onFinish }: { hash: string; onFinish: () => void
return t('homepage.gallery.downloadAsyncCreateZip');
case 'downloading':
return t('homepage.gallery.downloadAsyncDownloading', {current: data.context.countDone, total: data.fileCount})
case 'save_zip':
return t('homepage.gallery.downloadAsyncSaveZip');
case 'caching':
return t('homepage.gallery.downloadAsyncCaching');
default:
Expand All @@ -468,8 +486,10 @@ function AsyncDownload({ hash, onFinish }: { hash: string; onFinish: () => void
return 0.05;
case 'downloading':
const imageProgressDone = data.context.countDone / data.fileCount;
return 0.05 + Math.round(imageProgressDone * (90 - 5)) / 100;
case 'caching':
return 0.05 + Math.round(imageProgressDone * (80 - 5)) / 100;
case 'save_zip':
return 0.85;
case 'caching':
return 0.95;
default:
return 1;
Expand All @@ -479,10 +499,10 @@ function AsyncDownload({ hash, onFinish }: { hash: string; onFinish: () => void

return (
<>
<p>{getMessageForState(downloadState.data)}</p>
<div className="w-full bg-gray-200 rounded-full dark:bg-gray-700">
<div className="bg-app-green text-xs font-medium text-gray-100 text-center p-0.5 leading-none rounded-full" style={{ width: `${progress * 100}%` }}> {Math.ceil(progress * 100 * 100) / 100}%</div>
</div>
<p>{getMessageForState(downloadState.data)}</p>
</>
);
}
Expand Down
8 changes: 8 additions & 0 deletions pwa/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,21 @@ import App from './App';
import './index.css';
import { init as initI18n, currentLanguage } from './utils/i18n';
import axios from 'axios';
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
import relativeTime from 'dayjs/plugin/relativeTime';
import 'dayjs/locale/de';

(async () => {
// Set different base URLs for client and server
axios.defaults.baseURL = document.documentElement.dataset.apiUrl;

await initI18n(currentLanguage());

dayjs.locale(currentLanguage());
dayjs.extend(duration);
dayjs.extend(relativeTime);

const rootElement = document.getElementById('root');
if (!rootElement) throw new Error('Failed to find the root element');
ReactDOM.createRoot(rootElement).render(
Expand Down
Loading

0 comments on commit 8a7c0bd

Please sign in to comment.