mirror of
https://github.com/kokonect-link/cherrypick
synced 2024-12-03 09:18:58 +09:00
improve(friendly): 모바일 환경에서의 플로팅 버튼 디자인 업데이트, 데스크톱 모드에서 타임라인에 알림창 추가
This commit is contained in:
parent
ceda332de7
commit
d948860d3c
103
packages/client/src/components/global/CPAvatar-Friendly.vue
Normal file
103
packages/client/src/components/global/CPAvatar-Friendly.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<span v-if="disableLink" v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :style="{ color }" :title="acct(user)" @click="onClick">
|
||||
<img class="inner" :src="url" decoding="async"/>
|
||||
<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
|
||||
</span>
|
||||
<MkA v-else v-user-preview="disablePreview ? undefined : user.id" class="eiwwqkts _noSelect" :style="{ color }" :to="userPage(user)" :title="acct(user)" :target="target">
|
||||
<img class="inner" :src="url" decoding="async"/>
|
||||
<MkUserOnlineIndicator v-if="showIndicator" class="indicator" :user="user"/>
|
||||
</MkA>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, watch } from 'vue';
|
||||
import * as misskey from 'misskey-js';
|
||||
import { getStaticImageUrl } from '@/scripts/get-static-image-url';
|
||||
import { extractAvgColorFromBlurhash } from '@/scripts/extract-avg-color-from-blurhash';
|
||||
import { acct, userPage } from '@/filters/user';
|
||||
import MkUserOnlineIndicator from '@/components/user-online-indicator.vue';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
user: misskey.entities.User;
|
||||
target?: string | null;
|
||||
disableLink?: boolean;
|
||||
disablePreview?: boolean;
|
||||
showIndicator?: boolean;
|
||||
}>(), {
|
||||
target: null,
|
||||
disableLink: false,
|
||||
disablePreview: false,
|
||||
showIndicator: false,
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(ev: 'click', v: MouseEvent): void;
|
||||
}>();
|
||||
|
||||
const url = $computed(() => defaultStore.state.disableShowingAnimatedImages
|
||||
? getStaticImageUrl(props.user.avatarUrl)
|
||||
: props.user.avatarUrl);
|
||||
|
||||
function onClick(ev: MouseEvent) {
|
||||
emit('click', ev);
|
||||
}
|
||||
|
||||
let color = $ref();
|
||||
|
||||
watch(() => props.user.avatarBlurhash, () => {
|
||||
color = extractAvgColorFromBlurhash(props.user.avatarBlurhash);
|
||||
}, {
|
||||
immediate: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@keyframes earwiggleleft {
|
||||
from { transform: rotate(37.6deg) skew(30deg); }
|
||||
25% { transform: rotate(10deg) skew(30deg); }
|
||||
50% { transform: rotate(20deg) skew(30deg); }
|
||||
75% { transform: rotate(0deg) skew(30deg); }
|
||||
to { transform: rotate(37.6deg) skew(30deg); }
|
||||
}
|
||||
|
||||
@keyframes earwiggleright {
|
||||
from { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
30% { transform: rotate(-10deg) skew(-30deg); }
|
||||
55% { transform: rotate(-20deg) skew(-30deg); }
|
||||
75% { transform: rotate(0deg) skew(-30deg); }
|
||||
to { transform: rotate(-37.6deg) skew(-30deg); }
|
||||
}
|
||||
|
||||
.eiwwqkts {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
flex-shrink: 0;
|
||||
// border-radius: 100%;
|
||||
line-height: 16px;
|
||||
|
||||
> .inner {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
border-radius: 0 80% 0 0;
|
||||
z-index: 1;
|
||||
overflow: hidden;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
> .indicator {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 20%;
|
||||
height: 20%;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="hoawjimk">
|
||||
<XBanner v-for="media in mediaList.filter(media => !previewable(media))" :key="media.id" :media="media"/>
|
||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container" :class="{ isFriendly }">
|
||||
<div v-if="mediaList.filter(media => previewable(media)).length > 0" class="gird-container">
|
||||
<div ref="gallery" :data-count="mediaList.filter(media => previewable(media)).length">
|
||||
<template v-for="media in mediaList.filter(media => previewable(media))">
|
||||
<XVideo v-if="media.type.startsWith('video')" :key="media.id" :video="media"/>
|
||||
@ -25,8 +25,6 @@ import * as os from '@/os';
|
||||
import { FILE_TYPE_BROWSERSAFE } from '@/const';
|
||||
import { defaultStore } from '@/store';
|
||||
|
||||
const isFriendly = $ref(localStorage.getItem('ui') === 'friendly');
|
||||
|
||||
const props = defineProps<{
|
||||
mediaList: misskey.entities.DriveFile[];
|
||||
raw?: boolean;
|
||||
@ -179,12 +177,6 @@ const previewable = (file: misskey.entities.DriveFile): boolean => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.isFriendly {
|
||||
@media (min-width: 1500px) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
109
packages/client/src/pages/notifications-friendly.vue
Normal file
109
packages/client/src/pages/notifications-friendly.vue
Normal file
@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<MkStickyContainer>
|
||||
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
|
||||
<MkSpacer v-if="isFriendly">
|
||||
<div v-if="tab === 'all' || tab === 'unread'">
|
||||
<XNotifications class="notifications" :include-types="includeTypes" :unread-only="unreadOnly"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'mentions'">
|
||||
<XNotes :pagination="mentionsPagination"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'directNotes'">
|
||||
<XNotes :pagination="directNotesPagination"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
|
||||
<MkSpacer v-else :content-max="800">
|
||||
<div v-if="tab === 'all' || tab === 'unread'">
|
||||
<XNotifications class="notifications" :include-types="includeTypes" :unread-only="unreadOnly"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'mentions'">
|
||||
<XNotes :pagination="mentionsPagination"/>
|
||||
</div>
|
||||
<div v-else-if="tab === 'directNotes'">
|
||||
<XNotes :pagination="directNotesPagination"/>
|
||||
</div>
|
||||
</MkSpacer>
|
||||
</MkStickyContainer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { notificationTypes } from 'misskey-js';
|
||||
import XNotifications from '@/components/notifications.vue';
|
||||
import XNotes from '@/components/notes.vue';
|
||||
import * as os from '@/os';
|
||||
import { i18n } from '@/i18n';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
|
||||
const isFriendly = $ref(localStorage.getItem('ui') === 'friendly');
|
||||
|
||||
let tab = $ref('all');
|
||||
let includeTypes = $ref<string[] | null>(null);
|
||||
let unreadOnly = $computed(() => tab === 'unread');
|
||||
|
||||
const mentionsPagination = {
|
||||
endpoint: 'notes/mentions' as const,
|
||||
limit: 10,
|
||||
};
|
||||
|
||||
const directNotesPagination = {
|
||||
endpoint: 'notes/mentions' as const,
|
||||
limit: 10,
|
||||
params: {
|
||||
visibility: 'specified',
|
||||
},
|
||||
};
|
||||
|
||||
function setFilter(ev) {
|
||||
const typeItems = notificationTypes.map(t => ({
|
||||
text: i18n.t(`_notification._types.${t}`),
|
||||
active: includeTypes && includeTypes.includes(t),
|
||||
action: () => {
|
||||
includeTypes = [t];
|
||||
},
|
||||
}));
|
||||
const items = includeTypes != null ? [{
|
||||
icon: 'fas fa-times',
|
||||
text: i18n.ts.clear,
|
||||
action: () => {
|
||||
includeTypes = null;
|
||||
},
|
||||
}, null, ...typeItems] : typeItems;
|
||||
os.popupMenu(items, ev.currentTarget ?? ev.target);
|
||||
}
|
||||
|
||||
const headerActions = $computed(() => [tab === 'all' ? {
|
||||
text: i18n.ts.filter,
|
||||
icon: 'fas fa-filter',
|
||||
highlighted: includeTypes != null,
|
||||
handler: setFilter,
|
||||
} : undefined, tab === 'all' ? {
|
||||
text: i18n.ts.markAllAsRead,
|
||||
icon: 'fas fa-check',
|
||||
handler: () => {
|
||||
os.apiWithDialog('notifications/mark-all-as-read');
|
||||
},
|
||||
} : undefined].filter(x => x !== undefined));
|
||||
|
||||
const headerTabs = $computed(() => [{
|
||||
key: 'all',
|
||||
title: i18n.ts.all,
|
||||
}, {
|
||||
key: 'unread',
|
||||
title: i18n.ts.unread,
|
||||
}, {
|
||||
key: 'mentions',
|
||||
title: i18n.ts.mentions,
|
||||
icon: 'fas fa-at',
|
||||
}, {
|
||||
key: 'directNotes',
|
||||
title: i18n.ts.directNotes,
|
||||
icon: 'fas fa-envelope',
|
||||
}]);
|
||||
|
||||
definePageMetadata(computed(() => ({
|
||||
title: i18n.ts.notifications,
|
||||
icon: 'fas fa-bell',
|
||||
})));
|
||||
</script>
|
@ -5,7 +5,7 @@
|
||||
<MkPageHeader v-else v-model:tab="src" :actions="headerActions" :tabs="headerTabs"/>
|
||||
</template>
|
||||
|
||||
<MkSpacer v-if="isFriendly"> <!-- if notification view added, style="width: 70%" -->
|
||||
<MkSpacer v-if="isFriendly">
|
||||
<div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf">
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
|
||||
<XPostForm v-if="$store.reactiveState.showFixedPostForm.value" class="post-form _block" fixed/>
|
||||
@ -23,12 +23,6 @@
|
||||
</div>
|
||||
</MkSpacer>
|
||||
|
||||
<!--
|
||||
<MkContainer v-if="isFriendly" :scrollable="true">
|
||||
<XNotifications :include-types="includeTypes" style="width: 30%; padding: 24px 24px 24px 0"/>
|
||||
</MkContainer>
|
||||
-->
|
||||
|
||||
<MkSpacer v-else :content-max="800">
|
||||
<div ref="rootEl" v-hotkey.global="keymap" class="cmuxhskf">
|
||||
<XTutorial v-if="$store.reactiveState.tutorial.value != -1" class="tutorial _block"/>
|
||||
@ -60,9 +54,7 @@ import { i18n } from '@/i18n';
|
||||
import { instance } from '@/instance';
|
||||
import { $i } from '@/account';
|
||||
import { definePageMetadata } from '@/scripts/page-metadata';
|
||||
import XNotifications from '@/components/notifications.vue';
|
||||
import MkStickyContainerTL from '@/components/global/sticky-container-timeline.vue';
|
||||
import MkContainer from '@/components/ui/container.vue';
|
||||
|
||||
const isFriendly = $ref(localStorage.getItem('ui') === 'friendly');
|
||||
const DESKTOP_THRESHOLD = 1100;
|
||||
|
@ -12,11 +12,15 @@
|
||||
</main>
|
||||
</MkStickyContainer>
|
||||
|
||||
<div v-if="isDesktop" ref="widgetsEl" class="widgets tl-noti">
|
||||
<XNotifications style="height: 100%;" @mounted="attachSticky"/>
|
||||
</div>
|
||||
|
||||
<div v-if="isDesktop" ref="widgetsEl" class="widgets">
|
||||
<XWidgets @mounted="attachSticky"/>
|
||||
</div>
|
||||
|
||||
<button v-if="isMobile" class="navButton nav _button" @click="drawerMenuShowing = true"><MkAvatar class="avatar" v-if="!canBack" :user="$i" :disable-preview="true" :show-indicator="true"/></button>
|
||||
<button v-if="isMobile" class="navButton nav _button" @click="drawerMenuShowing = true"><CPAvatar class="avatar" :user="$i" :disable-preview="true" :show-indicator="false"/></button>
|
||||
|
||||
<button v-if="isMobile" class="postButton post _button" @click="os.post()"><i class="fas fa-pencil-alt"></i></button>
|
||||
|
||||
@ -77,9 +81,11 @@ import { Router } from '@/nirax';
|
||||
import { mainRouter } from '@/router';
|
||||
import { PageMetadata, provideMetadataReceiver, setPageMetadata } from '@/scripts/page-metadata';
|
||||
import { deviceKind } from '@/scripts/device-kind';
|
||||
import CPAvatar from '@/components/global/CPAvatar-Friendly.vue';
|
||||
const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue'));
|
||||
const XSidebar = defineAsyncComponent(() => import('@/ui/friendly/navbar.vue'));
|
||||
const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue'));
|
||||
const XNotifications = defineAsyncComponent(() => import('@/pages/notifications.vue'));
|
||||
|
||||
localStorage.setItem('ui', 'friendly');
|
||||
|
||||
@ -228,7 +234,7 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
|
||||
.dkgtipfy {
|
||||
$ui-font-size: 1em; // TODO: どこかに集約したい
|
||||
$widgets-hide-threshold: 1090px;
|
||||
$float-button-size: 58px;
|
||||
$float-button-size: 70px;
|
||||
|
||||
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||
min-height: calc(var(--vh, 1vh) * 100);
|
||||
@ -245,7 +251,7 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
|
||||
}
|
||||
|
||||
> .contents {
|
||||
width: 100%;
|
||||
width: 70%;
|
||||
min-width: 0;
|
||||
background: var(--bg);
|
||||
}
|
||||
@ -260,23 +266,31 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
|
||||
}
|
||||
}
|
||||
|
||||
> .tl-noti {
|
||||
width: 30%;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
> .navButton {
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: calc(70px + env(safe-area-inset-bottom));
|
||||
left: 10px;
|
||||
bottom: calc(40px + env(safe-area-inset-bottom));
|
||||
left: -10px;
|
||||
width: $float-button-size;
|
||||
height: $float-button-size;
|
||||
// box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
font-size: 22px;
|
||||
// background: var(--panel);
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 0 10px 6px rgba(0, 0, 0, 0.14), 0 0 18px 1px rgba(0, 0, 0, 0.12);
|
||||
background: var(--panel);
|
||||
// opacity: 0.7;
|
||||
border-radius: 0 80% 0 0;
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
|
||||
> .avatar {
|
||||
width: $float-button-size;
|
||||
height: $float-button-size;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
vertical-align: middle;
|
||||
opacity: 0.7;
|
||||
border-radius: 0 80% 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -284,15 +298,17 @@ const wallpaper = localStorage.getItem('wallpaper') != null;
|
||||
display: block;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
bottom: calc(70px + env(safe-area-inset-bottom));
|
||||
right: 0;
|
||||
bottom: calc(40px + env(safe-area-inset-bottom));
|
||||
right: -10px;
|
||||
width: $float-button-size;
|
||||
height: $float-button-size;
|
||||
box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12);
|
||||
font-size: 22px;
|
||||
font-size: 18px;
|
||||
background: var(--accent);
|
||||
opacity: 0.7;
|
||||
border-radius: 20%;
|
||||
// opacity: 0.7;
|
||||
border-radius: 80% 0 0 0;
|
||||
-webkit-backdrop-filter: var(--blur, blur(15px));
|
||||
backdrop-filter: var(--blur, blur(15px));
|
||||
|
||||
i {
|
||||
color: white;
|
||||
|
Loading…
Reference in New Issue
Block a user