From 21b10603fea5a709535ec1450a05f68314701c45 Mon Sep 17 00:00:00 2001 From: Ekke <ekke@ekke.jp> Date: Mon, 20 Mar 2023 20:21:54 +0900 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E3=83=8A=E3=83=93=E3=82=B2?= =?UTF-8?q?=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=90=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E3=82=AB=E3=82=B9=E3=82=BF=E3=83=9E=E3=82=A4=E3=82=BA=E3=82=92?= =?UTF-8?q?=E3=83=89=E3=83=A9=E3=83=83=E3=82=B0&=E3=83=89=E3=83=AD?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=81=A7=E8=A1=8C=E3=81=88=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=81=99=E3=82=8B=20(#10356)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(frontend): ナビゲーションバーのカスタマイズをドラッグ&ドロップで行えるようにする * eslintのエラーを修正 * ハンドルをつかんでドラッグするように変更 * eslintのエラーを修正 * デザインの軽微な変更 * 修正 * Update CHANGELOG.md * Update CHANGELOG.md * ドラッグハンドルを3本線から2本線に --------- Co-authored-by: root <root@Adam-Windows> --- CHANGELOG.md | 3 + .../frontend/src/pages/settings/navbar.vue | 131 +++++++++++++++--- 2 files changed, 117 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e2ea1102..fc016c464 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,9 @@ - APオブジェクトを入力してフェッチする機能とユーザーやノートの検索機能を分離 - ナビゲーションバーの項目に「プロフィール」を追加できるように - AiScriptを0.13.1に更新 +- ナビゲーションバーのカスタマイズをドラッグ&ドロップで行えるように + +### Bugfixes - oEmbedをサポートしているウェブサイトのプレビューができるように - YouTubeをoEmbedでロードし、プレビューで共有ボタンを押すとOSの共有画面がでるように - ([FirefoxでSpotifyのプレビューを開けるとフルサイズじゃなくプレビューサイズだけ再生できる問題](https://bugzilla.mozilla.org/show_bug.cgi?id=1792395)があります) diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index ead551e7c..dc1347726 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -1,9 +1,34 @@ <template> <div class="_gaps_m"> - <MkTextarea v-model="items" tall manual-save> + <FormSlot> <template #label>{{ i18n.ts.navbar }}</template> - <template #caption><button class="_textButton" @click="addItem">{{ i18n.ts.addItem }}</button></template> - </MkTextarea> + <MkContainer :show-header="false"> + <Sortable + v-model="items" + :animation="150" + class="navbar_items" + handle=".item_handle" + @start="e=>e.item.classList.add('active')" + @end="e=>e.item.classList.remove('active')" + > + <template #item="{element,index}"> + <div + v-if="element === '-' || navbarItemDef[element]" + class="item" + > + <button class="item_handle _button" ><i class="ti ti-menu"></i></button> + <i class="icon ti-fw" :class="navbarItemDef[element]?.icon"></i><span class="text">{{ navbarItemDef[element]?.title ?? i18n.ts.divider }}</span> + <button class="navbar_item_remove _button" @click="removeItem(index)"><i class="ti ti-trash"></i></button> + </div> + </template> + </Sortable> + </MkContainer> + </FormSlot> + <div class="_buttons"> + <MkButton @click="addItem">{{ i18n.ts.addItem }}</MkButton> + <MkButton danger @click="reset"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton> + <MkButton primary class="save" @click="save"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> + </div> <MkRadios v-model="menuDisplay"> <template #label>{{ i18n.ts.display }}</template> @@ -12,26 +37,27 @@ <option value="top">{{ i18n.ts._menuDisplay.top }}</option> <!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ i18n.ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 --> </MkRadios> - - <MkButton danger @click="reset()"><i class="ti ti-reload"></i> {{ i18n.ts.default }}</MkButton> </div> </template> <script lang="ts" setup> -import { computed, ref, watch } from 'vue'; -import MkTextarea from '@/components/MkTextarea.vue'; +import { computed, defineAsyncComponent, ref, watch } from 'vue'; import MkRadios from '@/components/MkRadios.vue'; import MkButton from '@/components/MkButton.vue'; +import FormSlot from '@/components/form/slot.vue'; +import MkContainer from '@/components/MkContainer.vue'; import * as os from '@/os'; import { navbarItemDef } from '@/navbar'; import { defaultStore } from '@/store'; import { unisonReload } from '@/scripts/unison-reload'; import { i18n } from '@/i18n'; import { definePageMetadata } from '@/scripts/page-metadata'; +import { deepClone } from '@/scripts/clone'; -const items = ref(defaultStore.state.menu.join('\n')); +const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); + +const items = ref(deepClone(defaultStore.state.menu)); -const split = computed(() => items.value.trim().split('\n').filter(x => x.trim() !== '')); const menuDisplay = computed(defaultStore.makeGetterSetter('menuDisplay')); async function reloadAsk() { @@ -55,23 +81,22 @@ async function addItem() { }], }); if (canceled) return; - items.value = [...split.value, item].join('\n'); + items.value = [...items.value, item]; +} + +function removeItem(index: number) { + items.value.splice(index, 1); } async function save() { - defaultStore.set('menu', split.value); + defaultStore.set('menu', items.value); await reloadAsk(); } function reset() { - defaultStore.reset('menu'); - items.value = defaultStore.state.menu.join('\n'); + items.value = defaultStore.def.menu.default; } -watch(items, async () => { - await save(); -}); - watch(menuDisplay, async () => { await reloadAsk(); }); @@ -85,3 +110,75 @@ definePageMetadata({ icon: 'ti ti-list', }); </script> +<style lang="scss"> +.navbar_items { + flex: 1; + + .item { + position: relative; + display: block; + line-height: 2.85rem; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + text-align: left; + box-sizing: border-box; + color: var(--navFg); + + .icon { + position: relative; + width: 32px; + margin-right: 8px; + } + + .text { + position: relative; + font-size: 0.9em; + } + + .navbar_item_remove { + position: absolute; + z-index: 10000; + width: 32px; + height: 32px; + color: #ff2a2a; + right: 8px; + opacity: 0.8; + } + + .item_handle{ + cursor: move; + width: 32px; + height: 32px; + margin: 0 8px; + opacity: 0.5; + } + + &.active { + text-decoration: none; + color: var(--accent); + + &:before { + content: ""; + display: block; + height: 100%; + width: 100%; + aspect-ratio: 1; + margin: auto; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + border-radius: 999px; + background: var(--accentedBg); + } + + > .icon, > .text { + opacity: 1; + } + } + } +} +</style>