feat(frontend): タイトルバーを表示できるように

This commit is contained in:
syuilo 2025-04-29 09:43:15 +09:00
parent 2e91cd6d45
commit d6ae4c980b
11 changed files with 213 additions and 100 deletions

View File

@ -13,6 +13,7 @@
### Client ### Client
- Feat: チャットウィジェットを追加 - Feat: チャットウィジェットを追加
- Feat: デッキにチャットカラムを追加 - Feat: デッキにチャットカラムを追加
- Feat: タイトルバーを表示できるように
- Enhance: Unicode絵文字をslugから入力する際に`:ok:`のように最後の`:`を入力したあとにUnicode絵文字に変換できるように - Enhance: Unicode絵文字をslugから入力する際に`:ok:`のように最後の`:`を入力したあとにUnicode絵文字に変換できるように
- Enhance: コントロールパネルでジョブキューをクリアできるように - Enhance: コントロールパネルでジョブキューをクリアできるように
- Enhance: テーマでページヘッダーの色を変更できるように - Enhance: テーマでページヘッダーの色を変更できるように

View File

@ -31,6 +31,7 @@ html {
margin: auto; margin: auto;
width: 64px; width: 64px;
height: 64px; height: 64px;
border-radius: 10px;
pointer-events: none; pointer-events: none;
} }

View File

@ -53,6 +53,7 @@ html.embed.noborder #splash {
margin: auto; margin: auto;
width: 64px; width: 64px;
height: 64px; height: 64px;
border-radius: 10px;
pointer-events: none; pointer-events: none;
} }

View File

@ -42,6 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only
</SearchMarker> </SearchMarker>
<div class="_gaps_s"> <div class="_gaps_s">
<SearchMarker :keywords="['titlebar', 'show']">
<MkPreferenceContainer k="showTitlebar">
<MkSwitch v-model="showTitlebar">
<template #label><SearchLabel>{{ i18n.ts.showTitlebar }}</SearchLabel></template>
</MkSwitch>
</MkPreferenceContainer>
</SearchMarker>
<SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']"> <SearchMarker :keywords="['avatar', 'icon', 'decoration', 'show']">
<MkPreferenceContainer k="showAvatarDecorations"> <MkPreferenceContainer k="showAvatarDecorations">
<MkSwitch v-model="showAvatarDecorations"> <MkSwitch v-model="showAvatarDecorations">
@ -742,6 +750,7 @@ const lang = ref(miLocalStorage.getItem('lang'));
const dataSaver = ref(prefer.s.dataSaver); const dataSaver = ref(prefer.s.dataSaver);
const overridedDeviceKind = prefer.model('overridedDeviceKind'); const overridedDeviceKind = prefer.model('overridedDeviceKind');
const showTitlebar = prefer.model('showTitlebar');
const keepCw = prefer.model('keepCw'); const keepCw = prefer.model('keepCw');
const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior'); const serverDisconnectedBehavior = prefer.model('serverDisconnectedBehavior');
const hemisphere = prefer.model('hemisphere'); const hemisphere = prefer.model('hemisphere');

View File

