Classic UI
This commit is contained in:
		@@ -36,7 +36,7 @@
 | 
			
		||||
<script lang="ts">
 | 
			
		||||
import { defineComponent } from 'vue';
 | 
			
		||||
import MkModal from '@client/components/ui/modal.vue';
 | 
			
		||||
import { sidebarDef } from '@client/sidebar';
 | 
			
		||||
import { menuDef } from '@client/menu';
 | 
			
		||||
import { instanceName } from '@client/config';
 | 
			
		||||
 | 
			
		||||
export default defineComponent({
 | 
			
		||||
@@ -48,7 +48,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			menuDef: sidebarDef,
 | 
			
		||||
			menuDef: menuDef,
 | 
			
		||||
			items: [],
 | 
			
		||||
			instanceName,
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -191,6 +191,8 @@ export default defineComponent({
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	> .content {
 | 
			
		||||
		--stickyTop: 0px;
 | 
			
		||||
 | 
			
		||||
		&.omitted {
 | 
			
		||||
			position: relative;
 | 
			
		||||
			max-height: var(--maxHeight);
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ export default defineComponent({
 | 
			
		||||
 | 
			
		||||
	props: {
 | 
			
		||||
		widgets: {
 | 
			
		||||
			type: Array,
 | 
			
		||||
			required: true,
 | 
			
		||||
		},
 | 
			
		||||
		edit: {
 | 
			
		||||
 
 | 
			
		||||
@@ -5,7 +5,7 @@ import { i18n } from '@client/i18n';
 | 
			
		||||
import { $i } from './account';
 | 
			
		||||
import { unisonReload } from '@client/scripts/unison-reload';
 | 
			
		||||
 | 
			
		||||
export const sidebarDef = {
 | 
			
		||||
export const menuDef = {
 | 
			
		||||
	notifications: {
 | 
			
		||||
		title: 'notifications',
 | 
			
		||||
		icon: 'fas fa-bell',
 | 
			
		||||
@@ -26,7 +26,7 @@
 | 
			
		||||
				<template #label>{{ $ts.clientSettings }}</template>
 | 
			
		||||
				<FormLink :active="page === 'general'" replace to="/settings/general"><template #icon><i class="fas fa-cogs"></i></template>{{ $ts.general }}</FormLink>
 | 
			
		||||
				<FormLink :active="page === 'theme'" replace to="/settings/theme"><template #icon><i class="fas fa-palette"></i></template>{{ $ts.theme }}</FormLink>
 | 
			
		||||
				<FormLink :active="page === 'sidebar'" replace to="/settings/sidebar"><template #icon><i class="fas fa-list-ul"></i></template>{{ $ts.sidebar }}</FormLink>
 | 
			
		||||
				<FormLink :active="page === 'menu'" replace to="/settings/menu"><template #icon><i class="fas fa-list-ul"></i></template>{{ $ts.menu }}</FormLink>
 | 
			
		||||
				<FormLink :active="page === 'sounds'" replace to="/settings/sounds"><template #icon><i class="fas fa-music"></i></template>{{ $ts.sounds }}</FormLink>
 | 
			
		||||
				<FormLink :active="page === 'plugin'" replace to="/settings/plugin"><template #icon><i class="fas fa-plug"></i></template>{{ $ts.plugins }}</FormLink>
 | 
			
		||||
			</FormGroup>
 | 
			
		||||
@@ -121,7 +121,7 @@ export default defineComponent({
 | 
			
		||||
				case 'theme': return defineAsyncComponent(() => import('./theme.vue'));
 | 
			
		||||
				case 'theme/install': return defineAsyncComponent(() => import('./theme.install.vue'));
 | 
			
		||||
				case 'theme/manage': return defineAsyncComponent(() => import('./theme.manage.vue'));
 | 
			
		||||
				case 'sidebar': return defineAsyncComponent(() => import('./sidebar.vue'));
 | 
			
		||||
				case 'menu': return defineAsyncComponent(() => import('./menu.vue'));
 | 
			
		||||
				case 'sounds': return defineAsyncComponent(() => import('./sounds.vue'));
 | 
			
		||||
				case 'custom-css': return defineAsyncComponent(() => import('./custom-css.vue'));
 | 
			
		||||
				case 'deck': return defineAsyncComponent(() => import('./deck.vue'));
 | 
			
		||||
 
 | 
			
		||||
@@ -1,18 +1,18 @@
 | 
			
		||||
<template>
 | 
			
		||||
<FormBase>
 | 
			
		||||
	<FormTextarea v-model:value="items" tall>
 | 
			
		||||
		<span>{{ $ts.sidebar }}</span>
 | 
			
		||||
	<FormTextarea v-model:value="items" tall manual-save>
 | 
			
		||||
		<span>{{ $ts.menu }}</span>
 | 
			
		||||
		<template #desc><button class="_textButton" @click="addItem">{{ $ts.addItem }}</button></template>
 | 
			
		||||
	</FormTextarea>
 | 
			
		||||
 | 
			
		||||
	<FormRadios v-model="sidebarDisplay">
 | 
			
		||||
	<FormRadios v-model="menuDisplay">
 | 
			
		||||
		<template #desc>{{ $ts.display }}</template>
 | 
			
		||||
		<option value="full">{{ $ts._sidebar.full }}</option>
 | 
			
		||||
		<option value="icon">{{ $ts._sidebar.icon }}</option>
 | 
			
		||||
		<!-- <MkRadio v-model="sidebarDisplay" value="hide" disabled>{{ $ts._sidebar.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
 | 
			
		||||
		<option value="sideFull">{{ $ts._menuDisplay.sideFull }}</option>
 | 
			
		||||
		<option value="sideIcon">{{ $ts._menuDisplay.sideIcon }}</option>
 | 
			
		||||
		<option value="top">{{ $ts._menuDisplay.top }}</option>
 | 
			
		||||
		<!-- <MkRadio v-model="menuDisplay" value="hide" disabled>{{ $ts._menuDisplay.hide }}</MkRadio>--> <!-- TODO: サイドバーを完全に隠せるようにすると、別途ハンバーガーボタンのようなものをUIに表示する必要があり面倒 -->
 | 
			
		||||
	</FormRadios>
 | 
			
		||||
 | 
			
		||||
	<FormButton @click="save()" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
 | 
			
		||||
	<FormButton @click="reset()" danger><i class="fas fa-redo"></i> {{ $ts.default }}</FormButton>
 | 
			
		||||
</FormBase>
 | 
			
		||||
</template>
 | 
			
		||||
@@ -26,7 +26,7 @@ import FormBase from '@client/components/form/base.vue';
 | 
			
		||||
import FormGroup from '@client/components/form/group.vue';
 | 
			
		||||
import FormButton from '@client/components/form/button.vue';
 | 
			
		||||
import * as os from '@client/os';
 | 
			
		||||
import { sidebarDef } from '@client/sidebar';
 | 
			
		||||
import { menuDef } from '@client/menu';
 | 
			
		||||
import { defaultStore } from '@client/store';
 | 
			
		||||
import * as symbols from '@client/symbols';
 | 
			
		||||
import { unisonReload } from '@client/scripts/unison-reload';
 | 
			
		||||
@@ -44,11 +44,11 @@ export default defineComponent({
 | 
			
		||||
	data() {
 | 
			
		||||
		return {
 | 
			
		||||
			[symbols.PAGE_INFO]: {
 | 
			
		||||
				title: this.$ts.sidebar,
 | 
			
		||||
				title: this.$ts.menu,
 | 
			
		||||
				icon: 'fas fa-list-ul'
 | 
			
		||||
			},
 | 
			
		||||
			menuDef: sidebarDef,
 | 
			
		||||
			items: '',
 | 
			
		||||
			menuDef: menuDef,
 | 
			
		||||
			items: defaultStore.state.menu.join('\n'),
 | 
			
		||||
		}
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
@@ -57,11 +57,17 @@ export default defineComponent({
 | 
			
		||||
			return this.items.trim().split('\n').filter(x => x.trim() !== '');
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		sidebarDisplay: defaultStore.makeGetterSetter('sidebarDisplay')
 | 
			
		||||
		menuDisplay: defaultStore.makeGetterSetter('menuDisplay')
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	created() {
 | 
			
		||||
		this.items = this.$store.state.menu.join('\n');
 | 
			
		||||
	watch: {
 | 
			
		||||
		menuDisplay() {
 | 
			
		||||
			this.reloadAsk();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		items() {
 | 
			
		||||
			this.save();
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	mounted() {
 | 
			
		||||
@@ -85,7 +91,6 @@ export default defineComponent({
 | 
			
		||||
			});
 | 
			
		||||
			if (canceled) return;
 | 
			
		||||
			this.items = [...this.splited, item].join('\n');
 | 
			
		||||
			this.save();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		save() {
 | 
			
		||||
@@ -96,7 +101,6 @@ export default defineComponent({
 | 
			
		||||
		reset() {
 | 
			
		||||
			this.$store.reset('menu');
 | 
			
		||||
			this.items = this.$store.state.menu.join('\n');
 | 
			
		||||
			this.reloadAsk();
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
		async reloadAsk() {
 | 
			
		||||
@@ -7,8 +7,9 @@ export class StickySidebar {
 | 
			
		||||
	private isTop = false;
 | 
			
		||||
	private isBottom = false;
 | 
			
		||||
	private offsetTop: number;
 | 
			
		||||
	private globalHeaderHeight: number = 59;
 | 
			
		||||
 | 
			
		||||
	constructor(container: StickySidebar['container'], marginTop = 0) {
 | 
			
		||||
	constructor(container: StickySidebar['container'], marginTop = 0, globalHeaderHeight = 0) {
 | 
			
		||||
		this.container = container;
 | 
			
		||||
		this.el = this.container.children[0] as HTMLElement;
 | 
			
		||||
		this.el.style.position = 'sticky';
 | 
			
		||||
@@ -16,30 +17,31 @@ export class StickySidebar {
 | 
			
		||||
		this.container.prepend(this.spacer);
 | 
			
		||||
		this.marginTop = marginTop;
 | 
			
		||||
		this.offsetTop = this.container.getBoundingClientRect().top;
 | 
			
		||||
		this.globalHeaderHeight = globalHeaderHeight;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public calc(scrollTop: number) {
 | 
			
		||||
		if (scrollTop > this.lastScrollTop) { // downscroll
 | 
			
		||||
			const overflow = Math.max(0, (this.el.clientHeight + this.marginTop) - window.innerHeight);
 | 
			
		||||
			const overflow = Math.max(0, this.globalHeaderHeight + (this.el.clientHeight + this.marginTop) - window.innerHeight);
 | 
			
		||||
			this.el.style.bottom = null;
 | 
			
		||||
			this.el.style.top = `${-overflow + this.marginTop}px`;
 | 
			
		||||
			this.el.style.top = `${-overflow + this.marginTop + this.globalHeaderHeight}px`;
 | 
			
		||||
 | 
			
		||||
			this.isBottom = (scrollTop + window.innerHeight) >= (this.el.offsetTop + this.el.clientHeight);
 | 
			
		||||
 | 
			
		||||
			if (this.isTop) {
 | 
			
		||||
				this.isTop = false;
 | 
			
		||||
				this.spacer.style.marginTop = `${Math.max(0, this.lastScrollTop + this.marginTop - this.offsetTop)}px`;
 | 
			
		||||
				this.spacer.style.marginTop = `${Math.max(0, this.globalHeaderHeight + this.lastScrollTop + this.marginTop - this.offsetTop)}px`;
 | 
			
		||||
			}
 | 
			
		||||
		} else { // upscroll
 | 
			
		||||
			const overflow = (this.el.clientHeight + this.marginTop) - window.innerHeight;
 | 
			
		||||
			const overflow = this.globalHeaderHeight + (this.el.clientHeight + this.marginTop) - window.innerHeight;
 | 
			
		||||
			this.el.style.top = null;
 | 
			
		||||
			this.el.style.bottom = `${-overflow}px`;
 | 
			
		||||
 | 
			
		||||
			this.isTop = scrollTop <= this.el.offsetTop;
 | 
			
		||||
			this.isTop = scrollTop + this.marginTop + this.globalHeaderHeight <= this.el.offsetTop;
 | 
			
		||||
 | 
			
		||||
			if (this.isBottom) {
 | 
			
		||||
				this.isBottom = false;
 | 
			
		||||
				this.spacer.style.marginTop = `${this.lastScrollTop + this.marginTop - this.offsetTop - overflow}px`;
 | 
			
		||||
				this.spacer.style.marginTop = `${this.globalHeaderHeight + this.lastScrollTop + this.marginTop - this.offsetTop - overflow}px`;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -90,6 +90,7 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
			
		||||
		default: [] as {
 | 
			
		||||
			name: string;
 | 
			
		||||
			id: string;
 | 
			
		||||
			place: string;
 | 
			
		||||
			data: Record<string, any>;
 | 
			
		||||
		}[]
 | 
			
		||||
	},
 | 
			
		||||
@@ -185,9 +186,9 @@ export const defaultStore = markRaw(new Storage('base', {
 | 
			
		||||
		where: 'device',
 | 
			
		||||
		default: false
 | 
			
		||||
	},
 | 
			
		||||
	sidebarDisplay: {
 | 
			
		||||
	menuDisplay: {
 | 
			
		||||
		where: 'device',
 | 
			
		||||
		default: 'full' as 'full' | 'icon'
 | 
			
		||||
		default: 'sideFull' as 'sideFull' | 'sideIcon' | 'top'
 | 
			
		||||
	},
 | 
			
		||||
	reportError: {
 | 
			
		||||
		where: 'device',
 | 
			
		||||
 
 | 
			
		||||
@@ -161,7 +161,7 @@ hr {
 | 
			
		||||
	background: none;
 | 
			
		||||
	border: none;
 | 
			
		||||
	cursor: pointer;
 | 
			
		||||
	color: var(--fg);
 | 
			
		||||
	color: inherit;
 | 
			
		||||
	touch-action: manipulation;
 | 
			
		||||
	tap-highlight-color: transparent;
 | 
			
		||||
	-webkit-tap-highlight-color: transparent;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
			}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		};
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										274
									
								
								src/client/ui/default.header.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										274
									
								
								src/client/ui/default.header.vue
									
									
									
									
									
										Normal file
									
								
							@@ -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>
 | 
			
		||||
@@ -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);
 | 
			
		||||
		},
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
			]);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
		};
 | 
			
		||||
	},
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user