wip
This commit is contained in:
		
							parent
							
								
									6a9187c1ba
								
							
						
					
					
						commit
						911dedf3d7
					
				
					 11 changed files with 315 additions and 233 deletions
				
			
		|  | @ -97,6 +97,7 @@ cantRenote: "この投稿はRenoteできません。" | |||
| cantReRenote: "RenoteをRenoteすることはできません。" | ||||
| quote: "引用" | ||||
| pinnedNote: "ピン留めされたノート" | ||||
| pinned: "ピン留め" | ||||
| you: "あなた" | ||||
| clickToShow: "クリックして表示" | ||||
| sensitive: "閲覧注意" | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| <template> | ||||
| <transition name="zoom-in-top" appear @after-leave="$emit('closed')"> | ||||
| 	<div class="buebdbiu _acrylic _shadow" v-if="showing"> | ||||
| <transition name="tooltip" appear @after-leave="$emit('closed')"> | ||||
| 	<div class="buebdbiu _acrylic _shadow" v-show="showing" ref="content"> | ||||
| 		<slot>{{ text }}</slot> | ||||
| 	</div> | ||||
| </transition> | ||||
|  | @ -35,19 +35,43 @@ export default defineComponent({ | |||
| 
 | ||||
| 			const rect = this.source.getBoundingClientRect(); | ||||
| 
 | ||||
| 			let x = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); | ||||
| 			let y = rect.top + window.pageYOffset + this.source.offsetHeight; | ||||
| 			const contentWidth = this.$refs.content.offsetWidth; | ||||
| 			const contentHeight = this.$refs.content.offsetHeight; | ||||
| 
 | ||||
| 			x -= (this.$el.offsetWidth / 2); | ||||
| 			let left = rect.left + window.pageXOffset + (this.source.offsetWidth / 2); | ||||
| 			let top = rect.top + window.pageYOffset + this.source.offsetHeight; | ||||
| 
 | ||||
| 			this.$el.style.left = x + 'px'; | ||||
| 			this.$el.style.top = y + 'px'; | ||||
| 			left -= (this.$el.offsetWidth / 2); | ||||
| 
 | ||||
| 			if (left + contentWidth - window.pageXOffset > window.innerWidth) { | ||||
| 				left = window.innerWidth - contentWidth + window.pageXOffset - 1; | ||||
| 			} | ||||
| 
 | ||||
| 			if (top + contentHeight - window.pageYOffset > window.innerHeight) { | ||||
| 				top = rect.top + window.pageYOffset - contentHeight; | ||||
| 				this.$refs.content.style.transformOrigin = 'center bottom'; | ||||
| 			} | ||||
| 
 | ||||
| 			this.$el.style.left = left + 'px'; | ||||
| 			this.$el.style.top = top + 'px'; | ||||
| 		}); | ||||
| 	}, | ||||
| }) | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .tooltip-enter-active, | ||||
| .tooltip-leave-active { | ||||
| 	opacity: 1; | ||||
| 	transform: scale(1); | ||||
| 	transition: transform 200ms cubic-bezier(0.23, 1, 0.32, 1), opacity 200ms cubic-bezier(0.23, 1, 0.32, 1); | ||||
| } | ||||
| .tooltip-enter-from, | ||||
| .tooltip-leave-active { | ||||
| 	opacity: 0; | ||||
| 	transform: scale(0.75); | ||||
| } | ||||
| 
 | ||||
| .buebdbiu { | ||||
| 	position: absolute; | ||||
| 	z-index: 11000; | ||||
|  | @ -57,6 +81,6 @@ export default defineComponent({ | |||
| 	text-align: center; | ||||
| 	border-radius: 4px; | ||||
| 	pointer-events: none; | ||||
| 	transform-origin: center -16px; | ||||
| 	transform-origin: center top; | ||||
| } | ||||
| </style> | ||||
|  |  | |||
							
								
								
									
										153
									
								
								src/client/components/widgets.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										153
									
								
								src/client/components/widgets.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,153 @@ | |||
| <template> | ||||
| <div class="vjoppmmu"> | ||||
| 	<template v-if="edit"> | ||||
| 		<header> | ||||
| 			<MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)"> | ||||
| 				<template #label>{{ $ts.selectWidget }}</template> | ||||
| 				<option v-for="widget in widgetDefs" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> | ||||
| 			</MkSelect> | ||||
| 			<MkButton inline @click="addWidget" primary><Fa :icon="faPlus"/> {{ $ts.add }}</MkButton> | ||||
| 			<MkButton inline @click="$emit('exit')">{{ $ts.close }}</MkButton> | ||||
| 		</header> | ||||
| 		<XDraggable | ||||
| 			v-model="_widgets" | ||||
| 			item-key="id" | ||||
| 			animation="150" | ||||
| 		> | ||||
| 			<template #item="{element}"> | ||||
| 				<div class="customize-container"> | ||||
| 					<button class="config _button" @click.prevent.stop="configWidget(element.id)"><Fa :icon="faCog"/></button> | ||||
| 					<button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button> | ||||
| 					<component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="updateWidget(element.id, $event)"/> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 		</XDraggable> | ||||
| 	</template> | ||||
| 	<component v-else class="widget" v-for="widget in widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="updateWidget(widget.id, $event)"/> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; | ||||
| import MkSelect from '@/components/ui/select.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import { widgets as widgetDefs } from '@/widgets'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), | ||||
| 		MkSelect, | ||||
| 		MkButton, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
| 		widgets: { | ||||
| 			required: true, | ||||
| 		}, | ||||
| 		edit: { | ||||
| 			type: Boolean, | ||||
| 			required: true, | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['updateWidgets', 'addWidget', 'removeWidget', 'updateWidget', 'exit'], | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			widgetAdderSelected: null, | ||||
| 			widgetDefs, | ||||
| 			settings: {}, | ||||
| 			faTimes, faPlus, faCog | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		_widgets: { | ||||
| 			get() { | ||||
| 				return this.widgets; | ||||
| 			}, | ||||
| 			set(value) { | ||||
| 				this.$emit('updateWidgets', value); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		configWidget(id) { | ||||
| 			this.settings[id](); | ||||
| 		}, | ||||
| 
 | ||||
| 		addWidget() { | ||||
| 			if (this.widgetAdderSelected == null) return; | ||||
| 
 | ||||
| 			this.$emit('addWidget', { | ||||
| 				name: this.widgetAdderSelected, | ||||
| 				id: uuid(), | ||||
| 				data: {} | ||||
| 			}); | ||||
| 
 | ||||
| 			this.widgetAdderSelected = null; | ||||
| 		}, | ||||
| 
 | ||||
| 		removeWidget(widget) { | ||||
| 			this.$emit('removeWidget', widget); | ||||
| 		}, | ||||
| 
 | ||||
| 		updateWidget(id, data) { | ||||
| 			this.$emit('updateWidget', { id, data }); | ||||
| 		}, | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .vjoppmmu { | ||||
| 	> header { | ||||
| 		margin: 16px 0; | ||||
| 
 | ||||
| 		> * { | ||||
| 			width: 100%; | ||||
| 			padding: 4px; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .widget, .customize-container { | ||||
| 		margin: var(--margin) 0; | ||||
| 
 | ||||
| 		&:first-of-type { | ||||
| 			margin-top: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.customize-container { | ||||
| 		position: relative; | ||||
| 		cursor: move; | ||||
| 
 | ||||
| 		> *:not(.remove):not(.config) { | ||||
| 			pointer-events: none; | ||||
| 		} | ||||
| 
 | ||||
| 		> .config, | ||||
| 		> .remove { | ||||
| 			position: absolute; | ||||
| 			z-index: 10000; | ||||
| 			top: 8px; | ||||
| 			width: 32px; | ||||
| 			height: 32px; | ||||
| 			color: #fff; | ||||
| 			background: rgba(#000, 0.7); | ||||
| 			border-radius: 4px; | ||||
| 		} | ||||
| 
 | ||||
| 		> .config { | ||||
| 			right: 8px + 8px + 32px; | ||||
| 		} | ||||
| 
 | ||||
| 		> .remove { | ||||
| 			right: 8px; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -4,6 +4,7 @@ import { popup } from '@/os'; | |||
| 
 | ||||
| const start = isDeviceTouch ? 'touchstart' : 'mouseover'; | ||||
| const end = isDeviceTouch ? 'touchend' : 'mouseleave'; | ||||
| const delay = 100; | ||||
| 
 | ||||
| export default { | ||||
| 	mounted(el: HTMLElement, binding, vn) { | ||||
|  | @ -47,13 +48,13 @@ export default { | |||
| 		el.addEventListener(start, () => { | ||||
| 			clearTimeout(self.showTimer); | ||||
| 			clearTimeout(self.hideTimer); | ||||
| 			self.showTimer = setTimeout(show, 300); | ||||
| 			self.showTimer = setTimeout(show, delay); | ||||
| 		}, { passive: true }); | ||||
| 
 | ||||
| 		el.addEventListener(end, () => { | ||||
| 			clearTimeout(self.showTimer); | ||||
| 			clearTimeout(self.hideTimer); | ||||
| 			self.hideTimer = setTimeout(self.close, 300); | ||||
| 			self.hideTimer = setTimeout(self.close, delay); | ||||
| 		}, { passive: true }); | ||||
| 
 | ||||
| 		el.addEventListener('click', () => { | ||||
|  |  | |||
|  | @ -488,19 +488,6 @@ hr { | |||
| 	transform: scale(0.9); | ||||
| } | ||||
| 
 | ||||
| .zoom-in-top-enter-active, | ||||
| .zoom-in-top-leave-active { | ||||
| 	opacity: 1; | ||||
| 	transform: scaleY(1); | ||||
| 	transition: transform 300ms cubic-bezier(0.23, 1, 0.32, 1), opacity 300ms cubic-bezier(0.23, 1, 0.32, 1); | ||||
| 	transform-origin: center top; | ||||
| } | ||||
| .zoom-in-top-enter-from, | ||||
| .zoom-in-top-leave-active { | ||||
| 	opacity: 0; | ||||
| 	transform: scaleY(0); | ||||
| } | ||||
| 
 | ||||
| @keyframes blink { | ||||
| 	0% { opacity: 1; transform: scale(1); } | ||||
| 	30% { opacity: 1; transform: scale(1); } | ||||
|  |  | |||
|  | @ -10,10 +10,10 @@ | |||
| 				</button> | ||||
| 			</div> | ||||
| 			<div class="right"> | ||||
| 				<MkA class="item" to="/my/messaging"><Fa class="icon" :icon="faComments"/><i v-if="$i.hasUnreadMessagingMessage"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/messages"><Fa class="icon" :icon="faEnvelope"/><i v-if="$i.hasUnreadSpecifiedNotes"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/mentions"><Fa class="icon" :icon="faAt"/><i v-if="$i.hasUnreadMentions"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/notifications"><Fa class="icon" :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/messaging" v-tooltip="$ts.messaging"><Fa class="icon" :icon="faComments"/><i v-if="$i.hasUnreadMessagingMessage"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/messages" v-tooltip="$ts.directNotes"><Fa class="icon" :icon="faEnvelope"/><i v-if="$i.hasUnreadSpecifiedNotes"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/mentions" v-tooltip="$ts.mentions"><Fa class="icon" :icon="faAt"/><i v-if="$i.hasUnreadMentions"><Fa :icon="faCircle"/></i></MkA> | ||||
| 				<MkA class="item" to="/my/notifications" v-tooltip="$ts.notifications"><Fa class="icon" :icon="faBell"/><i v-if="$i.hasUnreadNotification"><Fa :icon="faCircle"/></i></MkA> | ||||
| 			</div> | ||||
| 		</header> | ||||
| 		<div class="body"> | ||||
|  | @ -63,10 +63,10 @@ | |||
| 				</button> | ||||
| 			</div> | ||||
| 			<div class="right"> | ||||
| 				<button class="_button item search" @click="search"> | ||||
| 				<button class="_button item search" @click="search" v-tooltip="$ts.search"> | ||||
| 					<Fa :icon="faSearch"/> | ||||
| 				</button> | ||||
| 				<MkA class="item" to="/settings"><Fa class="icon" :icon="faCog"/></MkA> | ||||
| 				<MkA class="item" to="/settings" v-tooltip="$ts.settings"><Fa class="icon" :icon="faCog"/></MkA> | ||||
| 			</div> | ||||
| 		</footer> | ||||
| 	</div> | ||||
|  | @ -97,11 +97,12 @@ | |||
| 			</div> | ||||
| 
 | ||||
| 			<div class="right"> | ||||
| 				<div class="instance">{{ instanceName }}</div> | ||||
| 				<XHeaderClock class="clock"/> | ||||
| 				<button class="_button button search" @click="search"> | ||||
| 				<button class="_button button search" @click="search" v-tooltip="$ts.search"> | ||||
| 					<Fa :icon="faSearch"/> | ||||
| 				</button> | ||||
| 				<button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow"> | ||||
| 				<button class="_button button follow" v-if="tl.startsWith('channel:') && currentChannel" :class="{ followed: currentChannel.isFollowing }" @click="toggleChannelFollow" v-tooltip="currentChannel.isFollowing ? $ts.unfollow : $ts.follow"> | ||||
| 					<Fa v-if="currentChannel.isFollowing" :icon="faStar"/> | ||||
| 					<Fa v-else :icon="farStar"/> | ||||
| 				</button> | ||||
|  | @ -121,6 +122,9 @@ | |||
| 	</main> | ||||
| 
 | ||||
| 	<XSide class="side" ref="side"/> | ||||
| 	<div class="side"> | ||||
| 		<XWidgets/> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<XCommon/> | ||||
| </div> | ||||
|  | @ -132,6 +136,7 @@ import { faLayerGroup, faBars, faHome, faCircle, faWindowMaximize, faColumns, fa | |||
| import { faBell, faStar as farStar, faEnvelope, faComments } from '@fortawesome/free-regular-svg-icons'; | ||||
| import { instanceName, url } from '@/config'; | ||||
| import XSidebar from '@/components/sidebar.vue'; | ||||
| import XWidgets from './widgets.vue'; | ||||
| import XCommon from '../_common_/common.vue'; | ||||
| import XSide from './side.vue'; | ||||
| import XTimeline from './timeline.vue'; | ||||
|  | @ -147,6 +152,7 @@ export default defineComponent({ | |||
| 	components: { | ||||
| 		XCommon, | ||||
| 		XSidebar, | ||||
| 		XWidgets, | ||||
| 		XSide, // NOTE: dynamic importするとAsyncComponentWrapperが間に入るせいでref取得できなくて面倒になる | ||||
| 		XTimeline, | ||||
| 		XPostForm, | ||||
|  | @ -187,6 +193,7 @@ export default defineComponent({ | |||
| 			featuredChannels: null, | ||||
| 			currentChannel: null, | ||||
| 			menuDef: sidebarDef, | ||||
| 			instanceName, | ||||
| 			faLayerGroup, faBars, faBell, faHome, faCircle, faPencilAlt, faShareAlt, faSatelliteDish, faListUl, faSatellite, faCog, faSearch, faPlus, faStar, farStar, faAt, faLink, faEllipsisH, faGlobe, faComments, faEnvelope, | ||||
| 		}; | ||||
| 	}, | ||||
|  | @ -310,8 +317,7 @@ export default defineComponent({ | |||
| 	$ui-font-size: 1em; // TODO: どこかに集約したい | ||||
| 
 | ||||
| 	// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ | ||||
| 	min-height: calc(var(--vh, 1vh) * 100); | ||||
| 	box-sizing: border-box; | ||||
| 	height: calc(var(--vh, 1vh) * 100); | ||||
| 	display: flex; | ||||
| 
 | ||||
| 	> .nav { | ||||
|  | @ -518,6 +524,10 @@ export default defineComponent({ | |||
| 				margin-left: auto; | ||||
| 				padding-left: 8px; | ||||
| 
 | ||||
| 				> .instance { | ||||
| 					margin-right: 16px; | ||||
| 				} | ||||
| 
 | ||||
| 				> .clock { | ||||
| 					margin-right: 16px; | ||||
| 				} | ||||
|  | @ -552,6 +562,7 @@ export default defineComponent({ | |||
| 	} | ||||
| 
 | ||||
| 	> .side { | ||||
| 		width: 350px; | ||||
| 		border-left: solid 1px var(--divider); | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -120,12 +120,8 @@ export default defineComponent({ | |||
| 	--section-padding: 16px; | ||||
| 	--margin: var(--marginHalf); | ||||
| 
 | ||||
| 	width: 390px; | ||||
| 
 | ||||
| 	> .container { | ||||
| 		position: fixed; | ||||
| 		width: 390px; | ||||
| 		height: 100vh; | ||||
| 		height: 100%; | ||||
| 		overflow: auto; | ||||
| 		box-sizing: border-box; | ||||
| 
 | ||||
|  |  | |||
							
								
								
									
										13
									
								
								src/client/ui/chat/store.ts
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/client/ui/chat/store.ts
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,13 @@ | |||
| import { markRaw } from 'vue'; | ||||
| import { Storage } from '../../pizzax'; | ||||
| 
 | ||||
| export const store = markRaw(new Storage('chatUi', { | ||||
| 	widgets: { | ||||
| 		where: 'account', | ||||
| 		default: [] as { | ||||
| 			name: string; | ||||
| 			id: string; | ||||
| 			data: Record<string, any>; | ||||
| 		}[] | ||||
| 	}, | ||||
| })); | ||||
							
								
								
									
										61
									
								
								src/client/ui/chat/widgets.vue
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								src/client/ui/chat/widgets.vue
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,61 @@ | |||
| <template> | ||||
| <div class="qydbhufi"> | ||||
| 	<XWidgets :edit="edit" :widgets="widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> | ||||
| 
 | ||||
| 	<button v-if="edit" @click="edit = false" class="_textButton" style="font-size: 0.9em;">{{ $ts.editWidgetsExit }}</button> | ||||
| 	<button v-else @click="edit = true" class="_textButton" style="font-size: 0.9em;">{{ $ts.editWidgets }}</button> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import XWidgets from '@/components/widgets.vue'; | ||||
| import { store } from './store.ts'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XWidgets, | ||||
| 	}, | ||||
| 
 | ||||
| 	data() { | ||||
| 		return { | ||||
| 			edit: false, | ||||
| 			widgets: store.reactiveState.widgets | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		addWidget(widget) { | ||||
| 			store.set('widgets', [widget, ...store.state.widgets]); | ||||
| 		}, | ||||
| 
 | ||||
| 		removeWidget(widget) { | ||||
| 			store.set('widgets', store.state.widgets.filter(w => w.id != widget.id)); | ||||
| 		}, | ||||
| 
 | ||||
| 		updateWidget({ id, data }) { | ||||
| 			store.set('widgets', store.state.widgets.map(w => w.id === id ? { | ||||
| 				...w, | ||||
| 				data: data | ||||
| 			} : w)); | ||||
| 		}, | ||||
| 
 | ||||
| 		updateWidgets(widgets) { | ||||
| 			store.set('widgets', widgets); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
| </script> | ||||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .qydbhufi { | ||||
| 	height: 100%; | ||||
| 	box-sizing: border-box; | ||||
| 	overflow: auto; | ||||
| 	padding: var(--margin); | ||||
| 
 | ||||
| 	::v-deep(._panel) { | ||||
| 		box-shadow: none; | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  | @ -3,49 +3,22 @@ | |||
| 	<template #header><Fa :icon="faWindowMaximize" style="margin-right: 8px;"/>{{ column.name }}</template> | ||||
| 
 | ||||
| 	<div class="wtdtxvec"> | ||||
| 		<template v-if="edit"> | ||||
| 			<header> | ||||
| 				<MkSelect v-model:value="widgetAdderSelected" style="margin-bottom: var(--margin)"> | ||||
| 					<template #label>{{ $ts.selectWidget }}</template> | ||||
| 					<option v-for="widget in widgets" :value="widget" :key="widget">{{ $t(`_widgets.${widget}`) }}</option> | ||||
| 				</MkSelect> | ||||
| 				<MkButton inline @click="addWidget" primary><Fa :icon="faPlus"/> {{ $ts.add }}</MkButton> | ||||
| 				<MkButton inline @click="edit = false">{{ $ts.close }}</MkButton> | ||||
| 			</header> | ||||
| 			<XDraggable | ||||
| 				v-model="_widgets" | ||||
| 				item-key="id" | ||||
| 				animation="150" | ||||
| 			> | ||||
| 				<template #item="{element}"> | ||||
| 					<div class="customize-container" @click="widgetFunc(element.id)"> | ||||
| 						<button class="remove _button" @click.prevent.stop="removeWidget(element)"><Fa :icon="faTimes"/></button> | ||||
| 						<component :is="`mkw-${element.name}`" :widget="element" :setting-callback="setting => settings[element.id] = setting" :column="column" @updateProps="saveWidget(element.id, $event)"/> | ||||
| 					</div> | ||||
| 				</template> | ||||
| 			</XDraggable> | ||||
| 		</template> | ||||
| 		<component v-else class="widget" v-for="widget in column.widgets" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" :column="column" @updateProps="saveWidget(widget.id, $event)"/> | ||||
| 		<XWidgets :edit="edit" :widgets="column.widgets" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="edit = false"/> | ||||
| 	</div> | ||||
| </XColumn> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { faWindowMaximize, faTimes, faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; | ||||
| import MkSelect from '@/components/ui/select.vue'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| import XWidgets from '@/components/widgets.vue'; | ||||
| import XColumn from './column.vue'; | ||||
| import { widgets } from '../../widgets'; | ||||
| import { addColumnWidget, removeColumnWidget, setColumnWidgets, updateColumnWidget } from './deck-store'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		XColumn, | ||||
| 		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), | ||||
| 		MkSelect, | ||||
| 		MkButton, | ||||
| 		XWidgets, | ||||
| 	}, | ||||
| 
 | ||||
| 	props: { | ||||
|  | @ -62,49 +35,27 @@ export default defineComponent({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			edit: false, | ||||
| 			widgetAdderSelected: null, | ||||
| 			widgets, | ||||
| 			settings: {}, | ||||
| 			faWindowMaximize, faTimes, faPlus | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		_widgets: { | ||||
| 			get() { | ||||
| 				return this.column.widgets; | ||||
| 			}, | ||||
| 			set(value) { | ||||
| 				setColumnWidgets(this.column.id, value); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		widgetFunc(id) { | ||||
| 			this.settings[id](); | ||||
| 		}, | ||||
| 
 | ||||
| 		addWidget() { | ||||
| 			if (this.widgetAdderSelected == null) return; | ||||
| 
 | ||||
| 			addColumnWidget(this.column.id, { | ||||
| 				name: this.widgetAdderSelected, | ||||
| 				id: uuid(), | ||||
| 				data: {} | ||||
| 			}); | ||||
| 
 | ||||
| 			this.widgetAdderSelected = null; | ||||
| 		addWidget(widget) { | ||||
| 			addColumnWidget(this.column.id, widget); | ||||
| 		}, | ||||
| 
 | ||||
| 		removeWidget(widget) { | ||||
| 			removeColumnWidget(this.column.id, widget); | ||||
| 		}, | ||||
| 
 | ||||
| 		saveWidget(id, data) { | ||||
| 		updateWidget({ id, data }) { | ||||
| 			updateColumnWidget(this.column.id, id, data); | ||||
| 		}, | ||||
| 
 | ||||
| 		updateWidgets(widgets) { | ||||
| 			setColumnWidgets(this.column.id, widgets); | ||||
| 		}, | ||||
| 
 | ||||
| 		func() { | ||||
| 			this.edit = !this.edit; | ||||
| 		} | ||||
|  | @ -114,46 +65,12 @@ export default defineComponent({ | |||
| 
 | ||||
| <style lang="scss" scoped> | ||||
| .wtdtxvec { | ||||
| 	._panel { | ||||
| 	--margin: 8px; | ||||
| 
 | ||||
| 	padding: 0 var(--margin); | ||||
| 
 | ||||
| 	::v-deep(._panel) { | ||||
| 		box-shadow: none; | ||||
| 	} | ||||
| 
 | ||||
| 	> header { | ||||
| 		padding: 16px; | ||||
| 
 | ||||
| 		> * { | ||||
| 			width: 100%; | ||||
| 			padding: 4px; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .widget, .customize-container { | ||||
| 		margin: 8px; | ||||
| 
 | ||||
| 		&:first-of-type { | ||||
| 			margin-top: 0; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	.customize-container { | ||||
| 		position: relative; | ||||
| 		cursor: move; | ||||
| 
 | ||||
| 		> *:not(.remove) { | ||||
| 			pointer-events: none; | ||||
| 		} | ||||
| 
 | ||||
| 		> .remove { | ||||
| 			position: absolute; | ||||
| 			z-index: 2; | ||||
| 			top: 8px; | ||||
| 			right: 8px; | ||||
| 			width: 32px; | ||||
| 			height: 32px; | ||||
| 			color: #fff; | ||||
| 			background: rgba(#000, 0.7); | ||||
| 			border-radius: 4px; | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
|  | @ -1,46 +1,21 @@ | |||
| <template> | ||||
| <div class="efzpzdvf"> | ||||
| 	<template v-if="editMode"> | ||||
| 		<MkButton primary @click="addWidget" class="add"><Fa :icon="faPlus"/></MkButton> | ||||
| 		<XDraggable | ||||
| 			v-model="widgets" | ||||
| 			item-key="id" | ||||
| 			handle=".handle" | ||||
| 			animation="150" | ||||
| 			class="sortable" | ||||
| 		> | ||||
| 			<template #item="{element}"> | ||||
| 				<div class="customize-container _panel"> | ||||
| 					<header> | ||||
| 						<span class="handle"><Fa :icon="faBars"/></span>{{ $t('_widgets.' + element.name) }}<button class="remove _button" @click="removeWidget(element)"><Fa :icon="faTimes"/></button> | ||||
| 					</header> | ||||
| 					<div @click="widgetFunc(element.id)"> | ||||
| 						<component class="_inContainer_ _forceContainerFull_" :is="`mkw-${element.name}`" :widget="element" :ref="element.id" :setting-callback="setting => settings[element.id] = setting" @updateProps="saveWidget(element.id, $event)"/> | ||||
| 					</div> | ||||
| 				</div> | ||||
| 			</template> | ||||
| 		</XDraggable> | ||||
| 		<button @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button> | ||||
| 	</template> | ||||
| 	<template v-else> | ||||
| 		<component v-for="widget in widgets" class="_inContainer_ _forceContainerFull_" :is="`mkw-${widget.name}`" :key="widget.id" :widget="widget" @updateProps="saveWidget(widget.id, $event)"/> | ||||
| 		<button @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button> | ||||
| 	</template> | ||||
| 	<XWidgets :edit="editMode" :widgets="$store.reactiveState.widgets.value" @add-widget="addWidget" @remove-widget="removeWidget" @update-widget="updateWidget" @update-widgets="updateWidgets" @exit="editMode = false"/> | ||||
| 
 | ||||
| 	<button v-if="editMode" @click="editMode = false" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faCheck"/> {{ $ts.editWidgetsExit }}</button> | ||||
| 	<button v-else @click="editMode = true" class="_textButton" style="font-size: 0.9em;"><Fa :icon="faPencilAlt"/> {{ $ts.editWidgets }}</button> | ||||
| </div> | ||||
| </template> | ||||
| 
 | ||||
| <script lang="ts"> | ||||
| import { defineComponent, defineAsyncComponent } from 'vue'; | ||||
| import { v4 as uuid } from 'uuid'; | ||||
| import { faPencilAlt, faPlus, faBars, faTimes, faCheck } from '@fortawesome/free-solid-svg-icons'; | ||||
| import { widgets } from '@/widgets'; | ||||
| import XWidgets from '@/components/widgets.vue'; | ||||
| import * as os from '@/os'; | ||||
| import MkButton from '@/components/ui/button.vue'; | ||||
| 
 | ||||
| export default defineComponent({ | ||||
| 	components: { | ||||
| 		MkButton, | ||||
| 		XDraggable: defineAsyncComponent(() => import('vuedraggable').then(x => x.default)), | ||||
| 		XWidgets | ||||
| 	}, | ||||
| 
 | ||||
| 	emits: ['mounted'], | ||||
|  | @ -48,62 +23,35 @@ export default defineComponent({ | |||
| 	data() { | ||||
| 		return { | ||||
| 			editMode: false, | ||||
| 			settings: {}, | ||||
| 			faPencilAlt, faPlus, faBars, faTimes, faCheck, | ||||
| 		}; | ||||
| 	}, | ||||
| 
 | ||||
| 	computed: { | ||||
| 		widgets: { | ||||
| 			get() { | ||||
| 				return this.$store.reactiveState.widgets.value; | ||||
| 			}, | ||||
| 			set(value) { | ||||
| 				this.$store.set('widgets', value); | ||||
| 			} | ||||
| 		}, | ||||
| 	}, | ||||
| 
 | ||||
| 	mounted() { | ||||
| 		this.$emit('mounted', this.$el); | ||||
| 	}, | ||||
| 
 | ||||
| 	methods: { | ||||
| 		widgetFunc(id) { | ||||
| 			this.settings[id](); | ||||
| 		}, | ||||
| 
 | ||||
| 		async addWidget() { | ||||
| 			const { canceled, result: widget } = await os.dialog({ | ||||
| 				type: null, | ||||
| 				title: this.$ts.chooseWidget, | ||||
| 				select: { | ||||
| 					items: widgets.map(widget => ({ | ||||
| 						value: widget, | ||||
| 						text: this.$t('_widgets.' + widget), | ||||
| 					})) | ||||
| 				}, | ||||
| 				showCancelButton: true | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 
 | ||||
| 			this.$store.set('widgets', [...this.$store.state.widgets, { | ||||
| 				name: widget, | ||||
| 				id: uuid(), | ||||
| 		addWidget(widget) { | ||||
| 			this.$store.set('widgets', [{ | ||||
| 				...widget, | ||||
| 				place: null, | ||||
| 				data: {} | ||||
| 			}]); | ||||
| 			}, ...this.$store.state.widgets]); | ||||
| 		}, | ||||
| 
 | ||||
| 		removeWidget(widget) { | ||||
| 			this.$store.set('widgets', this.$store.state.widgets.filter(w => w.id != widget.id)); | ||||
| 		}, | ||||
| 
 | ||||
| 		saveWidget(id, data) { | ||||
| 		updateWidget({ id, data }) { | ||||
| 			this.$store.set('widgets', this.$store.state.widgets.map(w => w.id === id ? { | ||||
| 				...w, | ||||
| 				data: data | ||||
| 			} : w)); | ||||
| 		}, | ||||
| 
 | ||||
| 		updateWidgets(widgets) { | ||||
| 			this.$store.set('widgets', widgets); | ||||
| 		} | ||||
| 	} | ||||
| }); | ||||
|  | @ -129,35 +77,5 @@ export default defineComponent({ | |||
| 	> .add { | ||||
| 		margin: 0 auto; | ||||
| 	} | ||||
| 
 | ||||
| 	.customize-container { | ||||
| 		margin: 8px 0; | ||||
| 
 | ||||
| 		> header { | ||||
| 			position: relative; | ||||
| 			line-height: 32px; | ||||
| 
 | ||||
| 			> .handle { | ||||
| 				padding: 0 8px; | ||||
| 				cursor: move; | ||||
| 			} | ||||
| 
 | ||||
| 			> .remove { | ||||
| 				position: absolute; | ||||
| 				top: 0; | ||||
| 				right: 0; | ||||
| 				padding: 0 8px; | ||||
| 				line-height: 32px; | ||||
| 			} | ||||
| 		} | ||||
| 
 | ||||
| 		> div { | ||||
| 			padding: 8px; | ||||
| 
 | ||||
| 			> * { | ||||
| 				pointer-events: none; | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| </style> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue