diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-09 12:33:08 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-09 12:33:08 +0900 |
| commit | 27c056cbbf6e85a033e85adbafd95a179a9db755 (patch) | |
| tree | d38be1c8fd56bf3d3a6f6a078f643b75292a7dfc /src/client/components | |
| parent | :art: (diff) | |
| download | sharkey-27c056cbbf6e85a033e85adbafd95a179a9db755.tar.gz sharkey-27c056cbbf6e85a033e85adbafd95a179a9db755.tar.bz2 sharkey-27c056cbbf6e85a033e85adbafd95a179a9db755.zip | |
tweak ui
Diffstat (limited to 'src/client/components')
| -rw-r--r-- | src/client/components/global/header.vue | 319 | ||||
| -rw-r--r-- | src/client/components/index.ts | 2 | ||||
| -rw-r--r-- | src/client/components/modal-page-window.vue | 3 | ||||
| -rw-r--r-- | src/client/components/page-window.vue | 6 |
4 files changed, 323 insertions, 7 deletions
diff --git a/src/client/components/global/header.vue b/src/client/components/global/header.vue new file mode 100644 index 0000000000..977912e2bd --- /dev/null +++ b/src/client/components/global/header.vue @@ -0,0 +1,319 @@ +<template> +<div class="fdidabkb" :class="{ slim: narrow, thin }" :style="{ background: bg }" @click="onClick"> + <template v-if="info"> + <div class="titleContainer" @click="showTabsPopup"> + <i v-if="info.icon" class="icon" :class="info.icon"></i> + <MkAvatar v-else-if="info.avatar" class="avatar" :user="info.avatar" :disable-preview="true" :show-indicator="true"/> + + <div class="title"> + <MkUserName v-if="info.userName" :user="info.userName" :nowrap="false" class="title"/> + <div v-else-if="info.title" class="title">{{ info.title }}</div> + <div class="subtitle" v-if="!narrow && info.subtitle"> + {{ info.subtitle }} + </div> + <div class="subtitle activeTab" v-if="narrow && hasTabs"> + {{ info.tabs.find(tab => tab.active)?.title }} + <i class="chevron fas fa-chevron-down"></i> + </div> + </div> + </div> + <div class="tabs" v-if="!narrow"> + <button class="tab _button" v-for="tab in info.tabs" :class="{ active: tab.active }" @click="tab.onClick" v-tooltip="tab.title"> + <i v-if="tab.icon" class="icon" :class="tab.icon"></i> + <span v-if="!tab.iconOnly" class="title">{{ tab.title }}</span> + </button> + </div> + </template> + <div class="buttons right"> + <template v-if="info && info.actions && !narrow"> + <template v-for="action in info.actions"> + <MkButton class="fullButton" v-if="action.asFullButton" @click.stop="action.handler" primary><i :class="action.icon" style="margin-right: 6px;"></i>{{ action.text }}</MkButton> + <button v-else class="_button button" :class="{ highlighted: action.highlighted }" @click.stop="action.handler" @touchstart="preventDrag" v-tooltip="action.text"><i :class="action.icon"></i></button> + </template> + </template> + <button v-if="shouldShowMenu" class="_button button" @click.stop="showMenu" @touchstart="preventDrag" v-tooltip="$ts.menu"><i class="fas fa-ellipsis-h"></i></button> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import * as tinycolor from 'tinycolor2'; +import { popupMenu } from '@client/os'; +import { url } from '@client/config'; +import { scrollToTop } from '@client/scripts/scroll'; +import MkButton from '@client/components/ui/button.vue'; + +export default defineComponent({ + components: { + MkButton + }, + + props: { + info: { + required: true + }, + menu: { + required: false + }, + thin: { + required: false, + default: false + }, + }, + + data() { + return { + bg: null, + narrow: false, + height: 0, + }; + }, + + computed: { + hasTabs(): boolean { + return this.info.tabs && this.info.tabs.length > 0; + }, + + shouldShowMenu() { + if (this.info == null) return false; + if (this.info.actions != null && this.narrow) return true; + if (this.info.menu != null) return true; + if (this.info.share != null) return true; + if (this.menu != null) return true; + return false; + } + }, + + mounted() { + const rawBg = this.info?.bg || 'var(--bg)'; + const bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); + bg.setAlpha(0.85); + this.bg = bg.toRgbString(); + + if (this.$el.parentElement == null) return; + this.narrow = this.$el.parentElement.offsetWidth < 500; + new ResizeObserver((entries, observer) => { + this.narrow = this.$el.parentElement.offsetWidth < 500; + }).observe(this.$el.parentElement); + }, + + methods: { + share() { + navigator.share({ + url: url + this.info.path, + ...this.info.share, + }); + }, + + showMenu(ev) { + let menu = this.info.menu ? this.info.menu() : []; + if (this.narrow && this.info.actions) { + menu = [...this.info.actions.map(x => ({ + text: x.text, + icon: x.icon, + action: x.handler + })), menu.length > 0 ? null : undefined, ...menu]; + } + if (this.info.share) { + if (menu.length > 0) menu.push(null); + menu.push({ + text: this.$ts.share, + icon: 'fas fa-share-alt', + action: this.share + }); + } + if (this.menu) { + if (menu.length > 0) menu.push(null); + menu = menu.concat(this.menu); + } + popupMenu(menu, ev.currentTarget || ev.target); + }, + + showTabsPopup(ev) { + if (!this.hasTabs) return; + if (!this.narrow) return; + ev.preventDefault(); + ev.stopPropagation(); + const menu = this.info.tabs.map(tab => ({ + text: tab.title, + icon: tab.icon, + action: tab.onClick, + })); + popupMenu(menu, ev.currentTarget || ev.target); + }, + + preventDrag(ev) { + ev.stopPropagation(); + }, + + onClick(ev) { + scrollToTop(this.$el, { behavior: 'smooth' }); + } + } +}); +</script> + +<style lang="scss" scoped> +.fdidabkb { + --height: 60px; + display: flex; + position: sticky; + top: var(--stickyTop, 0); + z-index: 1000; + width: 100%; + -webkit-backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--blur, blur(15px)); + + &.thin { + --height: 50px; + } + + &.slim { + text-align: center; + + > .titleContainer { + margin: 0 auto; + } + + > .buttons { + &.right { + margin-left: 0; + } + } + } + + > .buttons { + --margin: 8px; + display: flex; + align-items: center; + height: var(--height); + margin: 0 var(--margin); + + &.right { + margin-left: auto; + } + + &:empty { + width: var(--height); + } + + > .button { + display: flex; + align-items: center; + justify-content: center; + height: calc(var(--height) - (var(--margin) * 2)); + width: calc(var(--height) - (var(--margin) * 2)); + box-sizing: border-box; + position: relative; + border-radius: 5px; + + &:hover { + background: rgba(0, 0, 0, 0.05); + } + + &.highlighted { + color: var(--accent); + } + } + + > .fullButton { + & + .fullButton { + margin-left: 12px; + } + } + } + + > .titleContainer { + display: flex; + align-items: center; + overflow: auto; + white-space: nowrap; + text-align: left; + font-weight: bold; + flex-shrink: 0; + margin-left: 24px; + + > .avatar { + $size: 32px; + display: inline-block; + width: $size; + height: $size; + vertical-align: bottom; + margin: 0 8px; + pointer-events: none; + } + + > .icon { + margin-right: 8px; + } + + > .title { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.1; + + > .subtitle { + opacity: 0.6; + font-size: 0.8em; + font-weight: normal; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + + &.activeTab { + text-align: center; + + > .chevron { + display: inline-block; + margin-left: 6px; + } + } + } + } + } + + > .tabs { + margin-left: 16px; + font-size: 0.8em; + overflow: auto; + white-space: nowrap; + + > .tab { + display: inline-block; + position: relative; + padding: 0 10px; + height: 100%; + font-weight: normal; + opacity: 0.7; + + &:hover { + opacity: 1; + } + + &.active { + opacity: 1; + + &:after { + content: ""; + display: block; + position: absolute; + bottom: 0; + left: 0; + right: 0; + margin: 0 auto; + width: 100%; + height: 3px; + background: var(--accent); + } + } + + > .icon + .title { + margin-left: 8px; + } + } + } +} +</style> diff --git a/src/client/components/index.ts b/src/client/components/index.ts index 8b914c5eec..8f071dfce1 100644 --- a/src/client/components/index.ts +++ b/src/client/components/index.ts @@ -13,6 +13,7 @@ import i18n from './global/i18n'; import loading from './global/loading.vue'; import error from './global/error.vue'; import ad from './global/ad.vue'; +import header from './global/header.vue'; export default function(app: App) { app.component('I18n', i18n); @@ -28,4 +29,5 @@ export default function(app: App) { app.component('MkLoading', loading); app.component('MkError', error); app.component('MkAd', ad); + app.component('MkHeader', header); } diff --git a/src/client/components/modal-page-window.vue b/src/client/components/modal-page-window.vue index 31931a89b7..c20e2b3087 100644 --- a/src/client/components/modal-page-window.vue +++ b/src/client/components/modal-page-window.vue @@ -11,7 +11,6 @@ <button class="_button" @click="$refs.modal.close()"><i class="fas fa-times"></i></button> </div> <div class="body _flat_"> - <XHeader v-if="!pageInfo?.hide" :info="pageInfo"/> <keep-alive> <component :is="component" v-bind="props" :ref="changePage"/> </keep-alive> @@ -23,7 +22,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import MkModal from '@client/components/ui/modal.vue'; -import XHeader from '@client/ui/_common_/header.vue'; import { popout } from '@client/scripts/popout'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { resolve } from '@client/router'; @@ -34,7 +32,6 @@ import * as os from '@client/os'; export default defineComponent({ components: { MkModal, - XHeader, }, inject: { diff --git a/src/client/components/page-window.vue b/src/client/components/page-window.vue index a8a8b76100..ec4c03c3d8 100644 --- a/src/client/components/page-window.vue +++ b/src/client/components/page-window.vue @@ -9,14 +9,14 @@ > <template #header> <template v-if="pageInfo"> - {{ pageInfo.title }} + <i v-if="pageInfo.icon" class="icon" :class="pageInfo.icon" style="margin-right: 0.5em;"></i> + <span>{{ pageInfo.title }}</span> </template> </template> <template #headerLeft> <button v-if="history.length > 0" class="_button" @click="back()"><i class="fas fa-arrow-left"></i></button> </template> <div class="yrolvcoq _flat_"> - <XHeader :info="pageInfo"/> <component :is="component" v-bind="props" :ref="changePage"/> </div> </XWindow> @@ -25,7 +25,6 @@ <script lang="ts"> import { defineComponent } from 'vue'; import XWindow from '@client/components/ui/window.vue'; -import XHeader from '@client/ui/_common_/header.vue'; import { popout } from '@client/scripts/popout'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { resolve } from '@client/router'; @@ -35,7 +34,6 @@ import * as symbols from '@client/symbols'; export default defineComponent({ components: { XWindow, - XHeader, }, inject: { |