306 lines
8.6 KiB
Vue
306 lines
8.6 KiB
Vue
|
<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>
|