-
Notifications
You must be signed in to change notification settings - Fork 387
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
Show custom user tags on beatmapset #11750
Changes from 21 commits
545c945
4ae47f9
4a55d98
b3fcdfa
d883075
3c9a6f5
1503ceb
f5552af
5ad134a
3c9c2e5
9e01f3d
715db03
dd68826
2b3bb42
05db2c1
0eefd0b
c9ad28d
421504c
efad0be
cde18d0
572b046
73162ed
585fa90
efd6470
be47400
3dee3fc
bdffa17
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,14 +10,29 @@ | |
/** | ||
* @property-read Beatmap $beatmap | ||
* @property int $beatmap_id | ||
* @property \Carbon\Carbon $created_at | ||
* @property int $tag_id | ||
* @property \Carbon\Carbon $updated_at | ||
* @property-read User $user | ||
* @property int $user_id | ||
*/ | ||
class BeatmapTag extends Model | ||
{ | ||
protected $primaryKey = ':composite'; | ||
notbakaneko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
protected $primaryKeys = ['beatmap_id', 'tag_id', 'user_id']; | ||
public $incrementing = false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. position public before protected? |
||
|
||
public static function topTagIdsQuery(int $beatmapId, int $limit = 50) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why is it not a scope |
||
{ | ||
return static::where('beatmap_id', $beatmapId) | ||
->whereHas('user', fn ($userQuery) => $userQuery->default()) | ||
->groupBy('tag_id') | ||
->select('tag_id') | ||
->selectRaw('COUNT(*) as count') | ||
->orderBy('count', 'desc') | ||
->orderBy('tag_id', 'asc') | ||
->limit($limit); | ||
} | ||
|
||
public function beatmap() | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
<?php | ||
|
||
// 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. | ||
|
||
declare(strict_types=1); | ||
|
||
namespace App\Singletons; | ||
|
||
use App\Models\Tag; | ||
use App\Traits\Memoizes; | ||
use App\Transformers\TagTransformer; | ||
use Illuminate\Support\Collection; | ||
|
||
class Tags | ||
{ | ||
use Memoizes; | ||
|
||
/** | ||
* @return Collection<Tag> | ||
*/ | ||
public function all(): Collection | ||
{ | ||
return $this->memoize(__FUNCTION__, fn () => Tag::all()); | ||
} | ||
|
||
public function get(int $id): ?Tag | ||
{ | ||
$allById = $this->memoize( | ||
'allById', | ||
fn () => $this->all()->keyBy('id'), | ||
); | ||
|
||
return $allById[$id] ?? null; | ||
} | ||
|
||
public function json(): array | ||
{ | ||
return $this->memoize( | ||
__FUNCTION__, | ||
fn () => json_collection($this->all(), new TagTransformer()), | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// See the LICENCE file in the repository root for full licence text. | ||
|
||
import { BeatmapsetJsonForShow } from 'interfaces/beatmapset-extended-json'; | ||
import TagJson from 'interfaces/tag-json'; | ||
import UserJson from 'interfaces/user-json'; | ||
import { keyBy } from 'lodash'; | ||
import { action, computed, makeObservable, observable, runInAction } from 'mobx'; | ||
|
@@ -10,6 +11,7 @@ import core from 'osu-core-singleton'; | |
import { find, findDefault, group } from 'utils/beatmap-helper'; | ||
import { parse } from 'utils/beatmapset-page-hash'; | ||
import { parseJson } from 'utils/json'; | ||
import { present } from 'utils/string'; | ||
import { currentUrl } from 'utils/turbolinks'; | ||
|
||
export type ScoreLoadingState = null | 'error' | 'loading' | 'supporter_only' | 'unranked'; | ||
|
@@ -23,6 +25,16 @@ interface State { | |
showingNsfwWarning: boolean; | ||
} | ||
|
||
type TagJsonWithCount = TagJson & { count: number }; | ||
|
||
function asTagJsonWithCount(tag: TagJson) { | ||
return { | ||
count: 0, | ||
...tag, | ||
}; | ||
} | ||
|
||
|
||
export default class Controller { | ||
@observable hoveredBeatmap: null | BeatmapJsonForBeatmapsetShow = null; | ||
@observable state: State; | ||
|
@@ -70,6 +82,41 @@ export default class Controller { | |
return this.beatmaps.get(this.currentBeatmap.mode) ?? []; | ||
} | ||
|
||
@computed | ||
get relatedTags() { | ||
notbakaneko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const map = new Map<number, TagJson>(); | ||
|
||
for (const tag of this.beatmapset.related_tags) { | ||
map.set(tag.id, tag); | ||
} | ||
|
||
return map; | ||
} | ||
|
||
@computed | ||
get tags() { | ||
const userTags: TagJsonWithCount[] = []; | ||
|
||
if (this.currentBeatmap.top_tag_ids != null) { | ||
for (const tagId of this.currentBeatmap.top_tag_ids) { | ||
const maybeTag = this.relatedTags.get(tagId.tag_id); | ||
if (maybeTag == null) continue; | ||
|
||
const tag = asTagJsonWithCount(maybeTag); | ||
tag.count = tagId.count; | ||
userTags.push(asTagJsonWithCount(tag)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can just push (also why is it converting the thing twice) |
||
} | ||
} | ||
|
||
return { | ||
mapperTags: this.beatmapset.tags.split(' ').filter(present), | ||
userTags: userTags.sort((a, b) => { | ||
const diff = b.count - a.count; | ||
return diff !== 0 ? diff : a.name.localeCompare(b.name); | ||
}), | ||
}; | ||
} | ||
|
||
@computed | ||
get usersById() { | ||
return keyBy(this.beatmapset.related_users, 'id') as Partial<Record<number, UserJson>>; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,15 @@ export default class Info extends React.Component<Props> { | |
return ret; | ||
} | ||
|
||
private get tags() { | ||
const tags = this.controller.tags; | ||
|
||
return [ | ||
...tags.userTags.map((tag) => tag.name), | ||
...tags.mapperTags, | ||
]; | ||
} | ||
|
||
private get withEditDescription() { | ||
return this.controller.beatmapset.description.bbcode != null; | ||
} | ||
|
@@ -84,10 +93,6 @@ export default class Info extends React.Component<Props> { | |
} | ||
|
||
render() { | ||
const tags = this.controller.beatmapset.tags | ||
.split(' ') | ||
.filter(present); | ||
|
||
return ( | ||
<div className='beatmapset-info u-fancy-scrollbar'> | ||
{this.isEditingDescription && | ||
|
@@ -191,14 +196,14 @@ export default class Info extends React.Component<Props> { | |
</div> | ||
</div> | ||
|
||
{tags.length > 0 && | ||
{this.tags.length > 0 && | ||
<div className='beatmapset-info__row beatmapset-info__row--value-overflow'> | ||
<h3 className='beatmapset-info__header'> | ||
{trans('beatmapsets.show.info.tags')} | ||
</h3> | ||
<div className='beatmapset-info__value-overflow'> | ||
{tags.map((tag, i) => ( | ||
<React.Fragment key={`${tag}-${i}`}> | ||
{this.tags.map((tag) => ( | ||
<React.Fragment key={`${tag}`}> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the |
||
<a | ||
className='beatmapset-info__link' | ||
href={route('beatmapsets.index', { q: tag })} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mutexed seems not very useful when it's just db access? Like, nothing would work if the db is unhealthy.