Skip to content

Commit

Permalink
feat: option images, config resize
Browse files Browse the repository at this point in the history
  • Loading branch information
imorland committed Mar 27, 2024
1 parent 7698424 commit 822a777
Show file tree
Hide file tree
Showing 13 changed files with 271 additions and 42 deletions.
7 changes: 6 additions & 1 deletion extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
->patch('/fof/polls/{id}/votes', 'fof.polls.votes', Controllers\MultipleVotesPollController::class)
->post('/fof/polls/pollImage', 'fof.polls.upload-image', Controllers\UploadPollImageController::class)
->post('/fof/polls/pollImage/{pollId}', 'fof.polls.upload-image-poll', Controllers\UploadPollImageController::class)
->delete('/fof/polls/pollImage/{pollId}', 'fof.polls.delete-image-poll', Controllers\DeletePollImageController::class),
->delete('/fof/polls/pollImage/{pollId}', 'fof.polls.delete-image-poll', Controllers\DeletePollImageController::class)
->post('/fof/polls/pollOptionImage', 'fof.polls.upload-option-image-option', Controllers\UploadPollOptionImageController::class)
->post('/fof/polls/pollOptionImage/{optionId}', 'fof.polls.upload-option-image', Controllers\UploadPollOptionImageController::class)
->delete('/fof/polls/pollOptionImage/{optionId}', 'fof.polls.delete-option-image', Controllers\DeletePollOptionImageController::class),

