Skip to content

Commit

Permalink
Merge branch 'im/global-polls' into im/v2.2-alpha
Browse files Browse the repository at this point in the history
# Conflicts:
#	js/dist/forum.js
#	js/dist/forum.js.map
  • Loading branch information
novacuum committed Mar 6, 2024
2 parents 292db4f + e0e0f64 commit 2910da3
Show file tree
Hide file tree
Showing 17 changed files with 383 additions and 107 deletions.
4 changes: 3 additions & 1 deletion extend.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@
->get('/fof/polls/{id}', 'fof.polls.show', Controllers\ShowPollController::class)
->patch('/fof/polls/{id}', 'fof.polls.edit', Controllers\EditPollController::class)
->delete('/fof/polls/{id}', 'fof.polls.delete', Controllers\DeletePollController::class)
->patch('/fof/polls/{id}/votes', 'fof.polls.votes', Controllers\MultipleVotesPollController::class),
->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),

(new Extend\Model(Post::class))
->hasMany('polls', Poll::class, 'post_id', 'id'),
Expand Down
159 changes: 125 additions & 34 deletions js/dist/forum.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion js/dist/forum.js.map

Large diffs are not rendered by default.

15 changes: 12 additions & 3 deletions js/src/forum/components/Poll/PollImage.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import type Mithril from 'mithril';
import Component from 'flarum/common/Component';
import Component, { ComponentAttrs } from 'flarum/common/Component';

