2023-11-01 13:50:20 +00:00
|
|
|
import { Component, linkEvent } from "inferno";
|
2021-03-15 17:18:03 +00:00
|
|
|
import { Helmet } from "inferno-helmet";
|
2021-03-22 13:22:23 +00:00
|
|
|
import { i18n } from "../i18next";
|
2023-10-31 13:31:03 +00:00
|
|
|
import { T } from "inferno-i18next";
|
2023-11-01 13:50:20 +00:00
|
|
|
import { BottomSpacer, SELECT_CLASSES, TEXT_GRADIENT } from "./common";
|
2023-10-31 13:31:03 +00:00
|
|
|
import {
|
|
|
|
API_LIBRARIES,
|
2023-11-01 13:50:20 +00:00
|
|
|
APP_LIST,
|
2023-10-31 13:31:03 +00:00
|
|
|
AppDetails,
|
|
|
|
AppLink,
|
2023-11-01 13:50:20 +00:00
|
|
|
Platform,
|
2023-10-31 13:31:03 +00:00
|
|
|
SourceType,
|
|
|
|
} from "./app-definitions";
|
|
|
|
import { Icon } from "./icon";
|
2023-11-01 13:50:20 +00:00
|
|
|
import { I18nKeys } from "i18next";
|
2023-11-03 13:50:12 +00:00
|
|
|
import { sortRandom } from "../utils";
|
2023-10-31 13:31:03 +00:00
|
|
|
|
|
|
|
const TitleBlock = () => (
|
2023-11-01 13:50:20 +00:00
|
|
|
<div className="flex flex-col items-center pt-16 mb-4">
|
2023-10-31 13:31:03 +00:00
|
|
|
<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>
|
|
|
|
);
|
|
|
|
|
|
|
|
const ApiLibrariesBlock = () => (
|
|
|
|
<div>
|
|
|
|
<AppTitle title={i18n.t("api_libraries")} />
|
|
|
|
<div className="card card-bordered bg-neutral-900 shadow-xl md:w-1/2">
|
|
|
|
<div className="card-body">
|
|
|
|
<ul>
|
|
|
|
{API_LIBRARIES.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>
|
|
|
|
);
|
2021-03-15 17:18:03 +00:00
|
|
|
|
2023-11-01 13:50:20 +00:00
|
|
|
interface State {
|
|
|
|
apps: AppDetails[];
|
|
|
|
platform: Platform;
|
|
|
|
}
|
|
|
|
|
|
|
|
export class Apps extends Component<any, State> {
|
|
|
|
state: State = {
|
|
|
|
apps: [],
|
|
|
|
platform: Platform.All,
|
|
|
|
};
|
|
|
|
|
2021-03-15 17:18:03 +00:00
|
|
|
constructor(props: any, context: any) {
|
|
|
|
super(props, context);
|
|
|
|
}
|
2022-02-18 17:54:06 +00:00
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
window.scrollTo(0, 0);
|
2023-11-01 13:50:20 +00:00
|
|
|
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));
|
|
|
|
}
|
|
|
|
|
2023-11-03 13:50:12 +00:00
|
|
|
// Random sort
|
|
|
|
apps = sortRandom(apps);
|
|
|
|
|
2023-11-01 13:50:20 +00:00
|
|
|
this.setState({ apps });
|
2022-02-18 17:54:06 +00:00
|
|
|
}
|
|
|
|
|
2021-03-15 17:18:03 +00:00
|
|
|
render() {
|
2023-06-09 09:49:31 +00:00
|
|
|
const title = i18n.t("apps_title");
|
2021-03-15 17:18:03 +00:00
|
|
|
return (
|
2023-10-31 13:31:03 +00:00
|
|
|
<div className="container mx-auto px-4">
|
2021-03-15 17:18:03 +00:00
|
|
|
<Helmet title={title}>
|
|
|
|
<meta property={"title"} content={title} />
|
|
|
|
</Helmet>
|
2023-10-31 13:31:03 +00:00
|
|
|
<TitleBlock />
|
2023-11-01 13:50:20 +00:00
|
|
|
{this.filterAndTitleBlock()}
|
|
|
|
<AppGrid apps={this.state.apps} />
|
2023-10-31 13:31:03 +00:00
|
|
|
<ApiLibrariesBlock />
|
|
|
|
<BottomSpacer />
|
2021-03-15 17:18:03 +00:00
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2023-11-01 13:50:20 +00:00
|
|
|
|
|
|
|
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();
|
2021-03-15 17:18:03 +00:00
|
|
|
}
|