From 9ea7b08689e5f9d87a99dee846d2d10b59f53718 Mon Sep 17 00:00:00 2001
From: Freeplay <freeplay@duck.com>
Date: Fri, 2 Jun 2023 13:28:52 -0400
Subject: [PATCH] start of instance kanban redesign

---
 packages/client/src/init.ts                |   2 -
 packages/client/src/ui/_common_/navbar.vue |   4 +-
 packages/client/src/ui/universal.vue       | 217 ++++++------
 packages/client/src/ui/visitor/kanban.vue  | 390 ++++++---------------
 4 files changed, 213 insertions(+), 400 deletions(-)

diff --git a/packages/client/src/init.ts b/packages/client/src/init.ts
index 7a004688d6..67325e9b0a 100644
--- a/packages/client/src/init.ts
+++ b/packages/client/src/init.ts
@@ -176,8 +176,6 @@ import { getAccountFromId } from "@/scripts/get-account-from-id";
 	const app = createApp(
 		window.location.search === "?zen"
 			? defineAsyncComponent(() => import("@/ui/zen.vue"))
-			: !$i
-			? defineAsyncComponent(() => import("@/ui/visitor.vue"))
 			: ui === "deck"
 			? defineAsyncComponent(() => import("@/ui/deck.vue"))
 			: defineAsyncComponent(() => import("@/ui/universal.vue")),
diff --git a/packages/client/src/ui/_common_/navbar.vue b/packages/client/src/ui/_common_/navbar.vue
index 68e4619ebb..7b1ad774ab 100644
--- a/packages/client/src/ui/_common_/navbar.vue
+++ b/packages/client/src/ui/_common_/navbar.vue
@@ -1,7 +1,7 @@
 <template>
 	<header class="mvcprjjd sidebar" :class="{ iconOnly }">
 		<div class="body">
-			<div class="top">
+			<div class="top" v-if="$i">
 				<div
 					class="banner"
 					:user="$i"
@@ -72,7 +72,7 @@
 				</template>
 				<div class="divider"></div>
 				<MkA
-					v-if="$i.isAdmin || $i.isModerator"
+					v-if="$i?.isAdmin || $i?.isModerator"
 					v-click-anime
 					v-tooltip.noDelay.right="i18n.ts.controlPanel"
 					class="item _button"
diff --git a/packages/client/src/ui/universal.vue b/packages/client/src/ui/universal.vue
index eb2857b2f5..d91660d4cb 100644
--- a/packages/client/src/ui/universal.vue
+++ b/packages/client/src/ui/universal.vue
@@ -19,117 +19,123 @@
 			</main>
 		</MkStickyContainer>
 
-		<div v-if="isDesktop" ref="widgetsEl" class="widgets">
-			<XWidgets @mounted="attachSticky" />
-		</div>
+		<template v-if="$i">
+			<div v-if="isDesktop" ref="widgetsEl" class="widgets">
+				<XWidgets @mounted="attachSticky" />
+			</div>
 
-		<button
-			v-if="!isDesktop && !isMobile"
-			class="widgetButton _button"
-			@click="widgetsShowing = true"
-		>
-			<i class="ph-stack ph-bold ph-lg"></i>
-		</button>
 
-		<div v-if="isMobile" class="buttons">
 			<button
-				:aria-label="i18n.t('menu')"
-				class="button nav _button"
-				@click="drawerMenuShowing = true"
-			>
-				<div class="button-wrapper">
-					<i class="ph-list ph-bold ph-lg"></i
-					><span v-if="menuIndicated" class="indicator"
-						><i class="ph-circle ph-fill"></i
-					></span>
-				</div>
-			</button>
-			<button
-				:aria-label="i18n.t('home')"
-				class="button home _button"
-				@click="
-					mainRouter.currentRoute.value.name === 'index'
-						? top()
-						: mainRouter.push('/');
-					updateButtonState();
-				"
-			>
-				<div
-					class="button-wrapper"
-					:class="buttonAnimIndex === 0 ? 'on' : ''"
-				>
-					<i class="ph-house ph-bold ph-lg"></i>
-				</div>
-			</button>
-			<button
-				:aria-label="i18n.t('notifications')"
-				class="button notifications _button"
-				@click="
-					mainRouter.push('/my/notifications');
-					updateButtonState();
-				"
-			>
-				<div
-					class="button-wrapper"
-					:class="buttonAnimIndex === 1 ? 'on' : ''"
-				>
-					<i class="ph-bell ph-bold ph-lg"></i
-					><span v-if="$i?.hasUnreadNotification" class="indicator"
-						><i class="ph-circle ph-fill"></i
-					></span>
-				</div>
-			</button>
-			<button
-				:aria-label="i18n.t('messaging')"
-				class="button messaging _button"
-				@click="
-					mainRouter.push('/my/messaging');
-					updateButtonState();
-				"
-			>
-				<div
-					class="button-wrapper"
-					:class="buttonAnimIndex === 2 ? 'on' : ''"
-				>
-					<i class="ph-chats-teardrop ph-bold ph-lg"></i
-					><span
-						v-if="$i?.hasUnreadMessagingMessage"
-						class="indicator"
-						><i class="ph-circle ph-fill"></i
-					></span>
-				</div>
-			</button>
-			<button
-				:aria-label="i18n.t('_deck._columns.widgets')"
-				class="button widget _button"
+				v-if="!isDesktop && !isMobile"
+				class="widgetButton _button"
 				@click="widgetsShowing = true"
 			>
-				<div class="button-wrapper">
-					<i class="ph-stack ph-bold ph-lg"></i>
-				</div>
+				<i class="ph-stack ph-bold ph-lg"></i>
 			</button>
-		</div>
 
-		<button
-			v-if="isMobile && mainRouter.currentRoute.value.name === 'index'"
-			ref="postButton"
-			:aria-label="i18n.t('note')"
-			class="postButton button post _button"
-			@click="os.post()"
-		>
-			<i class="ph-pencil ph-bold ph-lg"></i>
-		</button>
-		<button
-			v-if="
-				isMobile && mainRouter.currentRoute.value.name === 'messaging'
-			"
-			ref="postButton"
-			class="postButton button post _button"
-			:aria-label="i18n.t('startMessaging')"
-			@click="messagingStart"
-		>
-			<i class="ph-user-plus ph-bold ph-lg"></i>
-		</button>
+			<div v-if="isMobile" class="buttons">
+				<button
+					:aria-label="i18n.t('menu')"
+					class="button nav _button"
+					@click="drawerMenuShowing = true"
+				>
+					<div class="button-wrapper">
+						<i class="ph-list ph-bold ph-lg"></i
+						><span v-if="menuIndicated" class="indicator"
+							><i class="ph-circle ph-fill"></i
+						></span>
+					</div>
+				</button>
+				<button
+					:aria-label="i18n.t('home')"
+					class="button home _button"
+					@click="
+						mainRouter.currentRoute.value.name === 'index'
+							? top()
+							: mainRouter.push('/');
+						updateButtonState();
+					"
+				>
+					<div
+						class="button-wrapper"
+						:class="buttonAnimIndex === 0 ? 'on' : ''"
+					>
+						<i class="ph-house ph-bold ph-lg"></i>
+					</div>
+				</button>
+				<button
+					:aria-label="i18n.t('notifications')"
+					class="button notifications _button"
+					@click="
+						mainRouter.push('/my/notifications');
+						updateButtonState();
+					"
+				>
+					<div
+						class="button-wrapper"
+						:class="buttonAnimIndex === 1 ? 'on' : ''"
+					>
+						<i class="ph-bell ph-bold ph-lg"></i
+						><span v-if="$i?.hasUnreadNotification" class="indicator"
+							><i class="ph-circle ph-fill"></i
+						></span>
+					</div>
+				</button>
+				<button
+					:aria-label="i18n.t('messaging')"
+					class="button messaging _button"
+					@click="
+						mainRouter.push('/my/messaging');
+						updateButtonState();
+					"
+				>
+					<div
+						class="button-wrapper"
+						:class="buttonAnimIndex === 2 ? 'on' : ''"
+					>
+						<i class="ph-chats-teardrop ph-bold ph-lg"></i
+						><span
+							v-if="$i?.hasUnreadMessagingMessage"
+							class="indicator"
+							><i class="ph-circle ph-fill"></i
+						></span>
+					</div>
+				</button>
+				<button
+					:aria-label="i18n.t('_deck._columns.widgets')"
+					class="button widget _button"
+					@click="widgetsShowing = true"
+				>
+					<div class="button-wrapper">
+						<i class="ph-stack ph-bold ph-lg"></i>
+					</div>
+				</button>
+			</div>
+
+			<button
+				v-if="isMobile && mainRouter.currentRoute.value.name === 'index'"
+				ref="postButton"
+				:aria-label="i18n.t('note')"
+				class="postButton button post _button"
+				@click="os.post()"
+			>
+				<i class="ph-pencil ph-bold ph-lg"></i>
+			</button>
+			<button
+				v-if="
+					isMobile && mainRouter.currentRoute.value.name === 'messaging'
+				"
+				ref="postButton"
+				class="postButton button post _button"
+				:aria-label="i18n.t('startMessaging')"
+				@click="messagingStart"
+			>
+				<i class="ph-user-plus ph-bold ph-lg"></i>
+			</button>
+		</template>
+		<template v-else>
+			<XKanban class="kanban" full />
+		</template>
 
 		<transition :name="$store.state.animation ? 'menuDrawer-back' : ''">
 			<div
@@ -165,6 +171,7 @@
 import { defineAsyncComponent, provide, onMounted, computed, ref } from "vue";
 import XCommon from "./_common_/common.vue";
 import * as Acct from "calckey-js/built/acct";
+import XKanban from "./visitor/kanban.vue";
 import type { ComputedRef } from "vue";
 import type { PageMetadata } from "@/scripts/page-metadata";
 import { instanceName, ui } from "@/config";
@@ -552,8 +559,8 @@ console.log(mainRouter.currentRoute.value.name);
 	}
 
 	> .contents {
-		width: 100%;
 		min-width: 0;
+		flex-grow: 1;
 		$widgets-hide-threshold: 1090px;
 		@media (max-width: $widgets-hide-threshold) {
 			padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 96px);
diff --git a/packages/client/src/ui/visitor/kanban.vue b/packages/client/src/ui/visitor/kanban.vue
index 1e25d08b81..a21912fb27 100644
--- a/packages/client/src/ui/visitor/kanban.vue
+++ b/packages/client/src/ui/visitor/kanban.vue
@@ -1,313 +1,121 @@
 <template>
-	<div
-		class="rwqkcmrc"
-		:style="{
-			backgroundImage: transparent
-				? 'none'
-				: `url(${$instance.backgroundImageUrl})`,
-		}"
-	>
-		<div class="back" :class="{ transparent }"></div>
-		<div class="contents">
-			<div class="wrapper">
-				<h1 v-if="meta" :class="{ full }">
-					<MkA to="/" class="link"
-						><img
-							v-if="meta.logoImageUrl"
-							class="logo"
-							:src="meta.logoImageUrl"
-							alt="logo"
-						/><span v-else class="text">{{
-							instanceName
-						}}</span></MkA
+	<div class="instance-info-container">
+		<header
+			id="instance-info"
+		>
+			<img class="banner" :src="meta?.backgroundImageUrl" />
+			<div class="content">
+				<header>
+					<img 
+						class="logo"
+						:src="meta?.logoImageUrl"
+					/>
+					<h1>
+						<MkA
+							to="/" class="link"
+						>{{ instanceName }}</MkA>
+					</h1>
+				</header>
+				<div v-if="meta" class="about">
+					<Mfm
+						class="desc"
+						:class="{ collapsed: isLong && collapsed }"
+						:text="meta.description || i18n.ts.introMisskey"
+					></Mfm>
+					<XShowMoreButton
+						v-if="isLong"
+						v-model="collapsed"
+					></XShowMoreButton>
+				</div>
+
+				<FormSection>
+					<div class="_formLinksGrid">
+						<FormLink v-if="meta?.tosUrl" :to="meta.tosUrl"
+							><template #icon
+								><i
+									class="ph-scroll ph-bold ph-lg"
+								></i></template
+							>{{ i18n.ts.tos }}
+						</FormLink>
+					</div>
+				</FormSection>
+
+				<section class="announcements">
+					<h1>{{ i18n.ts.announcements }}</h1>
+					<MkPagination
+						v-slot="{ items }"
+						:pagination="announcements"
+						class="list"
 					>
