Skip to content

Commit

Permalink
Merge pull request #10409 from notbakaneko/feature/ts-discussions-main
Browse files Browse the repository at this point in the history
Convert beatmap discussions main component to typescript
  • Loading branch information
nanaya authored Jan 26, 2024
2 parents 169b75a + 1693c18 commit e7cc303
Show file tree
Hide file tree
Showing 60 changed files with 1,897 additions and 1,620 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ jobs:
- name: Install js dependencies
run: yarn --frozen-lockfile

- run: 'yarn lint --max-warnings 90 > /dev/null'
- run: 'yarn lint --max-warnings 89 > /dev/null'

- run: ./bin/update_licence.sh -nf

Expand Down
113 changes: 32 additions & 81 deletions resources/js/beatmap-discussions-history/main.tsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,49 @@
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the GNU Affero General Public License v3.0.
// See the LICENCE file in the repository root for full licence text.

import { BeatmapsContext } from 'beatmap-discussions/beatmaps-context';
import { BeatmapsetsContext } from 'beatmap-discussions/beatmapsets-context';
import { Discussion } from 'beatmap-discussions/discussion';
import { DiscussionsContext } from 'beatmap-discussions/discussions-context';
import BeatmapsetCover from 'components/beatmapset-cover';
import BeatmapsetDiscussionsBundleJson from 'interfaces/beatmapset-discussions-bundle-json';
import { keyBy } from 'lodash';
import { computed, makeObservable } from 'mobx';
import { observer } from 'mobx-react';
import { deletedUserJson } from 'models/user';
import * as React from 'react';
import BeatmapsetDiscussionsBundleStore from 'stores/beatmapset-discussions-bundle-store';
import { makeUrl } from 'utils/beatmapset-discussion-helper';
import { trans } from 'utils/lang';

interface Props {
bundle: BeatmapsetDiscussionsBundleJson;
}

