Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SPLAT-1442 ckeditor5 plugin #107

Open
wants to merge 31 commits into
base: 3.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
7df8f34
SPLAT-1442 add POC for ckeditor plugin
amalija-ramljak Mar 26, 2024
ebc3dab
SPLAT-1442 [wip] add poc for rendering proper structure for vue
amalija-ramljak Mar 28, 2024
3a1eba9
SPLAT-1442 enable plugin config, fix data casting flow to enable savi…
amalija-ramljak Apr 3, 2024
c1494f0
SPLAT-1442 fix json stringify for data param (encoding)
amalija-ramljak Apr 3, 2024
6de0251
SPLAT-1442 fix error when upload is disabled
amalija-ramljak Apr 4, 2024
a9f3af3
SPLAT-1442 fix attributes and rerendering, handle image alignment
amalija-ramljak Apr 4, 2024
8448657
SPLAT-1442 vue app prod build
amalija-ramljak Apr 4, 2024
99a7759
SPLAT-1442 change casting flow to render properly on FE
amalija-ramljak Apr 4, 2024
59253e4
SPLAT-1442 fix operation handling, stub to handle removal
amalija-ramljak Apr 4, 2024
5ee8b67
SPLAT-1442 define insert/delete endpoint config, define location id a…
amalija-ramljak Apr 4, 2024
5393475
SPLAT-1442 implement util to transform html to ckeditor view elements
amalija-ramljak Apr 10, 2024
006acf1
SPLAT-1442 [wip] handle customizable template, add css class and vari…
amalija-ramljak Apr 11, 2024
ed017ad
SPLAT-1442 fix and finish editor to view flow
amalija-ramljak Apr 18, 2024
1246d49
SPLAT-1442 fix alignment style
amalija-ramljak Apr 26, 2024
01422a1
SPLAT-1442 handle deleting from editor
amalija-ramljak Apr 26, 2024
0213433
SPLAT-1442 fix quotation marks
amalija-ramljak Apr 26, 2024
7ea236f
SPLAT-1442 fallback endpoints to default
amalija-ramljak Apr 26, 2024
2886a61
SPLAT-1442 fix field id uniqueness
amalija-ramljak May 17, 2024
983b595
SPLAT-1442 fix resource template
amalija-ramljak May 17, 2024
e057fd5
SPLAT-1442 add call to render ngrm views
amalija-ramljak May 17, 2024
8af7664
SPLAT-1442 missing export keyword
amalija-ramljak Jun 18, 2024
a121842
SPLAT-1442 fix service definition and configuration
amalija-ramljak Jul 15, 2024
f46c4d3
SPLAT-1442 fix upload context and allow upload by default
amalija-ramljak Jul 15, 2024
7405a47
SPLAT-1442 change text
amalija-ramljak Jul 15, 2024
9ad260c
SPLAT-1442 fix argument
amalija-ramljak Jul 15, 2024
5c38732
SPLAT-1442 show all variations to pick from (not just crops)
amalija-ramljak Jul 16, 2024
7ba832c
SPLAT-1442 replace placeholder element instead of injecting children
amalija-ramljak Sep 16, 2024
81b35e5
SPLAT-1442 fix scroll into view call
amalija-ramljak Sep 16, 2024
6741fda
SPLAT-1442 separate reading upload context from query
amalija-ramljak Sep 16, 2024
83749e8
SPLAT-1442 dont delete location on drag and drop
amalija-ramljak Sep 17, 2024
14cb815
SPLAT-1442 add image source for location
amalija-ramljak Sep 17, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 176 additions & 0 deletions bundle/Controller/Configuration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<?php

declare(strict_types=1);

namespace Netgen\Bundle\RemoteMediaBundle\Controller;

use Netgen\RemoteMedia\Core\Resolver\Variation as VariationResolver;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\RouterInterface;
use Symfony\Contracts\Translation\TranslatorInterface;