-				</h1>
-				<template v-if="full">
-					<div v-if="meta" class="about">
-						<div
-							class="desc"
-							v-html="meta.description || i18n.ts.introMisskey"
-						></div>
-					</div>
-					<div class="action">
-						<button class="_buttonPrimary" @click="signup()">
-							{{ i18n.ts.signup }}
-						</button>
-						<button class="_button" @click="signin()">
-							{{ i18n.ts.login }}
-						</button>
-					</div>
-					<div class="announcements panel">
-						<header>{{ i18n.ts.announcements }}</header>
-						<MkPagination
-							v-slot="{ items }"
-							:pagination="announcements"
-							class="list"
+						<article
+							v-for="announcement in items"
+							:key="announcement.id"
+							class="item"
 						>
-							<section
-								v-for="announcement in items"
-								:key="announcement.id"
-								class="item"
-							>
-								<div class="title">
-									{{ announcement.title }}
-								</div>
-								<div class="content">
-									<Mfm :text="announcement.text" />
-									<img
-										v-if="announcement.imageUrl"
-										:src="announcement.imageUrl"
-										alt="announcement image"
-									/>
-								</div>
-							</section>
-						</MkPagination>
-					</div>
-					<div v-if="poweredBy" class="powered-by">
-						<b
-							><MkA to="/">{{ host }}</MkA></b
-						>
-						<small
-							>Powered by
-							<a href="https://calckey.org/" target="_blank"
-								>Calckey</a
-							></small
-						>
-					</div>
-				</template>
+							<div class="title">
+								{{ announcement.title }}
+							</div>
+							<div class="content">
+								<Mfm :text="announcement.text" />
+								<img
+									v-if="announcement.imageUrl"
+									:src="announcement.imageUrl"
+									alt="announcement image"
+								/>
+							</div>
+						</article>
+					</MkPagination>
+				</section>
+				<div v-if="poweredBy" class="powered-by">
+					<b
+						><MkA to="/">{{ host }}</MkA></b
+					>
+					<small
+						>Powered by
+						<a href="https://calckey.org/" target="_blank"
+							>Calckey</a
+						></small
+					>
+				</div>
 			</div>
