summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsyuilo <4439005+syuilo@users.noreply.github.com>2025-05-22 14:57:35 +0900
committersyuilo <4439005+syuilo@users.noreply.github.com>2025-05-22 14:57:35 +0900
commit23542530e1d196a7d7d9e655a0082c35b1b0cbc7 (patch)
tree2aa96d80216430b2db83e4ef41ceac29d054ee94
parentclean up (diff)
downloadmisskey-23542530e1d196a7d7d9e655a0082c35b1b0cbc7.tar.gz
misskey-23542530e1d196a7d7d9e655a0082c35b1b0cbc7.tar.bz2
misskey-23542530e1d196a7d7d9e655a0082c35b1b0cbc7.zip
feat(frontend): モバイルデバイスで折りたたまれたUIの展開表示に全画面ページを使用できるように
-rw-r--r--CHANGELOG.md1
-rw-r--r--packages/frontend/src/components/MkFolder.vue60
-rw-r--r--packages/frontend/src/components/MkFolderPage.vue157
-rw-r--r--packages/frontend/src/os.ts2
-rw-r--r--packages/frontend/src/pages/settings/other.vue4
-rw-r--r--packages/frontend/src/preferences/def.ts3
6 files changed, 222 insertions, 5 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f49a4335b1..21ce5932b1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,7 @@
- チャットなど、一部の機能は引き続き設定に関わらずWebsocket接続が行われます
- Feat: 絵文字をミュート可能にする機能
- 絵文字(ユニコードの絵文字・カスタム絵文字)毎にミュートし、不可視化することができるようになりました
+- Feat: モバイルデバイスで折りたたまれたUIの展開表示に全画面ページを使用できるように(実験的)
- Enhance: メモリ使用量を軽減しました
- Enhance: 画像の高品質なプレースホルダを無効化してパフォーマンスを向上させるオプションを追加
- Enhance: 招待されているが参加していないルームを開いたときに、招待を承認するかどうか尋ねるように
diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue
index e86861c874..9f5bc8da6c 100644
--- a/packages/frontend/src/components/MkFolder.vue
+++ b/packages/frontend/src/components/MkFolder.vue
@@ -19,13 +19,42 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div :class="$style.headerRight">
<span :class="$style.headerRightText"><slot name="suffix"></slot></span>
- <i v-if="opened" class="ti ti-chevron-up icon"></i>
+ <i v-if="asPage" class="ti ti-chevron-right icon"></i>
+ <i v-else-if="opened" class="ti ti-chevron-up icon"></i>
<i v-else class="ti ti-chevron-down icon"></i>
</div>
</button>
</template>
- <div v-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : undefined, overflow: maxHeight ? `auto` : undefined }" :aria-hidden="!opened">
+ <div v-if="asPage">
+ <Teleport v-if="opened" defer :to="`#v-${pageId}-header`">
+ <slot name="label"></slot>
+ </Teleport>
+ <Teleport v-if="opened" defer :to="`#v-${pageId}-body`">
+ <MkStickyContainer>
+ <template #header>
+ <div v-if="$slots.header" :class="$style.inBodyHeader">
+ <slot name="header"></slot>
+ </div>
+ </template>
+
+ <div v-if="withSpacer" class="_spacer" :style="{ '--MI_SPACER-min': props.spacerMin + 'px', '--MI_SPACER-max': props.spacerMax + 'px' }">
+ <slot></slot>
+ </div>
+ <div v-else>
+ <slot></slot>
+ </div>
+
+ <template #footer>
+ <div v-if="$slots.footer" :class="$style.inBodyFooter">
+ <slot name="footer"></slot>
+ </div>
+ </template>
+ </MkStickyContainer>
+ </Teleport>
+ </div>
+
+ <div v-else-if="openedAtLeastOnce" :class="[$style.body, { [$style.bgSame]: bgSame }]" :style="{ maxHeight: maxHeight ? `${maxHeight}px` : undefined, overflow: maxHeight ? `auto` : undefined }" :aria-hidden="!opened">
<Transition
:enterActiveClass="prefer.s.animation ? $style.transition_toggle_enterActive : ''"
:leaveActiveClass="prefer.s.animation ? $style.transition_toggle_leaveActive : ''"
@@ -70,6 +99,9 @@ SPDX-License-Identifier: AGPL-3.0-only
import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
import { prefer } from '@/preferences.js';
import { getBgColor } from '@/utility/get-bg-color.js';
+import { pageFolderTeleportCount, popup } from '@/os.js';
+import MkFolderPage from '@/components/MkFolderPage.vue';
+import { deviceKind } from '@/utility/device-kind.js';
const props = withDefaults(defineProps<{
defaultOpen?: boolean;
@@ -77,18 +109,21 @@ const props = withDefaults(defineProps<{
withSpacer?: boolean;
spacerMin?: number;
spacerMax?: number;
+ canPage?: boolean;
}>(), {
defaultOpen: false,
maxHeight: null,
withSpacer: true,
spacerMin: 14,
spacerMax: 22,
+ canPage: true,
});
const rootEl = useTemplateRef('rootEl');
+const asPage = props.canPage && deviceKind === 'smartphone' && prefer.s['experimental.enableFolderPageView'];
const bgSame = ref(false);
-const opened = ref(props.defaultOpen);
-const openedAtLeastOnce = ref(props.defaultOpen);
+const opened = ref(asPage ? false : props.defaultOpen);
+const openedAtLeastOnce = ref(opened.value);
//#region interpolate-sizeに対応していないブラウザ向け(TODO: 主要ブラウザが対応したら消す)
function enter(el: Element) {
@@ -126,7 +161,22 @@ function afterLeave(el: Element) {
}
//#endregion
-function toggle() {
+let pageId = pageFolderTeleportCount.value;
+pageFolderTeleportCount.value += 1000;
+
+async function toggle() {
+ if (asPage && !opened.value) {
+ pageId++;
+ const { dispose } = await popup(MkFolderPage, {
+ pageId,
+ }, {
+ closed: () => {
+ opened.value = false;
+ dispose();
+ },
+ });
+ }
+
if (!opened.value) {
openedAtLeastOnce.value = true;
}
diff --git a/packages/frontend/src/components/MkFolderPage.vue b/packages/frontend/src/components/MkFolderPage.vue
new file mode 100644
index 0000000000..6d9ee1af1d
--- /dev/null
+++ b/packages/frontend/src/components/MkFolderPage.vue
@@ -0,0 +1,157 @@
+<!--
+SPDX-FileCopyrightText: syuilo and misskey-project
+SPDX-License-Identifier: AGPL-3.0-only
+-->
+
+<template>
+<Transition
+ name="x"
+ :enterActiveClass="prefer.s.animation ? $style.transition_x_enterActive : ''"
+ :leaveActiveClass="prefer.s.animation ? $style.transition_x_leaveActive : ''"
+ :enterFromClass="prefer.s.animation ? $style.transition_x_enterFrom : ''"
+ :leaveToClass="prefer.s.animation ? $style.transition_x_leaveTo : ''"
+ :duration="300" appear @afterLeave="onClosed"
+>
+ <div v-show="showing" :class="[$style.root]" :style="{ zIndex }">
+ <div :class="[$style.bg]" :style="{ zIndex }"></div>
+ <div :class="[$style.content]" :style="{ zIndex }">
+ <div :class="$style.header">
+ <button :class="$style.back" class="_button" @click="closePage"><i class="ti ti-chevron-left"></i></button>
+ <div :id="`v-${pageId}-header`" :class="$style.title"></div>
+ <div :class="$style.spacer"></div>
+ </div>
+ <div :id="`v-${pageId}-body`"></div>
+ </div>
+ </div>
+</Transition>
+</template>
+
+<script lang="ts" setup>
+import { onMounted, ref } from 'vue';
+import { claimZIndex } from '@/os.js';
+import { prefer } from '@/preferences.js';
+
+const props = withDefaults(defineProps<{
+ pageId: number,
+}>(), {
+ pageId: 0,
+});
+
+const emit = defineEmits<{
+ (_: 'closed'): void
+}>();
+
+const zIndex = claimZIndex('middle');
+const showing = ref(true);
+
+function closePage() {
+ showing.value = false;
+}
+
+function onClosed() {
+ emit('closed');
+}
+
+</script>
+
+<style lang="scss" module>
+.transition_x_enterActive {
+ > .bg {
+ transition: opacity 0.3s !important;
+ }
+
+ > .content {
+ transition: transform 0.3s cubic-bezier(0,0,.25,1) !important;
+ }
+}
+.transition_x_leaveActive {
+ > .bg {
+ transition: opacity 0.3s !important;
+ }
+
+ > .content {
+ transition: transform 0.3s cubic-bezier(0,0,.25,1) !important;
+ }
+}
+.transition_x_enterFrom,
+.transition_x_leaveTo {
+ > .bg {
+ opacity: 0;
+ }
+
+ > .content {
+ pointer-events: none;
+ transform: translateX(100%);
+ }
+}
+
+.root {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: clip;
+}
+
+.bg {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: var(--MI_THEME-modalBg);
+}
+
+.content {
+ position: fixed;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ margin: auto;
+ background: var(--MI_THEME-bg);
+ container-type: size;
+ overflow: auto;
+ overscroll-behavior: contain;
+}
+
+.header {
+ --height: 48px;
+
+ position: sticky;
+ top: 0;
+ left: 0;
+ height: var(--height);
+ z-index: 1;
+ display: flex;
+ align-items: center;
+ background: color(from var(--MI_THEME-panel) srgb r g b / 0.75);
+ -webkit-backdrop-filter: var(--MI-blur, blur(15px));
+ backdrop-filter: var(--MI-blur, blur(15px));
+ border-bottom: solid 0.5px var(--MI_THEME-divider);
+}
+
+.back {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: var(--height);
+ height: var(--height);
+ font-size: 16px;
+ color: var(--MI_THEME-accent);
+}
+
+.title {
+ margin: 0 auto;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.spacer {
+ width: var(--height);
+ height: var(--height);
+}
+</style>
diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts
index fea050e787..a513ae4902 100644
--- a/packages/frontend/src/os.ts
+++ b/packages/frontend/src/os.ts
@@ -790,3 +790,5 @@ export function launchUploader(
});
});
}
+
+export const pageFolderTeleportCount = ref(0);
diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue
index 83a6aa167c..7b6ad5e56e 100644
--- a/packages/frontend/src/pages/settings/other.vue
+++ b/packages/frontend/src/pages/settings/other.vue
@@ -96,6 +96,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="stackingRouterView">
<template #label>Enable stacking router view</template>
</MkSwitch>
+ <MkSwitch v-model="enableFolderPageView">
+ <template #label>Enable folder page view</template>
+ </MkSwitch>
</div>
</MkFolder>
</SearchMarker>
@@ -157,6 +160,7 @@ const enableCondensedLine = prefer.model('enableCondensedLine');
const skipNoteRender = prefer.model('skipNoteRender');
const devMode = prefer.model('devMode');
const stackingRouterView = prefer.model('experimental.stackingRouterView');
+const enableFolderPageView = prefer.model('experimental.enableFolderPageView');
watch(skipNoteRender, async () => {
await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true });
diff --git a/packages/frontend/src/preferences/def.ts b/packages/frontend/src/preferences/def.ts
index 7b0628e937..27402a8e6a 100644
--- a/packages/frontend/src/preferences/def.ts
+++ b/packages/frontend/src/preferences/def.ts
@@ -417,4 +417,7 @@ export const PREF_DEF = {
'experimental.stackingRouterView': {
default: false,
},
+ 'experimental.enableFolderPageView': {
+ default: false,
+ },
} satisfies PreferencesDefinition;