diff --git a/joinlemmy-translations b/joinlemmy-translations index b95dfda..afcb168 160000 --- a/joinlemmy-translations +++ b/joinlemmy-translations @@ -1 +1 @@ -Subproject commit b95dfdad19b866e86ed810d6f79c99e13066671e +Subproject commit afcb1684e80a42a6fecd6c0cebdf4caa64f92440 diff --git a/lemmy-translations b/lemmy-translations index 0a5b979..b9a566d 160000 --- a/lemmy-translations +++ b/lemmy-translations @@ -1 +1 @@ -Subproject commit 0a5b9790fbae41e8dc51021c2d20ec0f5743b1d8 +Subproject commit b9a566d8376c1ca4d122d63cb921accc3db6c79f diff --git a/recommended-instances.json b/recommended-instances.json index 2faa471..cc6790f 100644 --- a/recommended-instances.json +++ b/recommended-instances.json @@ -2,6 +2,8 @@ "recommended": [ "lemmy.ml", "lemm.ee", + "lemmy.world", + "feddit.it", "lemmygrad.ml", "reddthat.com", "hexbear.net", diff --git a/src/shared/components/common.tsx b/src/shared/components/common.tsx index b7eea2a..7c234b3 100644 --- a/src/shared/components/common.tsx +++ b/src/shared/components/common.tsx @@ -15,6 +15,13 @@ export const BACKGROUND_GRADIENT_1 = export const BACKGROUND_GRADIENT_2 = "min-h-full bg-gradient-to-b from-transparent to-black/[.30] to-20%"; +export const SELECT_CLASSES = + "select select-sm select-ghost select-bordered text-gray-400"; + +export function languageList() { + return Object.keys(i18n.services.resourceStore.data).sort(); +} + export const Badge = ({ content, outline = false }) => (
( @@ -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 ( -
-
-
-

{domain}

-
-
-
- -
-
-

{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;