final class Configuration extends AbstractController
{
public function __construct(
private readonly RouterInterface $router,
private readonly TranslatorInterface $translator,
private readonly VariationResolver $variationResolver,
) {
}

public function __invoke(Request $request): JsonResponse
{
return new JsonResponse([
'paths' => $this->resolvePaths(),
'translations' => $this->resolveTranslations(),
'availableVariations' => $this->resolveAvailableVariations($request),
'allVariations' => $this->resolveAllVariations($request),
'uploadContext' => $this->resolveUploadContext($request),
]);
}

private function resolvePaths(): array
{
return [
'browse_resources' => $this->router->generate('netgen_remote_media_ajax_resource_browse'),
'upload_resources' => $this->router->generate('netgen_remote_media_ajax_resource_upload'),
'load_facets' => $this->router->generate('netgen_remote_media_ajax_facets_load'),
'load_folders' => $this->router->generate('netgen_remote_media_ajax_folder_load'),
'create_folder' => $this->router->generate('netgen_remote_media_ajax_folder_create'),
];
}

private function resolveTranslations(): array
{
return [
'browse_title' => $this->translator->trans('ngrm.edit.vue.browse.title', [], 'ngremotemedia'),
'browse_select_type' => $this->translator->trans('ngrm.edit.vue.browse.facets.select_type', [], 'ngremotemedia'),
'browse_loading_types' => $this->translator->trans('ngrm.edit.vue.browse.facets.loading_types', [], 'ngremotemedia'),
'browse_all_types' => $this->translator->trans('ngrm.edit.vue.browse.facets.all_types', [], 'ngremotemedia'),
'browse_select_folder' => $this->translator->trans('ngrm.edit.vue.browse.facets.select_folder', [], 'ngremotemedia'),
'browse_loading_folders' => $this->translator->trans('ngrm.edit.vue.browse.facets.loading_folders', [], 'ngremotemedia'),
'browse_all_folders' => $this->translator->trans('ngrm.edit.vue.browse.facets.all_folders', [], 'ngremotemedia'),
'browse_select_tag' => $this->translator->trans('ngrm.edit.vue.browse.facets.select_tag', [], 'ngremotemedia'),
'browse_loading_tags' => $this->translator->trans('ngrm.edit.vue.browse.facets.loading_tags', [], 'ngremotemedia'),
'browse_all_tags' => $this->translator->trans('ngrm.edit.vue.browse.facets.all_tags', [], 'ngremotemedia'),
'browse_select_visibility' => $this->translator->trans('ngrm.edit.vue.browse.facets.select_visibility', [], 'ngremotemedia'),
'browse_loading_visibilities' => $this->translator->trans('ngrm.edit.vue.browse.facets.loading_visibilities', [], 'ngremotemedia'),
'browse_all_visibilities' => $this->translator->trans('ngrm.edit.vue.browse.facets.all_visibilities', [], 'ngremotemedia'),
'search' => $this->translator->trans('ngrm.edit.vue.browse.facets.search', [], 'ngremotemedia'),
'search_placeholder' => $this->translator->trans('ngrm.edit.vue.browse.facets.search_placeholder', [], 'ngremotemedia'),
'browse_empty_folder' => $this->translator->trans('ngrm.edit.vue.browse.empty_folder', [], 'ngremotemedia'),
'browse_empty_folder_hint' => $this->translator->trans('ngrm.edit.vue.browse.empty_folder_hint', [], 'ngremotemedia'),
'browse_select' => $this->translator->trans('ngrm.edit.vue.browse.select', [], 'ngremotemedia'),
'load_more' => $this->translator->trans('ngrm.edit.vue.browse.load_more', [], 'ngremotemedia'),
'crop_modal_title' => $this->translator->trans('ngrm.edit.vue.crop.modal_title', [], 'ngremotemedia'),
'crop_reset' => $this->translator->trans('ngrm.edit.vue.crop.reset', [], 'ngremotemedia'),
'crop_apply' => $this->translator->trans('ngrm.edit.vue.crop.apply', [], 'ngremotemedia'),
'crop_cancel' => $this->translator->trans('ngrm.edit.vue.crop.cancel', [], 'ngremotemedia'),
'crop_add' => $this->translator->trans('ngrm.edit.vue.crop.add', [], 'ngremotemedia'),
'crop_preview' => $this->translator->trans('ngrm.edit.vue.crop.preview', [], 'ngremotemedia'),
'crop_save_sizes' => $this->translator->trans('ngrm.edit.vue.crop.save_sizes', [], 'ngremotemedia'),
'crop_add_size' => $this->translator->trans('ngrm.edit.vue.crop.add_size', [], 'ngremotemedia'),
'crop_added_for_confirmation' => $this->translator->trans('ngrm.edit.vue.crop.added_for_confirmation', [], 'ngremotemedia'),
'crop_media_too_small' => $this->translator->trans('ngrm.edit.vue.crop.media_too_small', [], 'ngremotemedia'),
'media_gallery_empty_folder' => $this->translator->trans('ngrm.edit.vue.media_gallery.empty_folder', [], 'ngremotemedia'),
'media_gallery_upload_media' => $this->translator->trans('ngrm.edit.vue.media_gallery.upload_media', [], 'ngremotemedia'),
'media_gallery_select' => $this->translator->trans('ngrm.edit.vue.media_gallery.select', [], 'ngremotemedia'),
'media_gallery_load_more' => $this->translator->trans('ngrm.edit.vue.media_gallery.load_more', [], 'ngremotemedia'),
'Search for media' => $this->translator->trans("Search for media", [], 'ngremotemedia'),
'Load more' => $this->translator->trans("Load more", [], 'ngremotemedia'),
'Upload new media' => $this->translator->trans('Upload new media', [], 'ngremotemedia'),
'No results' => $this->translator->trans('No results', [], 'ngremotemedia'),
'Alternate text' => $this->translator->trans('Alternate text', [], 'ngremotemedia'),
'Class' => $this->translator->trans('CSS class', [], 'ngremotemedia'),
'Create new folder?' => $this->translator->trans('Create new folder?', [], 'ngremotemedia'),
'Folder' => $this->translator->trans('Folder', [], 'ngremotemedia'),
'All' => $this->translator->trans('All', [], 'ngremotemedia'),
'Add tag' => $this->translator->trans('Add tag', [], 'ngremotemedia'),
'Media type' => $this->translator->trans('Media type', [], 'ngremotemedia'),
'Image' => $this->translator->trans('Image and documents', [], 'ngremotemedia'),
'Video' => $this->translator->trans('Video', [], 'ngremotemedia'),
'Loading...' => $this->translator->trans('Loading...', [], 'ngremotemedia'),
'Cancel' => $this->translator->trans('Cancel', [], 'ngremotemedia'),
'Save all' => $this->translator->trans('Save all', [], 'ngremotemedia'),
'Generate' => $this->translator->trans('Generate', [], 'ngremotemedia'),
'Caption' => $this->translator->trans('Caption', [], 'ngremotemedia'),
'by' => $this->translator->trans('by', [], 'ngremotemedia'),
'name' => $this->translator->trans('name', [], 'ngremotemedia'),
'tag' => $this->translator->trans('tag', [], 'ngremotemedia'),
'Image is to small for this version' => $this->translator->trans('Image is to small for this version', [], 'ngremotemedia'),
'close' => $this->translator->trans('Close', [], 'ngremotemedia'),
'next' => $this->translator->trans('Next 25 &gt;', [], 'ngremotemedia'),
'prev' => $this->translator->trans('&lt; Previous 25', [], 'ngremotemedia'),
'interactions_scale' => $this->translator->trans('ngrm.edit.vue.interactions.scale', [], 'ngremotemedia'),
'interactions_no_media_selected' => $this->translator->trans('ngrm.edit.vue.interactions.no_media_selected', [], 'ngremotemedia'),
'interactions_remove_media' => $this->translator->trans('ngrm.edit.vue.interactions.remove_media', [], 'ngremotemedia'),
'interactions_select_media' => $this->translator->trans('ngrm.edit.vue.interactions.select_media', [], 'ngremotemedia'),
'interactions_manage_media' => $this->translator->trans('ngrm.edit.vue.interactions.manage_media', [], 'ngremotemedia'),
'interactions_quick_upload' => $this->translator->trans('ngrm.edit.vue.interactions.quick_upload', [], 'ngremotemedia'),
'preview_alternate_text' => $this->translator->trans('ngrm.edit.vue.preview.alternate_text', [], 'ngremotemedia'),
'preview_alternate_text_info' => $this->translator->trans('ngrm.edit.vue.preview.alternate_text_info', [], 'ngremotemedia'),
'preview_caption' => $this->translator->trans('ngrm.edit.vue.preview.caption', [], 'ngremotemedia'),
'preview_caption_info' => $this->translator->trans('ngrm.edit.vue.preview.caption_info', [], 'ngremotemedia'),
'preview_tags' => $this->translator->trans('ngrm.edit.vue.preview.tags', [], 'ngremotemedia'),
'preview_tags_info' => $this->translator->trans('ngrm.edit.vue.preview.tags_info', [], 'ngremotemedia'),
'preview_watermark_text' => $this->translator->trans('ngrm.edit.vue.preview.watermark_text', [], 'ngremotemedia'),
'preview_watermark_text_info' => $this->translator->trans('ngrm.edit.vue.preview.watermark_text_info', [], 'ngremotemedia'),
'preview_css_class' => $this->translator->trans('ngrm.edit.vue.preview.css_class', [], 'ngremotemedia'),
'preview_css_class_info' => $this->translator->trans('ngrm.edit.vue.preview.css_class_info', [], 'ngremotemedia'),
'preview_selected_variation' => $this->translator->trans('ngrm.edit.vue.preview.selected_variation', [], 'ngremotemedia'),
'preview_selected_variation_info' => $this->translator->trans('ngrm.edit.vue.preview.selected_variation_info', [], 'ngremotemedia'),
'preview_size' => $this->translator->trans('ngrm.edit.vue.preview.size', [], 'ngremotemedia'),
'upload_modal_title' => $this->translator->trans('ngrm.edit.vue.upload.modal_title', [], 'ngremotemedia'),
'upload_breadcrumbs_info' => $this->translator->trans('ngrm.edit.vue.upload.breadcrumbs_info', [], 'ngremotemedia'),
'upload_root_folder' => $this->translator->trans('ngrm.edit.vue.upload.root_folder', [], 'ngremotemedia'),
'upload_info_text' => $this->translator->trans('ngrm.edit.vue.upload.info_text', [], 'ngremotemedia'),
'upload_button_select' => $this->translator->trans('ngrm.edit.vue.upload.button.select', [], 'ngremotemedia'),
'upload_button_create' => $this->translator->trans('ngrm.edit.vue.upload.button.create', [], 'ngremotemedia'),
'upload_button_upload' => $this->translator->trans('ngrm.edit.vue.upload.button.upload', [], 'ngremotemedia'),
'upload_button_use_existing_resource' => $this->translator->trans('ngrm.edit.vue.upload.button.use_existing_resource', [], 'ngremotemedia'),
'upload_checkbox_overwrite' => $this->translator->trans('ngrm.edit.vue.upload.checkbox.overwrite', [], 'ngremotemedia'),
'upload_placeholder_new_folder' => $this->translator->trans('ngrm.edit.vue.upload.placeholder.new_folder', [], 'ngremotemedia'),
'upload_error_existing_resource' => $this->translator->trans('ngrm.edit.vue.upload.error.existing_resource', [], 'ngremotemedia'),
'upload_error_unsupported_resource_type' => $this->translator->trans('ngrm.edit.vue.upload.error.unsupported_resource_type', [], 'ngremotemedia'),
];
}

private function resolveAvailableVariations(Request $request): array
{
$variationGroup = $request->query->get('variationGroup');

$variations = $this->variationResolver->getAvailableCroppableVariations($variationGroup);

$availableVariations = [];
foreach ($variations as $variationName => $variationConfig) {
foreach ($variationConfig['transformations'] as $name => $config) {
if ($name !== 'crop') {
continue;
}

$availableVariations[$variationName] = $config;
}
}

return $availableVariations;
}

private function resolveAllVariations(Request $request): array
{
$variationGroup = $request->query->get('variationGroup');

$variations = $this->variationResolver->getAvailableVariations($variationGroup);

return array_keys($variations);;
}

/**
* @return array<string, string>
*/
private function resolveUploadContext(Request $request): array
{
return $request->query->get('uploadContext');
}
}
53 changes: 53 additions & 0 deletions bundle/Controller/Location/Create.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php

