From 78598a92f94d18fa6177f8ca4908954c1c9dc8bf Mon Sep 17 00:00:00 2001
From: syuilo <syuilotan@yahoo.co.jp>
Date: Sat, 26 Dec 2020 18:33:54 +0900
Subject: [PATCH] Improve deck

---
 locales/ja-JP.yml                  |  1 +
 src/client/ui/deck.vue             | 70 ++++++++++++------------------
 src/client/ui/deck/column-core.vue |  5 ++-
 src/client/ui/deck/column.vue      | 67 ++++++++++++----------------
 src/client/ui/deck/deck-store.ts   | 14 ------
 src/client/ui/deck/main-column.vue | 59 +++++++++++++++++++++++++
 src/server/web/boot.js             |  5 +++
 7 files changed, 123 insertions(+), 98 deletions(-)
 create mode 100644 src/client/ui/deck/main-column.vue

diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 9ac337b52a..a79e01c21e 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1524,6 +1524,7 @@ _deck:
   popRight: "右に出す"
 
   _columns:
+    main: "メイン"
     widgets: "ウィジェット"
     notifications: "通知"
     tl: "タイムライン"
diff --git a/src/client/ui/deck.vue b/src/client/ui/deck.vue
index 557df3a80d..453e169327 100644
--- a/src/client/ui/deck.vue
+++ b/src/client/ui/deck.vue
@@ -1,31 +1,25 @@
 <template>
-<div class="mk-deck" :class="`${deckStore.state.columnAlign}`" v-hotkey.global="keymap">
+<div class="mk-deck" :class="`${deckStore.state.columnAlign}`" v-hotkey.global="keymap" @contextmenu.self.prevent="onContextmenu">
 	<XSidebar ref="nav"/>
 
-	<!-- TODO: deckMainColumnPlace を見て位置変える -->
-	<DeckColumn class="column" v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'">
-		<template #header>
-			<XHeader :info="pageInfo"/>
-		</template>
-
-		<router-view v-slot="{ Component }">
-			<transition>
-				<keep-alive :include="['timeline']">
-					<component :is="Component" :ref="changePage"/>
-				</keep-alive>
-			</transition>
-		</router-view>
-	</DeckColumn>
-
 	<template v-for="ids in layout">
-		<div v-if="ids.length > 1" class="folder column">
+		<!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため -->
+		<section v-if="ids.length > 1"
+			class="folder column"
+			:style="{ width: Math.max(...columns.filter(c => ids.includes(c.id)).map(c => c.width)) + 'px' }"
+		>
 			<DeckColumnCore v-for="id in ids" :ref="id" :key="id" :column="columns.find(c => c.id === id)" :is-stacked="true" @parent-focus="moveFocus(id, $event)"/>
-		</div>
-		<DeckColumnCore v-else class="column" :ref="ids[0]" :key="ids[0]" :column="columns.find(c => c.id === ids[0])" @parent-focus="moveFocus(ids[0], $event)"/>
+		</section>
+		<DeckColumnCore v-else
+			class="column"
+			:ref="ids[0]"
+			:key="ids[0]"
+			:column="columns.find(c => c.id === ids[0])"
+			@parent-focus="moveFocus(ids[0], $event)"
+			:style="columns.find(c => c.id === ids[0]).flexible ? { flex: 1 } : { width: columns.find(c => c.id === ids[0]).width + 'px' }"
+		/>
 	</template>
 
-	<button @click="addColumn" class="_button add"><Fa :icon="faPlus"/></button>
-
 	<button v-if="$i" class="nav _button" @click="showNav()"><Fa :icon="faBars"/><i v-if="navIndicated"><Fa :icon="faCircle"/></i></button>
 	<button v-if="$i" class="post _buttonPrimary" @click="post()"><Fa :icon="faPencilAlt"/></button>
 
@@ -41,9 +35,7 @@ import { v4 as uuid } from 'uuid';
 import { host } from '@/config';
 import { search } from '@/scripts/search';
 import DeckColumnCore from '@/ui/deck/column-core.vue';
-import DeckColumn from '@/ui/deck/column.vue';
 import XSidebar from '@/components/sidebar.vue';
