diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-07-19 11:36:35 +0900 |
|---|---|---|
| committer | syuilo <Syuilotan@yahoo.co.jp> | 2021-07-19 11:36:35 +0900 |
| commit | 42d293ee60c671ba424671d49072fc6c880d44b0 (patch) | |
| tree | a053b98c9fa27ae2fecf618b1763c75ce645f1ee /src/client/ui | |
| parent | 認証の修正 (#7597) (diff) | |
| download | misskey-42d293ee60c671ba424671d49072fc6c880d44b0.tar.gz misskey-42d293ee60c671ba424671d49072fc6c880d44b0.tar.bz2 misskey-42d293ee60c671ba424671d49072fc6c880d44b0.zip | |
Classic UI
Diffstat (limited to 'src/client/ui')
| -rw-r--r-- | src/client/ui/_common_/sidebar.vue | 8 | ||||
| -rw-r--r-- | src/client/ui/chat/index.vue | 4 | ||||
| -rw-r--r-- | src/client/ui/deck.vue | 4 | ||||
| -rw-r--r-- | src/client/ui/default.header.vue | 274 | ||||
| -rw-r--r-- | src/client/ui/default.sidebar.vue | 8 | ||||
| -rw-r--r-- | src/client/ui/default.vue | 55 | ||||
| -rw-r--r-- | src/client/ui/default.widgets.vue | 16 | ||||
| -rw-r--r-- | src/client/ui/desktop.vue | 4 | ||||
| -rw-r--r-- | src/client/ui/universal.vue | 4 |
9 files changed, 345 insertions, 32 deletions
diff --git a/src/client/ui/_common_/sidebar.vue b/src/client/ui/_common_/sidebar.vue index df11877147..073907cde9 100644 --- a/src/client/ui/_common_/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -49,7 +49,7 @@ import { defineComponent } from 'vue'; import { host } from '@client/config'; import { search } from '@client/scripts/search'; import * as os from '@client/os'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import { getAccounts, addAccount, login } from '@client/account'; export default defineComponent({ @@ -67,7 +67,7 @@ export default defineComponent({ showing: false, accounts: [], connection: null, - menuDef: sidebarDef, + menuDef: menuDef, iconOnly: false, hidden: this.defaultHidden, }; @@ -92,7 +92,7 @@ export default defineComponent({ this.showing = false; }, - '$store.reactiveState.sidebarDisplay.value'() { + '$store.reactiveState.menuDisplay.value'() { this.calcViewState(); }, @@ -116,7 +116,7 @@ export default defineComponent({ methods: { calcViewState() { - this.iconOnly = (window.innerWidth <= 1279) || (this.$store.state.sidebarDisplay === 'icon'); + this.iconOnly = (window.innerWidth <= 1279) || (this.$store.state.menuDisplay === 'sideIcon'); if (!this.defaultHidden) { this.hidden = (window.innerWidth <= 650); } diff --git a/src/client/ui/chat/index.vue b/src/client/ui/chat/index.vue index c28436ed5c..d45369e8b0 100644 --- a/src/client/ui/chat/index.vue +++ b/src/client/ui/chat/index.vue @@ -142,7 +142,7 @@ import XTimeline from './timeline.vue'; import XHeaderClock from './header-clock.vue'; import * as os from '@client/os'; import { router } from '@client/router'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import { search } from '@client/scripts/search'; import copyToClipboard from '@client/scripts/copy-to-clipboard'; import { store } from './store'; @@ -190,7 +190,7 @@ export default defineComponent({ followedChannels: null, featuredChannels: null, currentChannel: null, - menuDef: sidebarDef, + menuDef: menuDef, sideViewOpening: false, instanceName, }; diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue index 935445a54d..4b0189ba77 100644 --- a/src/client/ui/deck.vue +++ b/src/client/ui/deck.vue @@ -37,7 +37,7 @@ import DeckColumnCore from '@client/ui/deck/column-core.vue'; import XSidebar from '@client/ui/_common_/sidebar.vue'; import { getScrollContainer } from '@client/scripts/scroll'; import * as os from '@client/os'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import XCommon from './_common_/common.vue'; import { deckStore, addColumn, loadDeck } from './deck/deck-store'; @@ -60,7 +60,7 @@ export default defineComponent({ return { deckStore, host: host, - menuDef: sidebarDef, + menuDef: menuDef, wallpaper: localStorage.getItem('wallpaper') != null, }; }, diff --git a/src/client/ui/default.header.vue b/src/client/ui/default.header.vue new file mode 100644 index 0000000000..a67883020f --- /dev/null +++ b/src/client/ui/default.header.vue @@ -0,0 +1,274 @@ +<template> +<div class="azykntjl"> + <div class="body"> + <div class="left"> + <MkA class="item index" active-class="active" to="/" exact v-click-anime v-tooltip="$ts.timeline"> + <i class="fas fa-home fa-fw"></i> + </MkA> + <template v-for="item in menu"> + <div v-if="item === '-'" class="divider"></div> + <component v-else-if="menuDef[item] && (menuDef[item].show !== false)" :is="menuDef[item].to ? 'MkA' : 'button'" class="item _button" :class="item" active-class="active" v-on="menuDef[item].action ? { click: menuDef[item].action } : {}" :to="menuDef[item].to" v-click-anime v-tooltip="$ts[menuDef[item].title]"> + <i class="fa-fw" :class="menuDef[item].icon"></i> + <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" class="item" active-class="active" to="/instance" :behavior="settingsWindowed ? 'modalWindow' : null" v-click-anime v-tooltip="$ts.instance"> + <i class="fas fa-server fa-fw"></i> + </MkA> + <button class="item _button" @click="more" v-click-anime> + <i class="fas fa-ellipsis-h fa-fw"></i> + <span v-if="otherNavItemIndicated" class="indicator"><i class="fas fa-circle"></i></span> + </button> + </div> + <div class="right"> + <MkA class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null" v-click-anime v-tooltip="$ts.settings"> + <i class="fas fa-cog fa-fw"></i> + </MkA> + <button class="item _button account" @click="openAccountMenu" v-click-anime> + <MkAvatar :user="$i" class="avatar"/><MkAcct class="acct" :user="$i"/> + </button> + <div class="post" @click="post"> + <MkButton class="button" primary full> + <i class="fas fa-pencil-alt fa-fw"></i> + </MkButton> + </div> + </div> + </div> +</div> +</template> + +<script lang="ts"> +import { defineComponent } from 'vue'; +import { host } from '@client/config'; +import { search } from '@client/scripts/search'; +import * as os from '@client/os'; +import { menuDef } from '@client/menu'; +import { getAccounts, addAccount, login } from '@client/account'; +import MkButton from '@client/components/ui/button.vue'; + +export default defineComponent({ + components: { + MkButton, + }, + + data() { + return { + host: host, + accounts: [], + connection: null, + menuDef: menuDef, + settingsWindowed: false, + }; + }, + + 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; + }, + }, + + watch: { + '$store.reactiveState.menuDisplay.value'() { + this.calcViewState(); + }, + }, + + created() { + window.addEventListener('resize', this.calcViewState); + this.calcViewState(); + }, + + methods: { + calcViewState() { + this.settingsWindowed = (window.innerWidth > 1400); + }, + + post() { + os.post(); + }, + + search() { + search(); + }, + + async openAccountMenu(ev) { + const storedAccounts = getAccounts().filter(x => x.id !== this.$i.id); + const accountsPromise = os.api('users/show', { userIds: storedAccounts.map(x => x.id) }); + + const accountItemPromises = storedAccounts.map(a => new Promise(res => { + accountsPromise.then(accounts => { + const account = accounts.find(x => x.id === a.id); + if (account == null) return res(null); + res({ + type: 'user', + user: account, + action: () => { this.switchAccount(account); } + }); + }); + })); + + os.modalMenu([...[{ + type: 'link', + text: this.$ts.profile, + to: `/@${ this.$i.username }`, + avatar: this.$i, + }, null, ...accountItemPromises, { + icon: 'fas fa-plus', + text: this.$ts.addAccount, + action: () => { + os.modalMenu([{ + text: this.$ts.existingAccount, + action: () => { this.addAccount(); }, + }, { + text: this.$ts.createAccount, + action: () => { this.createAccount(); }, + }], ev.currentTarget || ev.target); + }, + }]], ev.currentTarget || ev.target, { + align: 'left' + }); + }, + + more(ev) { + os.popup(import('@client/components/launch-pad.vue'), {}, { + }, 'closed'); + }, + + addAccount() { + os.popup(import('@client/components/signin-dialog.vue'), {}, { + done: res => { + addAccount(res.id, res.i); + os.success(); + }, + }, 'closed'); + }, + + createAccount() { + os.popup(import('@client/components/signup-dialog.vue'), {}, { + done: res => { + addAccount(res.id, res.i); + this.switchAccountWithToken(res.i); + }, + }, 'closed'); + }, + + switchAccount(account: any) { + const storedAccounts = getAccounts(); + const token = storedAccounts.find(x => x.id === account.id).token; + this.switchAccountWithToken(token); + }, + + switchAccountWithToken(token: string) { + login(token); + }, + } +}); +</script> + +<style lang="scss" scoped> +.azykntjl { + $height: 60px; + $avatar-size: 32px; + $avatar-margin: 8px; + + position: sticky; + top: 0; + z-index: 1000; + width: 100%; + height: $height; + background-color: var(--bg); + + > .body { + max-width: 1380px; + margin: 0 auto; + display: flex; + + > .right, + > .left { + + > .item { + position: relative; + font-size: 0.9em; + display: inline-block; + padding: 0 12px; + line-height: $height; + + > i, + > .avatar { + margin-right: 0; + } + + > i { + left: 10px; + } + + > .avatar { + width: $avatar-size; + height: $avatar-size; + vertical-align: middle; + } + + > .indicator { + position: absolute; + top: 0; + left: 0; + color: var(--navIndicator); + font-size: 8px; + animation: blink 1s infinite; + } + + &:hover { + text-decoration: none; + color: var(--navHoverFg); + } + + &.active { + color: var(--navActive); + } + } + + > .divider { + display: inline-block; + height: 16px; + margin: 0 10px; + border-right: solid 0.5px var(--divider); + } + + > .post { + display: inline-block; + + > .button { + width: 40px; + height: 40px; + padding: 0; + min-width: 0; + } + } + + > .account { + display: inline-flex; + align-items: center; + vertical-align: top; + margin-right: 8px; + + > .acct { + margin-left: 8px; + } + } + } + + > .right { + margin-left: auto; + } + } +} +</style> diff --git a/src/client/ui/default.sidebar.vue b/src/client/ui/default.sidebar.vue index c7e2d30c7a..2e0336878d 100644 --- a/src/client/ui/default.sidebar.vue +++ b/src/client/ui/default.sidebar.vue @@ -45,7 +45,7 @@ import { defineComponent } from 'vue'; import { host } from '@client/config'; import { search } from '@client/scripts/search'; import * as os from '@client/os'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import { getAccounts, addAccount, login } from '@client/account'; import MkButton from '@client/components/ui/button.vue'; import { StickySidebar } from '@client/scripts/sticky-sidebar'; @@ -62,7 +62,7 @@ export default defineComponent({ host: host, accounts: [], connection: null, - menuDef: sidebarDef, + menuDef: menuDef, iconOnly: false, settingsWindowed: false, }; @@ -83,7 +83,7 @@ export default defineComponent({ }, watch: { - '$store.reactiveState.sidebarDisplay.value'() { + '$store.reactiveState.menuDisplay.value'() { this.calcViewState(); }, @@ -108,7 +108,7 @@ export default defineComponent({ methods: { calcViewState() { - this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon'); + this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.menuDisplay === 'sideIcon'); this.settingsWindowed = (window.innerWidth > 1400); }, diff --git a/src/client/ui/default.vue b/src/client/ui/default.vue index 3c87bf7ab4..f18685d78c 100644 --- a/src/client/ui/default.vue +++ b/src/client/ui/default.vue @@ -1,9 +1,16 @@ <template> <div class="mk-app" :class="{ wallpaper, isMobile }"> - <div class="columns" :class="{ fullView }"> - <div class="sidebar" ref="sidebar" v-if="!isMobile"> - <XSidebar/> - </div> + <XHeaderMenu v-if="showMenuOnTop"/> + + <div class="columns" :class="{ fullView, withGlobalHeader: showMenuOnTop }"> + <template v-if="!isMobile"> + <div class="sidebar" v-if="!showMenuOnTop"> + <XSidebar/> + </div> + <div class="widgets left" ref="widgetsLeft" v-else> + <XWidgets @mounted="attachSticky('widgetsLeft')" :place="'left'"/> + </div> + </template> <main class="main _panel" @contextmenu.stop="onContextmenu"> <header class="header" @click="onHeaderClick"> @@ -20,8 +27,8 @@ </div> </main> - <div v-if="isDesktop" class="widgets" ref="widgets"> - <XWidgets @mounted="attachSticky"/> + <div v-if="isDesktop" class="widgets right" ref="widgetsRight"> + <XWidgets @mounted="attachSticky('widgetsRight')" :place="null"/> </div> </div> @@ -60,7 +67,7 @@ import XDrawerSidebar from '@client/ui/_common_/sidebar.vue'; import XCommon from './_common_/common.vue'; import XHeader from './_common_/header.vue'; import * as os from '@client/os'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import * as symbols from '@client/symbols'; const DESKTOP_THRESHOLD = 1100; @@ -72,13 +79,14 @@ export default defineComponent({ XSidebar, XDrawerSidebar, XHeader, + XHeaderMenu: defineAsyncComponent(() => import('./default.header.vue')), XWidgets: defineAsyncComponent(() => import('./default.widgets.vue')), }, data() { return { pageInfo: null, - menuDef: sidebarDef, + menuDef: menuDef, isMobile: window.innerWidth <= MOBILE_THRESHOLD, isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, widgetsShowing: false, @@ -94,6 +102,10 @@ export default defineComponent({ if (this.menuDef[def].indicated) return true; } return false; + }, + + showMenuOnTop(): boolean { + return !this.isMobile && this.$store.state.menuDisplay === 'top'; } }, @@ -130,8 +142,8 @@ export default defineComponent({ } }, - attachSticky() { - const sticky = new StickySidebar(this.$refs.widgets, 16); + attachSticky(ref) { + const sticky = new StickySidebar(this.$refs[ref], this.$store.state.menuDisplay === 'top' ? 0 : 16, this.$store.state.menuDisplay === 'top' ? 60 : 0); // TODO: ヘッダーの高さを60pxと決め打ちしているのを直す window.addEventListener('scroll', () => { sticky.calc(window.scrollY); }, { passive: true }); @@ -285,7 +297,7 @@ export default defineComponent({ > .header { position: sticky; z-index: 1000; - top: 0; + top: var(--globalHeaderHeight, 0px); height: $header-height; line-height: $header-height; -webkit-backdrop-filter: blur(32px); @@ -296,7 +308,7 @@ export default defineComponent({ > .content { background: var(--bg); - --stickyTop: #{$header-height}; + --stickyTop: calc(var(--globalHeaderHeight, 0px) + #{$header-height}); } @media (max-width: 850px) { @@ -317,12 +329,31 @@ export default defineComponent({ @media (max-width: $widgets-hide-threshold) { display: none; } + + &.left { + margin-right: 16px; + } } > .sidebar { margin-top: 16px; } + &.withGlobalHeader { + --globalHeaderHeight: 60px; // TODO: 60pxと決め打ちしているのを直す + + > .main { + margin-top: 2px; + border-radius: var(--radius); + box-shadow: 0 0 0 2px var(--divider); + } + + > .widgets { + --stickyTop: var(--globalHeaderHeight); + margin-top: 0px; + } + } + @media (max-width: 850px) { margin: 0; diff --git a/src/client/ui/default.widgets.vue b/src/client/ui/default.widgets.vue index cf5d1e07ce..0bacc83d52 100644 --- a/src/client/ui/default.widgets.vue +++ b/src/client/ui/default.widgets.vue @@ -1,6 +1,6 @@ <template> <div class="ddiqwdnk"> - <XWidgets class="widgets" :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> + <XWidgets class="widgets" :edit="editMode" :widgets="$store.reactiveState.widgets.value.filter(w => w.place === place)" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> <MkAd class="a" prefer="square"/> <button v-if="editMode" @click="editMode = false" class="_textButton edit" style="font-size: 0.9em;"><i class="fas fa-check"></i> {{ $ts.editWidgetsExit }}</button> @@ -11,13 +11,18 @@ <script lang="ts"> import { defineComponent, defineAsyncComponent } from 'vue'; import XWidgets from '@client/components/widgets.vue'; -import * as os from '@client/os'; export default defineComponent({ components: { XWidgets }, + props: { + place: { + type: String, + } + }, + emits: ['mounted'], data() { @@ -34,7 +39,7 @@ export default defineComponent({ addWidget(widget) { this.$store.set('widgets', [{ ...widget, - place: null, + place: this.place, }, ...this.$store.state.widgets]); }, @@ -50,7 +55,10 @@ export default defineComponent({ }, updateWidgets(widgets) { - this.$store.set('widgets', widgets); + this.$store.set('widgets', [ + ...this.$store.state.widgets.filter(w => w.place !== this.place), + ...widgets + ]); } } }); diff --git a/src/client/ui/desktop.vue b/src/client/ui/desktop.vue index a60aed6841..b256527a4a 100644 --- a/src/client/ui/desktop.vue +++ b/src/client/ui/desktop.vue @@ -13,7 +13,7 @@ import { search } from '@client/scripts/search'; import XCommon from './_common_/common.vue'; import * as os from '@client/os'; import XSidebar from '@client/ui/_common_/sidebar.vue'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import { ColdDeviceStorage } from '@client/store'; export default defineComponent({ @@ -33,7 +33,7 @@ export default defineComponent({ data() { return { host: host, - menuDef: sidebarDef, + menuDef: menuDef, wallpaper: localStorage.getItem('wallpaper') != null, }; }, diff --git a/src/client/ui/universal.vue b/src/client/ui/universal.vue index fb67ea8985..1e8c4b36d5 100644 --- a/src/client/ui/universal.vue +++ b/src/client/ui/universal.vue @@ -61,7 +61,7 @@ import XCommon from './_common_/common.vue'; import XHeader from './_common_/header.vue'; import XSide from './default.side.vue'; import * as os from '@client/os'; -import { sidebarDef } from '@client/sidebar'; +import { menuDef } from '@client/menu'; import * as symbols from '@client/symbols'; const DESKTOP_THRESHOLD = 1100; @@ -87,7 +87,7 @@ export default defineComponent({ return { pageInfo: null, isDesktop: window.innerWidth >= DESKTOP_THRESHOLD, - menuDef: sidebarDef, + menuDef: menuDef, navHidden: false, widgetsShowing: false, wallpaper: localStorage.getItem('wallpaper') != null, |