Tweak UI
This commit is contained in:
		
							parent
							
								
									c22ff4c556
								
							
						
					
					
						commit
						a88e486468
					
				
					 9 changed files with 312 additions and 61 deletions
				
			
		|  | @ -20,12 +20,16 @@ export default defineComponent({ | |||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .rbusrurv { | ||||
| 	// 他のCSSからも参照されるので消さないように | ||||
| 	--formXPadding: 32px; | ||||
| 	--formYPadding: 32px; | ||||
| 
 | ||||
| 	line-height: 1.4em; | ||||
| 	background: var(--bg); | ||||
| 	padding: 32px; | ||||
| 	padding: var(--formYPadding) var(--formXPadding); | ||||
| 
 | ||||
| 	&:not(.wide).max-width_400px { | ||||
| 		padding: 32px 0; | ||||
| 		--formXPadding: 0px; | ||||
| 
 | ||||
| 		> ::v-deep(*) { | ||||
| 			._formPanel { | ||||
|  |  | |||
|  | @ -10,9 +10,17 @@ | |||
| } | ||||
| 
 | ||||
| ._formLabel { | ||||
| 	position: sticky; | ||||
| 	top: var(--stickyTop, 0px); | ||||
| 	background: var(--bg); | ||||
| 	z-index: 2; | ||||
| 	font-size: 80%; | ||||
| 	padding: 0 16px 8px 16px; | ||||
| 	opacity: 0.8; | ||||
| 	margin: -8px calc(var(--formXPadding) * -1) 0 calc(var(--formXPadding) * -1); | ||||
| 	padding: 8px calc(16px + var(--formXPadding)) 8px calc(16px + var(--formXPadding)); | ||||
| 	color: var(--fgTransparentWeak); | ||||
| 	background: var(--X17); | ||||
| 	-webkit-backdrop-filter: blur(10px); | ||||
| 	backdrop-filter: blur(10px); | ||||
| 
 | ||||
| 	&:empty { | ||||
| 		display: none; | ||||
|  |  | |||
|  | @ -93,6 +93,10 @@ export default defineComponent({ | |||
| 			os.pageWindow(this.to); | ||||
| 		}, | ||||
| 
 | ||||
| 		modalWindow() { | ||||
| 			os.modalPageWindow(this.to); | ||||
| 		}, | ||||
| 
 | ||||
| 		popout() { | ||||
| 			popout(this.to); | ||||
| 		}, | ||||
|  | @ -111,6 +115,8 @@ export default defineComponent({ | |||
| 			if (this.behavior) { | ||||
| 				if (this.behavior === 'window') { | ||||
| 					return this.window(); | ||||
| 				} else if (this.behavior === 'modalWindow') { | ||||
| 					return this.modalWindow(); | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										211
									
								
								src/client/components/modal-page-window.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								src/client/components/modal-page-window.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,211 @@ | |||
| <template> | ||||
| <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> | ||||
| 	<div class="hrmcaedk _popup _narrow_" :style="{ width: `${width}px`, height: (height ? `min(${height}px, 100%)` : '100%') }"> | ||||
| 		<div class="header"> | ||||
| 			<button class="_button" @click="back()" v-if="history.length > 0"><Fa :icon="faChevronLeft"/></button> | ||||
| 			<button class="_button" style="pointer-events: none;" v-else><!-- マージンのバランスを取るためのダミー --></button> | ||||
| 			<span class="title"> | ||||
| 				<XHeader :info="pageInfo" :with-back="false"/> | ||||
| 			</span> | ||||
| 			<button class="_button" @click="$refs.modal.close()"><Fa :icon="faTimes"/></button> | ||||
| 		</div> | ||||
| 		<div class="body _flat_"> | ||||
| 			<component :is="component" v-bind="props" :ref="changePage"/> | ||||
| 		</div> | ||||
| 	</div> | ||||
| </MkModal> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent } from 'vue'; | ||||
| import { faExternalLinkAlt, faExpandAlt, faLink, faChevronLeft, faColumns, faTimes } from '@fortawesome/free-solid-svg-icons'; | ||||
| 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'; | ||||
| import { url } from '@client/config'; | ||||
| import * as symbols from '@client/symbols'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkModal, | ||||
| 		XHeader, | ||||
| 	}, | ||||
| 
 | ||||
| 	inject: { | ||||
| 		sideViewHook: { | ||||
| 			default: null | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	provide() { | ||||
| 		return { | ||||
| 			navHook: (path) => { | ||||
| 				this.navigate(path); | ||||
| 			} | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		initialPath: { | ||||
| 			type: String, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		initialComponent: { | ||||
| 			type: Object, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		initialProps: { | ||||
| 			type: Object, | ||||
| 			required: false, | ||||
| 			default: () => {}, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['closed'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			width: 850, | ||||
| 			height: 650, | ||||
| 			pageInfo: null, | ||||
| 			path: this.initialPath, | ||||
| 			component: this.initialComponent, | ||||
| 			props: this.initialProps, | ||||
| 			history: [], | ||||
| 			faChevronLeft, faTimes, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		url(): string { | ||||
| 			return url + this.path; | ||||
| 		}, | ||||
| 
 | ||||
| 		contextmenu() { | ||||
| 			return [{ | ||||
| 				type: 'label', | ||||
| 				text: this.path, | ||||
| 			}, { | ||||
| 				icon: faExpandAlt, | ||||
| 				text: this.$ts.showInPage, | ||||
| 				action: this.expand | ||||
| 			}, this.sideViewHook ? { | ||||
| 				icon: faColumns, | ||||
| 				text: this.$ts.openInSideView, | ||||
| 				action: () => { | ||||
| 					this.sideViewHook(this.path); | ||||
| 					this.$refs.window.close(); | ||||
| 				} | ||||
| 			} : undefined, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$ts.popout, | ||||
| 				action: this.popout | ||||
| 			}, null, { | ||||
| 				icon: faExternalLinkAlt, | ||||
| 				text: this.$ts.openInNewTab, | ||||
| 				action: () => { | ||||
| 					window.open(this.url, '_blank'); | ||||
| 					this.$refs.window.close(); | ||||
| 				} | ||||
| 			}, { | ||||
| 				icon: faLink, | ||||
| 				text: this.$ts.copyLink, | ||||
| 				action: () => { | ||||
| 					copyToClipboard(this.url); | ||||
| 				} | ||||
| 			}]; | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		changePage(page) { | ||||
| 			if (page == null) return; | ||||
| 			if (page[symbols.PAGE_INFO]) { | ||||
| 				this.pageInfo = page[symbols.PAGE_INFO]; | ||||
| 			} | ||||
| 		}, | ||||
| 
 | ||||
| 		navigate(path, record = true) { | ||||
| 			if (record) this.history.push(this.path); | ||||
| 			this.path = path; | ||||
| 			const { component, props } = resolve(path); | ||||
| 			this.component = component; | ||||
| 			this.props = props; | ||||
| 		}, | ||||
| 
 | ||||
| 		back() { | ||||
| 			this.navigate(this.history.pop(), false); | ||||
| 		}, | ||||
| 
 | ||||
| 		expand() { | ||||
| 			this.$router.push(this.path); | ||||
| 			this.$refs.window.close(); | ||||
| 		}, | ||||
| 
 | ||||
| 		popout() { | ||||
| 			popout(this.path, this.$el); | ||||
| 			this.$refs.window.close(); | ||||
| 		}, | ||||
| 	}, | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .hrmcaedk { | ||||
| 	overflow: hidden; | ||||
| 	display: flex; | ||||
| 	flex-direction: column; | ||||
| 	contain: content; | ||||
| 
 | ||||
| 	--root-margin: 24px; | ||||
| 
 | ||||
| 	@media (max-width: 500px) { | ||||
| 		--root-margin: 16px; | ||||
| 	} | ||||
| 
 | ||||
| 	> .header { | ||||
| 		$height: 54px; | ||||
| 		$height-narrow: 42px; | ||||
| 		display: flex; | ||||
| 		flex-shrink: 0; | ||||
| 		box-shadow: 0px 1px var(--divider); | ||||
| 
 | ||||
| 		> button { | ||||
| 			height: $height; | ||||
| 			width: $height; | ||||
| 
 | ||||
| 			@media (max-width: 500px) { | ||||
| 				height: $height-narrow; | ||||
| 				width: $height-narrow; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> .title { | ||||
| 			flex: 1; | ||||
| 			line-height: $height; | ||||
| 			padding-left: 32px; | ||||
| 			font-weight: bold; | ||||
| 			white-space: nowrap; | ||||
| 			overflow: hidden; | ||||
| 			text-overflow: ellipsis; | ||||
| 			pointer-events: none; | ||||
| 
 | ||||
| 			@media (max-width: 500px) { | ||||
| 				line-height: $height-narrow; | ||||
| 				padding-left: 16px; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> button + .title { | ||||
| 			padding-left: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .body { | ||||
| 		overflow: auto; | ||||
| 		background: var(--bg); | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <MkModal ref="modal" @click="$emit('click')" @closed="$emit('closed')"> | ||||
| 	<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: height ? `${height}px` : null }"> | ||||
| 	<div class="ebkgoccj _popup _narrow_" @keydown="onKeydown" :style="{ width: `${width}px`, height: scroll ? (height ? `${height}px` : null) :  (height ? `min(${height}px, 100%)` : '100%') }"> | ||||
| 		<div class="header"> | ||||
| 			<button class="_button" v-if="withOkButton" @click="$emit('close')"><Fa :icon="faTimes"/></button> | ||||
| 			<span class="title"> | ||||
|  | @ -61,6 +61,11 @@ export default defineComponent({ | |||
| 			required: false, | ||||
| 			default: true, | ||||
| 		}, | ||||
| 		scroll: { | ||||
| 			type: Boolean, | ||||
| 			required: false, | ||||
| 			default: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['click', 'close', 'closed', 'ok'], | ||||
|  |  | |||
|  | @ -203,6 +203,15 @@ export function pageWindow(path: string) { | |||
| 	}, {}, 'closed'); | ||||
| } | ||||
| 
 | ||||
| export function modalPageWindow(path: string) { | ||||
| 	const { component, props } = resolve(path); | ||||
| 	popup(import('@client/components/modal-page-window.vue'), { | ||||
| 		initialPath: path, | ||||
| 		initialComponent: markRaw(component), | ||||
| 		initialProps: props, | ||||
| 	}, {}, 'closed'); | ||||
| } | ||||
| 
 | ||||
| export function dialog(props: Record<string, any>) { | ||||
| 	return new Promise((resolve, reject) => { | ||||
| 		popup(import('@client/components/dialog.vue'), props, { | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| <template> | ||||
| <div class="vvcocwet" :class="{ wide: !narrow }" ref="el"> | ||||
| 	<FormBase class="nav" v-if="!narrow || page == null" :force-wide="!narrow"> | ||||
| 	<div class="nav" v-if="!narrow || page == null"> | ||||
| 		<FormBase> | ||||
| 			<FormGroup> | ||||
| 				<template #label>{{ $ts.basicSettings }}</template> | ||||
| 				<FormLink :active="page === 'profile'" replace to="/settings/profile"><template #icon><Fa :icon="faUser"/></template>{{ $ts.profile }}</FormLink> | ||||
|  | @ -35,6 +36,7 @@ | |||
| 				<FormButton @click="logout" danger>{{ $ts.logout }}</FormButton> | ||||
| 			</FormGroup> | ||||
| 		</FormBase> | ||||
| 	</div> | ||||
| 	<div class="main"> | ||||
| 		<component :is="component" :key="page" @info="onInfo" v-bind="pageProps"/> | ||||
| 	</div> | ||||
|  | @ -64,7 +66,7 @@ export default defineComponent({ | |||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		page: { | ||||
| 		initialPage: { | ||||
| 			type: String, | ||||
| 			required: false | ||||
| 		} | ||||
|  | @ -75,6 +77,7 @@ export default defineComponent({ | |||
| 			title: i18n.locale.settings, | ||||
| 			icon: faCog | ||||
| 		}); | ||||
| 		const page = ref(props.initialPage); | ||||
| 		const narrow = ref(false); | ||||
| 		const view = ref(null); | ||||
| 		const el = ref(null); | ||||
|  | @ -83,8 +86,8 @@ export default defineComponent({ | |||
| 		}; | ||||
| 		const pageProps = ref({}); | ||||
| 		const component = computed(() => { | ||||
| 			if (props.page == null) return null; | ||||
| 			switch (props.page) { | ||||
| 			if (page.value == null) return null; | ||||
| 			switch (page.value) { | ||||
| 				case 'profile': return defineAsyncComponent(() => import('./profile.vue')); | ||||
| 				case 'privacy': return defineAsyncComponent(() => import('./privacy.vue')); | ||||
| 				case 'reaction': return defineAsyncComponent(() => import('./reaction.vue')); | ||||
|  | @ -117,10 +120,10 @@ export default defineComponent({ | |||
| 				case 'registry': return defineAsyncComponent(() => import('./registry.vue')); | ||||
| 				case 'experimental-features': return defineAsyncComponent(() => import('./experimental-features.vue')); | ||||
| 			} | ||||
| 			if (props.page.startsWith('registry/keys/system/')) { | ||||
| 			if (page.value.startsWith('registry/keys/system/')) { | ||||
| 				return defineAsyncComponent(() => import('./registry.keys.vue')); | ||||
| 			} | ||||
| 			if (props.page.startsWith('registry/value/system/')) { | ||||
| 			if (page.value.startsWith('registry/value/system/')) { | ||||
| 				return defineAsyncComponent(() => import('./registry.value.vue')); | ||||
| 			} | ||||
| 		}); | ||||
|  | @ -128,12 +131,12 @@ export default defineComponent({ | |||
| 		watch(component, () => { | ||||
| 			pageProps.value = {}; | ||||
| 
 | ||||
| 			if (props.page) { | ||||
| 				if (props.page.startsWith('registry/keys/system/')) { | ||||
| 					pageProps.value.scope = props.page.replace('registry/keys/system/', '').split('/'); | ||||
| 			if (page.value) { | ||||
| 				if (page.value.startsWith('registry/keys/system/')) { | ||||
| 					pageProps.value.scope = page.value.replace('registry/keys/system/', '').split('/'); | ||||
| 				} | ||||
| 				if (props.page.startsWith('registry/value/system/')) { | ||||
| 					const path = props.page.replace('registry/value/system/', '').split('/'); | ||||
| 				if (page.value.startsWith('registry/value/system/')) { | ||||
| 					const path = page.value.replace('registry/value/system/', '').split('/'); | ||||
| 					pageProps.value.xKey = path.pop(); | ||||
| 					pageProps.value.scope = path; | ||||
| 				} | ||||
|  | @ -144,12 +147,20 @@ export default defineComponent({ | |||
| 			}); | ||||
| 		}, { immediate: true }); | ||||
| 
 | ||||
| 		watch(() => props.initialPage, () => { | ||||
| 			page.value = props.initialPage; | ||||
| 		}); | ||||
| 
 | ||||
| 		onMounted(() => { | ||||
| 			narrow.value = el.value.offsetWidth < 1025; | ||||
| 			narrow.value = el.value.offsetWidth < 800; | ||||
| 			if (!narrow.value) { | ||||
| 				page.value = 'profile'; | ||||
| 			} | ||||
| 		}); | ||||
| 
 | ||||
| 		return { | ||||
| 			[symbols.PAGE_INFO]: INFO, | ||||
| 			page, | ||||
| 			narrow, | ||||
| 			view, | ||||
| 			el, | ||||
|  | @ -176,25 +187,20 @@ export default defineComponent({ | |||
| 		display: flex; | ||||
| 		max-width: 1100px; | ||||
| 		margin: 0 auto; | ||||
| 		height: 100%; | ||||
| 
 | ||||
| 		> .nav { | ||||
| 			width: 32%; | ||||
| 			box-sizing: border-box; | ||||
| 			border-right: solid 0.5px var(--divider); | ||||
| 			overflow: auto; | ||||
| 		} | ||||
| 
 | ||||
| 		> .main { | ||||
| 			flex: 1; | ||||
| 			min-width: 0; | ||||
| 			overflow: auto; | ||||
| 			--baseContentWidth: 100%; | ||||
| 
 | ||||
| 			::v-deep(._section) { | ||||
| 				padding: 0 0 32px 0; | ||||
| 
 | ||||
| 				& + ._section { | ||||
| 					padding-top: 32px; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -22,7 +22,7 @@ export const router = createRouter({ | |||
| 		{ path: '/@:user/pages/:page', component: page('page'), props: route => ({ pageName: route.params.page, username: route.params.user }) }, | ||||
| 		{ path: '/@:user/pages/:pageName/view-source', component: page('page-editor/page-editor'), props: route => ({ initUser: route.params.user, initPageName: route.params.pageName }) }, | ||||
| 		{ path: '/@:acct/room', props: true, component: page('room/room') }, | ||||
| 		{ path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ page: route.params.page || null }) }, | ||||
| 		{ path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) }, | ||||
| 		{ path: '/announcements', component: page('announcements') }, | ||||
| 		{ path: '/about', component: page('about') }, | ||||
| 		{ path: '/about-misskey', component: page('about-misskey') }, | ||||
|  |  | |||
|  | @ -27,7 +27,7 @@ | |||
| 		<Fa :icon="faEllipsisH" fixed-width/><span class="text">{{ $ts.more }}</span> | ||||
| 		<i v-if="otherNavItemIndicated"><Fa :icon="faCircle"/></i> | ||||
| 	</button> | ||||
| 	<MkA class="item" active-class="active" to="/settings"> | ||||
| 	<MkA class="item" active-class="active" to="/settings" :behavior="settingsWindowed ? 'modalWindow' : null"> | ||||
| 		<Fa :icon="faCog" fixed-width/><span class="text">{{ $ts.settings }}</span> | ||||
| 	</MkA> | ||||
| </div> | ||||
|  | @ -57,6 +57,7 @@ export default defineComponent({ | |||
| 			connection: null, | ||||
| 			menuDef: sidebarDef, | ||||
| 			iconOnly: false, | ||||
| 			settingsWindowed: false, | ||||
| 			faGripVertical, faChevronLeft, faComments, faHashtag, faBroadcastTower, faFireAlt, faEllipsisH, faPencilAlt, faBars, faTimes, faBell, faSearch, faUserCog, faCog, faUser, faHome, faStar, faCircle, faAt, faEnvelope, faListUl, faPlus, faUserClock, faLaugh, faUsers, faTachometerAlt, faExchangeAlt, faGlobe, faChartBar, faCloud, faServer, faProjectDiagram | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -102,6 +103,7 @@ export default defineComponent({ | |||
| 	methods: { | ||||
| 		calcViewState() { | ||||
| 			this.iconOnly = (window.innerWidth <= 1400) || (this.$store.state.sidebarDisplay === 'icon'); | ||||
| 			this.settingsWindowed = (window.innerWidth > 1400); | ||||
| 		}, | ||||
| 
 | ||||
| 		post() { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue