mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-06-30 12:33:31 +00:00
Bug fixes (#2566)
* changed tag assign mode behaviour * added min/max zoom and filekinds logic * changed colour of system data bar
This commit is contained in:
parent
f7b0e3bd06
commit
a8231aca4f
|
@ -1,6 +1,9 @@
|
|||
import { Circle } from '@phosphor-icons/react';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import {
|
||||
ExplorerItem,
|
||||
getItemObject,
|
||||
Tag,
|
||||
Target,
|
||||
useLibraryMutation,
|
||||
|
@ -9,8 +12,6 @@ import {
|
|||
useSelector
|
||||
} from '@sd/client';
|
||||
import { Shortcut, toast } from '@sd/ui';
|
||||
import clsx from 'clsx';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useIsDark, useKeybind, useLocale, useOperatingSystem } from '~/hooks';
|
||||
import { keybindForOs } from '~/util/keybinds';
|
||||
|
||||
|
@ -87,27 +88,30 @@ export const ExplorerTagBar = () => {
|
|||
const element = tagsRef.current;
|
||||
if (element) {
|
||||
setIsTagsOverflowing(
|
||||
element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth
|
||||
element.scrollHeight > element.clientHeight ||
|
||||
element.scrollWidth > element.clientWidth
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const element = tagsRef.current;
|
||||
if (!element) return;
|
||||
//handles initial render when not resizing
|
||||
setIsTagsOverflowing(element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth)
|
||||
setIsTagsOverflowing(
|
||||
element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth
|
||||
);
|
||||
//make sure state updates when window resizing
|
||||
window.addEventListener('resize', () => {
|
||||
updateOverflowState();
|
||||
})
|
||||
});
|
||||
//remove listeners on unmount
|
||||
return () => {
|
||||
window.removeEventListener('resize', () => {
|
||||
updateOverflowState();
|
||||
})
|
||||
}
|
||||
}, [tagsRef])
|
||||
});
|
||||
};
|
||||
}, [tagsRef]);
|
||||
|
||||
const [tagListeningForKeyPress, setTagListeningForKeyPress] = useState<number | undefined>();
|
||||
|
||||
|
@ -147,22 +151,51 @@ export const ExplorerTagBar = () => {
|
|||
|
||||
if (!tag) return;
|
||||
|
||||
try {
|
||||
await mutation.mutateAsync({
|
||||
targets,
|
||||
tag_id: tag.id,
|
||||
unassign: false
|
||||
});
|
||||
// extract the list of tags from each object in the selected items
|
||||
const targetsTagList = Array.from(explorer.selectedItems.entries()).map(
|
||||
// issues with type here. unsure as to why, and not causing any noticeable errors, so ignoring for now with as any
|
||||
(item) => (item[0] as any).object.item.tags
|
||||
);
|
||||
|
||||
toast(
|
||||
t('tags_bulk_assigned', {
|
||||
tag_name: tag.name,
|
||||
file_count: targets.length
|
||||
}),
|
||||
{
|
||||
type: 'success'
|
||||
}
|
||||
);
|
||||
// iterate through each tag in the selected items and check if the tag we want to assign is already assigned
|
||||
const areAllAssigned = targetsTagList.every((tags) => {
|
||||
return tags.some((t: { tag_id: any }) => t.tag_id === tag.id);
|
||||
});
|
||||
|
||||
try {
|
||||
if (areAllAssigned) {
|
||||
await mutation.mutateAsync({
|
||||
targets,
|
||||
tag_id: tag.id,
|
||||
unassign: true
|
||||
});
|
||||
|
||||
toast(
|
||||
t('tags_bulk_unassigned', {
|
||||
tag_name: tag.name,
|
||||
file_count: targets.length
|
||||
}),
|
||||
{
|
||||
type: 'success'
|
||||
}
|
||||
);
|
||||
} else {
|
||||
await mutation.mutateAsync({
|
||||
targets,
|
||||
tag_id: tag.id,
|
||||
unassign: false
|
||||
});
|
||||
|
||||
toast(
|
||||
t('tags_bulk_assigned', {
|
||||
tag_name: tag.name,
|
||||
file_count: targets.length
|
||||
}),
|
||||
{
|
||||
type: 'success'
|
||||
}
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
let msg: string = t('error_unknown');
|
||||
|
||||
|
@ -199,7 +232,7 @@ export const ExplorerTagBar = () => {
|
|||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
'flex flex-row flex-wrap-reverse items-center justify-between gap-1 border-t border-t-app-line bg-app/90 px-3.5 py-2 text-ink-dull backdrop-blur-lg',
|
||||
'flex flex-row flex-wrap-reverse items-center justify-between gap-1 border-t border-t-app-line bg-app/90 px-3.5 py-2 text-ink-dull backdrop-blur-lg'
|
||||
)}
|
||||
>
|
||||
<em className="text-sm tracking-wide">{t('tags_bulk_instructions')}</em>
|
||||
|
|
|
@ -432,14 +432,15 @@ export const QuickPreview = () => {
|
|||
<div className="flex flex-1 items-center justify-end gap-1">
|
||||
<Tooltip label={t('zoom_in')}>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
setMagnification(
|
||||
(currentMagnification) =>
|
||||
currentMagnification +
|
||||
currentMagnification * 0.2
|
||||
)
|
||||
}
|
||||
// this is same formula as intrest calculation
|
||||
onClick={() => {
|
||||
magnification < 2 &&
|
||||
setMagnification(
|
||||
(currentMagnification) =>
|
||||
currentMagnification +
|
||||
currentMagnification * 0.2
|
||||
);
|
||||
}}
|
||||
// this is same formula as interest calculation
|
||||
>
|
||||
<MagnifyingGlassPlus />
|
||||
</IconButton>
|
||||
|
@ -447,13 +448,14 @@ export const QuickPreview = () => {
|
|||
|
||||
<Tooltip label={t('zoom_out')}>
|
||||
<IconButton
|
||||
onClick={() =>
|
||||
setMagnification(
|
||||
(currentMagnification) =>
|
||||
currentMagnification / (1 + 0.2)
|
||||
)
|
||||
}
|
||||
// this is same formula as intrest calculation
|
||||
onClick={() => {
|
||||
magnification > 0.5 &&
|
||||
setMagnification(
|
||||
(currentMagnification) =>
|
||||
currentMagnification / (1 + 0.2)
|
||||
);
|
||||
}}
|
||||
// this is same formula as interest calculation
|
||||
>
|
||||
<MagnifyingGlassMinus />
|
||||
</IconButton>
|
||||
|
|
|
@ -8,14 +8,20 @@ import { KindStatistic, uint32ArrayToBigInt, useLibraryQuery } from '@sd/client'
|
|||
import { Card, Tooltip } from '@sd/ui';
|
||||
import { useIsDark, useLocale } from '~/hooks';
|
||||
|
||||
const INFO_ICON_CLASSLIST = 'inline size-3 text-ink-faint opacity-0';
|
||||
const INFO_ICON_CLASSLIST =
|
||||
'inline size-3 text-ink-faint opacity-0 ml-1 transition-opacity duration-300 group-hover:opacity-70';
|
||||
const TOTAL_FILES_CLASSLIST =
|
||||
'flex items-center justify-between whitespace-nowrap text-sm font-medium text-ink-dull mt-2 px-1';
|
||||
const UNIDENTIFIED_FILES_CLASSLIST = 'relative flex items-center text-xs text-ink-faint';
|
||||
const BARS_CONTAINER_CLASSLIST =
|
||||
'relative mx-2.5 grid grow grid-cols-[repeat(auto-fit,_minmax(0,_1fr))] grid-rows-[136px_12px] items-end justify-items-center gap-x-1.5 gap-y-1 self-stretch';
|
||||
|
||||
const mapFractionalValue = (numerator: bigint, denominator: bigint, maxValue: bigint): string => {
|
||||
const result = ((numerator * maxValue) / denominator).toString();
|
||||
return result;
|
||||
if (denominator === 0n) return '0';
|
||||
const result = (numerator * maxValue) / denominator;
|
||||
// ensures min width except for empty bars (numerator = 0)
|
||||
if (numerator != 0n && result < 1) return '1';
|
||||
return result.toString();
|
||||
};
|
||||
|
||||
const formatNumberWithCommas = (number: number | bigint) => number.toLocaleString();
|
||||
|
@ -40,6 +46,27 @@ interface FileKind {
|
|||
|
||||
interface FileKindStatsProps {}
|
||||
|
||||
const defaultFileKinds: FileKind[] = [
|
||||
{ kind: 'Package', count: 0n, id: 4 },
|
||||
{ kind: 'Archive', count: 0n, id: 8 },
|
||||
{ kind: 'Executable', count: 0n, id: 9 },
|
||||
{ kind: 'Encrypted', count: 0n, id: 11 },
|
||||
{ kind: 'Key', count: 0n, id: 12 },
|
||||
{ kind: 'Link', count: 0n, id: 13 },
|
||||
{ kind: 'WebPageArchive', count: 0n, id: 14 },
|
||||
{ kind: 'Widget', count: 0n, id: 15 },
|
||||
{ kind: 'Album', count: 0n, id: 16 },
|
||||
{ kind: 'Collection', count: 0n, id: 17 },
|
||||
{ kind: 'Font', count: 0n, id: 18 },
|
||||
{ kind: 'Mesh', count: 0n, id: 19 },
|
||||
{ kind: 'Code', count: 0n, id: 20 },
|
||||
{ kind: 'Database', count: 0n, id: 21 },
|
||||
{ kind: 'Book', count: 0n, id: 22 },
|
||||
{ kind: 'Config', count: 0n, id: 23 },
|
||||
{ kind: 'Dotfile', count: 0n, id: 24 },
|
||||
{ kind: 'Screenshot', count: 0n, id: 25 }
|
||||
];
|
||||
|
||||
const FileKindStats: React.FC<FileKindStatsProps> = () => {
|
||||
const isDark = useIsDark();
|
||||
const navigate = useNavigate();
|
||||
|
@ -111,8 +138,15 @@ const FileKindStats: React.FC<FileKindStatsProps> = () => {
|
|||
id: item.kind
|
||||
}))
|
||||
);
|
||||
if (statistics.length < 10) {
|
||||
const additionalKinds = defaultFileKinds.filter(
|
||||
(defaultKind) => !statistics.some((stat) => stat.kind === defaultKind.id)
|
||||
);
|
||||
const kindsToAdd = additionalKinds.slice(0, 10 - statistics.length);
|
||||
setFileKinds((prevKinds) => [...prevKinds, ...kindsToAdd]);
|
||||
}
|
||||
|
||||
statistics.forEach((item) => {
|
||||
data.statistics.forEach((item: { name: string }) => {
|
||||
const iconName = item.name;
|
||||
if (!iconsRef.current[iconName]) {
|
||||
const img = new Image();
|
||||
|
@ -129,10 +163,7 @@ const FileKindStats: React.FC<FileKindStatsProps> = () => {
|
|||
});
|
||||
|
||||
const maxFileCount = sortedFileKinds && sortedFileKinds[0] ? sortedFileKinds[0].count : 0n;
|
||||
|
||||
const barGap = 12;
|
||||
const barCount = sortedFileKinds.length;
|
||||
const totalGapWidth = barGap * (barCount - 5);
|
||||
const makeBarClickHandler =
|
||||
(fileKind: FileKind): MouseEventHandler<HTMLDivElement> | undefined =>
|
||||
() => {
|
||||
|
@ -166,10 +197,7 @@ const FileKindStats: React.FC<FileKindStatsProps> = () => {
|
|||
</span>
|
||||
<div className="flex items-center">
|
||||
{t('total_files')}
|
||||
<Info
|
||||
weight="fill"
|
||||
className={`ml-1 ${INFO_ICON_CLASSLIST} opacity-0 transition-opacity duration-300 group-hover:opacity-70`}
|
||||
/>
|
||||
<Info weight="fill" className={INFO_ICON_CLASSLIST} />
|
||||
</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
|
@ -184,7 +212,7 @@ const FileKindStats: React.FC<FileKindStatsProps> = () => {
|
|||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mx-2.5 grid grow grid-cols-[repeat(auto-fit,_minmax(0,_1fr))] grid-rows-[136px_12px] items-end justify-items-center gap-x-1.5 gap-y-1 self-stretch">
|
||||
<div className={BARS_CONTAINER_CLASSLIST}>
|
||||
{sortedFileKinds.map((fileKind, index) => {
|
||||
const iconImage = iconsRef.current[fileKind.kind];
|
||||
const barColor = interpolateHexColor(
|
||||
|
@ -226,18 +254,12 @@ const FileKindStats: React.FC<FileKindStatsProps> = () => {
|
|||
transition={{ duration: 0.4, ease: [0.42, 0, 0.58, 1] }}
|
||||
style={{
|
||||
height: barHeight,
|
||||
minHeight: '2px',
|
||||
backgroundColor: barColor
|
||||
}}
|
||||
></motion.div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
<div
|
||||
className="sm col-span-1 row-start-2 row-end-auto text-center text-[10px] font-medium text-ink-faint"
|
||||
style={{
|
||||
borderRadius: '3px'
|
||||
}}
|
||||
>
|
||||
<div className="sm col-span-1 row-start-2 row-end-auto text-center text-[10px] font-medium text-ink-faint">
|
||||
{formatCount(fileKind.count)}
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -78,8 +78,6 @@ const LibraryStats = () => {
|
|||
const { library } = useLibraryContext();
|
||||
const stats = useLibraryQuery(['library.statistics']);
|
||||
const storageBarData = useLibraryQuery(['library.kindStatistics']).data?.statistics;
|
||||
console.log(storageBarData);
|
||||
console.log(stats);
|
||||
const { t } = useLocale();
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -87,17 +85,17 @@ const LibraryStats = () => {
|
|||
}, [stats.isLoading]);
|
||||
|
||||
const StatItemNames: Partial<Record<keyof Statistics, string>> = {
|
||||
total_library_bytes: t('library_bytes'),
|
||||
total_local_bytes_capacity: t('total_bytes_capacity'),
|
||||
total_local_bytes_free: t('total_bytes_free'),
|
||||
total_library_bytes: t('library_bytes'),
|
||||
library_db_size: t('library_db_size'),
|
||||
total_library_preview_media_bytes: t('preview_media_bytes')
|
||||
};
|
||||
|
||||
const StatDescriptions: Partial<Record<keyof Statistics, string>> = {
|
||||
total_library_bytes: t('library_bytes_description'),
|
||||
total_local_bytes_capacity: t('total_bytes_capacity_description'),
|
||||
total_local_bytes_free: t('total_bytes_free_description'),
|
||||
total_library_bytes: t('library_bytes_description'),
|
||||
library_db_size: t('library_db_size_description'),
|
||||
total_library_preview_media_bytes: t('preview_media_bytes_description')
|
||||
};
|
||||
|
@ -161,7 +159,7 @@ const LibraryStats = () => {
|
|||
sections.push({
|
||||
name: 'System Data',
|
||||
value: systemDataBytes,
|
||||
color: '#707070', // Gray for System Data
|
||||
color: '#2F3038', // Gray for System Data
|
||||
tooltip: 'System data that exists outside of your Spacedrive library'
|
||||
});
|
||||
|
||||
|
|
|
@ -678,8 +678,9 @@
|
|||
"tags_bulk_assigned": "Assigned tag \"{{tag_name}}\" to {{file_count}} $t(file, { \"count\": {{file_count}} }).",
|
||||
"tags_bulk_failed_with_tag": "Could not assign tag \"{{tag_name}}\" to {{file_count}} $t(file, { \"count\": {{file_count}} }): {{error_message}}",
|
||||
"tags_bulk_failed_without_tag": "Could not tag {{file_count}} $t(file, { \"count\": {{file_count}} }): {{error_message}}",
|
||||
"tags_bulk_instructions": "Select one or more files and press a key to assign the corresponding tag.",
|
||||
"tags_bulk_instructions": "Select one or more files and press a number key to assign/unassign the corresponding tag.",
|
||||
"tags_bulk_mode_active": "Tag assign mode is enabled.",
|
||||
"tags_bulk_unassigned": "Unassigned tag \"{{tag_name}}\" to {{file_count}} $t(file, { \"count\": {{file_count}} }).",
|
||||
"tags_description": "Manage your tags.",
|
||||
"tags_notice_message": "No items assigned to this tag.",
|
||||
"task": "task",
|
||||
|
|
Loading…
Reference in a new issue