@observer
export default class Main extends React.Component<Props> {
@computed
private get beatmaps() {
return keyBy(this.props.bundle.beatmaps, 'id');
}

@computed
private get beatmapsets() {
return keyBy(this.props.bundle.beatmapsets, 'id');
}

@computed
private get discussions() {
return keyBy(this.props.bundle.included_discussions, 'id');
}

@computed
private get users() {
const values = keyBy(this.props.bundle.users, 'id');
// eslint-disable-next-line id-blacklist
values.null = values.undefined = deletedUserJson;

return values;
}

constructor(props: Props) {
super(props);

makeObservable(this);
}
export default class Main extends React.Component<BeatmapsetDiscussionsBundleJson> {
private readonly store = new BeatmapsetDiscussionsBundleStore(this.props);

render() {
return (
<DiscussionsContext.Provider value={this.discussions}>
<BeatmapsetsContext.Provider value={this.beatmapsets}>
<BeatmapsContext.Provider value={this.beatmaps}>
<div className='modding-profile-list modding-profile-list--index'>
{this.props.bundle.discussions.length === 0 ? (
<div className='modding-profile-list__empty'>
{trans('beatmap_discussions.index.none_found')}
</div>
) : (this.props.bundle.discussions.map((discussion) => {
// TODO: handle in child component? Refactored state might not have beatmapset here (and uses Map)
const beatmapset = this.beatmapsets[discussion.beatmapset_id];

return beatmapset != null && (
<div key={discussion.id} className='modding-profile-list__row'>
<a
className='modding-profile-list__thumbnail'
href={makeUrl({ discussion })}
>
<BeatmapsetCover
beatmapset={beatmapset}
size='list'
/>
</a>
<Discussion
beatmapset={beatmapset}
currentBeatmap={discussion.beatmap_id != null ? this.beatmaps[discussion.beatmap_id] : null}
discussion={discussion}
isTimelineVisible={false}
preview
readonly
showDeleted
users={this.users}
/>
</div>
);
}))}
<div className='modding-profile-list modding-profile-list--index'>
{this.props.discussions.length === 0 ? (
<div className='modding-profile-list__empty'>
{trans('beatmap_discussions.index.none_found')}
</div>
) : (this.props.discussions.map((discussion) => {
// TODO: handle in child component? Refactored state might not have beatmapset here (and uses Map)
const beatmapset = this.store.beatmapsets.get(discussion.beatmapset_id);

return beatmapset != null && (
<div key={discussion.id} className='modding-profile-list__row'>
<a
className='modding-profile-list__thumbnail'
href={makeUrl({ discussion })}
>
<BeatmapsetCover
beatmapset={beatmapset}
size='list'
/>
</a>
<Discussion
discussion={discussion}
discussionsState={null}
isTimelineVisible={false}
store={this.store}
/>
</div>
</BeatmapsContext.Provider>
</BeatmapsetsContext.Provider>
</DiscussionsContext.Provider>
);
}))}
</div>
);
}
}
85 changes: 42 additions & 43 deletions resources/js/beatmap-discussions/beatmap-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,43 @@

import BeatmapListItem from 'components/beatmap-list-item';
import BeatmapExtendedJson from 'interfaces/beatmap-extended-json';
import BeatmapsetJson from 'interfaces/beatmapset-json';
import UserJson from 'interfaces/user-json';
import { deletedUser } from 'models/user';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { deletedUserJson } from 'models/user';
import * as React from 'react';
import { makeUrl } from 'utils/beatmapset-discussion-helper';
import { blackoutToggle } from 'utils/blackout';
import { classWithModifiers } from 'utils/css';
import { formatNumber } from 'utils/html';
import { nextVal } from 'utils/seq';
import DiscussionsState from './discussions-state';

interface Props {
beatmaps: BeatmapExtendedJson[];
beatmapset: BeatmapsetJson;
createLink: (beatmap: BeatmapExtendedJson) => string;
currentBeatmap: BeatmapExtendedJson;
getCount: (beatmap: BeatmapExtendedJson) => number | undefined;
onSelectBeatmap: (beatmapId: number) => void;
users: Partial<Record<number, UserJson>>;
discussionsState: DiscussionsState;
users: Map<number | null | undefined, UserJson>;
}

interface State {
showingSelector: boolean;
}

export default class BeatmapList extends React.PureComponent<Props, State> {
@observer
export default class BeatmapList extends React.Component<Props> {
private readonly eventId = `beatmapset-discussions-show-beatmap-list-${nextVal()}`;
@observable private showingSelector = false;

@computed
private get beatmaps() {
return this.props.discussionsState.groupedBeatmaps.get(this.props.discussionsState.currentBeatmap.mode) ?? [];
}

constructor(props: Props) {
super(props);

this.state = {
showingSelector: false,
};
makeObservable(this);
}

componentDidMount() {
$(document).on(`click.${this.eventId}`, this.onDocumentClick);
$(document).on(`turbolinks:before-cache.${this.eventId}`, this.hideSelector);
this.syncBlackout();
$(document).on(`turbolinks:before-cache.${this.eventId}`, this.handleBeforeCache);
blackoutToggle(this.showingSelector, 0.5);
}

componentWillUnmount() {
Expand All @@ -49,22 +48,22 @@ export default class BeatmapList extends React.PureComponent<Props, State> {

render() {
return (
<div className={classWithModifiers('beatmap-list', { selecting: this.state.showingSelector })}>
<div className={classWithModifiers('beatmap-list', { selecting: this.showingSelector })}>
<div className='beatmap-list__body'>
<a
className='beatmap-list__item beatmap-list__item--selected beatmap-list__item--large js-beatmap-list-selector'
href={this.props.createLink(this.props.currentBeatmap)}
href={makeUrl({ beatmap: this.props.discussionsState.currentBeatmap })}
onClick={this.toggleSelector}
>
<BeatmapListItem beatmap={this.props.currentBeatmap} mapper={null} modifiers='large' />
<BeatmapListItem beatmap={this.props.discussionsState.currentBeatmap} mapper={null} modifiers='large' />
<div className='beatmap-list__item-selector-button'>
<span className='fas fa-chevron-down' />
</div>
</a>

<div className='beatmap-list__selector-container'>
<div className='beatmap-list__selector'>
{this.props.beatmaps.map(this.beatmapListItem)}
{this.beatmaps.map(this.beatmapListItem)}
</div>
</div>
</div>
Expand All @@ -73,20 +72,20 @@ export default class BeatmapList extends React.PureComponent<Props, State> {
}

private readonly beatmapListItem = (beatmap: BeatmapExtendedJson) => {
const count = this.props.getCount(beatmap);
const count = this.props.discussionsState.unresolvedDiscussionCounts.byBeatmap[beatmap.id];

return (
<div
key={beatmap.id}
className={classWithModifiers('beatmap-list__item', { current: beatmap.id === this.props.currentBeatmap.id })}
className={classWithModifiers('beatmap-list__item', { current: beatmap.id === this.props.discussionsState.currentBeatmap.id })}
data-id={beatmap.id}
onClick={this.selectBeatmap}
>
<BeatmapListItem
beatmap={beatmap}
beatmapUrl={this.props.createLink(beatmap)}
beatmapset={this.props.beatmapset}
mapper={this.props.users[beatmap.user_id] ?? deletedUser}
beatmapUrl={makeUrl({ beatmap })}
beatmapset={this.props.discussionsState.beatmapset}
mapper={this.props.users.get(beatmap.user_id) ?? deletedUserJson}
showNonGuestMapper={false}
/>
{count != null &&
Expand All @@ -98,44 +97,44 @@ export default class BeatmapList extends React.PureComponent<Props, State> {
);
};

private readonly hideSelector = () => {
if (this.state.showingSelector) {
this.setSelector(false);
}
@action
private readonly handleBeforeCache = () => {
this.setShowingSelector(false);
};

@action
private readonly onDocumentClick = (e: JQuery.ClickEvent) => {
if (e.button !== 0) return;

if ($(e.target).closest('.js-beatmap-list-selector').length) {
return;
}

this.hideSelector();
this.setShowingSelector(false);
};

@action
private readonly selectBeatmap = (e: React.MouseEvent<HTMLElement>) => {
if (e.button !== 0) return;
e.preventDefault();

const beatmapId = parseInt(e.currentTarget.dataset.id ?? '', 10);
this.props.onSelectBeatmap(beatmapId);
};

private readonly setSelector = (state: boolean) => {
if (this.state.showingSelector !== state) {
this.setState({ showingSelector: state }, this.syncBlackout);
}
this.props.discussionsState.currentBeatmapId = beatmapId;
this.props.discussionsState.changeDiscussionPage('timeline');
};

private readonly syncBlackout = () => {
blackoutToggle(this.state.showingSelector, 0.5);
};
@action
private setShowingSelector(state: boolean) {
this.showingSelector = state;
blackoutToggle(state, 0.5);
}

@action
private readonly toggleSelector = (e: React.MouseEvent<HTMLElement>) => {
if (e.button !== 0) return;
e.preventDefault();

this.setSelector(!this.state.showingSelector);
this.setShowingSelector(!this.showingSelector);
};
}
12 changes: 6 additions & 6 deletions resources/js/beatmap-discussions/beatmap-owner-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Spinner } from 'components/spinner';
import UserAvatar from 'components/user-avatar';
import UserLink from 'components/user-link';
import BeatmapJson from 'interfaces/beatmap-json';
import BeatmapsetExtendedJson from 'interfaces/beatmapset-extended-json';
import BeatmapsetWithDiscussionsJson from 'interfaces/beatmapset-with-discussions-json';
import UserJson from 'interfaces/user-json';
import { route } from 'laroute';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
Expand All @@ -16,17 +16,17 @@ import { onErrorWithCallback } from 'utils/ajax';
import { classWithModifiers } from 'utils/css';
import { transparentGif } from 'utils/html';
import { trans } from 'utils/lang';

type BeatmapsetWithDiscussionJson = BeatmapsetExtendedJson;
import DiscussionsState from './discussions-state';

interface XhrCollection {
updateOwner: JQuery.jqXHR<BeatmapsetWithDiscussionJson>;
updateOwner: JQuery.jqXHR<BeatmapsetWithDiscussionsJson>;
userLookup: JQuery.jqXHR<UserJson>;
}

interface Props {
beatmap: BeatmapJson;
beatmapsetUser: UserJson;
discussionsState: DiscussionsState;
user: UserJson;
userByName: Map<string, UserJson>;
}
Expand Down Expand Up @@ -245,8 +245,8 @@ export default class BeatmapOwnerEditor extends React.Component<Props> {
data: { beatmap: { user_id: userId } },
method: 'PUT',
});
this.xhr.updateOwner.done((data) => runInAction(() => {
$.publish('beatmapsetDiscussions:update', { beatmapset: data });
this.xhr.updateOwner.done((beatmapset) => runInAction(() => {
this.props.discussionsState.update({ beatmapset });
this.editing = false;
})).fail(onErrorWithCallback(() => {
this.updateOwner(userId);
Expand Down
9 changes: 0 additions & 9 deletions resources/js/beatmap-discussions/beatmaps-context.ts

This file was deleted.

Loading

0 comments on commit e7cc303

Please sign in to comment.