mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-22 12:21:13 +00:00
Adding image upload views for admins and profiles. (#2424)
* Adding image upload views for admins and profiles. * Upgraded lemmy-js-client dep. * Removing this. * Upgrade to pnpm v9.0.1 --------- Co-authored-by: SleeplessOne1917 <28871516+SleeplessOne1917@users.noreply.github.com>
This commit is contained in:
parent
accf1b2d72
commit
9dcaff4301
8 changed files with 6198 additions and 4445 deletions
|
@ -1 +1 @@
|
||||||
Subproject commit a94ef775f3f923067b48c1719cda206dbcf1a9e5
|
Subproject commit f9783d686637197a389b8f10a907e0533c55b688
|
|
@ -60,7 +60,7 @@
|
||||||
"inferno-router": "^8.2.3",
|
"inferno-router": "^8.2.3",
|
||||||
"inferno-server": "^8.2.3",
|
"inferno-server": "^8.2.3",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"lemmy-js-client": "0.19.4-alpha.16",
|
"lemmy-js-client": "0.19.4-alpha.18",
|
||||||
"lodash.isequal": "^4.5.0",
|
"lodash.isequal": "^4.5.0",
|
||||||
"markdown-it": "^14.1.0",
|
"markdown-it": "^14.1.0",
|
||||||
"markdown-it-bidi": "^0.1.0",
|
"markdown-it-bidi": "^0.1.0",
|
||||||
|
@ -139,7 +139,7 @@
|
||||||
"sortpack"
|
"sortpack"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@8.14.3",
|
"packageManager": "pnpm@9.0.1+sha256.46d50ee2afecb42b185ebbd662dc7bdd52ef5be56bf035bb615cab81a75345df",
|
||||||
"engineStrict": true,
|
"engineStrict": true,
|
||||||
"importSort": {
|
"importSort": {
|
||||||
".js, .jsx, .ts, .tsx": {
|
".js, .jsx, .ts, .tsx": {
|
||||||
|
|
9794
pnpm-lock.yaml
9794
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
106
src/shared/components/common/media-uploads.tsx
Normal file
106
src/shared/components/common/media-uploads.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
|
import { ListMediaResponse, LocalImage } from "lemmy-js-client";
|
||||||
|
import { HttpService, I18NextService } from "../../services";
|
||||||
|
import { PersonListing } from "../person/person-listing";
|
||||||
|
import { tippyMixin } from "../mixins/tippy-mixin";
|
||||||
|
import { MomentTime } from "./moment-time";
|
||||||
|
import { PictrsImage } from "./pictrs-image";
|
||||||
|
import { getHttpBase } from "@utils/env";
|
||||||
|
import { toast } from "../../toast";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
uploads: ListMediaResponse;
|
||||||
|
showUploader?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
@tippyMixin
|
||||||
|
export class MediaUploads extends Component<Props, any> {
|
||||||
|
constructor(props: any, context: any) {
|
||||||
|
super(props, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(
|
||||||
|
nextProps: Readonly<{ children?: InfernoNode } & Props>,
|
||||||
|
): void {
|
||||||
|
if (this.props !== nextProps) {
|
||||||
|
this.setState({ loading: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const images = this.props.uploads.images;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="media-uploads table-responsive">
|
||||||
|
<table className="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{this.props.showUploader && (
|
||||||
|
<th>{I18NextService.i18n.t("uploader")}</th>
|
||||||
|
)}
|
||||||
|
<th colSpan={3}>{I18NextService.i18n.t("time")}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{images.map(i => (
|
||||||
|
<tr key={i.local_image.pictrs_alias}>
|
||||||
|
{this.props.showUploader && (
|
||||||
|
<td>
|
||||||
|
<PersonListing person={i.person} />
|
||||||
|
</td>
|
||||||
|
)}
|
||||||
|
<td>
|
||||||
|
<MomentTime published={i.local_image.published} />
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<PictrsImage
|
||||||
|
src={buildImageUrl(i.local_image.pictrs_alias)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>{this.deleteImageBtn(i.local_image)}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteImageBtn(image: LocalImage) {
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
onClick={linkEvent(image, this.handleDeleteImage)}
|
||||||
|
className="btn btn-danger"
|
||||||
|
>
|
||||||
|
{I18NextService.i18n.t("delete")}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleDeleteImage(image: LocalImage) {
|
||||||
|
const form = {
|
||||||
|
token: image.pictrs_delete_token,
|
||||||
|
filename: image.pictrs_alias,
|
||||||
|
};
|
||||||
|
const res = await HttpService.client.deleteImage(form);
|
||||||
|
const filename = image.pictrs_alias;
|
||||||
|
if (res.state === "success") {
|
||||||
|
const deletePictureText = I18NextService.i18n.t("picture_deleted", {
|
||||||
|
filename,
|
||||||
|
});
|
||||||
|
toast(deletePictureText);
|
||||||
|
} else if (res.state === "failed") {
|
||||||
|
const failedDeletePictureText = I18NextService.i18n.t(
|
||||||
|
"failed_to_delete_picture",
|
||||||
|
{
|
||||||
|
filename,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
toast(failedDeletePictureText, "danger");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildImageUrl(pictrsAlias: string): string {
|
||||||
|
return `${getHttpBase()}/pictrs/image/${pictrsAlias}`;
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ function handleSwitchTab({ ctx, tab }: { ctx: Tabs; tab: string }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class Tabs extends Component<TabsProps, TabsState> {
|
export default class Tabs extends Component<TabsProps, TabsState> {
|
||||||
constructor(props: TabsProps, context) {
|
constructor(props: TabsProps, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
GetFederatedInstancesResponse,
|
GetFederatedInstancesResponse,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
LemmyHttp,
|
LemmyHttp,
|
||||||
|
ListMediaResponse,
|
||||||
PersonView,
|
PersonView,
|
||||||
} from "lemmy-js-client";
|
} from "lemmy-js-client";
|
||||||
import { InitialFetchRequest } from "../../interfaces";
|
import { InitialFetchRequest } from "../../interfaces";
|
||||||
|
@ -37,10 +38,14 @@ import { TaglineForm } from "./tagline-form";
|
||||||
import { getHttpBaseInternal } from "../../utils/env";
|
import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { RouteComponentProps } from "inferno-router/dist/Route";
|
import { RouteComponentProps } from "inferno-router/dist/Route";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
|
import { MediaUploads } from "../common/media-uploads";
|
||||||
|
import { Paginator } from "../common/paginator";
|
||||||
|
import { snapToTop } from "@utils/browser";
|
||||||
|
|
||||||
type AdminSettingsData = RouteDataResponse<{
|
type AdminSettingsData = RouteDataResponse<{
|
||||||
bannedRes: BannedPersonsResponse;
|
bannedRes: BannedPersonsResponse;
|
||||||
instancesRes: GetFederatedInstancesResponse;
|
instancesRes: GetFederatedInstancesResponse;
|
||||||
|
uploadsRes: ListMediaResponse;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface AdminSettingsState {
|
interface AdminSettingsState {
|
||||||
|
@ -50,6 +55,8 @@ interface AdminSettingsState {
|
||||||
instancesRes: RequestState<GetFederatedInstancesResponse>;
|
instancesRes: RequestState<GetFederatedInstancesResponse>;
|
||||||
bannedRes: RequestState<BannedPersonsResponse>;
|
bannedRes: RequestState<BannedPersonsResponse>;
|
||||||
leaveAdminTeamRes: RequestState<GetSiteResponse>;
|
leaveAdminTeamRes: RequestState<GetSiteResponse>;
|
||||||
|
uploadsRes: RequestState<ListMediaResponse>;
|
||||||
|
uploadsPage: number;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
themeList: string[];
|
themeList: string[];
|
||||||
isIsomorphic: boolean;
|
isIsomorphic: boolean;
|
||||||
|
@ -76,13 +83,19 @@ export class AdminSettings extends Component<
|
||||||
bannedRes: EMPTY_REQUEST,
|
bannedRes: EMPTY_REQUEST,
|
||||||
instancesRes: EMPTY_REQUEST,
|
instancesRes: EMPTY_REQUEST,
|
||||||
leaveAdminTeamRes: EMPTY_REQUEST,
|
leaveAdminTeamRes: EMPTY_REQUEST,
|
||||||
|
uploadsRes: EMPTY_REQUEST,
|
||||||
|
uploadsPage: 1,
|
||||||
loading: false,
|
loading: false,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
loadingSettled() {
|
loadingSettled() {
|
||||||
return resourcesSettled([this.state.bannedRes, this.state.instancesRes]);
|
return resourcesSettled([
|
||||||
|
this.state.bannedRes,
|
||||||
|
this.state.instancesRes,
|
||||||
|
this.state.uploadsRes,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
|
@ -92,15 +105,17 @@ export class AdminSettings extends Component<
|
||||||
this.handleEditEmoji = this.handleEditEmoji.bind(this);
|
this.handleEditEmoji = this.handleEditEmoji.bind(this);
|
||||||
this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
|
this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
|
||||||
this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
|
this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
|
||||||
|
this.handleUploadsPageChange = this.handleUploadsPageChange.bind(this);
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
const { bannedRes, instancesRes } = this.isoData.routeData;
|
const { bannedRes, instancesRes, uploadsRes } = this.isoData.routeData;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
bannedRes,
|
bannedRes,
|
||||||
instancesRes,
|
instancesRes,
|
||||||
|
uploadsRes,
|
||||||
isIsomorphic: true,
|
isIsomorphic: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -115,6 +130,7 @@ export class AdminSettings extends Component<
|
||||||
return {
|
return {
|
||||||
bannedRes: await client.getBannedPersons(),
|
bannedRes: await client.getBannedPersons(),
|
||||||
instancesRes: await client.getFederatedInstances(),
|
instancesRes: await client.getFederatedInstances(),
|
||||||
|
uploadsRes: await client.listAllMedia(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,6 +272,21 @@ export class AdminSettings extends Component<
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
key: "uploads",
|
||||||
|
label: I18NextService.i18n.t("uploads"),
|
||||||
|
getNode: isSelected => (
|
||||||
|
<div
|
||||||
|
className={classNames("tab-pane", {
|
||||||
|
active: isSelected,
|
||||||
|
})}
|
||||||
|
role="tabpanel"
|
||||||
|
id="uploads-tab-pane"
|
||||||
|
>
|
||||||
|
{this.uploads()}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -266,22 +297,34 @@ export class AdminSettings extends Component<
|
||||||
this.setState({
|
this.setState({
|
||||||
bannedRes: LOADING_REQUEST,
|
bannedRes: LOADING_REQUEST,
|
||||||
instancesRes: LOADING_REQUEST,
|
instancesRes: LOADING_REQUEST,
|
||||||
|
uploadsRes: LOADING_REQUEST,
|
||||||
themeList: [],
|
themeList: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const [bannedRes, instancesRes, themeList] = await Promise.all([
|
const [bannedRes, instancesRes, uploadsRes, themeList] = await Promise.all([
|
||||||
HttpService.client.getBannedPersons(),
|
HttpService.client.getBannedPersons(),
|
||||||
HttpService.client.getFederatedInstances(),
|
HttpService.client.getFederatedInstances(),
|
||||||
|
HttpService.client.listAllMedia({
|
||||||
|
page: this.state.uploadsPage,
|
||||||
|
}),
|
||||||
fetchThemeList(),
|
fetchThemeList(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
bannedRes,
|
bannedRes,
|
||||||
instancesRes,
|
instancesRes,
|
||||||
|
uploadsRes,
|
||||||
themeList,
|
themeList,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fetchUploadsOnly() {
|
||||||
|
const uploadsRes = await HttpService.client.listAllMedia({
|
||||||
|
page: this.state.uploadsPage,
|
||||||
|
});
|
||||||
|
this.setState({ uploadsRes });
|
||||||
|
}
|
||||||
|
|
||||||
admins() {
|
admins() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -341,6 +384,30 @@ export class AdminSettings extends Component<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uploads() {
|
||||||
|
switch (this.state.uploadsRes.state) {
|
||||||
|
case "loading":
|
||||||
|
return (
|
||||||
|
<h5>
|
||||||
|
<Spinner large />
|
||||||
|
</h5>
|
||||||
|
);
|
||||||
|
case "success": {
|
||||||
|
const uploadsRes = this.state.uploadsRes.data;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MediaUploads showUploader uploads={uploadsRes} />
|
||||||
|
<Paginator
|
||||||
|
page={this.state.uploadsPage}
|
||||||
|
onChange={this.handleUploadsPageChange}
|
||||||
|
nextDisabled={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async handleEditSite(form: EditSite) {
|
async handleEditSite(form: EditSite) {
|
||||||
this.setState({ loading: true });
|
this.setState({ loading: true });
|
||||||
|
|
||||||
|
@ -397,4 +464,10 @@ export class AdminSettings extends Component<
|
||||||
updateEmojiDataModel(res.data.custom_emoji);
|
updateEmojiDataModel(res.data.custom_emoji);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async handleUploadsPageChange(val: number) {
|
||||||
|
this.setState({ uploadsPage: val });
|
||||||
|
snapToTop();
|
||||||
|
await this.fetchUploadsOnly();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,8 @@ import {
|
||||||
GetPersonDetailsResponse,
|
GetPersonDetailsResponse,
|
||||||
GetSiteResponse,
|
GetSiteResponse,
|
||||||
LemmyHttp,
|
LemmyHttp,
|
||||||
|
ListMedia,
|
||||||
|
ListMediaResponse,
|
||||||
LockPost,
|
LockPost,
|
||||||
MarkCommentReplyAsRead,
|
MarkCommentReplyAsRead,
|
||||||
MarkPersonMentionAsRead,
|
MarkPersonMentionAsRead,
|
||||||
|
@ -95,13 +97,16 @@ import { PersonDetails } from "./person-details";
|
||||||
import { PersonListing } from "./person-listing";
|
import { PersonListing } from "./person-listing";
|
||||||
import { getHttpBaseInternal } from "../../utils/env";
|
import { getHttpBaseInternal } from "../../utils/env";
|
||||||
import { IRoutePropsWithFetch } from "../../routes";
|
import { IRoutePropsWithFetch } from "../../routes";
|
||||||
|
import { MediaUploads } from "../common/media-uploads";
|
||||||
|
|
||||||
type ProfileData = RouteDataResponse<{
|
type ProfileData = RouteDataResponse<{
|
||||||
personResponse: GetPersonDetailsResponse;
|
personRes: GetPersonDetailsResponse;
|
||||||
|
uploadsRes: ListMediaResponse;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
interface ProfileState {
|
interface ProfileState {
|
||||||
personRes: RequestState<GetPersonDetailsResponse>;
|
personRes: RequestState<GetPersonDetailsResponse>;
|
||||||
|
uploadsRes: RequestState<ListMediaResponse>;
|
||||||
personBlocked: boolean;
|
personBlocked: boolean;
|
||||||
banReason?: string;
|
banReason?: string;
|
||||||
banExpireDays?: number;
|
banExpireDays?: number;
|
||||||
|
@ -188,6 +193,7 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
private isoData = setIsoData<ProfileData>(this.context);
|
private isoData = setIsoData<ProfileData>(this.context);
|
||||||
state: ProfileState = {
|
state: ProfileState = {
|
||||||
personRes: EMPTY_REQUEST,
|
personRes: EMPTY_REQUEST,
|
||||||
|
uploadsRes: EMPTY_REQUEST,
|
||||||
personBlocked: false,
|
personBlocked: false,
|
||||||
siteRes: this.isoData.site_res,
|
siteRes: this.isoData.site_res,
|
||||||
showBanDialog: false,
|
showBanDialog: false,
|
||||||
|
@ -240,10 +246,12 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
|
|
||||||
// Only fetch the data if coming from another route
|
// Only fetch the data if coming from another route
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
const personRes = this.isoData.routeData.personResponse;
|
const personRes = this.isoData.routeData.personRes;
|
||||||
|
const uploadsRes = this.isoData.routeData.uploadsRes;
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
personRes,
|
personRes,
|
||||||
|
uploadsRes,
|
||||||
isIsomorphic: true,
|
isIsomorphic: true,
|
||||||
personBlocked: isPersonBlocked(personRes),
|
personBlocked: isPersonBlocked(personRes),
|
||||||
};
|
};
|
||||||
|
@ -267,10 +275,21 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
page,
|
page,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
personRes,
|
personRes,
|
||||||
personBlocked: isPersonBlocked(personRes),
|
personBlocked: isPersonBlocked(personRes),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (view === PersonDetailsView.Uploads) {
|
||||||
|
this.setState({ uploadsRes: LOADING_REQUEST });
|
||||||
|
const form: ListMedia = {
|
||||||
|
page,
|
||||||
|
limit: fetchLimit,
|
||||||
|
};
|
||||||
|
const uploadsRes = await HttpService.client.listMedia(form);
|
||||||
|
this.setState({ uploadsRes });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get amCurrentUser() {
|
get amCurrentUser() {
|
||||||
|
@ -298,6 +317,16 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
new LemmyHttp(getHttpBaseInternal(), { headers }),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let uploadsRes: RequestState<ListMediaResponse> = EMPTY_REQUEST;
|
||||||
|
|
||||||
|
if (view === PersonDetailsView.Uploads) {
|
||||||
|
const form: ListMedia = {
|
||||||
|
page,
|
||||||
|
limit: fetchLimit,
|
||||||
|
};
|
||||||
|
uploadsRes = await client.listMedia(form);
|
||||||
|
}
|
||||||
|
|
||||||
const form: GetPersonDetails = {
|
const form: GetPersonDetails = {
|
||||||
username: username,
|
username: username,
|
||||||
sort,
|
sort,
|
||||||
|
@ -305,9 +334,11 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
page,
|
page,
|
||||||
limit: fetchLimit,
|
limit: fetchLimit,
|
||||||
};
|
};
|
||||||
|
const personRes = await client.getPersonDetails(form);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
personResponse: await client.getPersonDetails(form),
|
personRes,
|
||||||
|
uploadsRes,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +350,25 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
: siteName;
|
: siteName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderUploadsRes() {
|
||||||
|
switch (this.state.uploadsRes.state) {
|
||||||
|
case "loading":
|
||||||
|
return (
|
||||||
|
<h5>
|
||||||
|
<Spinner large />
|
||||||
|
</h5>
|
||||||
|
);
|
||||||
|
case "success": {
|
||||||
|
const uploadsRes = this.state.uploadsRes.data;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<MediaUploads uploads={uploadsRes} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
renderPersonRes() {
|
renderPersonRes() {
|
||||||
switch (this.state.personRes.state) {
|
switch (this.state.personRes.state) {
|
||||||
case "loading":
|
case "loading":
|
||||||
|
@ -349,6 +399,8 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
|
|
||||||
{this.selects}
|
{this.selects}
|
||||||
|
|
||||||
|
{this.renderUploadsRes()}
|
||||||
|
|
||||||
<PersonDetails
|
<PersonDetails
|
||||||
personRes={personRes}
|
personRes={personRes}
|
||||||
admins={siteRes.admins}
|
admins={siteRes.admins}
|
||||||
|
@ -414,11 +466,12 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
|
|
||||||
get viewRadios() {
|
get viewRadios() {
|
||||||
return (
|
return (
|
||||||
<div className="btn-group btn-group-toggle flex-wrap mb-2" role="group">
|
<div className="btn-group btn-group-toggle flex-wrap" role="group">
|
||||||
{this.getRadio(PersonDetailsView.Overview)}
|
{this.getRadio(PersonDetailsView.Overview)}
|
||||||
{this.getRadio(PersonDetailsView.Comments)}
|
{this.getRadio(PersonDetailsView.Comments)}
|
||||||
{this.getRadio(PersonDetailsView.Posts)}
|
{this.getRadio(PersonDetailsView.Posts)}
|
||||||
{this.amCurrentUser && this.getRadio(PersonDetailsView.Saved)}
|
{this.amCurrentUser && this.getRadio(PersonDetailsView.Saved)}
|
||||||
|
{this.getRadio(PersonDetailsView.Uploads)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -457,19 +510,23 @@ export class Profile extends Component<ProfileRouteProps, ProfileState> {
|
||||||
const profileRss = `/feeds/u/${username}.xml${getQueryString({ sort })}`;
|
const profileRss = `/feeds/u/${username}.xml${getQueryString({ sort })}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-2">
|
<div className="row align-items-center mb-3 g-3">
|
||||||
<span className="me-3">{this.viewRadios}</span>
|
<div className="col-auto">{this.viewRadios}</div>
|
||||||
|
<div className="col-auto">
|
||||||
<SortSelect
|
<SortSelect
|
||||||
sort={sort}
|
sort={sort}
|
||||||
onChange={this.handleSortChange}
|
onChange={this.handleSortChange}
|
||||||
hideHot
|
hideHot
|
||||||
hideMostComments
|
hideMostComments
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="col-auto">
|
||||||
<a href={profileRss} rel={relTags} title="RSS">
|
<a href={profileRss} rel={relTags} title="RSS">
|
||||||
<Icon icon="rss" classes="text-muted small mx-2" />
|
<Icon icon="rss" classes="text-muted small ps-0" />
|
||||||
</a>
|
</a>
|
||||||
<link rel="alternate" type="application/atom+xml" href={profileRss} />
|
<link rel="alternate" type="application/atom+xml" href={profileRss} />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -67,6 +67,7 @@ export enum PersonDetailsView {
|
||||||
Comments = "Comments",
|
Comments = "Comments",
|
||||||
Posts = "Posts",
|
Posts = "Posts",
|
||||||
Saved = "Saved",
|
Saved = "Saved",
|
||||||
|
Uploads = "Uploads",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum PurgeType {
|
export enum PurgeType {
|
||||||
|
|
Loading…
Reference in a new issue