parent
							
								
									af6dd4194f
								
							
						
					
					
						commit
						1163c85db6
					
				
					 9 changed files with 132 additions and 96 deletions
				
			
		|  | @ -1721,8 +1721,6 @@ _notification: | |||
| _deck: | ||||
|   alwaysShowMainColumn: "常にメインカラムを表示" | ||||
|   columnAlign: "カラムの寄せ" | ||||
|   columnMargin: "カラム間のマージン" | ||||
|   columnHeaderHeight: "カラムのヘッダー幅" | ||||
|   addColumn: "カラムを追加" | ||||
|   swapLeft: "左に移動" | ||||
|   swapRight: "右に移動" | ||||
|  |  | |||
|  | @ -10,18 +10,6 @@ | |||
| 		<option value="center">{{ i18n.ts.center }}</option> | ||||
| 	</FormRadios> | ||||
| 
 | ||||
| 	<FormRadios v-model="columnHeaderHeight" class="_formBlock"> | ||||
| 		<template #label>{{ i18n.ts._deck.columnHeaderHeight }}</template> | ||||
| 		<option :value="42">{{ i18n.ts.narrow }}</option> | ||||
| 		<option :value="45">{{ i18n.ts.medium }}</option> | ||||
| 		<option :value="48">{{ i18n.ts.wide }}</option> | ||||
| 	</FormRadios> | ||||
| 
 | ||||
| 	<FormInput v-model="columnMargin" type="number" class="_formBlock"> | ||||
| 		<template #label>{{ i18n.ts._deck.columnMargin }}</template> | ||||
| 		<template #suffix>px</template> | ||||
| 	</FormInput> | ||||
| 
 | ||||
| 	<FormLink class="_formBlock" @click="setProfile">{{ i18n.ts._deck.profile }}<template #suffix>{{ profile }}</template></FormLink> | ||||
| </div> | ||||
| </template> | ||||
|  | @ -41,8 +29,6 @@ import { definePageMetadata } from '@/scripts/page-metadata'; | |||
| const navWindow = computed(deckStore.makeGetterSetter('navWindow')); | ||||
| const alwaysShowMainColumn = computed(deckStore.makeGetterSetter('alwaysShowMainColumn')); | ||||
| const columnAlign = computed(deckStore.makeGetterSetter('columnAlign')); | ||||
| const columnMargin = computed(deckStore.makeGetterSetter('columnMargin')); | ||||
| const columnHeaderHeight = computed(deckStore.makeGetterSetter('columnHeaderHeight')); | ||||
| const profile = computed(deckStore.makeGetterSetter('profile')); | ||||
| 
 | ||||
| watch(navWindow, async () => { | ||||
|  |  | |||
|  | @ -12,6 +12,7 @@ | |||
| 		<option value="small">{{ i18n.ts.small }}</option> | ||||
| 		<option value="medium">{{ i18n.ts.medium }}</option> | ||||
| 		<option value="large">{{ i18n.ts.large }}</option> | ||||
| 		<option value="veryLarge">{{ i18n.ts.large }}+</option> | ||||
| 	</FormRadios> | ||||
| </div> | ||||
| </template> | ||||
|  |  | |||
|  | @ -77,6 +77,7 @@ | |||
| 		codeString: '#ffb675', | ||||
| 		codeNumber: '#cfff9e', | ||||
| 		codeBoolean: '#c59eff', | ||||
| 		deckDivider: '#000', | ||||
| 		htmlThemeColor: '@bg', | ||||
| 		X2: ':darken<2<@panel', | ||||
| 		X3: 'rgba(255, 255, 255, 0.05)', | ||||
|  |  | |||
|  | @ -77,6 +77,7 @@ | |||
| 		codeString: '#b98710', | ||||
| 		codeNumber: '#0fbbbb', | ||||
| 		codeBoolean: '#62b70c', | ||||
| 		deckDivider: ':darken<3<@bg', | ||||
| 		htmlThemeColor: '@bg', | ||||
| 		X2: ':darken<2<@panel', | ||||
| 		X3: 'rgba(0, 0, 0, 0.05)', | ||||
|  |  | |||
|  | @ -4,7 +4,8 @@ | |||
| 		verySmall: defaultStore.reactiveState.statusbarSize.value === 'verySmall', | ||||
| 		small: defaultStore.reactiveState.statusbarSize.value === 'small', | ||||
| 		medium: defaultStore.reactiveState.statusbarSize.value === 'medium', | ||||
| 		large: defaultStore.reactiveState.statusbarSize.value === 'large' | ||||
| 		large: defaultStore.reactiveState.statusbarSize.value === 'large', | ||||
| 		veryLarge: defaultStore.reactiveState.statusbarSize.value === 'veryLarge', | ||||
| 	}" | ||||
| > | ||||
| 	<div v-for="x in defaultStore.reactiveState.statusbars.value" :key="x.id" class="item" :class="{ black: x.black }"> | ||||
|  | @ -46,6 +47,11 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') | |||
| 		font-size: 0.875em; | ||||
| 	} | ||||
| 
 | ||||