export default class PollImage extends Component {
interface PollImageAttrs extends ComponentAttrs {
imageUrl: string;
alt: string;
}

export default class PollImage extends Component<PollImageAttrs> {
view(): Mithril.Children {
return;
return (
<div className="PollImage">
<img src={this.attrs.imageUrl} alt={this.attrs.alt} className="PollImage-image" />
</div>
);
}
}
29 changes: 26 additions & 3 deletions js/src/forum/components/PollForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,15 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {
protected optionImageUrls: Stream<string>[] = [];
protected question: Stream<string>;
protected subtitle: Stream<string>;
protected pollImage: Stream<string | null>;
protected imageAlt: Stream<string | null>;
protected endDate: Stream<string | null>;
protected publicPoll: Stream<boolean>;
protected allowMultipleVotes: Stream<boolean>;
protected hideVotes: Stream<boolean>;
protected allowChangeVote: Stream<boolean>;
protected maxVotes: Stream<number>;
protected datepickerMinDate: string = '';
protected pollImage: Stream<FormData | null> = Stream(null); // Stream to store uploaded image data

oninit(vnode: Mithril.Vnode): void {
super.oninit(vnode);
Expand All @@ -46,6 +47,8 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {

this.question = Stream(poll.question());
this.subtitle = Stream(poll.subtitle());
this.pollImage = Stream(poll.imageUrl());
this.imageAlt = Stream(poll.imageAlt());
this.endDate = Stream(this.formatDate(poll.endDate()));
this.publicPoll = Stream(poll.publicPoll());
this.allowMultipleVotes = Stream(poll.allowMultipleVotes());
Expand Down Expand Up @@ -95,15 +98,30 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {
);

items.add(
'poll-image',
'poll_image',
<div className="Form-group">
<label className="label">{app.translator.trans('fof-polls.forum.modal.poll_image.label')}</label>
<p className="helpText">{app.translator.trans('fof-polls.forum.modal.poll_image.help')}</p>
<UploadPollImageButton name="pollImage" stream={this.pollImage} />
<UploadPollImageButton name="pollImage" poll={this.state.poll} onUpload={this.pollImageUploadSuccess.bind(this)} />
<input type="hidden" name="pollImage" value={this.pollImage()} />
</div>,
90
);

if (this.pollImage()) {
items.add(
'poll_image_alt',
<div className="Form-group">
<label className="label">{app.translator.trans('fof-polls.forum.modal.poll_image.alt_label')}</label>

<input type="text" required name="imageAlt" className="FormControl" bidi={this.imageAlt} />

<p className="helpText">{app.translator.trans('fof-polls.forum.modal.poll_image.alt_help_text')}</p>
</div>,
90
);
}

items.add(
'answers',
<div className="PollModal--answers Form-group">
Expand Down Expand Up @@ -315,6 +333,7 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {
question: this.question(),
subtitle: this.subtitle(),
pollImage: this.pollImage(),
imageAlt: this.imageAlt(),
endDate: this.dateToTimestamp(this.endDate()) ?? false,
publicPoll: this.publicPoll(),
hideVotes: this.hideVotes(),
Expand Down Expand Up @@ -367,4 +386,8 @@ export default class PollForm extends Component<PollFormAttrs, PollFormState> {

return dayjsDate.format();
}

pollImageUploadSuccess(fileName: string): void {
this.pollImage(fileName);
}
}
14 changes: 8 additions & 6 deletions js/src/forum/components/PollView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,8 @@ export default class PollView extends Component<PollAttrs, PollState> {
);

return (
<div className="Poll" data-id={poll.id()}>
<div className={classList('Poll', poll.imageUrl() && 'Poll--image')} data-id={poll.id()}>
{this.controlsView(controls.toArray())}
{/* <div className="Poll-image">
<PollImage image={poll.image()} />
</div> */}
<div className="Poll-wrapper">{this.createMainView().toArray()}</div>
</div>
);
Expand All @@ -65,9 +62,14 @@ export default class PollView extends Component<PollAttrs, PollState> {
const items = new ItemList<Mithril.Children>();
const poll = this.attrs.poll;

if (poll.imageUrl()) {
items.add('image', <PollImage imageUrl={poll.imageUrl()} alt={poll.imageAlt()} />);
}

items.add('title', <h2 className="Poll-title">{poll.question()}</h2>);
items.add('subtitle', <p className="Poll-subtitle">{poll.subtitle()}</p>);
items.add('form', <form>{this.createFormItems().toArray()}</form>);
if (poll.subtitle()) items.add('subtitle', <p className="Poll-subtitle">{poll.subtitle()}</p>);

items.add('form', <form className="Poll-form">{this.createFormItems().toArray()}</form>);

return items;
}
Expand Down
41 changes: 29 additions & 12 deletions js/src/forum/components/UploadPollImageButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,40 @@ 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 Stream from 'flarum/common/utils/Stream';

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

export interface PollUploadObject {
fileUrl: string;
fileName: string;
}

export default class UploadPollImageButton extends Button<UploadPollImageButtonAttrs> {
loading: boolean = false;
uploadedImageUrl: string | undefined = undefined;
fileName: string | undefined = undefined;

view(vnode: Mithril.Vnode<UploadPollImageButtonAttrs>) {
this.attrs.loading = this.loading;
this.attrs.className = classList(this.attrs.className, 'Button');

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

return (
<div>
<p>
<img src={app.forum.attribute(this.attrs.name + 'Url')} alt="" />
<img src={imageUrl} alt="" />
</p>
<p>{super.view({ ...vnode, children: app.translator.trans('fof-polls.upload_image.remove_button') })}</p>
<p>{super.view({ ...vnode, children: app.translator.trans('fof-polls.forum.upload_image.remove_button') })}</p>
</div>
);
} else {
Expand Down Expand Up @@ -85,17 +92,27 @@ export default class UploadPollImageButton extends Button<UploadPollImageButtonA
}

resourceUrl() {
return app.forum.attribute('apiUrl') + '/' + this.attrs.name;
let url = app.forum.attribute('apiUrl') + '/fof/polls/' + this.attrs.name;
const poll = this.attrs.poll;

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

return url;
}

/**
* After a successful upload/removal, reload the page.
* After a successful upload/removal, redraw the page.
*
* @param {object} response
* @param {PollUploadObject} response
* @protected
*/
success(response) {
window.location.reload();
success(response: PollUploadObject) {
this.loading = false;
this.uploadedImageUrl = response.fileUrl;
this.fileName = response.fileName;

this.attrs.onUpload?.(response.fileName);
m.redraw();
}

/**
Expand All @@ -104,7 +121,7 @@ export default class UploadPollImageButton extends Button<UploadPollImageButtonA
* @param {object} response
* @protected
*/
failure(response) {
failure(response: object) {
this.loading = false;
m.redraw();
}
Expand Down
4 changes: 4 additions & 0 deletions js/src/forum/models/Poll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export default class Poll extends Model {
return Model.attribute<string | null>('imageUrl').call(this);
}

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

hasEnded() {
return Model.attribute<boolean>('hasEnded').call(this);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?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.
*/

use Flarum\Database\Migration;

return Migration::renameColumn('polls', 'image_url', 'image');
26 changes: 26 additions & 0 deletions migrations/2024_03_06_000001_create_polls_image_alt_column.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?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.
*/

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Schema\Builder;

return [
'up' => function (Builder $schema) {
$schema->table('polls', function (Blueprint $table) {
$table->string('image_alt')->nullable()->after('image');
});
},
'down' => function (Builder $schema) {
$schema->table('polls', function (Blueprint $table) {
$table->dropColumn('image_alt');
});
},
];
8 changes: 8 additions & 0 deletions resources/less/forum.less
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,14 @@
}
}

.PollImage {
width:100%;

&-image {
max-width: 100%;
}
}

.PollOption {
display: flex;
position: relative;
Expand Down
4 changes: 4 additions & 0 deletions resources/locale/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ fof-polls:
poll_image:
label: Poll Image
help: Upload an image to be displayed alongside the poll (optional).
alt_label: Image Alt Text
alt_help_text: This text is required when an image is set, it will be displayed if the image fails to load.

moderation:
add: Add Poll
Expand All @@ -111,3 +113,5 @@ fof-polls:
upload_image:
remove_button: Remove Image
upload_button: Select Image


89 changes: 89 additions & 0 deletions src/Api/Controllers/UploadPollImageController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?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\Poll;
use Illuminate\Contracts\Filesystem\Cloud;
use Illuminate\Contracts\Filesystem\Factory;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Intervention\Image\Constraint;
use Intervention\Image\Image;
use Intervention\Image\ImageManager;
use Laminas\Diactoros\Response\JsonResponse;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\UploadedFileInterface;
use Psr\Http\Server\RequestHandlerInterface;

class UploadPollImageController implements RequestHandlerInterface
{
protected $filenamePrefix = 'pollImage';

/**
* @var Cloud
*/
protected $uploadDir;

/**
* @var ImageManager
*/
protected $imageManager;

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

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

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

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

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

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

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

if ($pollId && $poll = Poll::find($pollId)) {
$poll->image = $uploadName;
$poll->save();
}

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

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

return $encodedImage;
}
}
Loading

0 comments on commit 2910da3

Please sign in to comment.