forked from Qortal/q-tube
Support for Names
This commit is contained in:
6
package-lock.json
generated
6
package-lock.json
generated
@@ -10,9 +10,9 @@
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.0.2",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/lab": "7.0.0-beta.12",
|
||||
"@mui/material": "^7.0.2",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@preact/signals-react": "^2.3.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
"compressorjs": "^1.2.1",
|
||||
@@ -20,7 +20,7 @@
|
||||
"jotai": "^2.12.4",
|
||||
"localforage": "^1.10.0",
|
||||
"moment": "^2.30.1",
|
||||
"qapp-core": "^1.0.27",
|
||||
"qapp-core": "^1.0.29",
|
||||
"quill": "^2.0.2",
|
||||
"quill-image-resize-module-react": "^3.0.0",
|
||||
"react": "^19.0.0",
|
||||
|
@@ -13,7 +13,7 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/lab": "7.0.0-beta.12",
|
||||
"@mui/lab": "^7.0.0-beta.12",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@preact/signals-react": "^2.3.0",
|
||||
"@reduxjs/toolkit": "^2.5.0",
|
||||
|
@@ -1,32 +1,34 @@
|
||||
import { Avatar, useTheme } from "@mui/material";
|
||||
import { AccountCircleSVG } from "../assets/svgs/AccountCircleSVG";
|
||||
import { menuIconSize } from "../constants/Misc";
|
||||
import { DropdownContainer, DropdownText } from "./layout/Navbar/Navbar-styles";
|
||||
import {
|
||||
DropdownContainer,
|
||||
DropdownText,
|
||||
AvatarContainer
|
||||
} from "./layout/Navbar/Navbar-styles";
|
||||
|
||||
export const UserDropDown = ({ userName, handleMyChannelLink, popMenuRef }) => {
|
||||
interface UserDropDownProps {
|
||||
userName: string;
|
||||
handleMyChannelLink: (username: string) => void;
|
||||
popMenuRef: React.RefObject<{ closePopover: () => void }>;
|
||||
}
|
||||
|
||||
export const UserDropDown = ({ userName, handleMyChannelLink, popMenuRef }: UserDropDownProps) => {
|
||||
const theme = useTheme();
|
||||
const userAvatar = `/arbitrary/THUMBNAIL/${userName}/avatar?async=true`;
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownContainer
|
||||
onClick={() => {
|
||||
handleMyChannelLink(userName);
|
||||
popMenuRef.current.closePopover();
|
||||
}}
|
||||
>
|
||||
{!userAvatar ? (
|
||||
<AccountCircleSVG
|
||||
color={theme.palette.text.primary}
|
||||
width={menuIconSize}
|
||||
height={menuIconSize}
|
||||
/>
|
||||
) : (
|
||||
<Avatar src={userAvatar}/>
|
||||
)}
|
||||
<Avatar src={userAvatar}>
|
||||
{userName?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<DropdownText>{userName}</DropdownText>
|
||||
</DropdownContainer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { Popover, useMediaQuery, useTheme } from "@mui/material";
|
||||
import { Popover, useMediaQuery, useTheme, Avatar } from "@mui/material";
|
||||
import { AccountCircleSVG } from "../../../../assets/svgs/AccountCircleSVG.tsx";
|
||||
import { headerIconSize, menuIconSize } from "../../../../constants/Misc.ts";
|
||||
import { BlockedNamesModal } from "../../../common/BlockedNamesModal/BlockedNamesModal.tsx";
|
||||
@@ -11,10 +11,13 @@ import {
|
||||
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
||||
import { useCallback, useRef, useState } from "react";
|
||||
import PersonOffIcon from "@mui/icons-material/PersonOff";
|
||||
import { RootState } from "../../../../state/store";
|
||||
import { useDispatch } from "react-redux";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { PopMenu, PopMenuRefType } from "../../../common/PopMenu.tsx";
|
||||
import { UserDropDown } from "../../../UserDropDown.tsx";
|
||||
import { Names } from "../../../../state/global/names.ts";
|
||||
import { setName } from "../../../../state/features/authSlice.ts";
|
||||
export interface NavBarMenuProps {
|
||||
isShowMenu: boolean;
|
||||
userAvatar: string;
|
||||
@@ -35,9 +38,10 @@ export const UserMenu = ({
|
||||
useState<boolean>(false);
|
||||
const popMenuRef = useRef<PopMenuRefType>(null);
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleMyChannelLink = useCallback((switchToName) => {
|
||||
userName = switchToName;
|
||||
const handleMyChannelLink = useCallback((switchToName: string) => {
|
||||
dispatch(setName(switchToName));
|
||||
navigate(`/channel/${switchToName}`);
|
||||
}, [navigate]);
|
||||
|
||||
@@ -54,23 +58,9 @@ export const UserMenu = ({
|
||||
MenuHeader={
|
||||
<AvatarContainer>
|
||||
{!isScreenSmall && <NavbarName>{userName}</NavbarName>}
|
||||
{!userAvatar ? (
|
||||
<AccountCircleSVG
|
||||
color={theme.palette.text.primary}
|
||||
width={headerIconSize}
|
||||
height={headerIconSize}
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={userAvatar}
|
||||
alt="User Avatar"
|
||||
width={headerIconSize}
|
||||
height={headerIconSize}
|
||||
style={{
|
||||
borderRadius: "50%",
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<Avatar src={userAvatar}>
|
||||
{userName?.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
</AvatarContainer>
|
||||
}
|
||||
>
|
||||
|
6
src/global.d.ts
vendored
6
src/global.d.ts
vendored
@@ -1,4 +1,9 @@
|
||||
// src/global.d.ts
|
||||
interface Location {
|
||||
service: string;
|
||||
name: string;
|
||||
identifier?: string;
|
||||
}
|
||||
interface QortalRequestOptions {
|
||||
action: string;
|
||||
name?: string;
|
||||
@@ -38,6 +43,7 @@ interface QortalRequestOptions {
|
||||
excludeBlocked?: boolean;
|
||||
exactMatchNames?: boolean;
|
||||
nameListFilter?: string[];
|
||||
location?: Location;
|
||||
}
|
||||
|
||||
declare function qortalRequest(options: QortalRequestOptions): Promise<any>;
|
||||
|
@@ -28,11 +28,23 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
const [videos, setVideos] = React.useState<Video[]>([]);
|
||||
const isLoading = useSignal(true);
|
||||
const { getVideo, checkAndUpdateVideo } = useFetchVideos();
|
||||
// For Pagination
|
||||
const pageRef = useRef(0);
|
||||
const [hasMore, setHasMore] = useState(true);
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
useEffect(() => {
|
||||
firstFetch.current = false;
|
||||
setVideos([]);
|
||||
pageRef.current = 0;
|
||||
setHasMore(true);
|
||||
}, [paramName]);
|
||||
|
||||
const getVideos = React.useCallback(async () => {
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const offset = videos.length;
|
||||
const offset = pageRef.current * PAGE_SIZE;
|
||||
console.log('getVideos ParamName:', paramName);
|
||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&name=${paramName}&exactmatchnames=true&offset=${offset}`;
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
@@ -57,16 +69,24 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
};
|
||||
});
|
||||
|
||||
const copiedVideos: Video[] = [...videos];
|
||||
structureData.forEach((video: Video) => {
|
||||
const index = videos.findIndex(p => p.id === video.id);
|
||||
if (index !== -1) {
|
||||
copiedVideos[index] = video;
|
||||
} else {
|
||||
copiedVideos.push(video);
|
||||
}
|
||||
setVideos(prev => {
|
||||
const updatedVideos = [...prev];
|
||||
|
||||
structureData.forEach(video => {
|
||||
const exists = updatedVideos.some(v => v.id === video.id);
|
||||
if (!exists) {
|
||||
updatedVideos.push(video);
|
||||
}
|
||||
});
|
||||
return updatedVideos;
|
||||
});
|
||||
setVideos(copiedVideos);
|
||||
|
||||
// If fewer than PAGE_SIZE results, we've reached the end
|
||||
if (structureData.length < PAGE_SIZE) {
|
||||
setHasMore(false);
|
||||
} else {
|
||||
pageRef.current += 1;
|
||||
}
|
||||
|
||||
for (const content of structureData) {
|
||||
if (content.user && content.id) {
|
||||
@@ -81,20 +101,20 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
console.log(error);
|
||||
isLoading.value = false;
|
||||
}
|
||||
}, [videos, hashMapVideos]);
|
||||
|
||||
const getVideosHandlerMount = React.useCallback(async () => {
|
||||
if (firstFetch.current) return;
|
||||
firstFetch.current = true;
|
||||
await getVideos();
|
||||
afterFetch.current = true;
|
||||
}, [getVideos]);
|
||||
}, [checkAndUpdateVideo, getVideo, hashMapVideos, paramName]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchVideos = async () => {
|
||||
firstFetch.current = true;
|
||||
console.log("Running useEffect: " + paramName);
|
||||
await getVideos();
|
||||
afterFetch.current = true;
|
||||
};
|
||||
|
||||
if (!firstFetch.current) {
|
||||
getVideosHandlerMount();
|
||||
fetchVideos();
|
||||
}
|
||||
}, [getVideosHandlerMount]);
|
||||
}, [paramName, getVideos]);
|
||||
|
||||
return (
|
||||
<VideoManagerRow>
|
||||
@@ -107,7 +127,10 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
|
||||
}}
|
||||
>
|
||||
<VideoList videos={videos} />
|
||||
<LazyLoad onLoadMore={getVideos} isLoading={isLoading.value}></LazyLoad>
|
||||
<LazyLoad
|
||||
onLoadMore={hasMore ? getVideos : undefined}
|
||||
isLoading={isLoading.value}
|
||||
/>
|
||||
</Box>
|
||||
</VideoManagerRow>
|
||||
);
|
||||
|
@@ -19,9 +19,14 @@ export const authSlice = createSlice({
|
||||
addUser: (state, action) => {
|
||||
state.user = action.payload;
|
||||
},
|
||||
setName: (state, action) => {
|
||||
state.user.name = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { addUser } = authSlice.actions;
|
||||
export const { setName } = authSlice.actions;
|
||||
|
||||
|
||||
export default authSlice.reducer;
|
@@ -66,12 +66,9 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||
const getAvatar = React.useCallback(
|
||||
async (author: string) => {
|
||||
try {
|
||||
const url = await qortalRequest({
|
||||
action: "GET_QDN_RESOURCE_URL",
|
||||
name: author,
|
||||
service: "THUMBNAIL",
|
||||
identifier: "qortal_avatar",
|
||||
});
|
||||
|
||||
const url = `/arbitrary/THUMBNAIL/${author}/qortal_avatar`;
|
||||
|
||||
if (url) {
|
||||
setUserAvatar(url);
|
||||
dispatch(
|
||||
|
Reference in New Issue
Block a user