joinlemmy-site/src/shared/components/apps.tsx
Dessalines 6e61947d03
Add third party tools (#301)
* Updating submodules

* Adding third party tools.

* Updating submodules

* Changing to moderation tools.

* Addressing PR comments.
2024-03-01 10:22:48 +01:00

217 lines
5.4 KiB
TypeScript

import { Component, linkEvent } from "inferno";
import { Helmet } from "inferno-helmet";
import { i18n } from "../i18next";
import { T } from "inferno-i18next";
import { BottomSpacer, SELECT_CLASSES, TEXT_GRADIENT } from "./common";
import {
API_LIBRARIES,
APP_LIST,
ToolDetails,
AppDetails,
AppLink,
Platform,
SourceType,
MODERATION_TOOLS,
} from "./app-definitions";
import { Icon } from "./icon";
import { I18nKeys } from "i18next";
import { sortRandom } from "../utils";
const TitleBlock = () => (
<div className="flex flex-col items-center pt-16 mb-4">
<T i18nKey="lemmy_apps" className="text-4xl font-bold mb-3">
#<span className={TEXT_GRADIENT}>#</span>
</T>
<p className="text-2xl text-gray-300 text-center">
{i18n.t("choose_from_apps")}
</p>
</div>
);
interface AppDetailsCardProps {
app: AppDetails;
}
const AppDetailsTitle = ({ app }: AppDetailsCardProps) => (
<div className="flex flex-row space-x-2 mb-2">
<img
src={app.icon || "/static/assets/images/lemmy.svg"}
className="rounded-xl w-7 h-7"
/>
<a href={app.link} className={`card-title text-2xl ${TEXT_GRADIENT}`}>
{app.name}
</a>
</div>
);
interface AppDetailsButtonsProps {
links: AppLink[];
}
const AppDetailsButtons = ({ links }: AppDetailsButtonsProps) => (
<div className="flex flex-row justify-between gap-2">
{links.map(l => (
<a
className="btn btn-sm btn-primary text-white normal-case"
href={l.link}
>
<Icon icon={l.icon} />
</a>
))}
</div>
);
const AppDetailsCard = ({ app }: AppDetailsCardProps) => (
<div className="card card-bordered bg-neutral-900 shadow-xl">
<div className="card-body items-center">
<AppDetailsTitle app={app} />
<img
src={app.banner || "/static/assets/images/lemmy.svg"}
className="rounded-xl max-h-96 mb-2"
/>
<p className="text-sm text-gray-300 mb-2">{app.description}</p>
{app.sourceType == SourceType.Closed && (
<div className="alert alert-warning">
<Icon icon="alert-octagon" />
<span>{i18n.t("closed_source_warning")}</span>
</div>
)}
<AppDetailsButtons links={app.links} />
</div>
</div>
);
const AppTitle = ({ title }) => (
<div className="text-2xl mb-3 text-gray-300">{title}</div>
);
interface AppGridProps {
apps: AppDetails[];
}
const AppGrid = ({ apps }: AppGridProps) => (
<div className="grid md:grid-cols-2 grid-cols-1 gap-4 mb-16">
{apps.map(a => (
<AppDetailsCard app={a} />
))}
</div>
);
interface ToolsBlockProps {
title: string;
items: ToolDetails[];
}
const ToolsBlock = ({ title, items }: ToolsBlockProps) => (
<div>
<AppTitle title={title} />
<div className="card card-bordered bg-neutral-900 shadow-xl">
<div className="card-body">
<ul>
{items.map(a => (
<li>
<span className={`${TEXT_GRADIENT} mr-2`}></span>
<span>
<a href={a.link} className={`${TEXT_GRADIENT}`}>
{a.name}
</a>
</span>
<span className="text-gray-300 mx-2">-</span>
<span className="text-gray-300">{a.description}</span>
</li>
))}
</ul>
</div>
</div>
</div>
);
interface State {
apps: AppDetails[];
platform: Platform;
}
export class Apps extends Component<any, State> {
state: State = {
apps: [],
platform: Platform.All,
};
constructor(props: any, context: any) {
super(props, context);
}
componentDidMount() {
window.scrollTo(0, 0);
this.buildAppList();
}
buildAppList() {
let apps = APP_LIST;
// Platform filter
if (this.state.platform !== Platform.All) {
apps = apps.filter(a => a.platforms.includes(this.state.platform));
}
// Random sort
apps = sortRandom(apps);
this.setState({ apps });
}
render() {
const title = i18n.t("apps_title");
return (
<div className="container mx-auto px-4">
<Helmet title={title}>
<meta property={"title"} content={title} />
</Helmet>
<TitleBlock />
{this.filterAndTitleBlock()}
<AppGrid apps={this.state.apps} />
<div className="grid md:grid-cols-2 grid-cols-1 gap-4 mb-16">
<ToolsBlock title={i18n.t("api_libraries")} items={API_LIBRARIES} />
<ToolsBlock
title={i18n.t("moderation_tools")}
items={MODERATION_TOOLS}
/>
</div>
<BottomSpacer />
</div>
);
}
filterAndTitleBlock() {
return (
<div id="search">
<div className="flex flex-row flex-wrap gap-4 mb-4">
<div className="flex-none"></div>
<div className="grow"></div>
<div>
<select
className={`${SELECT_CLASSES} mr-2`}
value={this.state.platform}
onChange={linkEvent(this, handlePlatformChange)}
name="platform_select"
>
{Object.entries(Platform).map(([name, platform]) => (
<option key={name} value={platform}>
{i18n.t(platform as string as I18nKeys)}
</option>
))}
</select>
</div>
</div>
</div>
);
}
}
function handlePlatformChange(i: Apps, event: any) {
let platform: Platform = (event.target.value as Platform) ?? Platform.All;
i.setState({
platform,
});
i.buildAppList();
}