Skip to content
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

Refactor username input lookup component #11451

Merged
merged 22 commits into from
Aug 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e5e0854
move lookup
notbakaneko Aug 26, 2024
add4658
remove ChatAnnounce only permission check; exclude no_profile users f…
notbakaneko Aug 26, 2024
6b49f55
extract UsernameInput lookup into separate component
notbakaneko Aug 26, 2024
5034ce1
switch supporter-tag to new lookup endpoint
notbakaneko Aug 26, 2024
2d96c51
freeze the preset user objects
notbakaneko Aug 26, 2024
77a3586
extract api call
notbakaneko Aug 26, 2024
dbbe120
update max-warnings
notbakaneko Aug 26, 2024
379f9ce
skip lookup for already resolved users
notbakaneko Aug 26, 2024
17bbfcc
responseText isn't always present
notbakaneko Aug 26, 2024
663436c
rename field
notbakaneko Aug 27, 2024
4df2392
make private
notbakaneko Aug 27, 2024
799fbdf
avoid constantly updating UsernameInput props (does nothing)
notbakaneko Aug 27, 2024
cb79dcb
just inline; didn't add any extra checks
notbakaneko Aug 27, 2024
87b0d35
doesn't have to run in the same action as initialize, just after it.
notbakaneko Aug 27, 2024
2aa68d9
rename function
notbakaneko Aug 27, 2024
d8a8a6b
support explicit numeric username lookup
notbakaneko Aug 27, 2024
5a5a970
only do username lookups for supporter tag
notbakaneko Aug 27, 2024
5b22028
would help if the file was saved
notbakaneko Aug 27, 2024
777e8c8
missing equality
notbakaneko Aug 28, 2024
10831df
grammar :thinking:
notbakaneko Aug 28, 2024
0a35bb4
Merge branch 'master' into feature/refactor-username-input-lookup
notbakaneko Aug 29, 2024
c4c18a4
Merge branch 'master' into feature/refactor-username-input-lookup
notbakaneko Aug 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 88 > /dev/null'
- run: 'yarn lint --max-warnings 87 > /dev/null'

- run: yarn pretty

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@

declare(strict_types=1);

namespace App\Http\Controllers\Chat;
namespace App\Http\Controllers\Users;

use App\Http\Controllers\Controller as BaseController;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Transformers\UserCompactTransformer;

