refactor(client): refactor ui components

This commit is contained in:
syuilo 2021-12-03 22:09:40 +09:00
parent 8223a069fe
commit fa36b88af4
7 changed files with 814 additions and 689 deletions

View file

@ -1,4 +1,4 @@
import { computed, ref } from 'vue'; import { computed, ref, reactive } from 'vue';
import { search } from '@/scripts/search'; import { search } from '@/scripts/search';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
@ -7,7 +7,7 @@ import { $i } from './account';
import { unisonReload } from '@/scripts/unison-reload'; import { unisonReload } from '@/scripts/unison-reload';
import { router } from './router'; import { router } from './router';
export const menuDef = { export const menuDef = reactive({
notifications: { notifications: {
title: 'notifications', title: 'notifications',
icon: 'fas fa-bell', icon: 'fas fa-bell',
@ -221,4 +221,4 @@ export const menuDef = {
}*/], ev.currentTarget || ev.target); }*/], ev.currentTarget || ev.target);
}, },
}, },
}; });

View file

@ -556,7 +556,7 @@ export function contextMenu(items: any[], ev: MouseEvent) {
}); });
} }
export function post(props: Record<string, any>) { export function post(props: Record<string, any> = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない // NOTE: MkPostFormDialogをdynamic importするとiOSでテキストエリアに自動フォーカスできない
// NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、 // NOTE: ただ、dynamic importしない場合、MkPostFormDialogインスタンスが使いまわされ、

View file

@ -0,0 +1,205 @@
<template>
<div class="kmwsukvl">
<div>
<button v-click-anime class="item _button account" @click="openAccountMenu">
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
</button>
<MkA v-click-anime class="item index" active-class="active" to="/" exact>
<i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span>
</MkA>
<template v-for="item in menu">
<div v-if="item === '-'" class="divider"></div>
<component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}">
<i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span>
<span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span>
</component>
</template>
<div class="divider"></div>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin">
<i class="fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span>
</MkA>
<button v-click-anime class="item _button" @click="more">
<i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<span v-if="otherMenuItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
</button>
<MkA v-click-anime class="item" active-class="active" to="/settings">
<i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
</MkA>
<button class="item _button post" data-cy-open-post-form @click="post">
<i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</button>
</div>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, toRef, watch } from 'vue';
import { host } from '@/config';
import { search } from '@/scripts/search';
import * as os from '@/os';
import { menuDef } from '@/menu';
import { openAccountMenu } from '@/account';
import { defaultStore } from '@/store';
export default defineComponent({
setup(props, context) {
const menu = toRef(defaultStore.state, 'menu');
const otherMenuItemIndicated = computed(() => {
for (const def in menuDef) {
if (menu.value.includes(def)) continue;
if (menuDef[def].indicated) return true;
}
return false;
});
return {
host: host,
accounts: [],
connection: null,
menu,
menuDef: menuDef,
otherMenuItemIndicated,
post: os.post,
search,
openAccountMenu,
more: () => {
os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed');
},
};
},
});
</script>
<style lang="scss" scoped>
.kmwsukvl {
$ui-font-size: 1em; // TODO:
$avatar-size: 32px;
$avatar-margin: 8px;
> div {
> .divider {
margin: 16px 16px;
border-top: solid 0.5px var(--divider);
}
> .item {
position: relative;
display: block;
padding-left: 24px;
font-size: $ui-font-size;
line-height: 2.85rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> i {
position: relative;
width: 32px;
}
> i,
> .avatar {
margin-right: $avatar-margin;
}
> .avatar {
width: $avatar-size;
height: $avatar-size;
vertical-align: middle;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative;
font-size: 0.9em;
}
&:hover {
text-decoration: none;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
&:hover, &.active {
&:before {
content: "";
display: block;
width: calc(100% - 24px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
}
}
&:first-child, &:last-child {
position: sticky;
z-index: 1;
padding-top: 8px;
padding-bottom: 8px;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
}
&:first-child {
top: 0;
&:hover, &.active {
&:before {
content: none;
}
}
}
&:last-child {
bottom: 0;
color: var(--fgOnAccent);
&:before {
content: "";
display: block;
width: calc(100% - 20px);
height: calc(100% - 20px);
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
}
}
}
}
</style>

View file

@ -1,385 +1,300 @@
<template> <template>
<div class="mvcprjjd"> <div class="mvcprjjd" :class="{ iconOnly }">
<transition name="nav-back"> <div>
<div v-if="showing" <button v-click-anime class="item _button account" @click="openAccountMenu">
class="nav-back _modalBg" <MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/>
@click="showing = false" </button>
@touchstart.passive="showing = false" <MkA v-click-anime class="item index" active-class="active" to="/" exact>
></div> <i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span>
</transition> </MkA>
<template v-for="item in menu">
<transition name="nav"> <div v-if="item === '-'" class="divider"></div>
<nav v-show="showing" class="nav" :class="{ iconOnly, hidden }"> <component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}">
<div> <i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span>
<button v-click-anime class="item _button account" @click="openAccountMenu"> <span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span>
<MkAvatar :user="$i" class="avatar"/><MkAcct class="text" :user="$i"/> </component>
</button> </template>
<MkA v-click-anime class="item index" active-class="active" to="/" exact> <div class="divider"></div>
<i class="fas fa-home fa-fw"></i><span class="text">{{ $ts.timeline }}</span> <MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin">
</MkA> <i class="fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span>
<template v-for="item in menu"> </MkA>
<div v-if="item === '-'" class="divider"></div> <button v-click-anime class="item _button" @click="more">
<component :is="menuDef[item].to ? 'MkA' : 'button'" v-else-if="menuDef[item] && (menuDef[item].show !== false)" v-click-anime class="item _button" :class="[item, { active: menuDef[item].active }]" active-class="active" :to="menuDef[item].to" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}"> <i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<i class="fa-fw" :class="menuDef[item].icon"></i><span class="text">{{ $ts[menuDef[item].title] }}</span> <span v-if="otherMenuItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
<span v-if="menuDef[item].indicated" class="indicator"><i class="fas fa-circle"></i></span> </button>
</component> <MkA v-click-anime class="item" active-class="active" to="/settings">
</template> <i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
<div class="divider"></div> </MkA>
<MkA v-if="$i.isAdmin || $i.isModerator" v-click-anime class="item" active-class="active" to="/admin"> <button class="item _button post" data-cy-open-post-form @click="post">
<i class="fas fa-door-open fa-fw"></i><span class="text">{{ $ts.controlPanel }}</span> <i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</MkA> </button>
<button v-click-anime class="item _button" @click="more"> </div>
<i class="fa fa-ellipsis-h fa-fw"></i><span class="text">{{ $ts.more }}</span>
<span v-if="otherNavItemIndicated" class="indicator"><i class="fas fa-circle"></i></span>
</button>
<MkA v-click-anime class="item" active-class="active" to="/settings">
<i class="fas fa-cog fa-fw"></i><span class="text">{{ $ts.settings }}</span>
</MkA>
<button class="item _button post" data-cy-open-post-form @click="post">
<i class="fas fa-pencil-alt fa-fw"></i><span class="text">{{ $ts.note }}</span>
</button>
</div>
</nav>
</transition>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, ref, watch } from 'vue';
import { host } from '@/config'; import { host } from '@/config';
import { search } from '@/scripts/search'; import { search } from '@/scripts/search';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu'; import { menuDef } from '@/menu';
import { openAccountMenu } from '@/account'; import { openAccountMenu } from '@/account';
import { defaultStore } from '@/store';
export default defineComponent({ export default defineComponent({
props: { setup(props, context) {
defaultHidden: { const iconOnly = ref(false);
type: Boolean,
required: false,
default: false,
}
},
data() { const menu = computed(() => defaultStore.state.menu);
return { const otherMenuItemIndicated = computed(() => {
host: host, for (const def in menuDef) {
showing: false, if (menu.value.includes(def)) continue;
accounts: [], if (menuDef[def].indicated) return true;
connection: null,
menuDef: menuDef,
iconOnly: false,
hidden: this.defaultHidden,
};
},
computed: {
menu(): string[] {
return this.$store.state.menu;
},
otherNavItemIndicated(): boolean {
for (const def in this.menuDef) {
if (this.menu.includes(def)) continue;
if (this.menuDef[def].indicated) return true;
} }
return false; return false;
}, });
const calcViewState = () => {
iconOnly.value = (window.innerWidth <= 1279) || (defaultStore.state.menuDisplay === 'sideIcon');
};
calcViewState();
window.addEventListener('resize', calcViewState);
watch(defaultStore.reactiveState.menuDisplay, () => {
calcViewState();
});
return {
host: host,
accounts: [],
connection: null,
menu,
menuDef: menuDef,
otherMenuItemIndicated,
iconOnly,
post: os.post,
search,
openAccountMenu,
more: () => {
os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed');
},
};
}, },
watch: {
$route(to, from) {
this.showing = false;
},
'$store.reactiveState.menuDisplay.value'() {
this.calcViewState();
},
iconOnly() {
this.$nextTick(() => {
this.$emit('change-view-mode');
});
},
hidden() {
this.$nextTick(() => {
this.$emit('change-view-mode');
});
}
},
created() {
window.addEventListener('resize', this.calcViewState);
this.calcViewState();
},
methods: {
calcViewState() {
this.iconOnly = (window.innerWidth <= 1279) || (this.$store.state.menuDisplay === 'sideIcon');
if (!this.defaultHidden) {
this.hidden = (window.innerWidth <= 650);
}
},
show() {
this.showing = true;
},
post() {
os.post();
},
search() {
search();
},
more(ev) {
os.popup(import('@/components/launch-pad.vue'), {}, {
}, 'closed');
},
openAccountMenu,
}
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.nav-enter-active,
.nav-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-enter-from,
.nav-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.nav-back-enter-active,
.nav-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.nav-back-enter-from,
.nav-back-leave-active {
opacity: 0;
}
.mvcprjjd { .mvcprjjd {
$ui-font-size: 1em; // TODO: $ui-font-size: 1em; // TODO:
$nav-width: 250px; $nav-width: 250px;
$nav-icon-only-width: 86px; $nav-icon-only-width: 86px;
$avatar-size: 32px;
$avatar-margin: 8px;
> .nav-back { flex: 0 0 $nav-width;
width: $nav-width;
box-sizing: border-box;
> div {
position: fixed;
top: 0;
left: 0;
z-index: 1001; z-index: 1001;
}
> .nav {
$avatar-size: 32px;
$avatar-margin: 8px;
flex: 0 0 $nav-width;
width: $nav-width; width: $nav-width;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box; box-sizing: border-box;
overflow: auto;
overflow-x: clip;
background: var(--navBg);
&.iconOnly { > .divider {
flex: 0 0 $nav-icon-only-width; margin: 16px 16px;
width: $nav-icon-only-width; border-top: solid 0.5px var(--divider);
&:not(.hidden) {
> div {
width: $nav-icon-only-width;
> .divider {
margin: 8px auto;
width: calc(100% - 32px);
}
> .item {
padding-left: 0;
padding: 18px 0;
width: 100%;
text-align: center;
font-size: $ui-font-size * 1.1;
line-height: initial;
> i,
> .avatar {
display: block;
margin: 0 auto;
}
> i {
opacity: 0.7;
}
> .text {
display: none;
}
&:hover, &.active {
> i, > .text {
opacity: 1;
}
}
&:first-child {
margin-bottom: 8px;
}
&:last-child {
margin-top: 8px;
}
}
}
}
} }
&.hidden { > .item {
position: fixed; position: relative;
top: 0; display: block;
left: 0; padding-left: 24px;
z-index: 1001; font-size: $ui-font-size;
} line-height: 2.85rem;
text-overflow: ellipsis;
&:not(.hidden) { overflow: hidden;
display: block !important; white-space: nowrap;
} width: 100%;
text-align: left;
> div {
position: fixed;
top: 0;
left: 0;
z-index: 1001;
width: $nav-width;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
box-sizing: border-box; box-sizing: border-box;
overflow: auto; color: var(--navFg);
overflow-x: clip;
background: var(--navBg);
> .divider { > i {
margin: 16px 16px; position: relative;
border-top: solid 0.5px var(--divider); width: 32px;
} }
> .item { > i,
> .avatar {
margin-right: $avatar-margin;
}
> .avatar {
width: $avatar-size;
height: $avatar-size;
vertical-align: middle;
}
> .indicator {
position: absolute;
top: 0;
left: 20px;
color: var(--navIndicator);
font-size: 8px;
animation: blink 1s infinite;
}
> .text {
position: relative; position: relative;
display: block; font-size: 0.9em;
padding-left: 24px; }
font-size: $ui-font-size;
line-height: 2.85rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
width: 100%;
text-align: left;
box-sizing: border-box;
color: var(--navFg);
> i { &:hover {
position: relative; text-decoration: none;
width: 32px; color: var(--navHoverFg);
} }
> i, &.active {
> .avatar { color: var(--navActive);
margin-right: $avatar-margin; }
}
> .avatar { &:hover, &.active {
width: $avatar-size; &:before {
height: $avatar-size; content: "";
vertical-align: middle; display: block;
} width: calc(100% - 24px);
height: 100%;
> .indicator { margin: auto;
position: absolute; position: absolute;
top: 0; top: 0;
left: 20px; left: 0;
color: var(--navIndicator); right: 0;
font-size: 8px; bottom: 0;
animation: blink 1s infinite; border-radius: 999px;
background: var(--accentedBg);
} }
}
> .text { &:first-child, &:last-child {
position: relative; position: sticky;
font-size: 0.9em; z-index: 1;
} padding-top: 8px;
padding-bottom: 8px;
background: var(--X14);
-webkit-backdrop-filter: var(--blur, blur(8px));
backdrop-filter: var(--blur, blur(8px));
}
&:hover { &:first-child {
text-decoration: none; top: 0;
color: var(--navHoverFg);
}
&.active {
color: var(--navActive);
}
&:hover, &.active { &:hover, &.active {
&:before { &:before {
content: ""; content: none;
display: block;
width: calc(100% - 24px);
height: 100%;
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: var(--accentedBg);
} }
} }
}
&:first-child, &:last-child { &:last-child {
position: sticky; bottom: 0;
z-index: 1; color: var(--fgOnAccent);
padding-top: 8px;
padding-bottom: 8px; &:before {
background: var(--X14); content: "";
-webkit-backdrop-filter: var(--blur, blur(8px)); display: block;
backdrop-filter: var(--blur, blur(8px)); width: calc(100% - 20px);
height: calc(100% - 20px);
margin: auto;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
border-radius: 999px;
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB));
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
}
}
}
&.iconOnly {
flex: 0 0 $nav-icon-only-width;
width: $nav-icon-only-width;
> div {
width: $nav-icon-only-width;
> .divider {
margin: 8px auto;
width: calc(100% - 32px);
}
> .item {
padding-left: 0;
padding: 18px 0;
width: 100%;
text-align: center;
font-size: $ui-font-size * 1.1;
line-height: initial;
> i,
> .avatar {
display: block;
margin: 0 auto;
}
> i {
opacity: 0.7;
}
> .text {
display: none;
}
&:hover, &.active {
> i, > .text {
opacity: 1;
}
} }
&:first-child { &:first-child {
top: 0; margin-bottom: 8px;
&:hover, &.active {
&:before {
content: none;
}
}
} }
&:last-child { &:last-child {
bottom: 0; margin-top: 8px;
color: var(--fgOnAccent); }
&:before { &:before {
content: ""; width: 100%;
display: block; border-radius: 0;
width: calc(100% - 20px); }
height: calc(100% - 20px);
margin: auto; &.post {
position: absolute; height: $nav-icon-only-width;
top: 0; }
left: 0;
right: 0; &.post:before {
bottom: 0; width: calc(100% - 32px);
border-radius: 999px; height: calc(100% - 32px);
background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); border-radius: 100%;
}
&:hover, &.active {
&:before {
background: var(--accentLighten);
}
}
} }
} }
} }

View file

@ -1,16 +1,14 @@
<template> <template>
<div class="mk-app" :class="{ wallpaper, isMobile }" :style="`--globalHeaderHeight:${globalHeaderHeight}px`"> <div class="gbhvwtnk" :class="{ wallpaper }" :style="`--globalHeaderHeight:${globalHeaderHeight}px`">
<XHeaderMenu v-if="showMenuOnTop" v-get-size="(w, h) => globalHeaderHeight = h"/> <XHeaderMenu v-if="showMenuOnTop" v-get-size="(w, h) => globalHeaderHeight = h"/>
<div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }"> <div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }">
<template v-if="!isMobile"> <div v-if="!showMenuOnTop" class="sidebar">
<div v-if="!showMenuOnTop" class="sidebar"> <XSidebar/>
<XSidebar/> </div>
</div> <div v-else ref="widgetsLeft" class="widgets left">
<div v-else ref="widgetsLeft" class="widgets left"> <XWidgets :place="'left'" @mounted="attachSticky('widgetsLeft')"/>
<XWidgets :place="'left'" @mounted="attachSticky('widgetsLeft')"/> </div>
</div>
</template>
<main class="main" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu"> <main class="main" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu">
<div class="content"> <div class="content">
@ -32,16 +30,6 @@
</div> </div>
</div> </div>
<div v-if="isMobile" class="buttons">
<button ref="navButton" class="button nav _button" @click="showDrawerNav"><i class="fas fa-bars"></i><span v-if="navIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
<button class="button post _button" @click="post"><i class="fas fa-pencil-alt"></i></button>
</div>
<XDrawerSidebar v-if="isMobile" ref="drawerNav" class="sidebar"/>
<transition name="tray-back"> <transition name="tray-back">
<div v-if="widgetsShowing" <div v-if="widgetsShowing"
class="tray-back _modalBg" class="tray-back _modalBg"
@ -65,20 +53,17 @@ import { defineComponent, defineAsyncComponent, markRaw } from 'vue';
import { instanceName } from '@/config'; import { instanceName } from '@/config';
import { StickySidebar } from '@/scripts/sticky-sidebar'; import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from './classic.sidebar.vue'; import XSidebar from './classic.sidebar.vue';
import XDrawerSidebar from '@/ui/_common_/sidebar.vue';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu'; import { menuDef } from '@/menu';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
const DESKTOP_THRESHOLD = 1100; const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 600;
export default defineComponent({ export default defineComponent({
components: { components: {
XCommon, XCommon,
XSidebar, XSidebar,
XDrawerSidebar,
XHeaderMenu: defineAsyncComponent(() => import('./classic.header.vue')), XHeaderMenu: defineAsyncComponent(() => import('./classic.header.vue')),
XWidgets: defineAsyncComponent(() => import('./classic.widgets.vue')), XWidgets: defineAsyncComponent(() => import('./classic.widgets.vue')),
}, },
@ -95,7 +80,6 @@ export default defineComponent({
pageInfo: null, pageInfo: null,
menuDef: menuDef, menuDef: menuDef,
globalHeaderHeight: 0, globalHeaderHeight: 0,
isMobile: window.innerWidth <= MOBILE_THRESHOLD,
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
widgetsShowing: false, widgetsShowing: false,
fullView: false, fullView: false,
@ -104,16 +88,8 @@ export default defineComponent({
}, },
computed: { computed: {
navIndicated(): boolean {
for (const def in this.menuDef) {
if (def === 'notifications') continue; //
if (this.menuDef[def].indicated) return true;
}
return false;
},
showMenuOnTop(): boolean { showMenuOnTop(): boolean {
return !this.isMobile && this.$store.state.menuDisplay === 'top'; return this.$store.state.menuDisplay === 'top';
} }
}, },
@ -136,7 +112,6 @@ export default defineComponent({
mounted() { mounted() {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
this.isMobile = (window.innerWidth <= MOBILE_THRESHOLD);
this.isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD); this.isDesktop = (window.innerWidth >= DESKTOP_THRESHOLD);
}, { passive: true }); }, { passive: true });
@ -179,22 +154,10 @@ export default defineComponent({
}, { passive: true }); }, { passive: true });
}, },
post() {
os.post();
},
top() { top() {
window.scroll({ top: 0, behavior: 'smooth' }); window.scroll({ top: 0, behavior: 'smooth' });
}, },
back() {
history.back();
},
showDrawerNav() {
this.$refs.drawerNav.show();
},
onTransition() { onTransition() {
if (window._scroll) window._scroll(); if (window._scroll) window._scroll();
}, },
@ -258,10 +221,9 @@ export default defineComponent({
opacity: 0; opacity: 0;
} }
.mk-app { .gbhvwtnk {
$ui-font-size: 1em; $ui-font-size: 1em;
$widgets-hide-threshold: 1200px; $widgets-hide-threshold: 1200px;
$nav-icon-only-width: 78px; // TODO:
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ // 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
min-height: calc(var(--vh, 1vh) * 100); min-height: calc(var(--vh, 1vh) * 100);
@ -272,21 +234,6 @@ export default defineComponent({
//backdrop-filter: var(--blur, blur(4px)); //backdrop-filter: var(--blur, blur(4px));
} }
&.isMobile {
> .columns {
display: block;
margin: 0;
> .main {
margin: 0;
padding-bottom: 92px;
border: none;
width: 100%;
border-radius: 0;
}
}
}
> .columns { > .columns {
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -372,76 +319,6 @@ export default defineComponent({
} }
} }
> .buttons {
position: fixed;
z-index: 1000;
bottom: 0;
padding: 16px;
display: flex;
width: 100%;
box-sizing: border-box;
-webkit-backdrop-filter: var(--blur, blur(32px));
backdrop-filter: var(--blur, blur(32px));
background-color: var(--header);
border-top: solid 0.5px var(--divider);
> .button {
position: relative;
flex: 1;
padding: 0;
margin: auto;
height: 64px;
border-radius: 8px;
background: var(--panel);
color: var(--fg);
&:not(:last-child) {
margin-right: 12px;
}
@media (max-width: 400px) {
height: 60px;
&:not(:last-child) {
margin-right: 8px;
}
}
&:hover {
background: var(--X2);
}
> .indicator {
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
> * {
font-size: 22px;
}
&:disabled {
cursor: default;
> * {
opacity: 0.5;
}
}
}
}
> .tray-back { > .tray-back {
z-index: 1001; z-index: 1001;
} }

View file

@ -1,8 +1,8 @@
<template> <template>
<div class="mk-deck" :class="`${deckStore.reactiveState.columnAlign.value}`" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }" <div class="mk-deck" :class="[{ isMobile }, `${deckStore.reactiveState.columnAlign.value}`]" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }"
@contextmenu.self.prevent="onContextmenu" @contextmenu.self.prevent="onContextmenu"
> >
<XSidebar ref="nav"/> <XSidebar v-if="!isMobile"/>
<template v-for="ids in layout"> <template v-for="ids in layout">
<!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため --> <!-- sectionを利用しているのはdeck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
@ -22,94 +22,76 @@
/> />
</template> </template>
<button v-if="$i" class="nav _button" @click="showNav()"><i class="fas fa-bars"></i><span v-if="navIndicated" class="indicator"><i class="fas fa-circle"></i></span></button> <div v-if="isMobile" class="buttons">
<button v-if="$i" class="post _buttonPrimary" @click="post()"><i class="fas fa-pencil-alt"></i></button> <button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="$router.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button>
</div>
<transition name="menu-back">
<div v-if="drawerMenuShowing"
class="menu-back _modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</transition>
<transition name="menu">
<XDrawerMenu v-if="drawerMenuShowing" class="menu"/>
</transition>
<XCommon/> <XCommon/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent } from 'vue'; import { computed, defineComponent, provide, ref, watch } from 'vue';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { host } from '@/config';
import DeckColumnCore from '@/ui/deck/column-core.vue'; import DeckColumnCore from '@/ui/deck/column-core.vue';
import XSidebar from '@/ui/_common_/sidebar.vue'; import XSidebar from '@/ui/_common_/sidebar.vue';
import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
import { getScrollContainer } from '@/scripts/scroll'; import { getScrollContainer } from '@/scripts/scroll';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu'; import { menuDef } from '@/menu';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import { deckStore, addColumn, loadDeck } from './deck/deck-store'; import { deckStore, addColumn as addColumnToStore, loadDeck } from './deck/deck-store';
import { useRoute } from 'vue-router';
import { $i } from '@/account';
import { i18n } from '@/i18n';
export default defineComponent({ export default defineComponent({
components: { components: {
XCommon, XCommon,
XSidebar, XSidebar,
XDrawerMenu,
DeckColumnCore, DeckColumnCore,
}, },
provide() { setup() {
return { const isMobile = ref(window.innerWidth <= 500);
shouldSpacerMin: true, window.addEventListener('resize', () => {
...deckStore.state.navWindow ? { isMobile.value = window.innerWidth <= 500;
navHook: (url) => { });
os.pageWindow(url);
}
} : {}
};
},
data() { const drawerMenuShowing = ref(false);
return {
deckStore,
host: host,
menuDef: menuDef,
wallpaper: localStorage.getItem('wallpaper') != null,
};
},
computed: { const route = useRoute();
columns() { watch(route, () => {
return deckStore.reactiveState.columns.value; drawerMenuShowing.value = false;
}, });
layout() {
return deckStore.reactiveState.layout.value; const columns = deckStore.reactiveState.columns;
}, const layout = deckStore.reactiveState.layout.value;
navIndicated(): boolean { const menuIndicated = computed(() => {
if (!this.$i) return false; if ($i == null) return false;
for (const def in this.menuDef) { for (const def in menuDef) {
if (this.menuDef[def].indicated) return true; if (menuDef[def].indicated) return true;
} }
return false; return false;
}, });
},
created() { const addColumn = async (ev) => {
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', this.onWheel);
loadDeck();
},
mounted() {
},
methods: {
onWheel(e) {
if (getScrollContainer(e.target) == null) {
document.documentElement.scrollLeft += e.deltaY > 0 ? 96 : -96;
}
},
showNav() {
this.$refs.nav.show();
},
post() {
os.post();
},
async addColumn(ev) {
const columns = [ const columns = [
'main', 'main',
'widgets', 'widgets',
@ -122,33 +104,83 @@ export default defineComponent({
]; ];
const { canceled, result: column } = await os.select({ const { canceled, result: column } = await os.select({
title: this.$ts._deck.addColumn, title: i18n.locale._deck.addColumn,
items: columns.map(column => ({ items: columns.map(column => ({
value: column, text: this.$t('_deck._columns.' + column) value: column, text: i18n.t('_deck._columns.' + column)
})) }))
}); });
if (canceled) return; if (canceled) return;
addColumn({ addColumnToStore({
type: column, type: column,
id: uuid(), id: uuid(),
name: this.$t('_deck._columns.' + column), name: i18n.t('_deck._columns.' + column),
width: 330, width: 330,
}); });
}, };
onContextmenu(e) { const onContextmenu = (ev) => {
os.contextMenu([{ os.contextMenu([{
text: this.$ts._deck.addColumn, text: i18n.locale._deck.addColumn,
icon: null, icon: null,
action: this.addColumn action: addColumn
}], e); }], ev);
}, };
}
provide('shouldSpacerMin', true);
if (deckStore.state.navWindow) {
provide('navHook', (url) => {
os.pageWindow(url);
});
}
document.documentElement.style.overflowY = 'hidden';
document.documentElement.style.scrollBehavior = 'auto';
window.addEventListener('wheel', (ev) => {
if (getScrollContainer(ev.target) == null) {
document.documentElement.scrollLeft += ev.deltaY > 0 ? 96 : -96;
}
});
loadDeck();
return {
isMobile,
deckStore,
drawerMenuShowing,
columns,
layout,
menuIndicated,
onContextmenu,
wallpaper: localStorage.getItem('wallpaper') != null,
post: os.post,
};
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.menu-enter-active,
.menu-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menu-enter-from,
.menu-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.menu-back-enter-active,
.menu-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menu-back-enter-from,
.menu-back-leave-active {
opacity: 0;
}
.mk-deck { .mk-deck {
$nav-hide-threshold: 650px; // TODO: $nav-hide-threshold: 650px; // TODO:
@ -172,6 +204,10 @@ export default defineComponent({
} }
} }
&.isMobile {
padding-bottom: 100px;
}
> .column { > .column {
flex-shrink: 0; flex-shrink: 0;
margin-right: var(--deckMargin); margin-right: var(--deckMargin);
@ -186,43 +222,88 @@ export default defineComponent({
} }
} }
> .post, > .buttons {
> .nav {
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
bottom: 32px; bottom: 0;
width: 64px; left: 0;
height: 64px; padding: 16px;
border-radius: 100%; display: flex;
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); width: 100%;
font-size: 22px; box-sizing: border-box;
@media (min-width: ($nav-hide-threshold + 1px)) { > .button {
display: none; position: relative;
flex: 1;
padding: 0;
margin: auto;
height: 64px;
border-radius: 8px;
background: var(--panel);
color: var(--fg);
&:not(:last-child) {
margin-right: 12px;
}
@media (max-width: 400px) {
height: 60px;
&:not(:last-child) {
margin-right: 8px;
}
}
&:hover {
background: var(--X2);
}
> .indicator {
position: absolute;
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
&:first-child {
margin-left: 0;
}
&:last-child {
margin-right: 0;
}
> * {
font-size: 22px;
}
&:disabled {
cursor: default;
> * {
opacity: 0.5;
}
}
} }
} }
> .post { > .menu-back {
right: 32px; z-index: 1001;
} }
> .nav { > .menu {
left: 32px; position: fixed;
background: var(--panel); top: 0;
color: var(--fg); left: 0;
z-index: 1001;
&:hover { // 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
background: var(--X2); height: calc(var(--vh, 1vh) * 100);
} width: 240px;
box-sizing: border-box;
> .indicator { overflow: auto;
position: absolute; background: var(--bg);
top: 0;
left: 0;
color: var(--indicator);
font-size: 16px;
animation: blink 1s infinite;
}
} }
} }
</style> </style>

View file

@ -1,9 +1,9 @@
<template> <template>
<div class="mk-app" :class="{ wallpaper }"> <div class="dkgtipfy" :class="{ wallpaper }">
<XSidebar ref="nav" class="sidebar"/> <XSidebar v-if="!isMobile" class="sidebar"/>
<div ref="contents" class="contents" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu"> <div class="contents" :style="{ background: pageInfo?.bg }" @contextmenu.stop="onContextmenu">
<main ref="main"> <main>
<div class="content"> <div class="content">
<MkStickyContainer> <MkStickyContainer>
<template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template> <template #header><MkHeader v-if="pageInfo && !pageInfo.hideHeader" :info="pageInfo"/></template>
@ -20,32 +20,44 @@
</main> </main>
</div> </div>
<XSide v-if="isDesktop" ref="side" class="side"/> <XSideView v-if="isDesktop" ref="side" class="side"/>
<div v-if="isDesktop" ref="widgets" class="widgets"> <div v-if="isDesktop" ref="widgetsEl" class="widgets">
<XWidgets @mounted="attachSticky"/> <XWidgets @mounted="attachSticky"/>
</div> </div>
<div class="buttons" :class="{ navHidden }"> <button class="widgetButton _button" :class="{ show: true }" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
<button ref="navButton" class="button nav _button" @click="showNav"><i class="fas fa-bars"></i><span v-if="navIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<div v-if="isMobile" class="buttons">
<button class="button nav _button" @click="drawerMenuShowing = true"><i class="fas fa-bars"></i><span v-if="menuIndicated" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button> <button class="button home _button" @click="$route.name === 'index' ? top() : $router.push('/')"><i class="fas fa-home"></i></button>
<button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button> <button class="button notifications _button" @click="$router.push('/my/notifications')"><i class="fas fa-bell"></i><span v-if="$i.hasUnreadNotification" class="indicator"><i class="fas fa-circle"></i></span></button>
<button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button> <button class="button widget _button" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button>
<button class="button post _button" @click="post"><i class="fas fa-pencil-alt"></i></button> <button class="button post _button" @click="post()"><i class="fas fa-pencil-alt"></i></button>
</div> </div>
<button class="widgetButton _button" :class="{ navHidden }" @click="widgetsShowing = true"><i class="fas fa-layer-group"></i></button> <transition name="menuDrawer-back">
<div v-if="drawerMenuShowing"
class="menuDrawer-back _modalBg"
@click="drawerMenuShowing = false"
@touchstart.passive="drawerMenuShowing = false"
></div>
</transition>
<transition name="tray-back"> <transition name="menuDrawer">
<XDrawerMenu v-if="drawerMenuShowing" class="menuDrawer"/>
</transition>
<transition name="widgetsDrawer-back">
<div v-if="widgetsShowing" <div v-if="widgetsShowing"
class="tray-back _modalBg" class="widgetsDrawer-back _modalBg"
@click="widgetsShowing = false" @click="widgetsShowing = false"
@touchstart.passive="widgetsShowing = false" @touchstart.passive="widgetsShowing = false"
></div> ></div>
</transition> </transition>
<transition name="tray"> <transition name="widgetsDrawer">
<XWidgets v-if="widgetsShowing" class="tray"/> <XWidgets v-if="widgetsShowing" class="widgetsDrawer"/>
</transition> </transition>
<XCommon/> <XCommon/>
@ -53,60 +65,69 @@
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, defineAsyncComponent } from 'vue'; import { defineComponent, defineAsyncComponent, provide, onMounted, computed, ref, watch } from 'vue';
import { instanceName } from '@/config'; import { instanceName } from '@/config';
import { StickySidebar } from '@/scripts/sticky-sidebar'; import { StickySidebar } from '@/scripts/sticky-sidebar';
import XSidebar from '@/ui/_common_/sidebar.vue'; import XSidebar from '@/ui/_common_/sidebar.vue';
import XDrawerMenu from '@/ui/_common_/sidebar-for-mobile.vue';
import XCommon from './_common_/common.vue'; import XCommon from './_common_/common.vue';
import XSide from './classic.side.vue'; import XSideView from './classic.side.vue';
import * as os from '@/os'; import * as os from '@/os';
import { menuDef } from '@/menu';
import * as symbols from '@/symbols'; import * as symbols from '@/symbols';
import { defaultStore } from '@/store';
import * as EventEmitter from 'eventemitter3';
import { menuDef } from '@/menu';
import { useRoute } from 'vue-router';
import { i18n } from '@/i18n';
const DESKTOP_THRESHOLD = 1100; const DESKTOP_THRESHOLD = 1100;
const MOBILE_THRESHOLD = 500;
export default defineComponent({ export default defineComponent({
components: { components: {
XCommon, XCommon,
XSidebar, XSidebar,
XDrawerMenu,
XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')), XWidgets: defineAsyncComponent(() => import('./universal.widgets.vue')),
XSide, // NOTE: dynamic importAsyncComponentWrapperref XSideView, // NOTE: dynamic importAsyncComponentWrapperref
}, },
provide() { setup() {
return { const isDesktop = ref(window.innerWidth >= DESKTOP_THRESHOLD);
sideViewHook: this.isDesktop ? (url) => { const isMobile = ref(window.innerWidth <= MOBILE_THRESHOLD);
this.$refs.side.navigate(url); window.addEventListener('resize', () => {
} : null isMobile.value = window.innerWidth <= MOBILE_THRESHOLD;
}; });
},
data() { const pageInfo = ref();
return { const widgetsEl = ref<HTMLElement>();
pageInfo: null, const widgetsShowing = ref(false);
isDesktop: window.innerWidth >= DESKTOP_THRESHOLD,
menuDef: menuDef,
navHidden: false,
widgetsShowing: false,
wallpaper: localStorage.getItem('wallpaper') != null,
};
},
computed: { const sideViewController = new EventEmitter();
navIndicated(): boolean {
for (const def in this.menuDef) { provide('sideViewHook', isDesktop.value ? (url) => {
sideViewController.emit('navigate', url);
} : null);
const menuIndicated = computed(() => {
for (const def in menuDef) {
if (def === 'notifications') continue; // if (def === 'notifications') continue; //
if (this.menuDef[def].indicated) return true; if (menuDef[def].indicated) return true;
} }
return false; return false;
} });
},
const drawerMenuShowing = ref(false);
const route = useRoute();
watch(route, () => {
drawerMenuShowing.value = false;
});
created() {
document.documentElement.style.overflowY = 'scroll'; document.documentElement.style.overflowY = 'scroll';
if (this.$store.state.widgets.length === 0) { if (defaultStore.state.widgets.length === 0) {
this.$store.set('widgets', [{ defaultStore.set('widgets', [{
name: 'calendar', name: 'calendar',
id: 'a', place: 'right', data: {} id: 'a', place: 'right', data: {}
}, { }, {
@ -117,123 +138,129 @@ export default defineComponent({
id: 'c', place: 'right', data: {} id: 'c', place: 'right', data: {}
}]); }]);
} }
},
mounted() { onMounted(() => {
this.adjustUI(); if (!isDesktop.value) {
window.addEventListener('resize', () => {
const ro = new ResizeObserver((entries, observer) => { if (window.innerWidth >= DESKTOP_THRESHOLD) isDesktop.value = true;
this.adjustUI(); }, { passive: true });
}
}); });
ro.observe(this.$refs.contents); const changePage = (page) => {
window.addEventListener('resize', this.adjustUI, { passive: true });
if (!this.isDesktop) {
window.addEventListener('resize', () => {
if (window.innerWidth >= DESKTOP_THRESHOLD) this.isDesktop = true;
}, { passive: true });
}
},
methods: {
changePage(page) {
if (page == null) return; if (page == null) return;
if (page[symbols.PAGE_INFO]) { if (page[symbols.PAGE_INFO]) {
this.pageInfo = page[symbols.PAGE_INFO]; pageInfo.value = page[symbols.PAGE_INFO];
document.title = `${this.pageInfo.title} | ${instanceName}`; document.title = `${pageInfo.value.title} | ${instanceName}`;
} }
}, };
adjustUI() { const onContextmenu = (ev) => {
const navWidth = this.$refs.nav.$el.offsetWidth;
this.navHidden = navWidth === 0;
},
showNav() {
this.$refs.nav.show();
},
attachSticky(el) {
const sticky = new StickySidebar(this.$refs.widgets);
window.addEventListener('scroll', () => {
sticky.calc(window.scrollY);
}, { passive: true });
},
post() {
os.post();
},
top() {
window.scroll({ top: 0, behavior: 'smooth' });
},
back() {
history.back();
},
onTransition() {
if (window._scroll) window._scroll();
},
onContextmenu(e) {
const isLink = (el: HTMLElement) => { const isLink = (el: HTMLElement) => {
if (el.tagName === 'A') return true; if (el.tagName === 'A') return true;
if (el.parentElement) { if (el.parentElement) {
return isLink(el.parentElement); return isLink(el.parentElement);
} }
}; };
if (isLink(e.target)) return; if (isLink(ev.target)) return;
if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(e.target.tagName) || e.target.attributes['contenteditable']) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return;
if (window.getSelection().toString() !== '') return; if (window.getSelection().toString() !== '') return;
const path = this.$route.path; const path = route.path;
os.contextMenu([{ os.contextMenu([{
type: 'label', type: 'label',
text: path, text: path,
}, { }, {
icon: 'fas fa-columns', icon: 'fas fa-columns',
text: this.$ts.openInSideView, text: i18n.locale.openInSideView,
action: () => { action: () => {
this.$refs.side.navigate(path); this.$refs.side.navigate(path);
} }
}, { }, {
icon: 'fas fa-window-maximize', icon: 'fas fa-window-maximize',
text: this.$ts.openInWindow, text: i18n.locale.openInWindow,
action: () => { action: () => {
os.pageWindow(path); os.pageWindow(path);
} }
}], e); }], ev);
}, };
}
const attachSticky = (el) => {
const sticky = new StickySidebar(widgetsEl.value);
window.addEventListener('scroll', () => {
sticky.calc(window.scrollY);
}, { passive: true });
};
return {
pageInfo,
isDesktop,
isMobile,
widgetsEl,
widgetsShowing,
drawerMenuShowing,
menuIndicated,
wallpaper: localStorage.getItem('wallpaper') != null,
changePage,
top: () => {
window.scroll({ top: 0, behavior: 'smooth' });
},
onTransition: () => {
if (window._scroll) window._scroll();
},
post: os.post,
onContextmenu,
attachSticky,
};
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.tray-enter-active, .widgetsDrawer-enter-active,
.tray-leave-active { .widgetsDrawer-leave-active {
opacity: 1; opacity: 1;
transform: translateX(0); transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
} }
.tray-enter-from, .widgetsDrawer-enter-from,
.tray-leave-active { .widgetsDrawer-leave-active {
opacity: 0; opacity: 0;
transform: translateX(240px); transform: translateX(240px);
} }
.tray-back-enter-active, .widgetsDrawer-back-enter-active,
.tray-back-leave-active { .widgetsDrawer-back-leave-active {
opacity: 1; opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
} }
.tray-back-enter-from, .widgetsDrawer-back-enter-from,
.tray-back-leave-active { .widgetsDrawer-back-leave-active {
opacity: 0; opacity: 0;
} }
.mk-app { .menuDrawer-enter-active,
.menuDrawer-leave-active {
opacity: 1;
transform: translateX(0);
transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menuDrawer-enter-from,
.menuDrawer-leave-active {
opacity: 0;
transform: translateX(-240px);
}
.menuDrawer-back-enter-active,
.menuDrawer-back-leave-active {
opacity: 1;
transition: opacity 300ms cubic-bezier(0.23, 1, 0.32, 1);
}
.menuDrawer-back-enter-from,
.menuDrawer-back-leave-active {
opacity: 0;
}
.dkgtipfy {
$ui-font-size: 1em; // TODO: $ui-font-size: 1em; // TODO:
$widgets-hide-threshold: 1090px; $widgets-hide-threshold: 1090px;
@ -285,6 +312,7 @@ export default defineComponent({
} }
} }
/*
> .widgetButton { > .widgetButton {
display: block; display: block;
position: fixed; position: fixed;
@ -305,12 +333,34 @@ export default defineComponent({
@media (min-width: ($widgets-hide-threshold + 1px)) { @media (min-width: ($widgets-hide-threshold + 1px)) {
display: none; display: none;
} }
}*/
> .widgetButton {
display: none;
}
> .widgetsDrawer-back {
z-index: 1001;
}
> .widgetsDrawer {
position: fixed;
top: 0;
right: 0;
z-index: 1001;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100);
padding: var(--margin);
box-sizing: border-box;
overflow: auto;
background: var(--bg);
} }
> .buttons { > .buttons {
position: fixed; position: fixed;
z-index: 1000; z-index: 1000;
bottom: 0; bottom: 0;
left: 0;
padding: 16px; padding: 16px;
display: flex; display: flex;
width: 100%; width: 100%;
@ -319,10 +369,6 @@ export default defineComponent({
backdrop-filter: var(--blur, blur(32px)); backdrop-filter: var(--blur, blur(32px));
background-color: var(--header); background-color: var(--header);
&:not(.navHidden) {
display: none;
}
> .button { > .button {
position: relative; position: relative;
flex: 1; flex: 1;
@ -380,22 +426,23 @@ export default defineComponent({
} }
} }
> .tray-back { > .menuDrawer-back {
z-index: 1001; z-index: 1001;
} }
> .tray { > .menuDrawer {
position: fixed; position: fixed;
top: 0; top: 0;
right: 0; left: 0;
z-index: 1001; z-index: 1001;
// 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ // 100vh ... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
height: calc(var(--vh, 1vh) * 100); height: calc(var(--vh, 1vh) * 100);
padding: var(--margin); width: 240px;
box-sizing: border-box; box-sizing: border-box;
overflow: auto; overflow: auto;
background: var(--bg); background: var(--bg);
} }
} }
</style> </style>