[MOB-34] Update thumb logic (#1100)

update thumb logic
This commit is contained in:
Utku
2023-07-17 14:47:57 +03:00
committed by GitHub
parent 0a1d721e58
commit 20eae57e75
4 changed files with 137 additions and 76 deletions

View File

@@ -1,10 +1,53 @@
import * as icons from '@sd/assets/icons';
import { PropsWithChildren } from 'react';
import { getIcon } from '@sd/assets/util';
import { PropsWithChildren, useEffect, useLayoutEffect, useMemo, useState } from 'react';
import { Image, View } from 'react-native';
import { DocumentDirectoryPath } from 'react-native-fs';
import { ExplorerItem, ObjectKind, getItemFilePath, getItemObject, isPath } from '@sd/client';
import {
ExplorerItem,
getExplorerItemData,
getItemFilePath,
getItemLocation,
isDarkTheme
} from '@sd/client';
import { flattenThumbnailKey, useExplorerStore } from '~/stores/explorerStore';
import { tw } from '../../lib/tailwind';
import FolderIcon from '../icons/FolderIcon';
export const getThumbnailUrlByThumbKey = (thumbKey: string[]) =>
`${DocumentDirectoryPath}/thumbnails/${thumbKey
.map((i) => encodeURIComponent(i))
.join('/')}.webp`;
const FileThumbWrapper = ({ children, size = 1 }: PropsWithChildren<{ size: number }>) => (
<View style={[tw`items-center justify-center`, { width: 80 * size, height: 80 * size }]}>
{children}
</View>
);
function useExplorerItemData(explorerItem: ExplorerItem) {
const explorerStore = useExplorerStore();
const newThumbnail = !!(
explorerItem.thumbnail_key &&
explorerStore.newThumbnails.has(flattenThumbnailKey(explorerItem.thumbnail_key))
);
return useMemo(() => {
const itemData = getExplorerItemData(explorerItem);
if (!itemData.hasLocalThumbnail) {
itemData.hasLocalThumbnail = newThumbnail;
}
return itemData;
}, [explorerItem, newThumbnail]);
}
enum ThumbType {
Icon,
// Original,
Thumbnail,
Location
}
type FileThumbProps = {
data: ExplorerItem;
@@ -13,87 +56,87 @@ type FileThumbProps = {
* default: `1`
*/
size?: number;
// loadOriginal?: boolean;
};
export const getThumbnailUrlById = (keyParts: string[]) =>
`${DocumentDirectoryPath}/thumbnails/${keyParts
.map((i) => encodeURIComponent(i))
.join('/')}.webp`;
export default function FileThumb({ size = 1, ...props }: FileThumbProps) {
const itemData = useExplorerItemData(props.data);
const locationData = getItemLocation(props.data);
const filePath = getItemFilePath(props.data);
type KindType = keyof typeof icons | 'Unknown';
const [src, setSrc] = useState<null | string>(null);
const [thumbType, setThumbType] = useState(ThumbType.Icon);
// const [loaded, setLoaded] = useState<boolean>(false);
function getExplorerItemData(data: ExplorerItem) {
const objectData = getItemObject(data);
const filePath = getItemFilePath(data);
useLayoutEffect(() => {
// Reset src when item changes, to allow detection of yet not updated src
setSrc(null);
// setLoaded(false);
return {
casId: filePath?.cas_id || null,
isDir: isPath(data) && data.item.is_dir,
kind: ObjectKind[objectData?.kind || 0] as KindType,
hasLocalThumbnail: data.has_local_thumbnail, // this will be overwritten if new thumbnail is generated
thumbnailKey: data.thumbnail_key,
extension: filePath?.extension
};
}
if (locationData) {
setThumbType(ThumbType.Location);
// } else if (props.loadOriginal) {
// setThumbType(ThumbType.Original);
} else if (itemData.hasLocalThumbnail) {
setThumbType(ThumbType.Thumbnail);
} else {
setThumbType(ThumbType.Icon);
}
}, [locationData, itemData]);
const FileThumbWrapper = ({ children, size = 1 }: PropsWithChildren<{ size: number }>) => (
<View style={[tw`items-center justify-center`, { width: 80 * size, height: 80 * size }]}>
{children}
</View>
);
// This sets the src to the thumbnail url
useEffect(() => {
const { casId, kind, isDir, extension, locationId, thumbnailKey } = itemData;
export default function FileThumb({ data, size = 1 }: FileThumbProps) {
const { casId, isDir, kind, hasLocalThumbnail, extension, thumbnailKey } =
getExplorerItemData(data);
// ???
// const locationId =
// itemLocationId ?? (parent?.type === 'Location' ? parent.location.id : null);
if (isPath(data) && data.item.is_dir) {
return (
<FileThumbWrapper size={size}>
<FolderIcon size={70 * size} />
</FileThumbWrapper>
);
}
if (hasLocalThumbnail && thumbnailKey) {
// TODO: Handle Image checkers bg?
return (
<FileThumbWrapper size={size}>
<Image
source={{ uri: getThumbnailUrlById(thumbnailKey) }}
resizeMode="contain"
style={tw`h-full w-full`}
/>
</FileThumbWrapper>
);
}
// Default icon
let icon = icons['Document'];
if (isDir) {
icon = icons['Folder'];
} else if (
kind &&
extension &&
icons[`${kind}_${extension.toLowerCase()}` as keyof typeof icons]
) {
// e.g. Document_pdf
icon = icons[`${kind}_${extension.toLowerCase()}` as keyof typeof icons];
} else if (kind !== 'Unknown' && kind && icons[kind]) {
icon = icons[kind];
}
// TODO: Handle video thumbnails (do we have ffmpeg on mobile?)
// // 10 percent of the size
// const videoBarsHeight = Math.floor(size / 10);
// // calculate 16:9 ratio for height from size
// const videoHeight = Math.floor((size * 9) / 16) + videoBarsHeight * 2;
switch (thumbType) {
// case ThumbType.Original:
// if (locationId) {
// setSrc(
// platform.getFileUrl(
// library.uuid,
// locationId,
// filePath?.id || props.data.item.id,
// // Workaround Linux webview not supporting playing video and audio through custom protocol urls
// kind == 'Video' || kind == 'Audio'
// )
// );
// } else {
// setThumbType(ThumbType.Thumbnail);
// }
// break;
case ThumbType.Thumbnail:
if (casId && thumbnailKey) {
setSrc(getThumbnailUrlByThumbKey(thumbnailKey));
} else {
setThumbType(ThumbType.Icon);
}
break;
case ThumbType.Location:
setSrc(getIcon('Folder', isDarkTheme(), extension, true));
break;
default:
if (isDir !== null) setSrc(getIcon(kind, isDarkTheme(), extension, isDir));
break;
}
}, [filePath?.id, itemData, props.data.item.id, thumbType]);
return (
<FileThumbWrapper size={size}>
<Image source={icon} style={{ width: 70 * size, height: 70 * size }} />
{(() => {
if (src == null) return null;
let source = null;
// getIcon returns number for some magic reason
if (typeof src === 'number') {
source = src;
} else {
source = { uri: src };
}
return <Image source={source} style={{ width: 70 * size, height: 70 * size }} />;
})()}
</FileThumbWrapper>
);
}

View File

@@ -1,5 +1,5 @@
import { Text, View } from 'react-native';
import { useBridgeQuery } from '@sd/client';
import { useBridgeQuery, useDebugState } from '@sd/client';
import { Input } from '~/components/form/Input';
import Card from '~/components/layout/Card';
import { Divider } from '~/components/primitive/Divider';
@@ -10,6 +10,8 @@ import { SettingsStackScreenProps } from '~/navigation/SettingsNavigator';
const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'GeneralSettings'>) => {
const { data: node } = useBridgeQuery(['nodeState']);
const debugState = useDebugState();
if (!node) return null;
return (
@@ -37,6 +39,18 @@ const GeneralSettingsScreen = ({ navigation }: SettingsStackScreenProps<'General
<SettingsTitle style={tw`mt-3`}>Node Port</SettingsTitle>
<Input value={node.p2p_port?.toString() ?? '5795'} keyboardType="numeric" />
</Card>
{/* TODO: Move this to Debug screen */}
{debugState.enabled && (
<Card style={tw`mt-4`}>
{/* Card Header */}
<Text style={tw`font-semibold text-ink`}>Debug</Text>
{/* Divider */}
<Divider style={tw`mb-4 mt-2`} />
<SettingsTitle>Data Folder</SettingsTitle>
{/* Useful for simulator, not so for real devices. */}
<Input value={node.data_path} />
</Card>
)}
</View>
);
};

View File

@@ -17,7 +17,7 @@ const state = {
newThumbnails: proxySet() as Set<string>
};
function flattenThumbnailKey(thumbKey: string[]) {
export function flattenThumbnailKey(thumbKey: string[]) {
return thumbKey.join('/');
}

View File

@@ -16,3 +16,7 @@ export function useThemeStore() {
export function getThemeStore() {
return themeStore;
}
export function isDarkTheme() {
return themeStore.theme === 'dark';
}