class UsersController extends BaseController
class LookupController extends Controller
{
public function index()
public function __construct()
{
priv_check('ChatAnnounce')->ensureCan();
$this->middleware('auth');
$this->middleware('throttle:30,1');
}

public function lookup()
nanaya marked this conversation as resolved.
Show resolved Hide resolved
{
// TODO: referer check?
$params = get_params(request()->all(), null, ['ids:string[]'], ['null_missing' => true]);
$ids = array_slice($params['ids'], 0, 50);

Expand All @@ -26,11 +31,12 @@ public function index()
if (ctype_digit($id)) {
$numericIds[] = $id;
} elseif (present($id)) {
$stringIds[] = $id;
$stringIds[] = $id[0] === '@' ? substr($id, 1) : $id;
}
}

$users = User::where(fn ($q) => $q->whereIn('user_id', $numericIds)->orWhereIn('username', $stringIds))
->where('group_id', '<>', app('groups')->byIdentifier('no_profile')->getKey())
->default()
->with(UserCompactTransformer::CARD_INCLUDES_PRELOAD)
->get();
Expand Down
9 changes: 0 additions & 9 deletions app/Http/Controllers/UsersController.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ public function __construct()
$this->middleware('guest', ['only' => ['create', 'store', 'storeWeb']]);
$this->middleware('auth', ['only' => [
'checkUsernameAvailability',
'checkUsernameExists',
'report',
'me',
'posts',
Expand Down Expand Up @@ -159,14 +158,6 @@ public function checkUsernameAvailability()
];
}

public function checkUsernameExists()
{
$username = get_string(request('username'));
$user = User::lookup($username, 'username') ?? UserNotFound::instance();

return json_item($user, 'UserCompact', ['cover', 'country']);
}

public function extraPages($_id, $page)
{
// TODO: counts basically duplicated from UserCompactTransformer
Expand Down
1 change: 1 addition & 0 deletions resources/css/bem-index.less
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@
@import "bem/user-verification";
@import "bem/user-verification-popup";
@import "bem/username-change";
@import "bem/username-input";
@import "bem/value-display";
@import "bem/warning-box";
@import "bem/wiki-main-page";
Expand Down
12 changes: 0 additions & 12 deletions resources/css/bem/chat-form.less
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,6 @@
flex: 1;
resize: none;
}

&--users {
flex: 1;
// can't do much about wrapping the input itself...
min-width: 40px;
}
}

&__spinner {
display: inline-flex;
align-items: center;
min-width: 1em;
}

&__title {
Expand Down
30 changes: 30 additions & 0 deletions resources/css/bem/username-input.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.

.username-input {
display: flex;
flex: 1;
flex-wrap: wrap;
gap: 2px;
justify-content: flex-start;
min-width: 0; // so user-card-brick overflows properly.

&--beatmap-owner-editor {
min-height: 20px;
}

&__input {
.reset-input();
color: hsl(var(--hsl-c1));

flex: 1;
// can't do much about wrapping the input itself...
min-width: 40px;
}

&__spinner {
display: inline-flex;
align-items: center;
min-width: 1em;
}
}
14 changes: 6 additions & 8 deletions resources/js/beatmap-discussions/beatmap-owner-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ import { onErrorWithCallback } from 'utils/ajax';
import { classWithModifiers } from 'utils/css';
import { transparentGif } from 'utils/html';
import { trans } from 'utils/lang';
import { apiLookupUsers } from 'utils/user';
import DiscussionsState from './discussions-state';

interface XhrCollection {
updateOwner: JQuery.jqXHR<BeatmapsetWithDiscussionsJson>;
userLookup: JQuery.jqXHR<UserJson>;
userLookup: ReturnType<typeof apiLookupUsers>;
}

interface Props {
Expand Down Expand Up @@ -260,13 +261,10 @@ export default class BeatmapOwnerEditor extends React.Component<Props> {

if (currentCheckingUser == null) return;

this.xhr.userLookup = $.ajax(route('users.check-username-exists'), {
data: { username: currentCheckingUser },
method: 'POST',
});
this.xhr.userLookup.done((user) => runInAction(() => {
if (user.id > 0) {
this.props.userByName.set(currentCheckingUser, user);
this.xhr.userLookup = apiLookupUsers([currentCheckingUser]);
this.xhr.userLookup.done((response) => runInAction(() => {
if (response.users.length > 0) {
this.props.userByName.set(currentCheckingUser, response.users[0]);
}
})).fail(
onErrorWithCallback(this.userLookup),
Expand Down
55 changes: 14 additions & 41 deletions resources/js/chat/create-announcement.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

import BigButton from 'components/big-button';
import InputContainer from 'components/input-container';
import { Spinner } from 'components/spinner';
import UserCardBrick from 'components/user-card-brick';
import UsernameInput from 'components/username-input';
import UserJson from 'interfaces/user-json';
import { action, computed, makeObservable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
Expand All @@ -15,14 +15,10 @@ import { trans } from 'utils/lang';

type Props = Record<string, never>;

const BusySpinner = ({ busy }: { busy: boolean }) => (
<div className='chat-form__spinner'>
{busy && <Spinner />}
</div>
);

@observer
export default class CreateAnnouncement extends React.Component<Props> {
private readonly usernameInputInitialProps;

@computed
private get canSend() {
return core.dataStore.chatState.isReady && !core.dataStore.chatState.isAddingChannel && this.model.isValid;
Expand All @@ -41,6 +37,8 @@ export default class CreateAnnouncement extends React.Component<Props> {
runInAction(() => {
this.model.initialize();
});

this.usernameInputInitialProps = runInAction(() => this.model.propsForUsernameInput);
}

render() {
Expand Down Expand Up @@ -84,18 +82,15 @@ export default class CreateAnnouncement extends React.Component<Props> {
>
<div className='chat-form__users'>
<UserCardBrick user={core.currentUserOrFail} />
{this.renderValidUsers()}
<input
className='chat-form__input chat-form__input--users'
<UsernameInput
id='chat-form-users'
ignoreCurrentUser
name='users'
onBlur={this.handleBlur}
onChange={this.handleUsersInputChange}
onKeyDown={this.handleUsersInputKeyDown}
onPaste={this.handleUsersInputPaste}
value={this.model.inputs.users}
onValidUsersChanged={this.handleValidUsersChanged}
onValueChanged={this.handleUsernameInputValueChanged}
{...this.usernameInputInitialProps}
/>
<BusySpinner busy={this.model.lookingUpUsers} />
</div>
</InputContainer>
<InputContainer
Expand Down Expand Up @@ -152,34 +147,12 @@ export default class CreateAnnouncement extends React.Component<Props> {
};

@action
private readonly handleRemoveUser = (user: UserJson) => {
this.model.validUsers.delete(user.id);
private readonly handleUsernameInputValueChanged = (value: string) => {
this.model.inputs.users = value;
};

@action
private readonly handleUsersInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.model.updateUsers(e.currentTarget.value, false);
private readonly handleValidUsersChanged = (value: Map<number, UserJson>) => {
this.model.validUsers = value;
};

@action
private readonly handleUsersInputKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
const elem = e.currentTarget;
if (e.key === 'Backspace' && elem.selectionStart === 0 && elem.selectionEnd === 0) {
const last = [...this.model.validUsers.keys()].pop();
if (last != null) {
this.model.validUsers.delete(last);
}
}
};

@action
private readonly handleUsersInputPaste = (e: React.SyntheticEvent<HTMLInputElement>) => {
this.model.updateUsers(e.currentTarget.value, true);
};

private renderValidUsers() {
return [...this.model.validUsers.values()].map((user) => (
<UserCardBrick key={user.id} onRemoveClick={this.handleRemoveUser} user={user} />
));
}
}
Loading