-import XHeader from './_common_/header.vue';
 import { getScrollContainer } from '@/scripts/scroll';
 import * as os from '@/os';
 import { sidebarDef } from '@/sidebar';
@@ -54,8 +46,6 @@ export default defineComponent({
 	components: {
 		XCommon,
 		XSidebar,
-		XHeader,
-		DeckColumn,
 		DeckColumnCore,
 	},
 
@@ -63,8 +53,6 @@ export default defineComponent({
 		return {
 			deckStore,
 			host: host,
-			pageInfo: null,
-			pageKey: 0,
 			menuDef: sidebarDef,
 			wallpaper: localStorage.getItem('wallpaper') != null,
 			faPlus, faPencilAlt, faChevronLeft, faBars, faCircle
@@ -95,12 +83,6 @@ export default defineComponent({
 		},
 	},
 
-	watch: {
-		$route(to, from) {
-			this.pageKey++;
-		},
-	},
-
 	created() {
 		document.documentElement.style.overflowY = 'hidden';
 		document.documentElement.style.scrollBehavior = 'auto';
@@ -111,13 +93,6 @@ export default defineComponent({
 	},
 
 	methods: {
-		changePage(page) {
-			if (page == null) return;
-			if (page.INFO) {
-				this.pageInfo = page.INFO;
-			}
-		},
-
 		onWheel(e) {
 			if (getScrollContainer(e.target) == null) {
 				document.documentElement.scrollLeft += e.deltaY > 0 ? 96 : -96;
@@ -138,6 +113,7 @@ export default defineComponent({
 
 		async addColumn(ev) {
 			const columns = [
+				'main',
 				'widgets',
 				'notifications',
 				'tl',
@@ -166,6 +142,14 @@ export default defineComponent({
 				width: 330,
 			});
 		},
+
+		onContextmenu(e) {
+			os.contextMenu([{
+				text: this.$ts._deck.addColumn,
+				icon: null,
+				action: this.addColumn
+			}], e);
+		},
 	}
 });
 </script>
@@ -175,7 +159,7 @@ export default defineComponent({
 	$nav-hide-threshold: 650px; // TODO: どこかに集約したい
 
 	// TODO: この値を設定で変えられるようにする?
-	$columnMargin: 12px;
+	$columnMargin: 32px;
 
 	$deckMargin: $columnMargin;
 
@@ -186,14 +170,14 @@ export default defineComponent({
 	height: calc(var(--vh, 1vh) * 100);
 	box-sizing: border-box;
 	flex: 1;
-	padding: $deckMargin 0 $deckMargin $deckMargin;
+	padding: $deckMargin;
 
 	&.center {
 		> .column:first-of-type {
 			margin-left: auto;
 		}
 
-		> .add {
+		> .column:last-of-type {
 			margin-right: auto;
 		}
 	}
diff --git a/src/client/ui/deck/column-core.vue b/src/client/ui/deck/column-core.vue
index 15cf763a52..c6a2104268 100644
--- a/src/client/ui/deck/column-core.vue
+++ b/src/client/ui/deck/column-core.vue
@@ -1,6 +1,7 @@
 <template>
 <!-- TODO: リファクタの余地がありそう -->
-<XWidgetsColumn v-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/>
+<XMainColumn v-if="column.type === 'main'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/>
+<XWidgetsColumn v-else-if="column.type === 'widgets'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/>
 <XNotificationsColumn v-else-if="column.type === 'notifications'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/>
 <XTlColumn v-else-if="column.type === 'tl'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/>
 <XListColumn v-else-if="column.type === 'list'" :column="column" :is-stacked="isStacked" @parent-focus="$emit('parent-focus', $event)"/>
@@ -12,6 +13,7 @@
 
 <script lang="ts">
 import { defineComponent } from 'vue';
+import XMainColumn from './main-column.vue';
 import XTlColumn from './tl-column.vue';
 import XAntennaColumn from './antenna-column.vue';
 import XListColumn from './list-column.vue';
@@ -22,6 +24,7 @@ import XDirectColumn from './direct-column.vue';
 
 export default defineComponent({
 	components: {
+		XMainColumn,
 		XTlColumn,
 		XAntennaColumn,
 		XListColumn,
diff --git a/src/client/ui/deck/column.vue b/src/client/ui/deck/column.vue
index 41dcc3e360..17aa922823 100644
--- a/src/client/ui/deck/column.vue
+++ b/src/client/ui/deck/column.vue
@@ -5,7 +5,6 @@
 	@dragleave="onDragleave"
 	@drop.prevent.stop="onDrop"
 	v-hotkey="keymap"
-	:style="{ width: `${width}px` }"
 >
 	<header :class="{ indicated }"
 		draggable="true"
@@ -14,7 +13,7 @@
 		@dragend="onDragend"
 		@contextmenu.prevent.stop="onContextmenu"
 	>
-		<button class="toggleActive _button" @click="toggleActive" v-if="isStacked">
+		<button class="toggleActive _button" @click="toggleActive" v-if="isStacked && !isMainColumn">
 			<template v-if="active"><Fa :icon="faAngleUp"/></template>
 			<template v-else><Fa :icon="faAngleDown"/></template>
 		</button>
@@ -35,7 +34,7 @@ import { defineComponent } from 'vue';
 import { faArrowUp, faArrowDown, faAngleUp, faAngleDown, faCaretDown, faArrowRight, faArrowLeft, faPencilAlt } from '@fortawesome/free-solid-svg-icons';
 import { faWindowMaximize, faTrashAlt, faWindowRestore } from '@fortawesome/free-regular-svg-icons';
 import * as os from '@/os';
-import { renameColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store';
+import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store';
 
 export default defineComponent({
 	props: {
@@ -78,11 +77,7 @@ export default defineComponent({
 
 	computed: {
 		isMainColumn(): boolean {
-			return this.column == null;
-		},
-
-		width(): number {
-			return this.isMainColumn ? 350 : this.column.width;
+			return this.column.type === 'main';
 		},
 
 		keymap(): any {
@@ -106,17 +101,13 @@ export default defineComponent({
 	},
 
 	mounted() {
-		if (!this.isMainColumn) {
-			os.deckGlobalEvents.on('column.dragStart', this.onOtherDragStart);
-			os.deckGlobalEvents.on('column.dragEnd', this.onOtherDragEnd);
-		}
+		os.deckGlobalEvents.on('column.dragStart', this.onOtherDragStart);
+		os.deckGlobalEvents.on('column.dragEnd', this.onOtherDragEnd);
 	},
 
 	beforeUnmount() {
-		if (!this.isMainColumn) {
-			os.deckGlobalEvents.off('column.dragStart', this.onOtherDragStart);
-			os.deckGlobalEvents.off('column.dragEnd', this.onOtherDragEnd);
-		}
+		os.deckGlobalEvents.off('column.dragStart', this.onOtherDragStart);
+		os.deckGlobalEvents.off('column.dragEnd', this.onOtherDragEnd);
 	},
 
 	methods: {
@@ -136,18 +127,27 @@ export default defineComponent({
 		getMenu() {
 			const items = [{
 				icon: faPencilAlt,
-				text: this.$ts.rename,
-				action: () => {
-					os.dialog({
-						title: this.$ts.rename,
-						input: {
-							default: this.column.name,
-							allowEmpty: false
+				text: this.$ts.edit,
+				action: async () => {
+					const { canceled, result } = await os.form(this.column.name, {
+						name: {
+							type: 'string',
+							label: this.$ts.name,
+							default: this.column.name
+						},
+						width: {
+							type: 'number',
+							label: this.$ts.width,
+							default: this.column.width
+						},
+						flexible: {
+							type: 'boolean',
+							label: this.$ts.flexible,
+							default: this.column.flexible
 						}
-					}).then(({ canceled, result: name }) => {
-						if (canceled) return;
-						renameColumn(this.column.id, name);
 					});
+					if (canceled) return;
+					updateColumn(this.column.id, result);
 				}
 			}, null, {
 				icon: faArrowLeft,
@@ -203,8 +203,7 @@ export default defineComponent({
 		},
 
 		onContextmenu(e) {
-			if (this.isMainColumn) return;
-			this.showMenu();
+			os.contextMenu(this.getMenu(), e);
 		},
 
 		showMenu() {
@@ -219,12 +218,6 @@ export default defineComponent({
 		},
 
 		onDragstart(e) {
-			// メインカラムはドラッグさせない
-			if (this.isMainColumn) {
-				e.preventDefault();
-				return;
-			}
-
 			e.dataTransfer.effectAllowed = 'move';
 			e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, this.column.id);
 			this.dragging = true;
@@ -235,12 +228,6 @@ export default defineComponent({
 		},
 
 		onDragover(e) {
-			// メインカラムにはドロップさせない
-			if (this.isMainColumn) {
-				e.dataTransfer.dropEffect = 'none';
-				return;
-			}
-
 			// 自分自身がドラッグされている場合
 			if (this.dragging) {
 				// 自分自身にはドロップさせない
diff --git a/src/client/ui/deck/deck-store.ts b/src/client/ui/deck/deck-store.ts
index 2d0012b566..7693401e15 100644
--- a/src/client/ui/deck/deck-store.ts
+++ b/src/client/ui/deck/deck-store.ts
@@ -36,10 +36,6 @@ export const deckStore = markRaw(new Storage('deck', {
 		where: 'deviceAccount',
 		default: true
 	},
-	mainColumnPlace: {
-		where: 'deviceAccount',
-		default: 'left' as 'left' | 'right'
-	},
 	navWindow: {
 		where: 'deviceAccount',
 		default: true
@@ -200,16 +196,6 @@ export function updateColumnWidget(id: Column['id'], widgetId: string, data: any
 	deckStore.set('columns', columns);
 }
 
-export function renameColumn(id: Column['id'], name: Column['name']) {
-	const columns = copy(deckStore.state.columns);
-	const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
-	const column = copy(deckStore.state.columns[columnIndex]);
-	if (column == null) return;
-	column.name = name;
-	columns[columnIndex] = column;
-	deckStore.set('columns', columns);
-}
-
 export function updateColumn(id: Column['id'], column: Partial<Column>) {
 	const columns = copy(deckStore.state.columns);
 	const columnIndex = deckStore.state.columns.findIndex(c => c.id === id);
diff --git a/src/client/ui/deck/main-column.vue b/src/client/ui/deck/main-column.vue
new file mode 100644
index 0000000000..f652b01f6e
--- /dev/null
+++ b/src/client/ui/deck/main-column.vue
@@ -0,0 +1,59 @@
+<template>
+<XColumn v-if="deckStore.state.alwaysShowMainColumn || $route.name !== 'index'" :column="column" :is-stacked="isStacked">
+	<template #header>
+		<XHeader :info="pageInfo"/>
+	</template>
+
+	<router-view v-slot="{ Component }">
+		<transition>
+			<keep-alive :include="['timeline']">
+				<component :is="Component" :ref="changePage"/>
+			</keep-alive>
+		</transition>
+	</router-view>
+</XColumn>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import XColumn from './column.vue';
+import XNotes from '@/components/notes.vue';
+import XHeader from '@/ui/_common_/header.vue';
+import { deckStore } from '@/ui/deck/deck-store';
+
+export default defineComponent({
+	components: {
+		XColumn,
+		XHeader,
+		XNotes
+	},
+
+	props: {
+		column: {
+			type: Object,
+			required: true
+		},
+		isStacked: {
+			type: Boolean,
+			required: true
+		}
+	},
+
+	data() {
+		return {
+			deckStore,
+			pageInfo: null,
+			pageKey: 0,
+		}
+	},
+
+	methods: {
+		changePage(page) {
+			if (page == null) return;
+			if (page.INFO) {
+				this.pageInfo = page.INFO;
+			}
+		},
+	}
+});
+</script>
diff --git a/src/server/web/boot.js b/src/server/web/boot.js
index 8b1fd9a619..12731e5d3b 100644
--- a/src/server/web/boot.js
+++ b/src/server/web/boot.js
@@ -104,6 +104,11 @@
 		document.documentElement.classList.add('useSystemFont');
 	}
 
+	const wallpaper = localStorage.getItem('wallpaper');
+	if (wallpaper) {
+		document.documentElement.style.backgroundImage = `url(${wallpaper})`;
+	}
+
 	// eslint-disable-next-line no-inner-declarations
 	function refresh() {
 		// Random