mirror of
https://iceshrimp.dev/limepotato/jormungandr-bite.git
synced 2024-11-22 01:47:39 -07:00
[client] Rework OAuth login page
This commit is contained in:
parent
be02bc78ad
commit
093f13646e
5 changed files with 164 additions and 52 deletions
|
@ -1032,6 +1032,7 @@ activeEmailValidationDescription: "Enables stricter validation of email addresse
|
||||||
navbar: "Navigation bar"
|
navbar: "Navigation bar"
|
||||||
shuffle: "Shuffle"
|
shuffle: "Shuffle"
|
||||||
account: "Account"
|
account: "Account"
|
||||||
|
signedInAs: "Signed in as"
|
||||||
move: "Move"
|
move: "Move"
|
||||||
pushNotification: "Push notifications"
|
pushNotification: "Push notifications"
|
||||||
subscribePushNotification: "Enable push notifications"
|
subscribePushNotification: "Enable push notifications"
|
||||||
|
|
|
@ -150,6 +150,7 @@ export async function openAccountMenu(
|
||||||
opts: {
|
opts: {
|
||||||
includeCurrentAccount?: boolean;
|
includeCurrentAccount?: boolean;
|
||||||
withExtraOperation: boolean;
|
withExtraOperation: boolean;
|
||||||
|
withoutProfileLink?: boolean;
|
||||||
active?: misskey.entities.UserDetailed["id"];
|
active?: misskey.entities.UserDetailed["id"];
|
||||||
onChoose?: (account: misskey.entities.UserDetailed) => void;
|
onChoose?: (account: misskey.entities.UserDetailed) => void;
|
||||||
},
|
},
|
||||||
|
@ -227,53 +228,56 @@ export async function openAccountMenu(
|
||||||
);
|
);
|
||||||
|
|
||||||
if (opts.withExtraOperation) {
|
if (opts.withExtraOperation) {
|
||||||
popupMenu(
|
const menu = [
|
||||||
[
|
...(!opts.withoutProfileLink ? [
|
||||||
...[
|
{
|
||||||
|
type: "link",
|
||||||
|
text: i18n.ts.profile,
|
||||||
|
to: `/@${$i.username}`,
|
||||||
|
avatar: $i,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
] : []),
|
||||||
|
...(opts.includeCurrentAccount ? [createItem($i)] : []),
|
||||||
|
...accountItemPromises,
|
||||||
|
...(opts.withoutProfileLink ? [null] : []),
|
||||||
|
{
|
||||||
|
type: "parent",
|
||||||
|
icon: "ph-plus ph-bold ph-lg",
|
||||||
|
text: i18n.ts.addAccount,
|
||||||
|
children: [
|
||||||
{
|
{
|
||||||
type: "link",
|
text: i18n.ts.existingAccount,
|
||||||
text: i18n.ts.profile,
|
|
||||||
to: `/@${$i.username}`,
|
|
||||||
avatar: $i,
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
...(opts.includeCurrentAccount ? [createItem($i)] : []),
|
|
||||||
...accountItemPromises,
|
|
||||||
{
|
|
||||||
type: "parent",
|
|
||||||
icon: "ph-plus ph-bold ph-lg",
|
|
||||||
text: i18n.ts.addAccount,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
text: i18n.ts.existingAccount,
|
|
||||||
action: () => {
|
|
||||||
showSigninDialog();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: i18n.ts.createAccount,
|
|
||||||
action: () => {
|
|
||||||
createAccount();
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "link",
|
|
||||||
icon: "ph-users ph-bold ph-lg",
|
|
||||||
text: i18n.ts.manageAccounts,
|
|
||||||
to: "/settings/accounts",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "button",
|
|
||||||
icon: "ph-sign-out ph-bold ph-lg",
|
|
||||||
text: i18n.ts.logout,
|
|
||||||
action: () => {
|
action: () => {
|
||||||
signout();
|
showSigninDialog();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: i18n.ts.createAccount,
|
||||||
|
action: () => {
|
||||||
|
createAccount();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
],
|
},
|
||||||
|
{
|
||||||
|
type: "link",
|
||||||
|
icon: "ph-users ph-bold ph-lg",
|
||||||
|
text: i18n.ts.manageAccounts,
|
||||||
|
to: "/settings/accounts",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: "button",
|
||||||
|
icon: "ph-sign-out ph-bold ph-lg",
|
||||||
|
text: i18n.ts.logout,
|
||||||
|
action: () => {
|
||||||
|
signout();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
popupMenu(
|
||||||
|
menu,
|
||||||
ev.currentTarget ?? ev.target,
|
ev.currentTarget ?? ev.target,
|
||||||
{
|
{
|
||||||
align: "left",
|
align: "left",
|
||||||
|
|
|
@ -188,7 +188,9 @@ function checkForSplash() {
|
||||||
});
|
});
|
||||||
|
|
||||||
const app = createApp(
|
const app = createApp(
|
||||||
window.location.search === "?zen"
|
window.location.pathname === "/oauth/authorize"
|
||||||
|
? defineAsyncComponent(() => import("@/ui/oauth.vue"))
|
||||||
|
: window.location.search === "?zen"
|
||||||
? defineAsyncComponent(() => import("@/ui/zen.vue"))
|
? defineAsyncComponent(() => import("@/ui/zen.vue"))
|
||||||
: !$i
|
: !$i
|
||||||
? defineAsyncComponent(() => import("@/ui/visitor.vue"))
|
? defineAsyncComponent(() => import("@/ui/visitor.vue"))
|
||||||
|
|
|
@ -1,35 +1,55 @@
|
||||||
<template>
|
<template>
|
||||||
<MkSpacer :content-max="800">
|
<MkSpacer :content-max="800">
|
||||||
<div v-if="$i">
|
<div v-if="$i">
|
||||||
<div v-if="state == 'waiting'" class="waiting _section">
|
<div v-if="state == 'waiting'" class="waiting _section" :class="[$style.section]">
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<MkLoading />
|
<MkLoading />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="state == 'denied'" class="denied _section">
|
<div v-if="state == 'denied'" class="denied _section" :class="[$style.section]">
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<p>{{ i18n.ts._auth.denied }}</p>
|
<p>{{ i18n.ts._auth.denied }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="state == 'error'" class="error _section">
|
<div v-else-if="state == 'error'" class="error _section" :class="[$style.section]">
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<p>{{ message }}</p>
|
<p>{{ message }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="state == 'accepted-oob'" class="accepted-oob _section">
|
<div v-else-if="state == 'accepted-oob'" class="accepted-oob _section" :class="[$style.section]">
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<p>{{ i18n.ts._auth.copyAsk }}</p>
|
<p>{{ i18n.ts._auth.copyAsk }}</p>
|
||||||
<pre>{{ code }}</pre>
|
<pre>{{ code }}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="state == 'accepted'" class="accepted _section">
|
<div v-else-if="state == 'accepted'" class="accepted _section" :class="[$style.section]">
|
||||||
<div class="_content">
|
<div class="_content">
|
||||||
<p>
|
<p>
|
||||||
{{ i18n.ts._auth.callback }}<MkEllipsis />
|
{{ i18n.ts._auth.callback }}<MkEllipsis />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="_section">
|
<div v-else class="_section" :class="[$style.section]">
|
||||||
|
<div :class="[$style.container]">
|
||||||
|
<button
|
||||||
|
v-click-anime
|
||||||
|
class="item _button"
|
||||||
|
:class="[$style.account]"
|
||||||
|
@click="openAccountMenu"
|
||||||
|
>
|
||||||
|
<MkAvatar
|
||||||
|
:user="$i"
|
||||||
|
:class="[$style.icon]"
|
||||||
|
disableLink
|
||||||
|
/><!-- <MkAcct class="text" :user="$i"/> -->
|
||||||
|
</button>
|
||||||
|
<div :class="[$style.left]">
|
||||||
|
<div>{{ i18n.ts.signedInAs }}:</div>
|
||||||
|
<div>@{{ $i.username }}<span :class="[$style.fade]">@{{ config.host }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<hr/>
|
||||||
|
<h2>Authorization required</h2>
|
||||||
<div v-if="name" class="_title">
|
<div v-if="name" class="_title">
|
||||||
{{ i18n.t("_auth.shareAccess", { name: name }) }}
|
{{ i18n.t("_auth.shareAccess", { name: name }) }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -70,13 +90,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {} from "vue";
|
|
||||||
import MkSignin from "@/components/MkSignin.vue";
|
import MkSignin from "@/components/MkSignin.vue";
|
||||||
import MkButton from "@/components/MkButton.vue";
|
import MkButton from "@/components/MkButton.vue";
|
||||||
import * as os from "@/os";
|
import * as os from "@/os";
|
||||||
import { $i, login } from "@/account";
|
import { $i, login, openAccountMenu as openAccountMenu_ } from "@/account";
|
||||||
import { appendQuery, query } from "@/scripts/url";
|
import { appendQuery, query } from "@/scripts/url";
|
||||||
import { i18n } from "@/i18n";
|
import { i18n } from "@/i18n";
|
||||||
|
import * as config from "@/config.js";
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
response_type: string;
|
response_type: string;
|
||||||
|
@ -161,6 +181,17 @@ function deny(): void {
|
||||||
async function onLogin(res): Promise<void> {
|
async function onLogin(res): Promise<void> {
|
||||||
await login(res.i);
|
await login(res.i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openAccountMenu(ev: MouseEvent) {
|
||||||
|
openAccountMenu_(
|
||||||
|
{
|
||||||
|
includeCurrentAccount: true,
|
||||||
|
withExtraOperation: true,
|
||||||
|
withoutProfileLink: true
|
||||||
|
},
|
||||||
|
ev,
|
||||||
|
);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" module>
|
<style lang="scss" module>
|
||||||
|
@ -169,6 +200,8 @@ async function onLogin(res): Promise<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
.permissions {
|
.permissions {
|
||||||
|
justify-content: center;
|
||||||
|
padding-top: var(--margin);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
@ -182,4 +215,36 @@ async function onLogin(res): Promise<void> {
|
||||||
background-color: var(--buttonBg);
|
background-color: var(--buttonBg);
|
||||||
color: var(--fg);
|
color: var(--fg);
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
display: inline-block;
|
||||||
|
width: 55px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section {
|
||||||
|
background: var(--panel);
|
||||||
|
padding: 20px 32px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-size: 1.05em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.fade {
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
40
packages/client/src/ui/oauth.vue
Normal file
40
packages/client/src/ui/oauth.vue
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
<template>
|
||||||
|
<div class="mk-app">
|
||||||
|
<RouterView />
|
||||||
|
|
||||||
|
<XCommon />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import type { ComputedRef } from "vue";
|
||||||
|
import { provide } from "vue";
|
||||||
|
import XCommon from "./_common_/common.vue";
|
||||||
|
import { mainRouter } from "@/router";
|
||||||
|
import type { PageMetadata } from "@/scripts/page-metadata";
|
||||||
|
import {
|
||||||
|
provideMetadataReceiver,
|
||||||
|
setPageMetadata,
|
||||||
|
} from "@/scripts/page-metadata";
|
||||||
|
import { instanceName } from "@/config";
|
||||||
|
|
||||||
|
let pageMetadata = $ref<null | ComputedRef<PageMetadata>>();
|
||||||
|
|
||||||
|
provide("router", mainRouter);
|
||||||
|
provideMetadataReceiver((info) => {
|
||||||
|
pageMetadata = info;
|
||||||
|
if (pageMetadata.value) {
|
||||||
|
document.title = `${pageMetadata.value.title} | ${instanceName}`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.documentElement.style.overflowY = "scroll";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mk-app {
|
||||||
|
// ほんとは単に 100vh と書きたいところだが... https://css-tricks.com/the-trick-to-viewport-units-on-mobile/
|
||||||
|
min-height: calc(var(--vh, 1vh) * 100);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
Loading…
Reference in a new issue