
* wip
* wip
* wip
* test
* wip rollup pluginでsearchIndexの情報生成
* wip
* SPDX
* wip: markerIdを自動付与
* rollupでビルド時・devモード時に毎回uuidを生成するように
* 開発サーバーでだけ必要な挙動は開発サーバーのみで
* 条件が逆
* wip: childrenの生成
* update comment
* update comment
* rename auto generated file
* hashをパスと行数から決定
* Update privacy.vue
* Update privacy.vue
* wip
* Update general.vue
* Update general.vue
* wip
* wip
* Update SearchMarker.vue
* wip
* Update profile.vue
* Update mute-block.vue
* Update mute-block.vue
* Update general.vue
* Update general.vue
* childrenがduplicate key errorを吐く問題をいったん解決
* マーカーの形を成形
* loggerを置きかえ
* とりあえず省略記法に対応
* Refactor and Format codes
* wip
* Update settings-search-index.ts
* wip
* wip
* とりあえず不確定要因の仮置きidを削除
* hashの生成を正規化(絶対パスになっていたのを緩和)
* pathの入力を省略可能に
* adminでもパス生成できるように
* Update settings-search-index.ts
* Update privacy.vue
* wip
* build searchIndex
* wip
* build
* Update general.vue
* build
* Update sounds.vue
* build
* build
* Update sounds.vue
* 🎨
* 🎨
* Update privacy.vue
* Update privacy.vue
* Update security.vue
* create-search-indexを多少改善
* build
* Update 2fa.vue
* wip
* 必ずtransformCodeCacheを利用するように, キャッシュの明確な受け渡しを定義
* キャッシュはdevServerでなくても更新
* Revert "wip"
This reverts commit 41bffd3a13
.
* inlining
* wip
* Update theme.vue
* 🎨
* wip normalize
* Update theme.vue
* キャッシュのパス変換
* build
* wip
* wip
* Update SearchMarker.vue
* i18n.ts['key'] の形式が取り出せない問題のFix
* build
* 仮でpath入れ
* 必ず絶対パスが使われるように
* wip
* 🎨
* storybookビルド時はcreateSearchIndexをしない
* inliningの構造化
* format code
* Update index.vue
* wip
* wip
* 🎨
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* wip
* clean up
* Update navbar.vue
* enhance: 検索で上下矢印を使用することで検索結果を移動できるように
* refactor
* fix(frontend): PageWindowでSearchMarkerが動作するように
* enhance(frontend): SearchMarkerの点滅を一定時間で止める
* lint fix
* fix: 子要素監視が抜けていたのを修正
* アニメーションの回数はCSSで制御するように
* refactor
* enhance(frontend): 検索インデックス作成時のログを削減
* revert
* fix
* fix
---------
Co-authored-by: tai-cha <dev@taichan.site>
Co-authored-by: taichan <40626578+tai-cha@users.noreply.github.com>
Co-authored-by: kakkokari-gtyih <67428053+kakkokari-gtyih@users.noreply.github.com>
159 lines
4.3 KiB
Vue
159 lines
4.3 KiB
Vue
<!--
|
|
SPDX-FileCopyrightText: syuilo and misskey-project
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
-->
|
|
|
|
<template>
|
|
<SearchMarker path="/settings/avatar-decoration" :label="i18n.ts.avatarDecorations" :keywords="['avatar', 'icon', 'decoration']" icon="ti ti-sparkles">
|
|
<div>
|
|
<div v-if="!loading" class="_gaps">
|
|
<MkInfo>{{ i18n.tsx._profile.avatarDecorationMax({ max: $i.policies.avatarDecorationLimit }) }} ({{ i18n.tsx.remainingN({ n: $i.policies.avatarDecorationLimit - $i.avatarDecorations.length }) }})</MkInfo>
|
|
|
|
<MkAvatar :class="$style.avatar" :user="$i" forceShowDecoration/>
|
|
|
|
<div v-if="$i.avatarDecorations.length > 0" v-panel :class="$style.current" class="_gaps_s">
|
|
<div>{{ i18n.ts.inUse }}</div>
|
|
|
|
<div :class="$style.decorations">
|
|
<XDecoration
|
|
v-for="(avatarDecoration, i) in $i.avatarDecorations"
|
|
:decoration="avatarDecorations.find(d => d.id === avatarDecoration.id)"
|
|
:angle="avatarDecoration.angle"
|
|
:flipH="avatarDecoration.flipH"
|
|
:offsetX="avatarDecoration.offsetX"
|
|
:offsetY="avatarDecoration.offsetY"
|
|
:active="true"
|
|
@click="openDecoration(avatarDecoration, i)"
|
|
/>
|
|
</div>
|
|
|
|
<MkButton danger @click="detachAllDecorations">{{ i18n.ts.detachAll }}</MkButton>
|
|
</div>
|
|
|
|
<div :class="$style.decorations">
|
|
<XDecoration
|
|
v-for="avatarDecoration in avatarDecorations"
|
|
:key="avatarDecoration.id"
|
|
:decoration="avatarDecoration"
|
|
@click="openDecoration(avatarDecoration)"
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div v-else>
|
|
<MkLoading/>
|
|
</div>
|
|
</div>
|
|
</SearchMarker>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { ref, defineAsyncComponent, computed } from 'vue';
|
|
import * as Misskey from 'misskey-js';
|
|
import XDecoration from './avatar-decoration.decoration.vue';
|
|
import MkButton from '@/components/MkButton.vue';
|
|
import * as os from '@/os.js';
|
|
import { misskeyApi } from '@/scripts/misskey-api.js';
|
|
import { i18n } from '@/i18n.js';
|
|
import { signinRequired } from '@/account.js';
|
|
import MkInfo from '@/components/MkInfo.vue';
|
|
import { definePageMetadata } from '@/scripts/page-metadata.js';
|
|
|
|
const $i = signinRequired();
|
|
|
|
const loading = ref(true);
|
|
const avatarDecorations = ref<Misskey.entities.GetAvatarDecorationsResponse>([]);
|
|
|
|
misskeyApi('get-avatar-decorations').then(_avatarDecorations => {
|
|
avatarDecorations.value = _avatarDecorations;
|
|
loading.value = false;
|
|
});
|
|
|
|
function openDecoration(avatarDecoration, index?: number) {
|
|
const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration.dialog.vue')), {
|
|
decoration: avatarDecoration,
|
|
usingIndex: index,
|
|
}, {
|
|
'attach': async (payload) => {
|
|
const decoration = {
|
|
id: avatarDecoration.id,
|
|
angle: payload.angle,
|
|
flipH: payload.flipH,
|
|
offsetX: payload.offsetX,
|
|
offsetY: payload.offsetY,
|
|
};
|
|
const update = [...$i.avatarDecorations, decoration];
|
|
await os.apiWithDialog('i/update', {
|
|
avatarDecorations: update,
|
|
});
|
|
$i.avatarDecorations = update;
|
|
},
|
|
'update': async (payload) => {
|
|
const decoration = {
|
|
id: avatarDecoration.id,
|
|
angle: payload.angle,
|
|
flipH: payload.flipH,
|
|
offsetX: payload.offsetX,
|
|
offsetY: payload.offsetY,
|
|
};
|
|
const update = [...$i.avatarDecorations];
|
|
update[index] = decoration;
|
|
await os.apiWithDialog('i/update', {
|
|
avatarDecorations: update,
|
|
});
|
|
$i.avatarDecorations = update;
|
|
},
|
|
'detach': async () => {
|
|
const update = [...$i.avatarDecorations];
|
|
update.splice(index, 1);
|
|
await os.apiWithDialog('i/update', {
|
|
avatarDecorations: update,
|
|
});
|
|
$i.avatarDecorations = update;
|
|
},
|
|
closed: () => dispose(),
|
|
});
|
|
}
|
|
|
|
function detachAllDecorations() {
|
|
os.confirm({
|
|
type: 'warning',
|
|
text: i18n.ts.areYouSure,
|
|
}).then(async ({ canceled }) => {
|
|
if (canceled) return;
|
|
await os.apiWithDialog('i/update', {
|
|
avatarDecorations: [],
|
|
});
|
|
$i.avatarDecorations = [];
|
|
});
|
|
}
|
|
|
|
const headerActions = computed(() => []);
|
|
|
|
const headerTabs = computed(() => []);
|
|
|
|
definePageMetadata(() => ({
|
|
title: i18n.ts.avatarDecorations,
|
|
icon: 'ti ti-sparkles',
|
|
}));
|
|
</script>
|
|
|
|
<style lang="scss" module>
|
|
.avatar {
|
|
display: inline-block;
|
|
width: 72px;
|
|
height: 72px;
|
|
margin: 16px auto;
|
|
}
|
|
|
|
.current {
|
|
padding: 16px;
|
|
border-radius: var(--MI-radius);
|
|
}
|
|
|
|
.decorations {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
|
grid-gap: 12px;
|
|
}
|
|
</style>
|