Support for Names

This commit is contained in:
MergeMerc
2025-06-02 06:57:42 -04:00
parent 3702360d18
commit 7f2f7d04bf
8 changed files with 87 additions and 64 deletions

6
package-lock.json generated
View File

@@ -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",

View File

@@ -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",

View File

@@ -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>
</>
)
}

View File

@@ -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
View File

@@ -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>;

View File

@@ -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>
);

View File

@@ -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;

View File

@@ -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(