(
@@ -58,14 +64,14 @@ interface InstanceCardGridProps {
instances: any[];
}
-const InstanceCardGrid = ({ title, instances }: InstanceCardGridProps) => (
-
-
-
- {instances.map(i => (
-
- ))}
-
+// TODO create the instance picker helper
+
+// - Language, Categories, and Sort Order (active, random)
+const InstanceCardGrid = ({ instances }: InstanceCardGridProps) => (
+
+ {instances.map(i => (
+
+ ))}
);
@@ -238,77 +244,98 @@ export const DetailsModal = ({
);
-function biasedRandom(activeUsers: number, avg: number, max: number): number {
- // Lets introduce a better bias to random shuffle instances list
- const influence = 1.25;
- const rnd = Math.random() * (max / influence) + activeUsers;
- const mix = Math.random() * influence;
- return rnd * (1 - mix) + avg * mix;
+interface Sort {
+ name: string;
+ icon: string;
}
-function average(arr: number[]): number {
- return arr.reduce((a, b) => a + b, 0) / arr.length;
-}
+const RANDOM_SORT: Sort = {
+ name: "random",
+ icon: "TBD",
+};
-interface InstanceList {
- recommended: any[];
- popular: any[];
-}
+const MOST_ACTIVE_SORT: Sort = {
+ name: "most_active",
+ icon: "TBD",
+};
-function buildInstanceList(): InstanceList {
- const recommendedList =
- instance_stats.recommended[i18n.language] ??
- instance_stats.recommended["en"];
+const LEAST_ACTIVE_SORT: Sort = {
+ name: "least_active",
+ icon: "TBD",
+};
- let recommended = [];
- let popular = [];
- let usersActiveMonth = [];
+const SORTS: Sort[] = [RANDOM_SORT, MOST_ACTIVE_SORT, LEAST_ACTIVE_SORT];
- // Loop over all the instances, and add them to the two lists
- for (const i of instance_stats.stats.instance_details) {
- if (recommendedList.indexOf(i.domain) > -1) {
- recommended.push(i);
- } else {
- popular.push(i);
- }
-
- usersActiveMonth.push(i.site_info.site_view.counts.users_active_month);
- }
-
- // Use these values for the shuffle
- const avgMonthlyUsers = average(usersActiveMonth);
- const maxMonthlyUsers = Math.max(...usersActiveMonth);
-
- // Randomize the recommended
- recommended = recommended
+function sortRandom(instances: any[]): any[] {
+ return instances
.map(value => ({ value, sort: Math.random() }))
.sort((a, b) => a.sort - b.sort)
.map(({ value }) => value);
-
- // BIASED sorting for instances, based on the min/max of users_active_month
- popular = popular
- .map(i => ({
- instance: i,
- sort: biasedRandom(
- i.site_info.site_view.counts.users_active_month,
- avgMonthlyUsers,
- maxMonthlyUsers,
- ),
- }))
- .sort((a, b) => b.sort - a.sort)
- .map(({ instance }) => instance);
-
- return { recommended, popular };
}
-export class Instances extends Component
{
+function sortActive(instances: any[]): any[] {
+ return instances.sort(
+ (a, b) =>
+ b.site_info.site_view.counts.users_active_month -
+ a.site_info.site_view.counts.users_active_month,
+ );
+}
+
+interface State {
+ sort: Sort;
+ language: string;
+ category: Category;
+}
+
+export class Instances extends Component {
+ state: State = {
+ sort: SORTS[0],
+ language: i18n.language.split("-")[0],
+ category: All_CATEGORY,
+ };
+
constructor(props: any, context: any) {
super(props, context);
}
+ buildInstanceList(): any[] {
+ let instances = instance_stats.stats.instance_details;
+ const recommended = RECOMMENDED_INSTANCES;
+
+ // Language Filter
+ if (this.state.language !== "all") {
+ const languageRecs = recommended.filter(r =>
+ r.languages.includes(this.state.language),
+ );
+ instances = instances.filter(i =>
+ languageRecs.map(r => r.domain).includes(i.domain),
+ );
+ }
+
+ // Category filter
+ if (this.state.category !== All_CATEGORY) {
+ const categoryRecs = recommended.filter(r =>
+ r.categories.includes(this.state.category),
+ );
+ instances = instances.filter(i =>
+ categoryRecs.map(c => c.domain).includes(i.domain),
+ );
+ }
+
+ // Sort
+ if (this.state.sort == SORTS[0]) {
+ instances = sortRandom(instances);
+ } else if (this.state.sort == SORTS[1]) {
+ instances = sortActive(instances);
+ } else {
+ instances = sortActive(instances).reverse();
+ }
+
+ return instances;
+ }
+
render() {
const title = i18n.t("join_title");
- const instances = buildInstanceList();
return (
@@ -317,49 +344,84 @@ export class Instances extends Component
{
-
+ {this.filterAndTitleBlock()}
);
}
- renderList(header: string, instances: any[]) {
+ // TODO i18n these
+ filterAndTitleBlock() {
return (
-
-
{header}
-
- {instances.map(instance => {
- let domain = instance.domain;
- let description = instance.site_info.site_view.site.description;
- let icon = instance.site_info.site_view.site.icon;
- return (
-
-
-
-
-
-
-
{description}
-
-
- );
- })}
+
+
+
+
+
+
+
+
+
+
+
+
);
}
}
+
+function handleSortChange(i: Instances, event: any) {
+ i.setState({ sort: SORTS.find(s => s.name == event.target.value) });
+}
+
+function handleCategoryChange(i: Instances, event: any) {
+ i.setState({ category: CATEGORIES.find(c => c.name == event.target.value) });
+}
+
+function handleLanguageChange(i: Instances, event: any) {
+ i.setState({ language: event.target.value });
+}
diff --git a/src/shared/components/navbar.tsx b/src/shared/components/navbar.tsx
index f602368..ffc6235 100644
--- a/src/shared/components/navbar.tsx
+++ b/src/shared/components/navbar.tsx
@@ -1,8 +1,9 @@
import { ChangeEvent, linkEvent } from "inferno";
import { Link } from "inferno-router";
import { Icon, IconSize } from "./icon";
-import { i18n, languages } from "../i18next";
+import { i18n, LANGUAGES } from "../i18next";
import classNames from "classnames";
+import { SELECT_CLASSES, languageList } from "./common";
const NavLink = ({ content }) =>
{content};
@@ -17,10 +18,6 @@ const NavLinks = () => (
>
);
-function languageList() {
- return Object.keys(i18n.services.resourceStore.data).sort();
-}
-
function handleLanguageChange(_: any, event: ChangeEvent
) {
location.href = `/?lang=${event.target.value}`;
}
@@ -52,7 +49,7 @@ export const Navbar = ({ footer = false }) => (
<>
diff --git a/src/shared/i18next.ts b/src/shared/i18next.ts
index c878fd9..db72852 100644
--- a/src/shared/i18next.ts
+++ b/src/shared/i18next.ts
@@ -25,7 +25,7 @@ import { pt_BR } from "./translations/pt_BR";
import { ru } from "./translations/ru";
import { zh } from "./translations/zh";
-export const languages = [
+export const LANGUAGES = [
{ resource: bg, code: "bg", name: "Български" },
{ resource: da, code: "da", name: "Dansk" },
{ resource: de, code: "de", name: "Deutsch" },
@@ -52,7 +52,7 @@ export const languages = [
];
const resources: Resource = {};
-languages.forEach(l => (resources[l.code] = l.resource));
+LANGUAGES.forEach(l => (resources[l.code] = l.resource));
function format(value: any, format: any): any {
return format === "uppercase" ? value.toUpperCase() : value;