@@ -238,15 +238,33 @@ SPDX-License-Identifier: AGPL-3.0-only
238
238
<MkFolder>
239
239
<template #icon><i class="ti ti-ghost"></i></template>
240
240
<template #label>{{ i18n.ts.proxyAccount }}</template>
241
+ <template v-if="proxyAccountForm.modified.value" #footer>
242
+ <MkFormFooter :form="proxyAccountForm"/>
243
+ </template>
241
244
242
245
<div class="_gaps">
243
246
<MkInfo>{{ i18n.ts.proxyAccountDescription }}</MkInfo>
244
- <MkKeyValue>
245
- <template #key>{{ i18n.ts.proxyAccount }}</template>
246
- <template #value>{{ proxyAccount ? `@${proxyAccount.username}` : i18n.ts.none }}</template>
247
- </MkKeyValue>
248
247
249
- <MkButton primary @click="chooseProxyAccount">{{ i18n.ts.selectAccount }}</MkButton>
248
+ <div class="_panel">
249
+ <div :class="$style.banner" :style="{ backgroundImage: proxyAccount.bannerUrl ? `url(${ proxyAccount.bannerUrl })` : null }">
250
+ <MkButton primary rounded :class="$style.bannerEdit" @click="changeProxyAccountBanner">{{ i18n.ts._profile.changeBanner }}</MkButton>
251
+ </div>
252
+ <div :class="$style.avatarContainer">
253
+ <MkAvatar :class="$style.avatar" :user="proxyAccount" forceShowDecoration @click="changeProxyAccountAvatar"/>
254
+ <div class="_buttonsCenter">
255
+ <MkButton primary rounded @click="changeProxyAccountAvatar">{{ i18n.ts._profile.changeAvatar }}</MkButton>
256
+ </div>
257
+ </div>
258
+ </div>
259
+
260
+ <MkInput v-model="proxyAccountForm.state.name" :max="30" :mfmAutocomplete="['emoji']">
261
+ <template #label>{{ i18n.ts._profile.name }}</template>
262
+ </MkInput>
263
+
264
+ <MkTextarea v-model="proxyAccountForm.state.description" :max="500" tall mfmAutocomplete :mfmPreview="true">
265
+ <template #label>{{ i18n.ts._profile.description }}</template>
266
+ <template #caption>{{ i18n.ts._profile.youCanIncludeHashtags }}</template>
267
+ </MkTextarea>
250
268
</div>
251
269
</MkFolder>
252
270
</div>
@@ -256,7 +274,7 @@ SPDX-License-Identifier: AGPL-3.0-only
256
274
</template>
257
275
258
276
<script lang="ts" setup>
259
- import { ref, computed } from 'vue';
277
+ import { ref, computed, reactive } from 'vue';
260
278
import XHeader from './_header_.vue';
261
279
import MkSwitch from '@/components/MkSwitch.vue';
262
280
import MkInput from '@/components/MkInput.vue';
@@ -274,10 +292,17 @@ import MkKeyValue from '@/components/MkKeyValue.vue';
274
292
import { useForm } from '@/scripts/use-form.js';
275
293
import MkFormFooter from '@/components/MkFormFooter.vue';
276
294
import MkRadios from '@/components/MkRadios.vue';
295
+ import { selectFile } from '@/scripts/select-file.js';
296
+ import { globalEvents } from '@/events.js';
297
+ import { claimAchievement } from '@/scripts/achievements.js';
277
298
278
299
const meta = await misskeyApi('admin/meta');
279
300
280
301
const proxyAccount = ref(meta.proxyAccountId ? await misskeyApi('users/show', { userId: meta.proxyAccountId }) : null);
302
+ const proxyAccountProfile = reactive({
303
+ name: proxyAccount.value.name,
304
+ description: proxyAccount.value.description,
305
+ });
281
306
282
307
const infoForm = useForm({
283
308
name: meta.name ?? '',
@@ -378,6 +403,69 @@ const federationForm = useForm({
378
403
fetchInstance(true);
379
404
});
380
405
406
+ const proxyAccountForm = useForm({
407
+ name: proxyAccountProfile.name,
408
+ description: proxyAccountProfile.description,
409
+ }, async (state) => {
410
+ await os.apiWithDialog('admin/update-proxy-account', {
411
+ name: state.name,
412
+ description: state.description,
413
+ });
414
+ fetchInstance(true);
415
+ });
416
+
417
+ function changeProxyAccountAvatar(ev) {
418
+ selectFile(ev.currentTarget ?? ev.target, i18n.ts.avatar).then(async (file) => {
419
+ let originalOrCropped = file;
420
+
421
+ const { canceled } = await os.confirm({
422
+ type: 'question',
423
+ text: i18n.ts.cropImageAsk,
424
+ okText: i18n.ts.cropYes,
425
+ cancelText: i18n.ts.cropNo,
426
+ });
427
+
428
+ if (!canceled) {
429
+ originalOrCropped = await os.cropImage(file, {
430
+ aspectRatio: 1,
431
+ });
432
+ }
433
+
434
+ const proxy = await os.apiWithDialog('admin/update-proxy-account', {
435
+ avatarId: originalOrCropped.id,
436
+ });
437
+ proxyAccount.value.avatarId = proxy.avatarId;
438
+ proxyAccount.value.avatarUrl = proxy.avatarUrl;
439
+ globalEvents.emit('requestClearPageCache');
440
+ });
441
+ }
442
+
443
+ function changeProxyAccountBanner(ev) {
444
+ selectFile(ev.currentTarget ?? ev.target, i18n.ts.banner).then(async (file) => {
445
+ let originalOrCropped = file;
446
+
447
+ const { canceled } = await os.confirm({
448
+ type: 'question',
449
+ text: i18n.ts.cropImageAsk,
450
+ okText: i18n.ts.cropYes,
451
+ cancelText: i18n.ts.cropNo,
452
+ });
453
+
454
+ if (!canceled) {
455
+ originalOrCropped = await os.cropImage(file, {
456
+ aspectRatio: 2,
457
+ });
458
+ }
459
+
460
+ const proxy = await os.apiWithDialog('admin/update-proxy-account', {
461
+ bannerId: originalOrCropped.id,
462
+ });
463
+ proxyAccount.value.bannerId = proxy.bannerId;
464
+ proxyAccount.value.bannerUrl = proxy.bannerUrl;
465
+ globalEvents.emit('requestClearPageCache');
466
+ });
467
+ }
468
+
381
469
function chooseProxyAccount() {
382
470
os.selectUser({ localOnly: true }).then(user => {
383
471
proxyAccount.value = user;
@@ -402,4 +490,32 @@ definePageMetadata(() => ({
402
490
font-size: 0.85em;
403
491
color: var(--MI_THEME-fgTransparentWeak);
404
492
}
493
+
494
+ .banner {
495
+ position: relative;
496
+ height: 130px;
497
+ background-size: cover;
498
+ background-position: center;
499
+ border-bottom: solid 1px var(--MI_THEME-divider);
500
+ overflow: clip;
501
+ }
502
+
503
+ .avatarContainer {
504
+ margin-top: -50px;
505
+ padding-bottom: 16px;
506
+ text-align: center;
507
+ }
508
+
509
+ .avatar {
510
+ display: inline-block;
511
+ width: 72px;
512
+ height: 72px;
513
+ margin: 0 auto 16px auto;
514
+ }
515
+
516
+ .bannerEdit {
517
+ position: absolute;
518
+ top: 16px;
519
+ right: 16px;
520
+ }
405
521
</style>
0 commit comments