enhance: プロフィール設定「追加情報」の並び替え・削除に対応 (#10766)

* (enhance) profile fields dnd

* Update CHANGELOG.md

* Fix typo

* fix lint

* fix styles

* fix lint

* (change) style

* (fix) label

* (fix) typo

* (fix) LINT ISSUES

* (change) style

* remove unnecessary style declaration

* (fix) breakpoint
This commit is contained in:
かっこかり 2023-05-06 07:06:12 +09:00 committed by GitHub
parent ae21b75687
commit 3a105024c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 103 additions and 15 deletions

View File

@ -49,6 +49,7 @@
- 新しい実績を追加 - 新しい実績を追加
- Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正 - Fix: AiScript APIのMk:dialogで何も返していなかったのをNULLを返すように修正
- Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正 - Fix: リアクションをホバーした時のユーザーリストで猫耳が切れてしまっていた問題を修正
- プロフィール設定「追加情報」の項目の削除と並び替えができるように
### Server ### Server
- channel/searchのqueryが空の場合に全てのチャンネルを返すように変更 - channel/searchのqueryが空の場合に全てのチャンネルを返すように変更

View File

@ -560,6 +560,7 @@ accountDeletedDescription: "このアカウントは削除されています。"
menu: "メニュー" menu: "メニュー"
divider: "分割線" divider: "分割線"
addItem: "項目を追加" addItem: "項目を追加"
rearrange: "並び替え"
relays: "リレー" relays: "リレー"
addRelay: "リレーの追加" addRelay: "リレーの追加"
inboxUrl: "inboxのURL" inboxUrl: "inboxのURL"

View File

@ -37,20 +37,41 @@
<template #icon><i class="ti ti-list"></i></template> <template #icon><i class="ti ti-list"></i></template>
<template #label>{{ i18n.ts._profile.metadataEdit }}</template> <template #label>{{ i18n.ts._profile.metadataEdit }}</template>
<div class="_gaps_m"> <div :class="$style.metadataRoot">
<div> <div :class="$style.metadataMargin">
<MkButton :disabled="fields.length >= 16" inline style="margin-right: 8px;" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> <MkButton :disabled="fields.length >= 16" inline style="margin-right: 8px;" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" inline danger style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
<MkButton v-else inline style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton>
<MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> <MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton>
</div> </div>
<FormSplit v-for="(record, i) in fields" :min-width="250">
<MkInput v-model="record.name" small> <Sortable
<template #label>{{ i18n.ts._profile.metadataLabel }} #{{ i + 1 }}</template> v-model="fields"
class="_gaps_s"
item-key="id"
:animation="150"
:handle="'.' + $style.dragItemHandle"
@start="e => e.item.classList.add('active')"
@end="e => e.item.classList.remove('active')"
>
<template #item="{element, index}">
<div :class="$style.fieldDragItem">
<button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button>
<button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button>
<div :class="$style.dragItemForm">
<FormSplit :min-width="200">
<MkInput v-model="element.name" small>
<template #label>{{ i18n.ts._profile.metadataLabel }}</template>
</MkInput> </MkInput>
<MkInput v-model="record.value" small> <MkInput v-model="element.value" small>
<template #label>{{ i18n.ts._profile.metadataContent }} #{{ i + 1 }}</template> <template #label>{{ i18n.ts._profile.metadataContent }}</template>
</MkInput> </MkInput>
</FormSplit> </FormSplit>
</div> </div>
</div>
</template>
</Sortable>
</div>
</MkFolder> </MkFolder>
<template #caption>{{ i18n.ts._profile.metadataDescription }}</template> <template #caption>{{ i18n.ts._profile.metadataDescription }}</template>
</FormSlot> </FormSlot>
@ -76,7 +97,7 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { computed, reactive, watch } from 'vue'; import { computed, reactive, ref, watch, defineAsyncComponent, onMounted, onUnmounted } from 'vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import MkInput from '@/components/MkInput.vue'; import MkInput from '@/components/MkInput.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@ -94,6 +115,8 @@ import { definePageMetadata } from '@/scripts/page-metadata';
import { claimAchievement } from '@/scripts/achievements'; import { claimAchievement } from '@/scripts/achievements';
import { defaultStore } from '@/store'; import { defaultStore } from '@/store';
const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default));
const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance')); const reactionAcceptance = computed(defaultStore.makeGetterSetter('reactionAcceptance'));
const profile = reactive({ const profile = reactive({
@ -113,22 +136,28 @@ watch(() => profile, () => {
deep: true, deep: true,
}); });
const fields = reactive($i.fields.map(field => ({ name: field.name, value: field.value }))); const fields = ref($i?.fields.map(field => ({ id: Math.random().toString(), name: field.name, value: field.value })) ?? []);
const fieldEditMode = ref(false);
function addField() { function addField() {
fields.push({ fields.value.push({
id: Math.random().toString(),
name: '', name: '',
value: '', value: '',
}); });
} }
while (fields.length < 4) { while (fields.value.length < 4) {
addField(); addField();
} }
function deleteField(index: number) {
fields.value.splice(index, 1);
}
function saveFields() { function saveFields() {
os.apiWithDialog('i/update', { os.apiWithDialog('i/update', {
fields: fields.filter(field => field.name !== '' && field.value !== ''), fields: fields.value.filter(field => field.name !== '' && field.value !== '').map(field => ({ name: field.name, value: field.value })),
}); });
} }
@ -248,3 +277,60 @@ definePageMetadata({
} }
} }
</style> </style>
<style lang="scss" module>
.metadataRoot {
container-type: inline-size;
}
.metadataMargin {
margin-bottom: 1.5em;
}
.fieldDragItem {
display: flex;
padding-bottom: .75em;
align-items: flex-end;
border-bottom: solid 0.5px var(--divider);
&:last-child {
border-bottom: 0;
}
/* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */
@container (max-width: 452px) {
align-items: center;
}
}
.dragItemHandle {
cursor: grab;
width: 32px;
height: 32px;
margin: 0 8px 0 0;
opacity: 0.5;
flex-shrink: 0;
&:active {
cursor: grabbing;
}
}
.dragItemRemove {
@extend .dragItemHandle;
color: #ff2a2a;
opacity: 1;
cursor: pointer;
&:hover, &:focus {
opacity: .7;
}
&:active {
cursor: pointer;
}
}
.dragItemForm {
flex-grow: 1;
}
</style>