@ -333,6 +333,9 @@ export const PREF_DEF = {
showNavbarSubButtons: { showNavbarSubButtons: {
default: true, default: true,
}, },
showTitlebar: {
default: false,
},
plugins: { plugins: {
default: [] as Plugin[], default: [] as Plugin[],
}, },

View File

@ -413,7 +413,7 @@ if ($i) {
#devTicker { #devTicker {
position: fixed; position: fixed;
top: 0; bottom: 0;
left: 0; left: 0;
z-index: 2147483647; z-index: 2147483647;
color: #ff0; color: #ff0;

View File

@ -121,6 +121,7 @@ function more() {
display: inline-block; display: inline-block;
width: 38px; width: 38px;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 8px;
} }
.bottom { .bottom {

View File

@ -7,7 +7,6 @@ SPDX-License-Identifier: AGPL-3.0-only
<div :class="[$style.root, { [$style.iconOnly]: iconOnly }]"> <div :class="[$style.root, { [$style.iconOnly]: iconOnly }]">
<div :class="$style.body"> <div :class="$style.body">
<div :class="$style.top"> <div :class="$style.top">
<div :class="$style.banner" :style="{ backgroundImage: `url(${ instance.bannerUrl })` }"></div>
<button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu"> <button v-tooltip.noDelay.right="instance.name ?? i18n.ts.instance" class="_button" :class="$style.instance" @click="openInstanceMenu">
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/> <img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon" style="viewTransitionName: navbar-serverIcon;"/>
</button> </button>
@ -183,12 +182,9 @@ function menuEdit() {
} }
.body { .body {
position: fixed; position: relative;
top: 0;
left: 0;
z-index: 1001;
width: var(--nav-icon-only-width); width: var(--nav-icon-only-width);
height: 100dvh; height: 100%;
box-sizing: border-box; box-sizing: border-box;
overflow: auto; overflow: auto;
overflow-x: clip; overflow-x: clip;
@ -303,18 +299,6 @@ function menuEdit() {
backdrop-filter: var(--MI-blur, blur(8px)); backdrop-filter: var(--MI-blur, blur(8px));
} }
.banner {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-size: cover;
background-position: center center;
-webkit-mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
mask-image: linear-gradient(0deg, rgba(0,0,0,0) 15%, rgba(0,0,0,0.75) 100%);
}
.instance { .instance {
position: relative; position: relative;
display: block; display: block;
@ -335,6 +319,7 @@ function menuEdit() {
display: inline-block; display: inline-block;
width: 38px; width: 38px;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 8px;
} }
.bottom { .bottom {
@ -559,6 +544,7 @@ function menuEdit() {
display: inline-block; display: inline-block;
width: 30px; width: 30px;
aspect-ratio: 1; aspect-ratio: 1;
border-radius: 8px;
} }
.bottom { .bottom {

View File

@ -0,0 +1,87 @@
<!--
SPDX-FileCopyrightText: syuilo and misskey-project
SPDX-License-Identifier: AGPL-3.0-only
-->
<template>
<div :class="$style.root">
<div :class="$style.title">
<img :src="instance.iconUrl || instance.faviconUrl || '/favicon.ico'" alt="" :class="$style.instanceIcon"/>
<span :class="$style.instanceTitle">{{ instance.name ?? host }}</span>
</div>
<div :class="$style.controls">
<span :class="$style.left">
<button v-if="canBack" class="_button" :class="$style.button" @click="goBack"><i class="ti ti-arrow-left"></i></button>
</span>
<span :class="$style.right">
</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { host } from '@@/js/config.js';
import { ref } from 'vue';
import { instance } from '@/instance.js';
import { prefer } from '@/preferences.js';
const canBack = ref(true);
function goBack() {
window.history.back();
}
</script>
<style lang="scss" module>
.root {
--height: 36px;
background: var(--MI_THEME-navBg);
height: var(--height);
font-size: 90%;
}
.title {
display: flex;
justify-content: center;
align-items: center;
text-align: center;
height: var(--height);
}
.controls {
position: absolute;
top: 0;
left: 0;
display: flex;
justify-content: center;
align-items: center;
height: var(--height);
}
.instanceIcon {
display: inline-block;
width: 20px;
aspect-ratio: 1;
border-radius: 5px;
margin-right: 8px;
}
.instanceTitle {
display: inline-block;
}
.left {
margin-right: auto;
}
.right {
margin-left: auto;
}
.button {
display: inline-block;
height: var(--height);
aspect-ratio: 1;
}
</style>

View File

@ -4,10 +4,13 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<div :class="[$style.root, { [$style.withWallpaper]: withWallpaper }]"> <div :class="[$style.root]">
<XTitlebar v-if="prefer.r.showTitlebar.value" style="flex-shrink: 0;"/>
<div :class="$style.nonTitlebarArea">
<XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/> <XSidebar v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'left'"/>
<div :class="$style.main"> <div :class="[$style.main, { [$style.withWallpaper]: withWallpaper, [$style.withSidebarAndTitlebar]: !isMobile && prefer.r['deck.navbarPosition'].value === 'left' && prefer.r.showTitlebar.value }]" :style="{ backgroundImage: prefer.s['deck.wallpaper'] != null ? `url(${ prefer.s['deck.wallpaper'] })` : null }">
<XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/> <XNavbarH v-if="!isMobile && prefer.r['deck.navbarPosition'].value === 'top'"/>
<XAnnouncements v-if="$i"/> <XAnnouncements v-if="$i"/>
@ -70,6 +73,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/> <XMobileFooterMenu v-if="isMobile" v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div> </div>
</div>
<XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/> <XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div> </div>
@ -82,6 +86,7 @@ import XCommon from './_common_/common.vue';
import XSidebar from '@/ui/_common_/navbar.vue'; import XSidebar from '@/ui/_common_/navbar.vue';
import XNavbarH from '@/ui/_common_/navbar-h.vue'; import XNavbarH from '@/ui/_common_/navbar-h.vue';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue'; import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
import XTitlebar from '@/ui/_common_/titlebar.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
@ -209,30 +214,25 @@ async function deleteProfile() {
window.document.documentElement.style.overflowY = 'hidden'; window.document.documentElement.style.overflowY = 'hidden';
window.document.documentElement.style.scrollBehavior = 'auto'; window.document.documentElement.style.scrollBehavior = 'auto';
if (prefer.s['deck.wallpaper'] != null) {
window.document.documentElement.style.backgroundImage = `url(${prefer.s['deck.wallpaper']})`;
}
</script> </script>
<style lang="scss" module> <style lang="scss" module>
.root { .root {
$nav-hide-threshold: 650px; // TODO:
--MI-margin: var(--MI-marginHalf); --MI-margin: var(--MI-marginHalf);
--columnGap: v-bind("gap + 'px'"); --columnGap: v-bind("gap + 'px'");
display: flex; display: flex;
flex-direction: column;
height: 100dvh; height: 100dvh;
box-sizing: border-box; box-sizing: border-box;
flex: 1; flex: 1;
background: var(--MI_THEME-navBg);
}
&.withWallpaper { .nonTitlebarArea {
.main { display: flex;
background: transparent; flex: 1;
}
}
} }
.main { .main {
@ -240,7 +240,15 @@ if (prefer.s['deck.wallpaper'] != null) {
min-width: 0; min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
&:not(.withWallpaper) {
background: var(--MI_THEME-deckBg); background: var(--MI_THEME-deckBg);
}
&.withSidebarAndTitlebar {
border-radius: 12px 0 0 0;
overflow: clip;
}
} }
.columnsWrapper { .columnsWrapper {

View File

@ -5,9 +5,12 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div :class="[$style.root, { '_forceShrinkSpacer': deviceKind === 'smartphone' }]"> <div :class="[$style.root, { '_forceShrinkSpacer': deviceKind === 'smartphone' }]">
<XTitlebar v-if="prefer.r.showTitlebar.value" style="flex-shrink: 0;"/>
<div :class="$style.nonTitlebarArea">
<XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!isDesktop" @widgetButtonClick="widgetsShowing = true"/> <XSidebar v-if="!isMobile" :class="$style.sidebar" :showWidgetButton="!isDesktop" @widgetButtonClick="widgetsShowing = true"/>
<div :class="$style.contents" @contextmenu.stop="onContextmenu"> <div :class="[$style.contents, !isMobile && prefer.r.showTitlebar.value ? $style.withSidebarAndTitlebar : null]" @contextmenu.stop="onContextmenu">
<div> <div>
<XPreferenceRestore v-if="shouldSuggestRestoreBackup"/> <XPreferenceRestore v-if="shouldSuggestRestoreBackup"/>
<XAnnouncements v-if="$i"/> <XAnnouncements v-if="$i"/>
@ -21,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets"> <div v-if="isDesktop && !pageMetadata?.needWideArea" :class="$style.widgets">
<XWidgets/> <XWidgets/>
</div> </div>
</div>
<XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/> <XCommon v-model:drawerMenuShowing="drawerMenuShowing" v-model:widgetsShowing="widgetsShowing"/>
</div> </div>
@ -34,6 +38,7 @@ import XCommon from './_common_/common.vue';
import type { PageMetadata } from '@/page.js'; import type { PageMetadata } from '@/page.js';
import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue'; import XMobileFooterMenu from '@/ui/_common_/mobile-footer-menu.vue';
import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue'; import XPreferenceRestore from '@/ui/_common_/PreferenceRestore.vue';
import XTitlebar from '@/ui/_common_/titlebar.vue';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { i18n } from '@/i18n.js'; import { i18n } from '@/i18n.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
@ -128,8 +133,14 @@ $widgets-hide-threshold: 1090px;
height: 100dvh; height: 100dvh;
overflow: clip; overflow: clip;
contain: strict; contain: strict;
box-sizing: border-box;
display: flex; display: flex;
flex-direction: column;
background: var(--MI_THEME-navBg);
}
.nonTitlebarArea {
display: flex;
flex: 1;
} }
.sidebar { .sidebar {
@ -142,7 +153,12 @@ $widgets-hide-threshold: 1090px;
flex: 1; flex: 1;
height: 100%; height: 100%;
min-width: 0; min-width: 0;
background: var(--MI_THEME-bg);
&.withSidebarAndTitlebar {
background: var(--MI_THEME-navBg);
border-radius: 12px 0 0 0;
overflow: clip;
}
} }
.content { .content {