Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/common/ImagePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,38 @@ import {
import Compressor from 'compressorjs';
import { fileToBase64 } from '../utils/base64';

async function isWebPAnimated(file: File): Promise<boolean> {
const buffer = await file.slice(0, 65536).arrayBuffer();
const bytes = new Uint8Array(buffer);

if (
bytes.length < 12 ||
bytes[0] !== 0x52 ||
bytes[1] !== 0x49 ||
bytes[2] !== 0x46 ||
bytes[3] !== 0x46 ||
bytes[8] !== 0x57 ||
bytes[9] !== 0x45 ||
bytes[10] !== 0x42 ||
bytes[11] !== 0x50
) {
return false;
}

for (let i = 12; i < bytes.length - 4; i++) {
if (
bytes[i] === 0x41 &&
bytes[i + 1] === 0x4e &&
bytes[i + 2] === 0x49 &&
bytes[i + 3] === 0x4d
) {
return true;
}
}

return false;
}

type Mode = 'single' | 'multi';

interface ImageResult {
Expand Down Expand Up @@ -60,6 +92,12 @@ export const ImagePicker: React.FC<ImageUploaderProps> = ({
continue;
}
fileToConvert = image;
} else if (
image.type === 'image/webp' &&
image.size <= 150 * 1024 &&
(await isWebPAnimated(image))
) {
fileToConvert = image;
} else {
fileToConvert = await new Promise<Blob>((resolve, reject) => {
new Compressor(image, {
Expand Down
13 changes: 11 additions & 2 deletions src/components/AudioPlayer/AudioPlayerControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -335,8 +335,17 @@ const AudioPlayerComponent = forwardRef<AudioPlayerHandle, AudioPlayerProps>(

useEffect(() => {
const audioElement = audioRef.current;
if (!audioElement || !isReady || !resourceUrl) return;
if (!audioElement || !isReady || !resourceUrl || !activeTrack) return;

const copiedActiveTrack = structuredClone(activeTrack);
if (copiedActiveTrack.name) {
copiedActiveTrack.name = encodeURIComponent(copiedActiveTrack.name);
}
if (copiedActiveTrack.identifier) {
copiedActiveTrack.identifier = encodeURIComponent(
copiedActiveTrack.identifier
);
}
// Generate unique track identifier
const trackId = `${activeTrack?.service}-${activeTrack?.name}-${activeTrack?.identifier}-${encryption?.key || 'none'}`;

Expand Down Expand Up @@ -371,7 +380,7 @@ const AudioPlayerComponent = forwardRef<AudioPlayerHandle, AudioPlayerProps>(
const qortalStreamUrl = await playEncryptedAudioWithQortalRequest({
keyBytes,
ivBytes,
qortalAudioResource: activeTrack,
qortalAudioResource: copiedActiveTrack,
});

if (abortController.signal.aborted) return;
Expand Down
41 changes: 37 additions & 4 deletions src/components/ResourceList/ResourceListDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ interface BaseProps {
scrollerRef?: React.RefObject<HTMLElement | null> | null;
filterDuplicateIdentifiers?: FilterDuplicateIdentifiersOptions;
secondaryDataSources?: SecondaryDataSource[];
isLoading?: boolean;
}

const defaultStyles = {
Expand All @@ -104,7 +105,39 @@ interface NonVirtualizedProps extends BaseProps {

type PropsResourceListDisplay = VirtualizedProps | NonVirtualizedProps;

export const MemorizedComponent = ({
/** When initialIsLoading is true, only this shell mounts — no hooks, effects, or list logic run. */
export const MemorizedComponent = (props: PropsResourceListDisplay) => {
const { isLoading: initialIsLoading = false } = props;
if (initialIsLoading) {
return (
<div
style={{
width: '100%',
height: props.disableVirtualization ? 'auto' : '100%',
}}
>
<ListLoader
isLoading={true}
resultsLength={0}
noResultsMessage={
props.defaultLoaderParams?.listNoResultsText || 'No results available'
}
loadingMessage={
props.defaultLoaderParams?.listLoadingText ||
'Retrieving list. Please wait.'
}
loaderList={props.loaderList}
loaderHeight={props.styles?.listLoadingHeight}
>
{null}
</ListLoader>
</div>
);
}
return <ResourceListDisplayContent {...props} />;
};

function ResourceListDisplayContent({
search,
listItem,
styles = defaultStyles,
Expand All @@ -129,7 +162,7 @@ export const MemorizedComponent = ({
scrollerRef,
filterDuplicateIdentifiers,
secondaryDataSources,
}: PropsResourceListDisplay) => {
}: PropsResourceListDisplay) {
const { identifierOperations, lists } = useGlobal();
const [generatedIdentifier, setGeneratedIdentifier] = useState('');
const [memoizedParams, setMemorizedParams] = useState('');
Expand Down Expand Up @@ -303,7 +336,7 @@ export const MemorizedComponent = ({
);

const resetSearch = useCallback(async () => {
resetScroll()
resetScroll();
lists.deleteList(listName);
getResourceList();
}, [listName, getResourceList, resetScroll]);
Expand Down Expand Up @@ -336,7 +369,6 @@ export const MemorizedComponent = ({
getResourceList();
}, [getResourceList, listName]); // Runs when dependencies change


useScrollTrackerRef(listName, list?.length > 0, scrollerRef);
const setSearchCacheExpiryDuration = useCacheStore(
(s) => s.setSearchCacheExpiryDuration
Expand Down Expand Up @@ -561,6 +593,7 @@ function arePropsEqual(
prevProps.disableVirtualization === nextProps.disableVirtualization &&
prevProps.direction === nextProps.direction &&
prevProps.onSeenLastItem === nextProps.onSeenLastItem &&
prevProps.isLoading === nextProps.isLoading &&
JSON.stringify(prevProps.filterDuplicateIdentifiers) ===
JSON.stringify(nextProps.filterDuplicateIdentifiers) &&
JSON.stringify(prevProps.search) === JSON.stringify(nextProps.search) &&
Expand Down
19 changes: 16 additions & 3 deletions src/components/VideoPlayer/VideoPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1273,7 +1273,13 @@ export const VideoPlayer = ({
return;

const resource = JSON.parse(videoLocactionStringified);

const copiedResource = structuredClone(resource);
if (copiedResource.name) {
copiedResource.name = encodeURIComponent(copiedResource.name);
}
if (copiedResource.identifier) {
copiedResource.identifier = encodeURIComponent(copiedResource.identifier);
}
try {
const setupPlayer = async () => {
const ref = videoRef as any;
Expand Down Expand Up @@ -1303,9 +1309,14 @@ export const VideoPlayer = ({
player: playerRef.current,
keyBytes: keyBytes,
ivBytes: ivBytes,
qortalVideoResource: resource,
qortalVideoResource: copiedResource,
}
);
await new Promise((res) => {
setTimeout(() => {
res(null);
}, 250);
});

if (qortalStreamUrl) {
const options = {
Expand Down Expand Up @@ -1641,7 +1652,9 @@ export const VideoPlayer = ({
player.off('ratechange', handleRateChange);
};
}, [isPlayerInitialized]);
const hideTimeoutRef = useRef<number | ReturnType<typeof setTimeout> | null>(null);
const hideTimeoutRef = useRef<number | ReturnType<typeof setTimeout> | null>(
null
);
const resetHideTimeout = () => {
setShowControlsMobile(true);
if (hideTimeoutRef.current) clearTimeout(hideTimeoutRef.current);
Expand Down
14 changes: 13 additions & 1 deletion src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ import {
VoteOnPollQortalRequest,
whichUIQortalRequest,
ChromecastCastQortalRequest,
NotificationPermissionQortalRequest,
NotificationHasPermissionQortalRequest,
NotificationAddQortalRequest,
NotificationGetQortalRequest,
NotificationMarkSeenQortalRequest,
NotificationRemoveQortalRequest,
} from './types/qortalRequests/interfaces';

declare global {
Expand Down Expand Up @@ -190,7 +196,13 @@ declare global {
| lockTabQortalRequest
| reencryptGroupKeysQortalRequest
| sessionPermissionsQortalRequest
| ChromecastCastQortalRequest;
| ChromecastCastQortalRequest
| NotificationPermissionQortalRequest
| NotificationHasPermissionQortalRequest
| NotificationAddQortalRequest
| NotificationGetQortalRequest
| NotificationMarkSeenQortalRequest
| NotificationRemoveQortalRequest;

function qortalRequest(options: QortalRequestOptions): Promise<any>;

Expand Down
4 changes: 2 additions & 2 deletions src/hooks/useResources.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ export const requestQueueProductPublishes = new RequestQueueWithPromise(12);
export const requestQueueProductPublishesBackup = new RequestQueueWithPromise(
6
);
export const requestQueueResourcesResultsOnly = new RequestQueueWithPromise(4);
export const requestQueueSearchResources = new RequestQueueWithPromise(8);
export const requestQueueResourcesResultsOnly = new RequestQueueWithPromise(6);
export const requestQueueSearchResources = new RequestQueueWithPromise(12);

export interface Resource {
qortalMetadata: QortalMetadata;
Expand Down
22 changes: 18 additions & 4 deletions src/hooks/useSubscriptionCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export type SubscriptionStatus =
| 'owner'
| 'no-subscription';

export const subscriptionAppPublicSalt =
'gnRp+Pao85XZlExcqynLS0+GaKCL3ia9E1sEm9XPaOA=';

export interface CheckSubscriptionStatusResult {
/**
* The subscription status
Expand Down Expand Up @@ -73,6 +76,7 @@ export interface CheckSubscriptionStatusResult {
}

export interface UseCheckSubscriptionStatusParams {
name: string | null;
/**
* The address to check subscription status for
*/
Expand Down Expand Up @@ -148,8 +152,8 @@ export interface UseCheckSubscriptionStatusReturn
export function useCheckSubscriptionStatus(
params: UseCheckSubscriptionStatusParams
): UseCheckSubscriptionStatusReturn {
const { address, groupId, enabled = true } = params;
const { identifierOperations } = useGlobal();
const { address, name, groupId, enabled = true } = params;
const { identifierOperations, lists } = useGlobal();
const [result, setResult] = useState<CheckSubscriptionStatusResult>({
status: 'no-subscription',
isSubscribed: false,
Expand All @@ -172,7 +176,7 @@ export function useCheckSubscriptionStatus(
return;
}

if (!address || !groupId || !identifierOperations) {
if (!address || !groupId || !identifierOperations || !lists || !name) {
console.log('[useCheckSubscriptionStatus] Missing required parameters');
setLoading(false);
return;
Expand All @@ -190,8 +194,10 @@ export function useCheckSubscriptionStatus(
try {
const statusResult = await checkSubscriptionStatus({
address,
name: name!,
groupId,
identifierOperations,
lists,
});

if (!cancelled) {
Expand Down Expand Up @@ -219,7 +225,15 @@ export function useCheckSubscriptionStatus(
return () => {
cancelled = true;
};
}, [address, groupId, identifierOperations, enabled, refreshKey]);
}, [
address,
name,
groupId,
identifierOperations,
enabled,
refreshKey,
lists,
]);

return {
...result,
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,5 @@ export { ListItem } from './state/cache';
export { SymmetricKeys } from './utils/encryption';
export { LoaderListStatus } from './common/ListLoader';
export { Coin } from './types/qortalRequests/types';
export { useIdentifiers } from './hooks/useIdentifiers';
export { useResources } from './hooks/useResources';
Loading
Loading