Skip to content

Commit

Permalink
- update poll detail view
Browse files Browse the repository at this point in the history
- enhance poll list view
  • Loading branch information
novacuum committed Feb 8, 2024
1 parent e02459c commit 7c2f830
Show file tree
Hide file tree
Showing 17 changed files with 960 additions and 172 deletions.
602 changes: 505 additions & 97 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.

2 changes: 1 addition & 1 deletion js/src/forum/addNavItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function addNavItem() {
'fof-polls-directory',
LinkButton.component(
{
href: app.route('fof_polls_directory'),
href: app.route('fof_polls_list'),
icon: 'fas fa-poll',
},
app.translator.trans('fof-polls.forum.page.nav')
Expand Down
4 changes: 2 additions & 2 deletions js/src/forum/components/ComposePollHero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class ComposePollHero extends Component {
icon="far fa-edit"
className="Button Button--primary IndexPage-newDiscussion GoodiesManagerLink"
itemClassName="App-primaryControl"
href={app.route('fof_polls_directory')}
href={app.route('fof_polls_list')}
>
{t(`${prfx}.polls_manager`)}
</LinkButton>
Expand All @@ -30,7 +30,7 @@ export default class ComposePollHero extends Component {
icon="far fa-arrow-up-right-from-square"
className="Button Button--primary IndexPage-newDiscussion GoodiePreviewLink"
itemClassName="App-primaryControl"
href={app.route('fof_polls_directory', { id: poll.id() })}
href={app.route('fof_polls_list', { id: poll.id() })}
external={true}
target="_blank"
>
Expand Down
122 changes: 113 additions & 9 deletions js/src/forum/components/Poll.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,136 @@
import * as Mithril from 'mithril';
import Component from 'flarum/common/Component';

import Mithril from 'mithril';
import Component, { ComponentAttrs } from 'flarum/common/Component';
import app from 'flarum/forum/app';
import PollTitle from './Poll/PollTitle';
import PollOptions from './Poll/PollOptions';
import PollSubmitButton from './Poll/PollSubmitButton';
import PollImage from './Poll/PollImage';
import PollDescription from './Poll/PollDescription';
import PollModel from '../models/Poll';
import PollState from '../states/PollState';
import Tooltip from 'flarum/common/components/Tooltip';
import Button from 'flarum/common/components/Button';
import ItemList from 'flarum/common/utils/ItemList';
import { slug } from '../../common';
import PollControls from '../utils/PollControls';

// Make translation calls shorter
const t = app.translator.trans.bind(app.translator);
const prfx = `${slug}.forum.poll`;

interface PollAttrs extends ComponentAttrs {
poll: PollModel;
}

export default class Poll extends Component<PollAttrs, PollState> {
oninit(vnode: Mithril.Vnode<ComponentAttrs, this>) {
super.oninit(vnode);
this.state = new PollState(this.attrs.poll);
}

export default class IndexPolls extends Component {
view(): Mithril.Children {
/* @type Poll */
const poll = this.attrs.poll;
const infoItems = this.infoItems(poll.maxVotes());
const state = this.state;

return (
<div className="Poll">
<div className="Poll" data-id={poll.id()}>
<div className="PollHeading">
<h3 className="PollHeading-title">{poll.question()}</h3>
{poll.canSeeVoters() && (
<Tooltip text={t('fof-polls.forum.public_poll')}>
<Button className="Button PollHeading-voters" onclick={state.showVoters} icon="fas fa-poll" />
</Tooltip>
)}

{poll.canEdit() && (
<Tooltip text={t('fof-polls.forum.moderation.edit')}>
<Button className="Button PollHeading-edit" onclick={this.editPoll.bind(this)} icon="fas fa-pen" />
</Tooltip>
)}
{poll.canDelete() && (
<Tooltip text={t('fof-polls.forum.moderation.delete')}>
<Button className="Button PollHeading-delete" onclick={this.deletePoll.bind(this)} icon="fas fa-trash" />
</Tooltip>
)}
</div>
<div className="Poll-image">
<PollImage image={poll.image} />
</div>
<div className="Poll-wrapper">
<PollTitle text={poll.question()} />
<PollDescription text="Ihre Meinung ist uns wichtig! Welche SBB-Entscheidungen möchten Sie mehr einbezogen sehen? Teilen Sie uns mit, welche Themen für Sie besonders relevant sind. Vielen Dank für Ihre Teilnahme!" />
<form>
<fieldset>
<legend className="sr-only">Antworten</legend>
<PollOptions options={poll.options()} />
<PollSubmitButton />
<PollOptions options={poll.options()} state={state} />
</fieldset>
<div className="Poll-sticky">
{!infoItems.isEmpty() && <div className="helpText PollInfoText">{infoItems.toArray()}</div>}

{state.showButton() && (
<Button className="Button Button--primary Poll-submit" loading={state.loadingOptions} onclick={state.onsubmit.bind(this)}>
{t('fof-polls.forum.poll.submit_button')}
</Button>
)}
</div>
</form>
</div>
</div>
);
}

deletePoll(): void {
PollControls.deleteAction(this.attrs.poll);
}

editPoll(): void {
PollControls.editAction(this.attrs.poll);
}

infoItems(maxVotes: number) {
const items = new ItemList<Mithril.Children>();
const poll = this.attrs.poll;

if (app.session.user && !poll.canVote() && !poll.hasEnded()) {
items.add(
'no-permission',
<span>
<i className="icon fas fa-times-circle fa-fw" />
{t('fof-polls.forum.no_permission')}
</span>
);
}

if (poll.endDate()) {
items.add(
'end-date',
<span>
<i class="icon fas fa-clock fa-fw" />
{poll.hasEnded() ? t('fof-polls.forum.poll_ended') : t('fof-polls.forum.days_remaining', { time: dayjs(poll.endDate()).fromNow() })}
</span>
);
}

if (poll.canVote()) {
items.add(
'max-votes',
<span>
<i className="icon fas fa-poll fa-fw" />
{t('fof-polls.forum.max_votes_allowed', { max: maxVotes })}
</span>
);

if (!poll.canChangeVote()) {
items.add(
'cannot-change-vote',
<span>
<i className={`icon fas fa-${this.state.hasVoted() ? 'times' : 'exclamation'}-circle fa-fw`} />
{t('fof-polls.forum.poll.cannot_change_vote')}
</span>
);
}
}

return items;
}
}
4 changes: 2 additions & 2 deletions js/src/forum/components/Poll/PollList.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ export default class PollList extends Component {
const pageSize = state.pageSize;

return (
<div className={classList('PollList', { 'PollList--searchResults': state.isSearchResults() })}>
<ul aria-busy={isLoading} className="PollList-polls">
<div className={classList('PollList UserDirectoryList', { 'PollList--searchResults': state.isSearchResults() })}>
<ul aria-busy={isLoading} className="PollList-polls UserDirectoryList-users">
{state.getPages().map((pg) => {
return pg.items.map((poll) => (
<li key={poll.id()} data-id={poll.id()}>
Expand Down
72 changes: 49 additions & 23 deletions js/src/forum/components/Poll/PollListItem.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as Mithril from 'mithril';
import Mithril from 'mithril';
import app from 'flarum/forum/app';
import Component, { ComponentAttrs } from 'flarum/common/Component';
import type Poll from '../../../common/models/Poll';
import type { PollListParams } from '../../states/PollListState';
import SubtreeRetainer from 'flarum/common/utils/SubtreeRetainer';
import classList from 'flarum/common/utils/classList';
Expand All @@ -12,6 +11,15 @@ import slidable from 'flarum/forum/utils/slidable';
import icon from 'flarum/common/helpers/icon';
import PollPage from './PollPage';
import abbreviateNumber from 'flarum/common/utils/abbreviateNumber';
import Poll from '../../models/Poll';
import PollControls from '../../utils/PollControls';
import { slug } from '../../../common';
import ItemList from 'flarum/common/utils/ItemList';
import listItems from 'flarum/common/helpers/listItems';

// Make translation calls shorter
const t = app.translator.trans.bind(app.translator);
const prfx = `${slug}.forum.list`;

export interface IPollListItemAttrs extends ComponentAttrs {
poll: Poll;
Expand Down Expand Up @@ -46,7 +54,7 @@ export default class PollListItem<CustomAttrs extends IPollListItemAttrs = IPoll

elementAttrs() {
return {
className: classList('PollListItem', {
className: classList('PollListItem User', {
active: this.active(),
'PollListItem--hidden': this.attrs.poll.isHidden(),
Slidable: 'ontouchstart' in window,
Expand All @@ -57,13 +65,12 @@ export default class PollListItem<CustomAttrs extends IPollListItemAttrs = IPoll
view() {
const poll = this.attrs.poll;

// TODO IMPLEMENT POLLCONTROLS
//const controls = PollControls.controls(poll, this).toArray();
const controls = PollControls.controls(poll, this).toArray();
const attrs = this.elementAttrs();

return (
<div {...attrs}>
{/* {this.controlsView(controls)} */}
{this.controlsView(controls)}
{this.contentView()}
{this.slidableUnderneathView()}
</div>
Expand All @@ -75,9 +82,10 @@ export default class PollListItem<CustomAttrs extends IPollListItemAttrs = IPoll
!!controls.length && (
<Dropdown
icon="fas fa-ellipsis-v"
className="PollListItem-controls"
className="UserCard-controls App-primaryControl PollListItem-controls"
menuClassName="Dropdown-menu--right"
buttonClassName="Button Button--icon Button--flat"
accessibleToggleLabel={app.translator.trans('fof-polls.forum.poll_controls.toggle_dropdown_accessible_label')}
accessibleToggleLabel={t('fof-polls.forum.poll_controls.toggle_dropdown_accessible_label')}
>
{controls}
</Dropdown>
Expand Down Expand Up @@ -107,10 +115,8 @@ export default class PollListItem<CustomAttrs extends IPollListItemAttrs = IPoll
return (
// <div className={classList('PollListItem-content', 'Slidable-content', { unread: isUnread, read: isRead })}>
<div className={classList('PollListItem-content')}>
{/* {this.authorAvatarView()}
{this.badgesView()} */}
{this.mainView()}
{this.voteCountItem()}
{this.infoView()}
</div>
);
}
Expand All @@ -119,13 +125,16 @@ export default class PollListItem<CustomAttrs extends IPollListItemAttrs = IPoll
const poll = this.attrs.poll;

return (
<Link href={app.route('fof_polls_compose', { id: poll.id() })} className="PollListItem-main">
<Link href={app.route('fof_polls_list', { id: poll.id() })} className="PollListItem-main">
<h2 className="PollListItem-title">{highlight(poll.question(), this.highlightRegExp)}</h2>
{/* <ul className="PollListItem-info">{listItems(this.infoItems().toArray())}</ul> */}
</Link>
);
}

infoView() {
return <ul className="UserCard-info">{listItems(this.infoItems().toArray())}</ul>;
}

oncreate(vnode: Mithril.VnodeDOM<CustomAttrs, this>) {
super.oncreate(vnode);

Expand Down Expand Up @@ -164,17 +173,34 @@ export default class PollListItem<CustomAttrs extends IPollListItemAttrs = IPoll
}
}

voteCountItem() {
infoItems(): ItemList<Mithril.Children> {
const poll = this.attrs.poll;

return (
<span className="PollListItem-count">
<span aria-hidden="true">{abbreviateNumber(poll.voteCount())}</span>

<span className="visually-hidden">
{app.translator.trans('fof-polls.forum.poll_list.total_votes_a11y_label', { count: poll.voteCount() })}
</span>
</span>
const items = new ItemList<Mithril.Children>();
const active = !poll.hasEnded();
const activeView = poll.endDate()
? [
icon('fas fa-clock'),
' ',
active ? t('fof-polls.forum.days_remaining', { time: dayjs(poll.endDate()).fromNow() }) : t('fof-polls.forum.poll_ended'),
]
: icon('fas fa-om');

items.add('active', <span className={classList('UserCard-lastSeen', { active })}>{activeView}</span>);

items.add(
'discussion-count',
<div className="userStat">
{icon('fas fa-poll fa-fw')}
{[
' ',
t('fof-user-directory.forum.page.usercard.discussion-count', {
count: abbreviateNumber(poll.voteCount()),
}),
]}
</div>,
70
);

return items;
}
}
14 changes: 10 additions & 4 deletions js/src/forum/components/Poll/PollOption.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import * as Mithril from 'mithril';
import Component from 'flarum/common/Component';
import Mithril from 'mithril';
import Component, { ComponentAttrs } from 'flarum/common/Component';
import PollOptionLabel from './PollOptionLabel';
import PollOptionInput from './PollOptionInput';
import PollOptionModel from '../../models/PollOption';

export default class PollOption extends Component {
interface PollOptionAttrs extends ComponentAttrs {
option: PollOptionModel;
onchange: (e: Event) => void;
}

export default class PollOption extends Component<PollOptionAttrs> {
view(): Mithril.Children {
const option = this.attrs.option;
return (
<label className="PollOption-tmp">
<PollOptionInput id={option.id()} isResult={false} name="vote" value="Vote for this option" />
<PollOptionInput id={option.id()} isResult={false} name="vote" value="Vote for this option" onchange={this.attrs.onchange} />
<span className="PollOption-information">
<PollOptionLabel id={option.id()} text={option.answer()} />
</span>
Expand Down
16 changes: 0 additions & 16 deletions js/src/forum/components/Poll/PollOptionDescription.tsx

This file was deleted.

2 changes: 2 additions & 0 deletions js/src/forum/components/Poll/PollOptionInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ interface PollOptionInputAttrs extends ComponentAttrs {
name: String; // for example privacy-setting
value: String; // for example Private to Project Members
isResult?: Boolean;
onchange: (e: Event) => void;
}

export default class PollOptionInput extends Component<PollOptionInputAttrs> {
Expand All @@ -20,6 +21,7 @@ export default class PollOptionInput extends Component<PollOptionInputAttrs> {
className="PollOption-input"
aria-labelledby={`${this.attrs.name}-${this.attrs.id}-label`}
aria-describedby={`${this.attrs.name}-${this.attrs.id}-description`}
onchange={this.attrs.onchange}
/>
);
}
Expand Down
Loading

0 comments on commit 7c2f830

Please sign in to comment.