declare(strict_types=1);

namespace Netgen\Bundle\RemoteMediaBundle\Controller\Location;

use InvalidArgumentException;
use Netgen\RemoteMedia\API\ProviderInterface;
use Netgen\RemoteMedia\API\Values\RemoteResourceLocation;
use Netgen\RemoteMedia\Exception\RemoteResourceNotFoundException;
use Netgen\RemoteMedia\Service\RemoteResourceService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;

final class Create extends AbstractController
{
public function __construct(
private ProviderInterface $provider,
private RemoteResourceService $service,
) {}

public function __invoke(Request $request): Response
{
$selectedImage = json_decode($request->getContent(), true);

if ($selectedImage['id'] === null) {
throw new InvalidArgumentException('No image selected.');
}

try {
$remoteResource = $this->provider->loadByRemoteId($selectedImage['id']);
} catch (RemoteResourceNotFoundException $e) {
$remoteResource = $this->provider->loadFromRemote($selectedImage['id']);
}

$this->service->handleRemoteUpdate(
$remoteResource,
[
'altText' => $selectedImage['alternateText'],
'caption' => $selectedImage['caption'],
'tags' => $selectedImage['tags'],
],
true,
);

$remoteResourceLocation = new RemoteResourceLocation($remoteResource);
$this->service->handleLocationUpdate($remoteResourceLocation, $selectedImage, true);

return new JsonResponse([ 'locationId' => $remoteResourceLocation->getId() ]);
}
}
23 changes: 23 additions & 0 deletions bundle/Controller/Location/Delete.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace Netgen\Bundle\RemoteMediaBundle\Controller\Location;

