Classic UI
This commit is contained in:
		
							parent
							
								
									04e27e160e
								
							
						
					
					
						commit
						42d293ee60
					
				
					 19 changed files with 391 additions and 67 deletions
				
			
		|  | @ -528,7 +528,7 @@ removeAllFollowing: "フォローを全解除" | |||
| removeAllFollowingDescription: "{host}からのフォローをすべて解除します。そのインスタンスがもう存在しなくなった場合などに実行してください。" | ||||
| userSuspended: "このユーザーは凍結されています。" | ||||
| userSilenced: "このユーザーはサイレンスされています。" | ||||
| sidebar: "サイドバー" | ||||
| menu: "メニュー" | ||||
| divider: "分割線" | ||||
| addItem: "項目を追加" | ||||
| rooms: "ルーム" | ||||
|  | @ -927,9 +927,10 @@ _channel: | |||
|   usersCount: "{n}人が参加中" | ||||
|   notesCount: "{n}投稿があります" | ||||
| 
 | ||||
| _sidebar: | ||||
|   full: "フル" | ||||
|   icon: "アイコン" | ||||
| _menuDisplay: | ||||
|   sideFull: "横" | ||||
|   sideIcon: "横(アイコン)" | ||||
|   top: "上部" | ||||
|   hide: "隠す" | ||||
| 
 | ||||
| _wordMute: | ||||
|  |  | |||
|  | @ -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, | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue