Skip to content

Commit

Permalink
more albums refactoring to use RTK
Browse files Browse the repository at this point in the history
  • Loading branch information
sickelap committed Nov 13, 2023
1 parent 859a116 commit 4c1fc1d
Show file tree
Hide file tree
Showing 8 changed files with 124 additions and 96 deletions.
58 changes: 46 additions & 12 deletions src/api_client/albums/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ import { DatePhotosGroupSchema, SimpleUserSchema } from "../../actions/photosAct
import i18n from "../../i18n";
import { api } from "../api";

const UserAlbumListSchema = z
.object({
id: z.number(),
title: z.string(),
cover_photo: PhotoSuperSimpleSchema,
photo_count: z.number(),
owner: SimpleUserSchema,
shared_to: SimpleUserSchema.array(),
created_on: z.string(),
favorited: z.boolean(),
})
.array();
const UserAlbumResponseSchema = z.object({
id: z.number(),
title: z.string(),
cover_photo: PhotoSuperSimpleSchema,
photo_count: z.number(),
owner: SimpleUserSchema,
shared_to: SimpleUserSchema.array(),
created_on: z.string(),
favorited: z.boolean(),
});

const UserAlbumListSchema = UserAlbumResponseSchema.array();

const UserAlbumListResponseSchema = z.object({
results: UserAlbumListSchema,
Expand Down Expand Up @@ -82,6 +82,12 @@ type SetUserAlbumCoverParams = {
photo: string;
};

type ShareUserAlbumParams = {
albumId: string;
userId: string;
share: boolean;
};

export const userAlbumsApi = api
.injectEndpoints({
endpoints: builder => ({
Expand Down Expand Up @@ -189,6 +195,28 @@ export const userAlbumsApi = api
});
},
}),
[Endpoints.shareUserAlbum]: builder.mutation<void, ShareUserAlbumParams>({
query: ({ albumId, userId, share }) => ({
url: `useralbum/share/`,
method: "POST",
body: { shared: share, album_id: albumId, target_user_id: userId },
}),
transformResponse: (response, meta, query) => {
if (query.share) {
showNotification({
message: i18n.t("toasts.sharingalbum"),
title: i18n.t("toasts.sharingalbumtitle"),
color: "teal",
});
} else {
showNotification({
message: i18n.t("toasts.unsharingalbum"),
title: i18n.t("toasts.unsharingalbumtitle"),
color: "teal",
});
}
},
}),
}),
})
.enhanceEndpoints<"UserAlbums" | "UserAlbum">({
Expand Down Expand Up @@ -218,16 +246,22 @@ export const userAlbumsApi = api
[Endpoints.addPhotoToUserAlbum]: {
invalidatesTags: ["UserAlbums", "UserAlbum"],
},
[Endpoints.shareUserAlbum]: {
// TODO(sickelap): invalidate only the album that was shared
invalidatesTags: ["UserAlbums", "UserAlbum"],
},
},
});

export const {
useFetchUserAlbumsQuery,
useFetchUserAlbumQuery,
useLazyFetchUserAlbumQuery,
useDeleteUserAlbumMutation,
useRenameUserAlbumMutation,
useCreateUserAlbumMutation,
useRemovePhotoFromUserAlbumMutation,
useSetUserAlbumCoverMutation,
useAddPhotoToUserAlbumMutation,
useShareUserAlbumMutation,
} = userAlbumsApi;
7 changes: 4 additions & 3 deletions src/api_client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ import type {
import type { RootState } from "../store/store";
import type { IUploadOptions, IUploadResponse } from "../store/upload/upload.zod";
import { UploadExistResponse, UploadResponse } from "../store/upload/upload.zod";
import type { IApiUserListResponse, IManageUser, IUser } from "../store/user/user.zod";
import { ManageUser, UserSchema } from "../store/user/user.zod";
import type { IManageUser, IUser, UserList } from "../store/user/user.zod";
import { ApiUserListResponseSchema, ManageUser, UserSchema } from "../store/user/user.zod";
import type { ServerStatsResponseType, StorageStatsResponseType } from "../store/util/util.zod";
import type { IWorkerAvailabilityResponse } from "../store/worker/worker.zod";
// eslint-disable-next-line import/no-cycle
Expand Down Expand Up @@ -163,11 +163,12 @@ export const api = createApi({
query: userId => `/user/${userId}/`,
transformResponse: (response: string) => UserSchema.parse(response),
}),
[Endpoints.fetchUserList]: builder.query<IApiUserListResponse, void>({
[Endpoints.fetchUserList]: builder.query<UserList, void>({
query: () => ({
url: "/user/",
method: "GET",
}),
transformResponse: (response: string) => ApiUserListResponseSchema.parse(response).results,
providesTags: ["UserList"],
}),
[Endpoints.uploadExists]: builder.query<boolean, string>({
Expand Down
4 changes: 4 additions & 0 deletions src/components/sharing/ModalAlbumShare.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.title {
font-size: 2rem;
font-weight: 600;
}
51 changes: 18 additions & 33 deletions src/components/sharing/ModalAlbumShare.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Divider, Modal, ScrollArea, Stack, TextInput, Title } from "@mantine/core";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";

import { fetchUserAlbum } from "../../actions/albumsActions";
import { fetchPublicUserList } from "../../actions/publicActions";
import { useAppDispatch, useAppSelector } from "../../store/store";
import { fuzzyMatch } from "../../util/util";
import { useFetchUserListQuery } from "../../api_client/api";
import { useAppSelector } from "../../store/store";
import classes from "./ModalAlbumShare.module.css";
import { UserEntry } from "./UserEntry";
import filterUsers from "./utils";

type Props = {
isOpen: boolean;
Expand All @@ -16,34 +16,15 @@ type Props = {

export function ModalAlbumShare(props: Props) {
const [userNameFilter, setUserNameFilter] = useState("");

const { pub, auth } = useAppSelector(store => store);

const dispatch = useAppDispatch();
const { auth } = useAppSelector(store => store);
const { t } = useTranslation();
const { isOpen, onRequestClose, albumID } = props;

useEffect(() => {
if (isOpen) {
dispatch(fetchPublicUserList());
dispatch(fetchUserAlbum(parseInt(albumID, 10)));
}
}, [isOpen, dispatch]);

let filteredUserList;
if (userNameFilter.length > 0) {
filteredUserList = pub.publicUserList.filter(
el => fuzzyMatch(userNameFilter, el.username) || fuzzyMatch(userNameFilter, `${el.first_name} ${el.last_name}`)
);
} else {
filteredUserList = pub.publicUserList;
}
filteredUserList = filteredUserList.filter(el => el.id !== auth.access.user_id);
const { data: users, isFetching: isUsersFetching, isSuccess: isUsersLoaded } = useFetchUserListQuery();

return (
<Modal
opened={isOpen}
title={<Title>{t("modalphotosshare.title")}</Title>}
title={<span className={classes.title}>{t("modalphotosshare.title")}</span>}
onClose={() => {
onRequestClose();
setUserNameFilter("");
Expand All @@ -58,12 +39,16 @@ export function ModalAlbumShare(props: Props) {
placeholder={t("modalphotosshare.name")}
/>
<Divider />
<ScrollArea>
<Stack>
{filteredUserList.length > 0 &&
filteredUserList.map(item => <UserEntry key={item.id} item={item} albumID={albumID} />)}
</Stack>
</ScrollArea>
{isUsersFetching && <div>{t("modalphotosshare.loading")}</div>}
{isUsersLoaded && (
<ScrollArea>
<Stack>
{filterUsers(userNameFilter, auth.access?.user_id, users).map(item => (
<UserEntry key={item.id} item={item} albumID={albumID} />
))}
</Stack>
</ScrollArea>
)}
</Stack>
</Modal>
);
Expand Down
52 changes: 15 additions & 37 deletions src/components/sharing/ModalPhotosShare.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import { ActionIcon, Avatar, Divider, Group, Modal, ScrollArea, Stack, Text, TextInput, Title } from "@mantine/core";
import { DateTime } from "luxon";
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Share, ShareOff } from "tabler-icons-react";

import { setPhotosShared } from "../../actions/photosActions";
import { fetchPublicUserList } from "../../actions/publicActions";
import { useFetchUserListQuery } from "../../api_client/api";
import { serverAddress } from "../../api_client/apiClient";
import { i18nResolvedLanguage } from "../../i18n";
import { useAppDispatch, useAppSelector } from "../../store/store";

function fuzzyMatch(str: string, pattern: string) {
if (pattern.split("").length > 0) {
const expr = pattern.split("").reduce((a, b) => `${a}.*${b}`);
return new RegExp(expr).test(str);
}
return false;
}
import classes from "./ModalAlbumShare.module.css";
import filterUsers from "./utils";

type Props = {
selectedImageHashes: any;
Expand All @@ -27,37 +21,19 @@ type Props = {
export function ModalPhotosShare(props: Props) {
const { t } = useTranslation();
const [userNameFilter, setUserNameFilter] = useState("");

const { auth, pub } = useAppSelector(store => store);
const { data: users = [], isFetching: isUsersLoading, isSuccess: isUsersLoaded } = useFetchUserListQuery();
const { auth } = useAppSelector(store => store);
const dispatch = useAppDispatch();

const { selectedImageHashes, isOpen, onRequestClose } = props;
let filteredUserList: any[];
if (userNameFilter.length > 0) {
filteredUserList = pub.publicUserList.filter(
(el: any) =>
fuzzyMatch(el.username.toLowerCase(), userNameFilter.toLowerCase()) ||
fuzzyMatch(`${el.first_name.toLowerCase()} ${el.last_name.toLowerCase()}`, userNameFilter.toLowerCase())
);
} else {
filteredUserList = pub.publicUserList;
}
filteredUserList = filteredUserList.filter((el: any) => el.id !== auth.access?.user_id);

const selectedImageSrcs = selectedImageHashes.map(
(image_hash: string) => `${serverAddress}/media/square_thumbnails/${image_hash}`
);

useEffect(() => {
if (isOpen) {
dispatch(fetchPublicUserList());
}
}, [isOpen, dispatch]);

return (
<Modal
opened={isOpen}
title={<Title>{t("modalphotosshare.title")}</Title>}
title={<span className={classes.title}>{t("modalphotosshare.title")}</span>}
onClose={() => {
onRequestClose();
setUserNameFilter("");
Expand All @@ -81,10 +57,11 @@ export function ModalPhotosShare(props: Props) {
/>
<Divider />

<ScrollArea>
<Stack>
{filteredUserList.length > 0 &&
filteredUserList.map(item => {
{isUsersLoading && <div>{t("modalphotosshare.loading")}</div>}
{isUsersLoaded && (
<ScrollArea>
<Stack>
{filterUsers(userNameFilter, auth.access?.user_id, users).map(item => {
let displayName = item.username;
if (item.first_name.length > 0 && item.last_name.length > 0) {
displayName = `${item.first_name} ${item.last_name}`;
Expand Down Expand Up @@ -123,8 +100,9 @@ export function ModalPhotosShare(props: Props) {
</Group>
);
})}
</Stack>
</ScrollArea>
</Stack>
</ScrollArea>
)}
</Stack>
</Modal>
);
Expand Down
23 changes: 23 additions & 0 deletions src/components/sharing/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { IUser } from "../../store/user/user.zod";

function fuzzyMatch(str: string, pattern: string) {
if (pattern.split("").length > 0) {
const expr = pattern.split("").reduce((a, b) => `${a}.*${b}`);
return new RegExp(expr).test(str);
}
return false;
}

export default function filterUsers(username: string, excludeUserId: number, users: IUser[] = []): IUser[] {
return users
.filter(user => {
if (username.length === 0) {
return true;
}
return (
fuzzyMatch(user.username.toLowerCase(), username.toLowerCase()) ||
fuzzyMatch(`${user.first_name.toLowerCase()} ${user.last_name.toLowerCase()}`, username.toLowerCase())
);
})
.filter(user => user.id !== excludeUserId);
}
Loading

0 comments on commit 4c1fc1d

Please sign in to comment.