-		</div>
+		</header>
 	</div>
 </template>
 
-<script lang="ts">
-import { defineComponent, defineAsyncComponent } from "vue";
+<script lang="ts" setup>
+import { ref } from "vue";
 import { host, instanceName } from "@/config";
 import * as os from "@/os";
 import MkPagination from "@/components/MkPagination.vue";
-import XSigninDialog from "@/components/MkSigninDialog.vue";
-import XSignupDialog from "@/components/MkSignupDialog.vue";
 import MkButton from "@/components/MkButton.vue";
+import FormSection from "@/components/form/section.vue";
+import FormLink from "@/components/form/link.vue";
+import XShowMoreButton from "@/components/MkShowMoreButton.vue";
 import { i18n } from "@/i18n";
+import { DetailedInstanceMetadata } from "calckey-js/built/entities";
 
-export default defineComponent({
-	components: {
-		MkPagination,
-		MkButton,
-	},
+defineProps<{
+	poweredBy?: boolean,
+}>()
 
-	props: {
-		full: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		transparent: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-		poweredBy: {
-			type: Boolean,
-			required: false,
-			default: false,
-		},
-	},
+const announcements = {
+	endpoint: "announcements",
+	limit: 10,
+}
+let meta = $ref<DetailedInstanceMetadata>();
 
-	data() {
-		return {
-			host,
-			instanceName,
-			pageInfo: null,
-			meta: null,
-			narrow: window.innerWidth < 1280,
-			announcements: {
-				endpoint: "announcements",
-				limit: 10,
-			},
-			i18n,
-		};
-	},
+let isLong = $ref(false);
+let collapsed = $ref(!isLong);
 
-	created() {
-		os.api("meta", { detail: true }).then((meta) => {
-			this.meta = meta;
-		});
-	},
-
-	methods: {
-		signin() {
-			os.popup(
-				XSigninDialog,
-				{
-					autoSet: true,
-				},
-				{},
-				"closed"
-			);
-		},
-
-		signup() {
-			os.popup(
-				XSignupDialog,
-				{
-					autoSet: true,
-				},
-				{},
-				"closed"
-			);
-		},
-	},
+os.api("meta", { detail: true }).then((res) => {
+	meta = res;
+	isLong = meta.description && (meta.description.length > 100);
 });
+
+
+
 </script>
 
 <style lang="scss" scoped>
-.rwqkcmrc {
-	position: relative;
-	text-align: center;
-	background-position: center;
-	background-size: cover;
-	// TODO: パララックスにしたい
 
-	> .back {
-		position: absolute;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 100%;
-		background: rgba(0, 0, 0, 0.3);
-
-		&.transparent {
-			-webkit-backdrop-filter: var(--blur, blur(12px));
-			backdrop-filter: var(--blur, blur(12px));
-		}
-	}
-
-	> .contents {
-		position: relative;
-		z-index: 1;
-		height: inherit;
-		overflow: auto;
-
-		> .wrapper {
-			max-width: 380px;
-			padding: 0 16px;
-			box-sizing: border-box;
-			margin: 0 auto;
-
-			> .panel {
-				-webkit-backdrop-filter: var(--blur, blur(8px));
-				backdrop-filter: var(--blur, blur(8px));
-				background: rgba(0, 0, 0, 0.5);
-				border-radius: var(--radius);
-
-				&,
-				* {
-					color: #fff !important;
-				}
-			}
-
-			> h1 {
-				display: block;
-				margin: 0;
-				padding: 32px 0 32px 0;
-				color: #fff;
-
-				&.full {
-					padding: 64px 0 0 0;
-
-					> .link {
-						> ::v-deep(.logo) {
-							max-height: 130px;
-						}
-					}
-				}
-
-				> .link {
-					display: block;
-
-					> ::v-deep(.logo) {
-						vertical-align: bottom;
-						max-height: 100px;
-					}
-				}
-			}
-
-			> .about {
-				display: block;
-				margin: 24px 0;
-				text-align: center;
-				box-sizing: border-box;
-				text-shadow: 0 0 8px black;
-				color: #fff;
-			}
-
-			> .action {
-				> button {
-					display: block;
-					width: 100%;
-					padding: 10px;
-					box-sizing: border-box;
-					text-align: center;
-					border-radius: 999px;
-
-					&._button {
-						background: var(--panel);
-					}
-
-					&:first-child {
-						margin-bottom: 16px;
-					}
-				}
-			}
-
-			> .announcements {
-				margin: 32px 0;
-				text-align: left;
-
-				> header {
-					padding: 12px 16px;
-					border-bottom: solid 1px rgba(255, 255, 255, 0.5);
-				}
-
-				> .list {
-					max-height: 300px;
-					overflow: auto;
-
-					> .item {
-						padding: 12px 16px;
-
-						& + .item {
-							border-top: solid 1px rgba(255, 255, 255, 0.5);
-						}
-
-						> .title {
-							font-weight: bold;
-						}
-
-						> .content {
-							> img {
-								max-width: 100%;
-							}
-						}
-					}
-				}
-			}
-
-			> .powered-by {
-				padding: 28px;
-				font-size: 14px;
-				text-align: center;
-				border-top: 1px solid rgba(255, 255, 255, 0.5);
-				color: #fff;
-
-				> small {
-					display: block;
-					margin-top: 8px;
-					opacity: 0.5;
-				}
-			}
-		}
-	}
-}
 </style>