(new Extend\Model(Post::class))
->hasMany('polls', Poll::class, 'post_id', 'id'),
Expand Down Expand Up @@ -103,6 +106,8 @@
->default('fof-polls.optionsColorBlend', true)
->default('fof-polls.directory-default-sort', 'default')
->default('fof-polls.enableGlobalPolls', false)
->default('fof-polls.image_height', 250)
->default('fof-polls.image_width', 250)
->serializeToForum('globalPollsEnabled', 'fof-polls.enableGlobalPolls', 'boolval')
->serializeToForum('allowPollOptionImage', 'fof-polls.allowOptionImage', 'boolval')
->serializeToForum('pollMaxOptions', 'fof-polls.maxOptions', 'intval')
Expand Down
10 changes: 10 additions & 0 deletions js/src/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,16 @@ app.initializers.add('fof/polls', () => {
label: app.translator.trans('fof-polls.admin.settings.enable_global_polls'),
help: app.translator.trans('fof-polls.admin.settings.enable_global_polls_help'),
})
.registerSetting({
setting: 'fof-polls.image_height',
type: 'number',
label: app.translator.trans('fof-polls.admin.settings.image_height'),
})
.registerSetting({
setting: 'fof-polls.image_width',
type: 'number',
label: app.translator.trans('fof-polls.admin.settings.image_width'),
})
.registerPermission(
{
icon: 'fas fa-poll',
Expand Down
71 changes: 44 additions & 27 deletions js/src/forum/components/PollForm.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Component, { ComponentAttrs } from 'flarum/common/Component';
import type Mithril from 'mithril';
import Mithril from 'mithril';
import app from 'flarum/forum/app';
import Button from 'flarum/common/components/Button';
import Switch from 'flarum/common/components/Switch';
Expand Down Expand Up @@ -131,7 +131,7 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {
<span>{app.translator.trans('fof-polls.forum.modal.options_label')}</span>
</label>

{this.displayOptions()}
{this.displayOptions().toArray()}

<Tooltip text={app.translator.trans('fof-polls.forum.modal.tooltip.options.add-button')}>
<Button className="Button PollModal--button Button--icon PollModal--add-button" icon="fas fa-plus" onclick={this.addOption.bind(this)} />
Expand Down Expand Up @@ -259,37 +259,50 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {
return items;
}

displayOptions() {
return Object.keys(this.options).map((option, i) => (
<div className="Form-group">
<fieldset className="Poll-answer-input">
<input
className="FormControl"
type="text"
name={'answer' + (i + 1)}
bidi={this.optionAnswers[i]}
placeholder={app.translator.trans('fof-polls.forum.modal.option_placeholder') + ' #' + (i + 1)}
/>
{app.forum.attribute('allowPollOptionImage') ? (
displayOptions(): ItemList<Mithril.Children> {
const items = new ItemList<Mithril.Children>();

this.options.forEach((option, i) => {
items.add(
'option-' + i,
<div className="Form-group">
<fieldset className="Poll-answer-input">
<input
className="FormControl"
type="text"
name={'answer' + (i + 1)}
bidi={this.optionAnswers[i]}
placeholder={app.translator.trans('fof-polls.forum.modal.option_placeholder') + ' #' + (i + 1)}
/>
{app.forum.attribute<boolean>('allowPollOptionImage') && (
<div className="Poll-answer-image">
{/* <input
className="FormControl"
type="text"
name={'answerImage' + (i + 1)}
bidi={this.optionImageUrls[i]}
placeholder={app.translator.trans('fof-polls.forum.modal.image_option_placeholder') + ' #' + (i + 1)}
/>
) : null}
</fieldset>
{i >= 2
? Button.component({
type: 'button',
className: 'Button PollModal--button Button--icon',
icon: 'fas fa-minus',
onclick: i >= 2 ? this.removeOption.bind(this, i) : '',
})
: ''}
</div>
));
/> */}
<label className="label">{app.translator.trans('fof-polls.forum.modal.poll_option_image.label')}</label>
<p className="helpText">{app.translator.trans('fof-polls.forum.modal.poll_option_image.help')}</p>
<UploadPollImageButton name="pollOptionImage" option={option} onUpload={this.pollOptionImageUploadSuccess.bind(this, i)} />
<input type="hidden" name={'answerImage' + (i + 1)} value={this.optionImageUrls[i]()} />
</div>
)}
</fieldset>
{i >= 2
? Button.component({
type: 'button',
className: 'Button PollModal--button Button--icon',
icon: 'fas fa-minus',
onclick: i >= 2 ? this.removeOption.bind(this, i) : '',
})
: ''}
</div>
);
});

return items;
}

addOption() {
Expand Down Expand Up @@ -390,4 +403,8 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {
pollImageUploadSuccess(fileName: string | null | undefined): void {
this.image(fileName);
}

pollOptionImageUploadSuccess(index: number, fileName: string | null | undefined): void {
this.optionImageUrls[index] = Stream(fileName);
}
}
8 changes: 6 additions & 2 deletions js/src/forum/components/UploadPollImageButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ import Button, { IButtonAttrs } from 'flarum/common/components/Button';
import classList from 'flarum/common/utils/classList';
import type Mithril from 'mithril';
import Poll from '../models/Poll';
import PollOption from '../models/PollOption';

export interface UploadPollImageButtonAttrs extends IButtonAttrs {
className?: string;
loading?: boolean;
name: string;
onclick: () => void;
poll?: Poll | null;
option?: PollOption | null;
onUpload: (fileName: string | null | undefined) => void;
}

Expand All @@ -27,8 +29,8 @@ export default class UploadPollImageButton extends Button<UploadPollImageButtonA
this.attrs.loading = this.loading;
this.attrs.className = classList(this.attrs.className, 'Button');

if (this.attrs.poll?.imageUrl() || this.uploadedImageUrl) {
const imageUrl = this.uploadedImageUrl || this.attrs.poll?.imageUrl();
if (this.attrs.poll?.imageUrl() || this.attrs.option?.imageUrl() || this.uploadedImageUrl) {
const imageUrl = this.uploadedImageUrl || this.attrs.poll?.imageUrl() || this.attrs.option?.imageUrl() || '';
this.attrs.onclick = this.remove.bind(this);

return (
Expand Down Expand Up @@ -94,8 +96,10 @@ export default class UploadPollImageButton extends Button<UploadPollImageButtonA
resourceUrl() {
let url = app.forum.attribute('apiUrl') + '/fof/polls/' + this.attrs.name;
const poll = this.attrs.poll;
const option = this.attrs.option;

if (poll?.exists) url += '/' + poll?.id();
if (option?.exists) url += '/' + option?.id();

return url;
}
Expand Down
2 changes: 1 addition & 1 deletion js/src/forum/models/PollOption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default class PollOption extends Model {
}

imageUrl() {
return Model.attribute<string>('imageUrl').call(this);
return Model.attribute<string | null>('imageUrl').call(this);
}

voteCount() {
Expand Down
2 changes: 2 additions & 0 deletions resources/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ fof-polls:
max_options: Maximum number of options per poll
options_color_blend: Color blend text in poll options
options_color_blend_help: Use this to use color mixing to make the poll options more readable. Disable if this feature causes issues with your forum's appearance, reducing readability.
image_height: Resize image height
image_width: Resize image width
permissions:
view_results_without_voting: View results without voting
start: Start a poll
Expand Down
1 change: 1 addition & 0 deletions src/Api/Controllers/DeletePollImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$actor = RequestUtil::getActor($request);
$pollId = Arr::get($request->getQueryParams(), 'pollId');

/** @var Poll $poll */
$poll = Poll::find($pollId);

$this->uploadDir->delete($poll->image);
Expand Down
47 changes: 47 additions & 0 deletions src/Api/Controllers/DeletePollOptionImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php

namespace FoF\Polls\Api\Controllers;

use Flarum\Http\RequestUtil;
use FoF\Polls\PollOption;
use Illuminate\Contracts\Filesystem\Cloud;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Support\Arr;
use Laminas\Diactoros\Response\EmptyResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;

class DeletePollOptionImageController implements RequestHandlerInterface
{
/**
* @var Cloud
*/
protected $uploadDir;

public function __construct(Factory $filesystemFactory)
{
$this->uploadDir = $filesystemFactory->disk('fof-polls');
}

public function handle(ServerRequestInterface $request): ResponseInterface
{
$actor = RequestUtil::getActor($request);
$optionId = Arr::get($request->getQueryParams(), 'optionId');

/** @var PollOption $option */
$option = PollOption::find($optionId);

// if the image_url is a fully qualified URL, we just set it to null
if (filter_var($option->image_url, FILTER_VALIDATE_URL)) {
} else {
// otherwise we check and delete it from the filesystem
$this->uploadDir->delete($option->image_url);
}

$option->image_url = null;
$option->save();

return new EmptyResponse(204);
}
}
57 changes: 48 additions & 9 deletions src/Api/Controllers/UploadPollImageController.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,11 @@
namespace FoF\Polls\Api\Controllers;

use Flarum\Http\RequestUtil;
use Flarum\Settings\SettingsRepositoryInterface;
use Flarum\User\Exception\PermissionDeniedException;
use FoF\Polls\Events\PollImageWillBeResized;
use FoF\Polls\Poll;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Contracts\Filesystem\Cloud;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Support\Arr;
Expand Down Expand Up @@ -41,12 +44,26 @@ class UploadPollImageController implements RequestHandlerInterface
*/
protected $imageManager;

/**
* @var Dispatcher
*/
protected $events;

/**
* @var SettingsRepositoryInterface
*/
protected $settings;

public function __construct(
Factory $filesystemFactory,
ImageManager $imageManager
ImageManager $imageManager,
Dispatcher $events,
SettingsRepositoryInterface $settings
) {
$this->imageManager = $imageManager;
$this->uploadDir = $filesystemFactory->disk('fof-polls');
$this->events = $events;
$this->settings = $settings;
}

public function handle(ServerRequestInterface $request): ResponseInterface
Expand All @@ -62,7 +79,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface

$encodedImage = $this->makeImage($file);

$uploadName = $this->filenamePrefix.'-'.Str::lower(Str::random(8)).'.png';
$uploadName = $this->uploadName();

$this->uploadDir->put($uploadName, $encodedImage);

Expand All @@ -71,19 +88,41 @@ public function handle(ServerRequestInterface $request): ResponseInterface
$poll->save();
}

return new JsonResponse([
'fileUrl' => $this->uploadDir->url($uploadName),
'fileName' => $uploadName,
]);
return $this->jsonResponse($uploadName);
}

protected function makeImage(UploadedFileInterface $file): Image
{
$encodedImage = $this->imageManager->make($file->getStream()->getMetadata('uri'))->resize(250, 250, function (Constraint $constraint) {
$image = $this->imageManager->make($file->getStream()->getMetadata('uri'));

$height = $this->settings->get('fof-polls.image_height');
$width = $this->settings->get('fof-polls.image_width');

$this->events->dispatch(new PollImageWillBeResized($image, $height, $width));

$encodedImage = $this->resizeImage($image, $height, $width);

return $encodedImage;
}

protected function resizeImage(Image $image, int $height = 250, int $width = 250, string $encoding = 'png'): Image
{
return $image->resize($height, $width, function (Constraint $constraint) {
$constraint->aspectRatio();
$constraint->upsize();
})->encode('png');
})->encode($encoding);
}

return $encodedImage;
protected function uploadName(): string
{
return $this->filenamePrefix.'-'.Str::lower(Str::random(8)).'.png';
}

protected function jsonResponse(string $uploadName): JsonResponse
{
return new JsonResponse([
'fileUrl' => $this->uploadDir->url($uploadName),
'fileName' => $uploadName,
]);
}
}
49 changes: 49 additions & 0 deletions src/Api/Controllers/UploadPollOptionImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

/*
* This file is part of fof/polls.
*
* Copyright (c) FriendsOfFlarum.
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace FoF\Polls\Api\Controllers;

use Flarum\Http\RequestUtil;
use Flarum\User\Exception\PermissionDeniedException;
use FoF\Polls\PollOption;
use Illuminate\Support\Arr;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class UploadPollOptionImageController extends UploadPollImageController
{
protected $filenamePrefix = 'pollOptionImage';

public function handle(ServerRequestInterface $request): ResponseInterface
{
$actor = RequestUtil::getActor($request);
$optionId = Arr::get($request->getQueryParams(), 'optionId');

if ($actor->cannot('startPoll') || $actor->cannot('startGlobalPoll')) {
throw new PermissionDeniedException('You do not have permission to upload poll option images');
}

$file = Arr::get($request->getUploadedFiles(), $this->filenamePrefix);

$encodedImage = $this->makeImage($file);

$uploadName = $uploadName = $this->uploadName();

$this->uploadDir->put($uploadName, $encodedImage);

if ($optionId && $option = PollOption::find($optionId)) {
$option->image_url = $uploadName;
$option->save();
}

return $this->jsonResponse($uploadName);
}
}
Loading

0 comments on commit 822a777

Please sign in to comment.