ui: avatar uploader

This commit is contained in:
undefined committed 2023-01-07 22:19:10 +08:00
1 parent dc7f2a877e
commit 9672436151
5 files changed
+106

No files matched your search

+20
View File
@@ -1,3 +1,4 @@
import path from 'path';
import yaml from 'js-yaml';
import { ObjectID } from 'mongodb';
import { camelCase } from '@hydrooj/utils/lib/utils';
@@ -20,6 +21,7 @@ import domain from '../model/domain';
import message from '../model/message';
import ProblemModel from '../model/problem';
import * as setting from '../model/setting';
import storage from '../model/storage';
import * as system from '../model/system';
import token from '../model/token';
import * as training from '../model/training';
@@ -288,6 +290,23 @@ class HomeSettingsHandler extends Handler {
}
}
class HomeAvatarHandler extends Handler {
@param('avatar', Types.String, true)
async post(domainId: string, input: string) {
if (input) await user.setById(this.user._id, { avatar: input });
else if (this.request.files.file) {
const file = this.request.files.file;
if (file.size > 8 * 1024 * 1024) throw new ValidationError('file');
const ext = path.extname(file.originalFilename);
if (!['.jpg', '.jpeg', '.png'].includes(ext)) throw new ValidationError('file');
await storage.put(`user/${this.user._id}/.avatar${ext}`, file.filepath, this.user._id);
// TODO: cached avatar
await user.setById(this.user._id, { avatar: `url:/file/${this.user._id}/.avatar${ext}` });
} else throw new ValidationError('avatar');
this.back();
}
}
class UserChangemailWithCodeHandler extends Handler {
@param('code', Types.String)
async get(domainId: string, code: string) {
@@ -444,6 +463,7 @@ export async function apply(ctx: Context) {
ctx.Route('home_security', '/home/security', HomeSecurityHandler, PRIV.PRIV_USER_PROFILE);
ctx.Route('user_changemail_with_code', '/home/changeMail/:code', UserChangemailWithCodeHandler, PRIV.PRIV_USER_PROFILE);
ctx.Route('home_settings', '/home/settings/:category', HomeSettingsHandler, PRIV.PRIV_USER_PROFILE);
ctx.Route('home_avatar', '/home/avatar', HomeAvatarHandler, PRIV.PRIV_USER_PROFILE);
ctx.Route('home_domain', '/home/domain', HomeDomainHandler, PRIV.PRIV_USER_PROFILE);
ctx.Route('home_domain_create', '/home/domain/create', HomeDomainCreateHandler, PRIV.PRIV_CREATE_DOMAIN);
if (system.get('server.message')) ctx.Route('home_messages', '/home/messages', HomeMessagesHandler, PRIV.PRIV_USER_PROFILE);
+1
View File
@@ -63,3 +63,4 @@ user_logout: Logout
user_lostpass: Lost Password
user_register: Register
wiki_help: Help
You can also upload your avatar to Gravatar and it will be automatically updated here.: You can also upload your avatar to <a href="https://gravatar.com/">Gravatar</a> and it will be automatically updated here.
+1
View File
@@ -960,6 +960,7 @@ Write Your Comment: 发表您的评论
Write Your Solution: 发表您的题解
Year: 年
You are not allowed to join the domain. The link is either invalid or expired.: 您无法加入该域,链接无效或已过期。
You can also upload your avatar to Gravatar and it will be automatically updated here.: 你也可以将头像上传到 <a href="https://gravatar.com/">Gravatar</a>,本站的头像将自动更新。
You can create your own training plans and share with others.: 您可以创建您自己的训练计划并且与他人分享。
You cannot submit for this problem because the contest is ended. You can click "Open in Problem Set" to view this problem in normal mode.: 该比赛已结束,您无法在比赛模式下递交该题目。您可以点击“在题库中打开”以普通模式查看和递交本题。
You cannot submit for this problem because the homework's deadline is due.: 作业已超过截止时间,您无法递交本题目。
@@ -0,0 +1,69 @@
import $ from 'jquery';
import Notification from 'vj/components/notification';
import { NamedPage } from 'vj/misc/Page';
import {
delay, i18n, request, tpl,
} from 'vj/utils';
export default new NamedPage('home_account', () => {
document.getElementsByName('avatar')[0].parentNode.parentNode.parentElement.remove();
const $type = $(tpl`
<select id="type" class="select">
<option value="gravatar">${i18n('Gravatar')}</option>
<option value="github">${i18n('Github')}</option>
<option value="qq">${i18n('QQ')}</option>
<option value="upload">${i18n('Upload')}</option>
</select>
`);
const $text = $(tpl`<input type="text" class="textbox" placeholder="${i18n('Avatar URL')}">`);
const $layout = $(`
<div class="row">
${[['type', 3], ['text', 6], ['btn', 3]].map(([i, w]) => `
<div class="medium-${w} columns form__item">
<div name="form_item_${i}"></div>
</div>
`).join(' ')}
</div>
`);
const $file: JQuery<HTMLInputElement> = $('<input type="file" style="display:none" accept=".jpg,.jpeg,.png">');
const $confirm = $(tpl`<button class="button rounded primary">${i18n('Confirm')}</button>`);
$('button.change-avatar').on('click', () => {
$('.changeAvatar').append($layout);
$('[name="form_item_type"]').append($type);
$('[name="form_item_text"]').append($text);
$('[name="form_item_text"]').append($file);
$('[name="form_item_btn"]').append($confirm);
$('button.change-avatar').hide();
$type.trigger('change');
});
$type.on('change', () => {
if ($type.val() === 'upload') {
$text.hide();
$file.show();
} else {
$text.show();
$file.hide();
const placeholder = $type.val() === 'gravatar'
? 'Email address'
: $type.val() === 'github'
? 'Github username'
: 'QQ ID';
$text.attr('placeholder', i18n(placeholder));
}
});
$confirm.on('click', async () => {
if ($type.val() === 'upload') {
const formData = new FormData();
formData.append('file', $file[0].files[0]);
await request.postFile('/home/avatar', formData);
Notification.success(i18n('Upload success.'));
} else {
await request.post('/home/avatar', {
avatar: `${$type.val()}:${$text.val()}`,
});
Notification.success(i18n('Updated.'));
}
await delay(800);
window.location.reload();
});
});
@@ -1,5 +1,20 @@
{% extends "layout/home_base.html" %}
{% block home_content %}
{% if category == "account" %}
<div class="section" id="upload_avatar" data-heading-extract-to="#menu-item-home_account" style="nojs--hide">
<div class="section__header">
<h1 class="section__title" id="avatar" data-heading>{{ _('Avatar') }}</h1>
</div>
<div class="section__body typo">
<p>{{ _('You can also upload your avatar to Gravatar and it will be automatically updated here.')|safe }}</p>
</div>
<div class="section__body">
<img width="128px" height="128px" src="{{ avatarUrl(current.avatar, 128) }}" alt="{{ current.uname }}">
<div class="changeAvatar"></div>
<button class="change-avatar button rounded primary">{{ _('Change avatar') }}</button>
</div>
</div>
{% endif %}
<div class="section" data-heading-extract-to="#menu-item-home_{{ category }}">
{% include "partials/setting.html" %}
</div>