use Netgen\RemoteMedia\API\ProviderInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;

final class Delete extends AbstractController
{
public function __construct(
private ProviderInterface $provider,
) {}

public function __invoke(int $locationId): Response
{
$this->provider->removeLocation($this->provider->loadLocation($locationId));

return new Response();
}
}
65 changes: 65 additions & 0 deletions bundle/Controller/Location/SelectedImage.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
<?php

declare(strict_types=1);

namespace Netgen\Bundle\RemoteMediaBundle\Controller\Location;

use Netgen\RemoteMedia\API\Values\AuthToken;
use Netgen\RemoteMedia\API\ProviderInterface;
use Netgen\RemoteMedia\Service\RemoteResourceService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;

final class SelectedImage extends AbstractController
{
public function __construct(
private ProviderInterface $provider,
private RemoteResourceService $service,
) {}

public function __invoke(int $locationId): JsonResponse
{
$remoteResourceLocation = $this->provider->loadLocation($locationId);
$remoteResource = $remoteResourceLocation->getRemoteResource();

$authenticatedLocation = $remoteResourceLocation;
$variationGroup = 'ngrm_interface';
$browseVariationName = 'browse';
$previewVariationName = 'preview';

if ($remoteResource->isProtected()) {
$token = AuthToken::fromDuration(600);
$authenticatedLocation = $this->provider->authenticateRemoteResourceLocation($remoteResourceLocation, $token);
$browseVariationName = 'browse_protected';
$previewVariationName = 'preview_protected';
}

$browseUrl = null;
$previewUrl = null;
if ($remoteResource->getType() === 'image') {
$browseUrl = $this->provider->buildVariation($authenticatedLocation, $variationGroup, $browseVariationName);
$previewUrl = $this->provider->buildVariation($authenticatedLocation, $variationGroup, $previewVariationName);
} else if ($remoteResource->getType() === 'video') {
$browseUrl = $this->provider->buildVideoThumbnailVariation($authenticatedLocation, $variationGroup, $browseVariationName);
$previewUrl = $this->provider->buildVideoThumbnailVariation($authenticatedLocation, $variationGroup, $previewVariationName);
}

return new JsonResponse([
'id' => $remoteResource->getRemoteId() ?? '',
'name' => $remoteResource->getName() ?? '',
'type' => $remoteResource->getType() ?? '',
'format' => $remoteResource->getMetadataProperty('format') ?? '',
'url' => $remoteResource->getUrl(),
'browse_url' => $browseUrl?->getUrl() ?? '',
'previewUrl' => $previewUrl?->getUrl() ?? '',
'alternateText' => $remoteResource->getAltText(),
'caption' => $remoteResource->getCaption(),
'watermarkText' => $remoteResourceLocation->getWatermarkText(),
'tags' => $remoteResource->getTags(),
'size' => $remoteResource->getSize(),
'variations' => $this->service->resolveCropSettingsJson($remoteResourceLocation),
'height' => $remoteResource->getMetadataProperty('height') ?? 0,
'width' => $remoteResource->getMetadataProperty('width') ?? 0,
]);
}
}
Loading
Loading