diff options
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkFolder.vue | 60 | ||||
| -rw-r--r-- | packages/frontend/src/components/MkFolderPage.vue | 157 | ||||
| -rw-r--r-- | packages/frontend/src/os.ts | 2 | ||||
| -rw-r--r-- | packages/frontend/src/pages/settings/other.vue | 4 | ||||
| -rw-r--r-- | packages/frontend/src/preferences/def.ts | 3 |
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; |