| 	&.veryLarge { | ||||
| 		--height: 30px; | ||||
| 		font-size: 0.9em; | ||||
| 	} | ||||
| 
 | ||||
| 	> .item { | ||||
| 		display: inline-flex; | ||||
| 		vertical-align: bottom; | ||||
|  |  | |||
|  | @ -1,12 +1,13 @@ | |||
| <template> | ||||
| <div | ||||
| 	class="mk-deck" :class="[{ isMobile }, `${deckStore.reactiveState.columnAlign.value}`]" :style="{ '--deckMargin': deckStore.reactiveState.columnMargin.value + 'px' }" | ||||
| 	class="mk-deck" :class="[{ isMobile }]" | ||||
| > | ||||
| 	<XSidebar v-if="!isMobile"/> | ||||
| 
 | ||||
| 	<div class="main"> | ||||
| 		<XStatusBars class="statusbars"/> | ||||
| 		<div ref="columnsEl" class="columns" @contextmenu.self.prevent="onContextmenu"> | ||||
| 		<div class="columnsWrapper"> | ||||
| 			<div ref="columnsEl" class="columns" :class="deckStore.reactiveState.columnAlign.value" @contextmenu.self.prevent="onContextmenu"> | ||||
| 				<template v-for="ids in layout"> | ||||
| 					<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> | ||||
| 					<section | ||||
|  | @ -28,6 +29,10 @@ | |||
| 					/> | ||||
| 				</template> | ||||
| 			</div> | ||||
| 			<div class="sideMenu"> | ||||
| 				<button class="_button button" @click="addColumn"><i class="fas fa-plus"></i></button> | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</div> | ||||
| 
 | ||||
| 	<div v-if="isMobile" class="buttons"> | ||||
|  | @ -183,22 +188,14 @@ function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { | |||
| 	// TODO: ここではなくて、各カラムで自身の幅に応じて上書きするようにしたい | ||||
| 	--margin: var(--marginHalf); | ||||
| 
 | ||||
| 	--deckDividerThickness: 5px; | ||||
| 
 | ||||
| 	display: flex; | ||||
| 	// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/ | ||||
| 	height: calc(var(--vh, 1vh) * 100); | ||||
| 	box-sizing: border-box; | ||||
| 	flex: 1; | ||||
| 
 | ||||
| 	&.center { | ||||
| 		> .column:first-of-type { | ||||
| 			margin-left: auto; | ||||
| 		} | ||||
| 
 | ||||
| 		> .column:last-of-type { | ||||
| 			margin-right: auto; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	&.isMobile { | ||||
| 		padding-bottom: 100px; | ||||
| 	} | ||||
|  | @ -209,27 +206,58 @@ function moveFocus(id: string, direction: 'up' | 'down' | 'left' | 'right') { | |||
| 		display: flex; | ||||
| 		flex-direction: column; | ||||
| 
 | ||||
| 		> .columns { | ||||
| 			display: flex; | ||||
| 		> .columnsWrapper { | ||||
| 			flex: 1; | ||||
| 			padding: var(--deckMargin); | ||||
| 			display: flex; | ||||
| 			flex-direction: row; | ||||
| 
 | ||||
| 			> .columns { | ||||
| 				flex: 1; | ||||
| 				display: flex; | ||||
| 				overflow-x: auto; | ||||
| 				overflow-y: clip; | ||||
| 
 | ||||
| 				&.center { | ||||
| 					> .column:first-of-type { | ||||
| 						margin-left: auto; | ||||
| 					} | ||||
| 
 | ||||
| 					> .column:last-of-type { | ||||
| 						margin-right: auto; | ||||
| 					} | ||||
| 				} | ||||
| 
 | ||||
| 				> .column { | ||||
| 					flex-shrink: 0; | ||||
| 				margin-right: var(--deckMargin); | ||||
| 					border-right: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 
 | ||||
| 					&:first-child { | ||||
| 						border-left: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 					} | ||||
| 
 | ||||
| 					&.folder { | ||||
| 						display: flex; | ||||
| 						flex-direction: column; | ||||
| 
 | ||||
| 						> *:not(:last-child) { | ||||
| 						margin-bottom: var(--deckMargin); | ||||
| 							border-bottom: solid var(--deckDividerThickness) var(--deckDivider); | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 
 | ||||
| 			> .sideMenu { | ||||
| 				display: flex; | ||||
| 				flex-direction: column; | ||||
| 				justify-content: center; | ||||
| 				width: 32px; | ||||
| 
 | ||||
| 				> .button { | ||||
| 					width: 100%; | ||||
| 					aspect-ratio: 1; | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	> .buttons { | ||||
|  |  | |||
|  | @ -1,13 +1,14 @@ | |||
| <template> | ||||
| <!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> | ||||
| <section v-hotkey="keymap" class="dnpfarvg _panel _narrow_" | ||||
| <section | ||||
| 	v-hotkey="keymap" class="dnpfarvg _narrow_" | ||||
| 	:class="{ paged: isMainColumn, naked, active, isStacked, draghover, dragging, dropready }" | ||||
| 	:style="{ '--deckColumnHeaderHeight': deckStore.reactiveState.columnHeaderHeight.value + 'px' }" | ||||
| 	@dragover.prevent.stop="onDragover" | ||||
| 	@dragleave="onDragleave" | ||||
| 	@drop.prevent.stop="onDrop" | ||||
| > | ||||
| 	<header :class="{ indicated }" | ||||
| 	<header | ||||
| 		:class="{ indicated }" | ||||
| 		draggable="true" | ||||
| 		@click="goTop" | ||||
| 		@dragstart="onDragstart" | ||||
|  | @ -22,7 +23,7 @@ | |||
| 			<slot name="action"></slot> | ||||
| 		</div> | ||||
| 		<span class="header"><slot name="header"></slot></span> | ||||
| 		<button v-if="func" v-tooltip="func.title" class="menu _button" @click.stop="func.handler"><i :class="func.icon || 'fas fa-cog'"></i></button> | ||||
| 		<button v-tooltip="i18n.ts.settings" class="menu _button" @click.stop="showSettingsMenu"><i class="fas fa-cog"></i></button> | ||||
| 	</header> | ||||
| 	<div v-show="active" ref="body"> | ||||
| 		<slot></slot> | ||||
|  | @ -39,9 +40,8 @@ export type DeckFunc = { | |||
| </script> | ||||
| <script lang="ts" setup> | ||||
| import { onBeforeUnmount, onMounted, provide, watch } from 'vue'; | ||||
| import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column , deckStore } from './deck-store'; | ||||
| import * as os from '@/os'; | ||||
| import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn, Column } from './deck-store'; | ||||
| import { deckStore } from './deck-store'; | ||||
| import { i18n } from '@/i18n'; | ||||
| 
 | ||||
| provide('shouldHeaderThin', true); | ||||
|  | @ -105,7 +105,7 @@ function onOtherDragEnd() { | |||
| function toggleActive() { | ||||
| 	if (!props.isStacked) return; | ||||
| 	updateColumn(props.column.id, { | ||||
| 		active: !props.column.active | ||||
| 		active: !props.column.active, | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  | @ -118,69 +118,83 @@ function getMenu() { | |||
| 				name: { | ||||
| 					type: 'string', | ||||
| 					label: i18n.ts.name, | ||||
| 					default: props.column.name | ||||
| 					default: props.column.name, | ||||
| 				}, | ||||
| 				width: { | ||||
| 					type: 'number', | ||||
| 					label: i18n.ts.width, | ||||
| 					default: props.column.width | ||||
| 					default: props.column.width, | ||||
| 				}, | ||||
| 				flexible: { | ||||
| 					type: 'boolean', | ||||
| 					label: i18n.ts.flexible, | ||||
| 					default: props.column.flexible | ||||
| 				} | ||||
| 					default: props.column.flexible, | ||||
| 				}, | ||||
| 			}); | ||||
| 			if (canceled) return; | ||||
| 			updateColumn(props.column.id, result); | ||||
| 		} | ||||
| 		}, | ||||
| 	}, null, { | ||||
| 		icon: 'fas fa-arrow-left', | ||||
| 		text: i18n.ts._deck.swapLeft, | ||||
| 		action: () => { | ||||
| 			swapLeftColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	}, { | ||||
| 		icon: 'fas fa-arrow-right', | ||||
| 		text: i18n.ts._deck.swapRight, | ||||
| 		action: () => { | ||||
| 			swapRightColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	}, props.isStacked ? { | ||||
| 		icon: 'fas fa-arrow-up', | ||||
| 		text: i18n.ts._deck.swapUp, | ||||
| 		action: () => { | ||||
| 			swapUpColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	} : undefined, props.isStacked ? { | ||||
| 		icon: 'fas fa-arrow-down', | ||||
| 		text: i18n.ts._deck.swapDown, | ||||
| 		action: () => { | ||||
| 			swapDownColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	} : undefined, null, { | ||||
| 		icon: 'fas fa-window-restore', | ||||
| 		text: i18n.ts._deck.stackLeft, | ||||
| 		action: () => { | ||||
| 			stackLeftColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	}, props.isStacked ? { | ||||
| 		icon: 'fas fa-window-maximize', | ||||
| 		text: i18n.ts._deck.popRight, | ||||
| 		action: () => { | ||||
| 			popRightColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	} : undefined, null, { | ||||
| 		icon: 'fas fa-trash-alt', | ||||
| 		text: i18n.ts.remove, | ||||
| 		danger: true, | ||||
| 		action: () => { | ||||
| 			removeColumn(props.column.id); | ||||
| 		} | ||||
| 		}, | ||||
| 	}]; | ||||
| 
 | ||||
| 	if (props.func) { | ||||
| 		items.unshift(null); | ||||
| 		items.unshift({ | ||||
| 			icon: props.func.icon, | ||||
| 			text: props.func.title, | ||||
| 			action: props.func.handler, | ||||
| 		}); | ||||
| 	} | ||||
| 
 | ||||
| 	return items; | ||||
| } | ||||
| 
 | ||||
| function showSettingsMenu(ev: MouseEvent) { | ||||
| 	os.popupMenu(getMenu(), ev.currentTarget ?? ev.target); | ||||
| } | ||||
| 
 | ||||
| function onContextmenu(ev: MouseEvent) { | ||||
| 	os.contextMenu(getMenu(), ev); | ||||
| } | ||||
|  | @ -188,7 +202,7 @@ function onContextmenu(ev: MouseEvent) { | |||
| function goTop() { | ||||
| 	body.scrollTo({ | ||||
| 		top: 0, | ||||
| 		behavior: 'smooth' | ||||
| 		behavior: 'smooth', | ||||
| 	}); | ||||
| } | ||||
| 
 | ||||
|  | @ -239,15 +253,13 @@ function onDrop(ev) { | |||
| <style lang="scss" scoped> | ||||
| .dnpfarvg { | ||||
| 	--root-margin: 10px; | ||||
| 	--deckColumnHeaderHeight: 42px; | ||||
| 
 | ||||
| 	height: 100%; | ||||
| 	overflow: hidden; | ||||
| 	contain: content; | ||||
| 	box-shadow: 0 0 8px 0 var(--shadow); | ||||
| 	contain: strict; | ||||
| 
 | ||||
| 	&.draghover { | ||||
| 		box-shadow: 0 0 0 2px var(--focus); | ||||
| 
 | ||||
| 		&:after { | ||||
| 			content: ""; | ||||
| 			display: block; | ||||
|  | @ -262,7 +274,18 @@ function onDrop(ev) { | |||
| 	} | ||||
| 
 | ||||
| 	&.dragging { | ||||
| 		box-shadow: 0 0 0 2px var(--focus); | ||||
| 		&:after { | ||||
| 			content: ""; | ||||
| 			display: block; | ||||
| 			position: absolute; | ||||
| 			z-index: 1000; | ||||
| 			top: 0; | ||||
| 			left: 0; | ||||
| 			width: 100%; | ||||
| 			height: 100%; | ||||
| 			background: var(--focus); | ||||
| 			opacity: 0.5; | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	&.dropready { | ||||
|  |  | |||
|  | @ -54,14 +54,6 @@ export const deckStore = markRaw(new Storage('deck', { | |||
| 		where: 'deviceAccount', | ||||
| 		default: true, | ||||
| 	}, | ||||
| 	columnMargin: { | ||||
| 		where: 'deviceAccount', | ||||
| 		default: 16, | ||||
| 	}, | ||||
| 	columnHeaderHeight: { | ||||
| 		where: 'deviceAccount', | ||||
| 		default: 42, | ||||
| 	}, | ||||
| })); | ||||
| 
 | ||||
| export const loadDeck = async () => { | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue