Introduce instances admin page
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -384,6 +384,12 @@ export default class AdminProfile extends Vue {
|
||||
{
|
||||
key: this.$t("Domain") as string,
|
||||
value: this.person.domain ? this.person.domain : this.$t("Local"),
|
||||
link: this.person.domain
|
||||
? {
|
||||
name: RouteName.INSTANCE,
|
||||
params: { domain: this.person.domain },
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
{
|
||||
key: this.$i18n.t("Uploaded media size"),
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.ADMIN }">{{
|
||||
$t("Admin")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.RELAYS }">{{
|
||||
$t("Federation")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="is-active" v-if="$route.name == RouteName.RELAY_FOLLOWINGS">
|
||||
<router-link :to="{ name: RouteName.RELAY_FOLLOWINGS }">{{
|
||||
$t("Followings")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="is-active" v-if="$route.name == RouteName.RELAY_FOLLOWERS">
|
||||
<router-link :to="{ name: RouteName.RELAY_FOLLOWERS }">{{
|
||||
$t("Followers")
|
||||
}}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<section>
|
||||
<h1 class="title">{{ $t("Instances") }}</h1>
|
||||
<div class="tabs is-boxed">
|
||||
<ul>
|
||||
<router-link
|
||||
tag="li"
|
||||
active-class="is-active"
|
||||
:to="{ name: RouteName.RELAY_FOLLOWINGS }"
|
||||
>
|
||||
<a>
|
||||
<b-icon icon="inbox-arrow-down"></b-icon>
|
||||
<span>
|
||||
{{ $t("Followings") }}
|
||||
<b-tag rounded>{{ relayFollowings.total }}</b-tag>
|
||||
</span>
|
||||
</a>
|
||||
</router-link>
|
||||
<router-link
|
||||
tag="li"
|
||||
active-class="is-active"
|
||||
:to="{ name: RouteName.RELAY_FOLLOWERS }"
|
||||
>
|
||||
<a>
|
||||
<b-icon icon="inbox-arrow-up"></b-icon>
|
||||
<span>
|
||||
{{ $t("Followers") }}
|
||||
<b-tag rounded>{{ relayFollowers.total }}</b-tag>
|
||||
</span>
|
||||
</a>
|
||||
</router-link>
|
||||
</ul>
|
||||
</div>
|
||||
<router-view></router-view>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { RELAY_FOLLOWERS, RELAY_FOLLOWINGS } from "@/graphql/admin";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { IFollower } from "@/types/actor/follower.model";
|
||||
import RouteName from "../../router/name";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
relayFollowings: {
|
||||
query: RELAY_FOLLOWINGS,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
},
|
||||
relayFollowers: {
|
||||
query: RELAY_FOLLOWERS,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables: {
|
||||
page: 1,
|
||||
limit: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Federation") as string,
|
||||
};
|
||||
},
|
||||
})
|
||||
export default class Follows extends Vue {
|
||||
RouteName = RouteName;
|
||||
|
||||
activeTab = 0;
|
||||
|
||||
relayFollowings: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
|
||||
relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tab-item {
|
||||
form {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
||||
268
js/src/views/Admin/Instance.vue
Normal file
268
js/src/views/Admin/Instance.vue
Normal file
@@ -0,0 +1,268 @@
|
||||
<template>
|
||||
<div v-if="instance">
|
||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.ADMIN }">{{
|
||||
$t("Admin")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.INSTANCES }">{{
|
||||
$t("Instances")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="is-active">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.INSTANCE,
|
||||
params: { domain: instance.domain },
|
||||
}"
|
||||
>{{ instance.domain }}</router-link
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<h1 class="text-2xl">{{ instance.domain }}</h1>
|
||||
<div class="grid md:grid-cols-4 gap-2 content-center text-center mt-2">
|
||||
<div class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.PROFILES,
|
||||
query: { domain: instance.domain },
|
||||
}"
|
||||
class="dark:text-white hover:dark:text-slate-300"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.personCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Profiles") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.ADMIN_GROUPS,
|
||||
query: { domain: instance.domain },
|
||||
}"
|
||||
class="dark:text-white hover:dark:text-slate-300"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.groupCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Groups") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800 dark:text-white hover:dark:text-slate-300"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.followingsCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Followings") }}</span>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800 dark:text-white hover:dark:text-slate-300"
|
||||
>
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.followersCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Followers") }}</span>
|
||||
</div>
|
||||
<div class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800">
|
||||
<router-link to="/" class="dark:text-white hover:dark:text-slate-300">
|
||||
<span class="mb-4 text-xl font-semibold block">{{
|
||||
instance.reportsCount
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Reports") }}</span>
|
||||
</router-link>
|
||||
</div>
|
||||
<div
|
||||
class="bg-gray-50 rounded-xl p-8 dark:bg-gray-800 dark:text-white hover:dark:text-slate-300"
|
||||
>
|
||||
<span class="mb-4 font-semibold block">{{
|
||||
formatBytes(instance.mediaSize)
|
||||
}}</span>
|
||||
<span class="text-sm block">{{ $t("Uploaded media size") }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 grid md:grid-cols-2 gap-4" v-if="instance.hasRelay">
|
||||
<div class="border bg-white p-6 shadow-md rounded-md">
|
||||
<button
|
||||
@click="removeInstanceFollow"
|
||||
v-if="instance.followedStatus == InstanceFollowStatus.APPROVED"
|
||||
class="bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Stop following instance") }}
|
||||
</button>
|
||||
<button
|
||||
@click="removeInstanceFollow"
|
||||
v-else-if="instance.followedStatus == InstanceFollowStatus.PENDING"
|
||||
class="bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Cancel follow request") }}
|
||||
</button>
|
||||
<button
|
||||
@click="followInstance"
|
||||
v-else
|
||||
class="bg-primary hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Follow instance") }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="border bg-white p-6 shadow-md rounded-md">
|
||||
<button
|
||||
@click="acceptInstance"
|
||||
v-if="instance.followerStatus == InstanceFollowStatus.PENDING"
|
||||
class="bg-green-700 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Accept follow") }}
|
||||
</button>
|
||||
<button
|
||||
@click="rejectInstance"
|
||||
v-else-if="instance.followerStatus != InstanceFollowStatus.NONE"
|
||||
class="bg-red-700 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-gray-400 focus:ring-offset-2 focus:ring-offset-gray-50 text-white hover:text-white font-semibold h-12 px-6 rounded-lg w-full flex items-center justify-center sm:w-auto"
|
||||
>
|
||||
{{ $t("Reject follow") }}
|
||||
</button>
|
||||
<p v-else>
|
||||
{{ $t("This instance doesn't follow yours.") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="md:h-48 py-16 text-center opacity-50">
|
||||
{{ $t("Only Mobilizon instances can be followed") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
import {
|
||||
ACCEPT_RELAY,
|
||||
ADD_INSTANCE,
|
||||
INSTANCE,
|
||||
REJECT_RELAY,
|
||||
REMOVE_RELAY,
|
||||
} from "@/graphql/admin";
|
||||
import { Component, Prop, Vue } from "vue-property-decorator";
|
||||
import { formatBytes } from "@/utils/datetime";
|
||||
import RouteName from "@/router/name";
|
||||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
import { IInstance } from "@/types/instance.model";
|
||||
import { ApolloCache, gql, Reference } from "@apollo/client/core";
|
||||
import { InstanceFollowStatus } from "@/types/enums";
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
instance: {
|
||||
query: INSTANCE,
|
||||
variables() {
|
||||
return {
|
||||
domain: this.domain,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
export default class Instance extends Vue {
|
||||
@Prop({ type: String, required: true }) domain!: string;
|
||||
|
||||
instance!: IInstance;
|
||||
|
||||
InstanceFollowStatus = InstanceFollowStatus;
|
||||
|
||||
formatBytes = formatBytes;
|
||||
|
||||
RouteName = RouteName;
|
||||
|
||||
async acceptInstance(): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: ACCEPT_RELAY,
|
||||
variables: {
|
||||
address: `relay@${this.domain}`,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (e.message) {
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async rejectInstance(): Promise<void> {
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: REJECT_RELAY,
|
||||
variables: {
|
||||
address: `relay@${this.domain}`,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (e.message) {
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async followInstance(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
try {
|
||||
await this.$apollo.mutate<{ addInstance: Instance }>({
|
||||
mutation: ADD_INSTANCE,
|
||||
variables: {
|
||||
domain: this.domain,
|
||||
},
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
Snackbar.open({
|
||||
message: err.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async removeInstanceFollow(): Promise<void> {
|
||||
const { instance } = this;
|
||||
try {
|
||||
await this.$apollo.mutate({
|
||||
mutation: REMOVE_RELAY,
|
||||
variables: {
|
||||
address: `relay@${this.domain}`,
|
||||
},
|
||||
update(cache: ApolloCache<any>) {
|
||||
cache.writeFragment({
|
||||
id: cache.identify(instance as unknown as Reference),
|
||||
fragment: gql`
|
||||
fragment InstanceFollowedStatus on Instance {
|
||||
followedStatus
|
||||
}
|
||||
`,
|
||||
data: {
|
||||
followedStatus: InstanceFollowStatus.NONE,
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (e.message) {
|
||||
Snackbar.open({
|
||||
message: e.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
305
js/src/views/Admin/Instances.vue
Normal file
305
js/src/views/Admin/Instances.vue
Normal file
@@ -0,0 +1,305 @@
|
||||
<template>
|
||||
<div>
|
||||
<nav class="breadcrumb" aria-label="breadcrumbs">
|
||||
<ul>
|
||||
<li>
|
||||
<router-link :to="{ name: RouteName.ADMIN }">{{
|
||||
$t("Admin")
|
||||
}}</router-link>
|
||||
</li>
|
||||
<li class="is-active">
|
||||
<router-link :to="{ name: RouteName.INSTANCES }">{{
|
||||
$t("Instances")
|
||||
}}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<section>
|
||||
<h1 class="title">{{ $t("Instances") }}</h1>
|
||||
<form @submit="followInstance" class="my-4">
|
||||
<b-field
|
||||
:label="$t('Follow a new instance')"
|
||||
custom-class="add-relay"
|
||||
horizontal
|
||||
>
|
||||
<b-field grouped expanded size="is-large">
|
||||
<p class="control">
|
||||
<b-input
|
||||
v-model="newRelayAddress"
|
||||
:placeholder="$t('Ex: mobilizon.fr')"
|
||||
/>
|
||||
</p>
|
||||
<p class="control">
|
||||
<b-button type="is-primary" native-type="submit">{{
|
||||
$t("Add an instance")
|
||||
}}</b-button>
|
||||
</p>
|
||||
</b-field>
|
||||
</b-field>
|
||||
</form>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<b-field :label="$t('Follow status')">
|
||||
<b-radio-button
|
||||
v-model="followStatus"
|
||||
:native-value="InstanceFilterFollowStatus.ALL"
|
||||
>{{ $t("All") }}</b-radio-button
|
||||
>
|
||||
<b-radio-button
|
||||
v-model="followStatus"
|
||||
:native-value="InstanceFilterFollowStatus.FOLLOWING"
|
||||
>{{ $t("Following") }}</b-radio-button
|
||||
>
|
||||
<b-radio-button
|
||||
v-model="followStatus"
|
||||
:native-value="InstanceFilterFollowStatus.FOLLOWED"
|
||||
>{{ $t("Followed") }}</b-radio-button
|
||||
>
|
||||
</b-field>
|
||||
<b-field
|
||||
:label="$t('Domain')"
|
||||
label-for="domain-filter"
|
||||
class="flex-auto"
|
||||
>
|
||||
<b-input
|
||||
id="domain-filter"
|
||||
:placeholder="$t('mobilizon-instance.tld')"
|
||||
:value="filterDomain"
|
||||
@input="debouncedUpdateDomainFilter"
|
||||
/>
|
||||
</b-field>
|
||||
</div>
|
||||
<div v-if="instances && instances.elements.length > 0" class="mt-3">
|
||||
<router-link
|
||||
:to="{
|
||||
name: RouteName.INSTANCE,
|
||||
params: { domain: instance.domain },
|
||||
}"
|
||||
class="flex items-center mb-2 rounded bg-secondary p-4 flex-wrap justify-center gap-x-2 gap-y-3"
|
||||
v-for="instance in instances.elements"
|
||||
:key="instance.domain"
|
||||
>
|
||||
<div class="grow overflow-hidden flex items-center gap-1">
|
||||
<img
|
||||
class="w-12"
|
||||
v-if="instance.hasRelay"
|
||||
src="../../assets/logo.svg"
|
||||
alt=""
|
||||
/>
|
||||
<b-icon
|
||||
class="is-large"
|
||||
v-else
|
||||
custom-size="mdi-36px"
|
||||
icon="cloud-question"
|
||||
/>
|
||||
<div class="">
|
||||
<h4 class="text-lg truncate">{{ instance.domain }}</h4>
|
||||
<span
|
||||
class="text-sm"
|
||||
v-if="instance.followedStatus === InstanceFollowStatus.APPROVED"
|
||||
>
|
||||
<b-icon icon="inbox-arrow-down" />
|
||||
{{ $t("Followed") }}</span
|
||||
>
|
||||
<span
|
||||
class="text-sm"
|
||||
v-else-if="
|
||||
instance.followedStatus === InstanceFollowStatus.PENDING
|
||||
"
|
||||
>
|
||||
<b-icon icon="inbox-arrow-down" />
|
||||
{{ $t("Followed, pending response") }}</span
|
||||
>
|
||||
<span
|
||||
class="text-sm"
|
||||
v-if="instance.followerStatus == InstanceFollowStatus.APPROVED"
|
||||
>
|
||||
<b-icon icon="inbox-arrow-up" />
|
||||
{{ $t("Follows us") }}</span
|
||||
>
|
||||
<span
|
||||
class="text-sm"
|
||||
v-if="instance.followerStatus == InstanceFollowStatus.PENDING"
|
||||
>
|
||||
<b-icon icon="inbox-arrow-up" />
|
||||
{{ $t("Follows us, pending approval") }}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-none flex gap-3 ltr:ml-3 rtl:mr-3">
|
||||
<p class="flex flex-col text-center">
|
||||
<span class="text-xl">{{ instance.eventCount }}</span
|
||||
><span class="text-sm">{{ $t("Events") }}</span>
|
||||
</p>
|
||||
<p class="flex flex-col text-center">
|
||||
<span class="text-xl">{{ instance.personCount }}</span
|
||||
><span class="text-sm">{{ $t("Profiles") }}</span>
|
||||
</p>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<div v-else-if="instances && instances.elements.length == 0">
|
||||
<empty-content icon="lan-disconnect" :inline="true">
|
||||
{{ $t("No instance found.") }}
|
||||
<template #desc>
|
||||
<span v-if="hasFilter">
|
||||
{{
|
||||
$t(
|
||||
"No instances match this filter. Try resetting filter fields?"
|
||||
)
|
||||
}}
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ $t("You haven't interacted with other instances yet.") }}
|
||||
</span>
|
||||
</template>
|
||||
</empty-content>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from "vue-property-decorator";
|
||||
import { ADD_INSTANCE, INSTANCES } from "@/graphql/admin";
|
||||
import { Paginate } from "@/types/paginate";
|
||||
import { IFollower } from "@/types/actor/follower.model";
|
||||
import RouteName from "../../router/name";
|
||||
import { IInstance } from "@/types/instance.model";
|
||||
import EmptyContent from "@/components/Utils/EmptyContent.vue";
|
||||
import VueRouter from "vue-router";
|
||||
import { debounce } from "lodash";
|
||||
import {
|
||||
InstanceFilterFollowStatus,
|
||||
InstanceFollowStatus,
|
||||
} from "@/types/enums";
|
||||
import { SnackbarProgrammatic as Snackbar } from "buefy";
|
||||
const { isNavigationFailure, NavigationFailureType } = VueRouter;
|
||||
|
||||
@Component({
|
||||
apollo: {
|
||||
instances: {
|
||||
query: INSTANCES,
|
||||
fetchPolicy: "cache-and-network",
|
||||
variables() {
|
||||
return {
|
||||
page: this.instancePage,
|
||||
limit: 10,
|
||||
filterDomain: this.filterDomain,
|
||||
filterFollowStatus: this.followStatus,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
metaInfo() {
|
||||
return {
|
||||
title: this.$t("Federation") as string,
|
||||
};
|
||||
},
|
||||
components: {
|
||||
EmptyContent,
|
||||
},
|
||||
})
|
||||
export default class Follows extends Vue {
|
||||
RouteName = RouteName;
|
||||
|
||||
newRelayAddress = "";
|
||||
|
||||
instances!: Paginate<IInstance>;
|
||||
|
||||
instancePage = 1;
|
||||
|
||||
relayFollowings: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
|
||||
relayFollowers: Paginate<IFollower> = { elements: [], total: 0 };
|
||||
|
||||
InstanceFilterFollowStatus = InstanceFilterFollowStatus;
|
||||
|
||||
InstanceFollowStatus = InstanceFollowStatus;
|
||||
|
||||
data(): Record<string, unknown> {
|
||||
return {
|
||||
debouncedUpdateDomainFilter: debounce(this.updateDomainFilter, 500),
|
||||
};
|
||||
}
|
||||
|
||||
updateDomainFilter(domain: string) {
|
||||
this.filterDomain = domain;
|
||||
}
|
||||
|
||||
get filterDomain(): string {
|
||||
return (this.$route.query.domain as string) || "";
|
||||
}
|
||||
|
||||
set filterDomain(domain: string) {
|
||||
this.pushRouter({ domain });
|
||||
}
|
||||
|
||||
get followStatus(): InstanceFilterFollowStatus {
|
||||
return (
|
||||
(this.$route.query.followStatus as InstanceFilterFollowStatus) ||
|
||||
InstanceFilterFollowStatus.ALL
|
||||
);
|
||||
}
|
||||
|
||||
set followStatus(followStatus: InstanceFilterFollowStatus) {
|
||||
this.pushRouter({ followStatus });
|
||||
}
|
||||
|
||||
get hasFilter(): boolean {
|
||||
return (
|
||||
this.followStatus !== InstanceFilterFollowStatus.ALL ||
|
||||
this.filterDomain !== ""
|
||||
);
|
||||
}
|
||||
|
||||
async followInstance(e: Event): Promise<void> {
|
||||
e.preventDefault();
|
||||
const domain = this.newRelayAddress.trim(); // trim to fix copy and paste domain name spaces and tabs
|
||||
try {
|
||||
await this.$apollo.mutate<{ relayFollowings: Paginate<IFollower> }>({
|
||||
mutation: ADD_INSTANCE,
|
||||
variables: {
|
||||
domain,
|
||||
},
|
||||
});
|
||||
this.newRelayAddress = "";
|
||||
this.$router.push({
|
||||
name: RouteName.INSTANCE,
|
||||
params: { domain },
|
||||
});
|
||||
} catch (err: any) {
|
||||
if (err.message) {
|
||||
Snackbar.open({
|
||||
message: err.message,
|
||||
type: "is-danger",
|
||||
position: "is-bottom",
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async pushRouter(args: Record<string, string>): Promise<void> {
|
||||
try {
|
||||
await this.$router.push({
|
||||
name: RouteName.INSTANCES,
|
||||
query: { ...this.$route.query, ...args },
|
||||
});
|
||||
} catch (e) {
|
||||
if (isNavigationFailure(e, NavigationFailureType.redirected)) {
|
||||
throw Error(e.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.tab-item {
|
||||
form {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user