* changed tag assign mode behaviour

* added min/max zoom and filekinds logic

* changed colour of system data bar
This commit is contained in:
Matthew Yung 2024-06-26 00:49:10 -07:00 committed by GitHub
parent f7b0e3bd06
commit a8231aca4f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 121 additions and 65 deletions

View file

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

View file

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

View file

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

View file

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

View file

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