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>