mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-06-30 11:23:33 +00:00
[ENG-1506] Tags in Explorer (#2532)
* wip * tags in explorer * cleanup * i18n
This commit is contained in:
parent
48f5715839
commit
a6cb61584e
|
@ -174,6 +174,15 @@ export default () => {
|
|||
explorerLayout.showPathBar = value;
|
||||
}}
|
||||
/>
|
||||
<RadixCheckbox
|
||||
checked={layoutStore.showTags}
|
||||
label={t('show_tags')}
|
||||
name="showTags"
|
||||
onCheckedChange={(value) => {
|
||||
if (typeof value !== 'boolean') return;
|
||||
explorerLayout.showTags = value;
|
||||
}}
|
||||
/>
|
||||
|
||||
{settings.layoutMode === 'grid' && (
|
||||
<RadixCheckbox
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
import clsx from 'clsx';
|
||||
import { memo, useMemo } from 'react';
|
||||
import {
|
||||
Tag,
|
||||
getItemFilePath,
|
||||
getItemObject,
|
||||
humanizeSize,
|
||||
useExplorerLayoutStore,
|
||||
useLibraryQuery,
|
||||
useSelector,
|
||||
type ExplorerItem
|
||||
} from '@sd/client';
|
||||
import clsx from 'clsx';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useLocale } from '~/hooks';
|
||||
|
||||
import { useExplorerContext } from '../../../Context';
|
||||
|
@ -52,7 +55,6 @@ export const GridViewItem = memo((props: GridViewItemProps) => {
|
|||
const InnerDroppable = () => {
|
||||
const item = useGridViewItemContext();
|
||||
const { isDroppable } = useExplorerDroppableContext();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
|
@ -63,7 +65,6 @@ const InnerDroppable = () => {
|
|||
>
|
||||
<ItemFileThumb />
|
||||
</div>
|
||||
|
||||
<ItemMetadata />
|
||||
</>
|
||||
);
|
||||
|
@ -103,6 +104,7 @@ const ItemFileThumb = () => {
|
|||
const ItemMetadata = () => {
|
||||
const item = useGridViewItemContext();
|
||||
const { isDroppable } = useExplorerDroppableContext();
|
||||
const explorerLayout = useExplorerLayoutStore();
|
||||
|
||||
const isRenaming = useSelector(explorerStore, (s) => s.isRenaming && item.selected);
|
||||
|
||||
|
@ -117,11 +119,35 @@ const ItemMetadata = () => {
|
|||
selected={item.selected}
|
||||
/>
|
||||
<ItemSize />
|
||||
{explorerLayout.showTags && <ItemTags />}
|
||||
{item.data.type === 'Label' && <LabelItemCount data={item.data} />}
|
||||
</ExplorerDraggable>
|
||||
);
|
||||
};
|
||||
|
||||
const ItemTags = () => {
|
||||
const item = useGridViewItemContext();
|
||||
const object = getItemObject(item.data);
|
||||
const filePath = getItemFilePath(item.data);
|
||||
const data = object || filePath;
|
||||
const tags = data && 'tags' in data ? data.tags : [];
|
||||
return (
|
||||
<div
|
||||
className='relative mt-1 flex w-full flex-row items-center justify-center'
|
||||
style={{
|
||||
left: tags.length * 1
|
||||
}}
|
||||
>
|
||||
{tags?.slice(0, 3).map((tag: {tag: Tag}, i: number) => (
|
||||
<div key={tag.tag.id} className='relative size-2.5 rounded-full border border-app' style={{
|
||||
backgroundColor: tag.tag.color!,
|
||||
right: i * 4,
|
||||
}}/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const ItemSize = () => {
|
||||
const item = useGridViewItemContext();
|
||||
const { showBytesInGridView } = useExplorerContext().useSettingsSnapshot();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Grid, useGrid } from '@virtual-grid/react';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
import { useExplorerLayoutStore } from '@sd/client';
|
||||
import { useExplorerContext } from '../../Context';
|
||||
import { getItemData, getItemId, uniqueId } from '../../util';
|
||||
import { useExplorerViewContext } from '../Context';
|
||||
|
@ -15,10 +16,12 @@ export const GridView = () => {
|
|||
const explorer = useExplorerContext();
|
||||
const explorerView = useExplorerViewContext();
|
||||
const explorerSettings = explorer.useSettingsSnapshot();
|
||||
const layoutStore = useExplorerLayoutStore();
|
||||
|
||||
const itemDetailsHeight = 44 + (explorerSettings.showBytesInGridView ? 20 : 0);
|
||||
const itemDetailsHeight = (layoutStore.showTags ? 60 : 44) + (explorerSettings.showBytesInGridView ? 20 : 0);
|
||||
const itemHeight = explorerSettings.gridItemSize + itemDetailsHeight;
|
||||
|
||||
const BOTTOM_PADDING = layoutStore.showTags ? 16 : 12;
|
||||
const grid = useGrid({
|
||||
scrollRef: explorer.scrollRef,
|
||||
count: explorer.items?.length ?? 0,
|
||||
|
@ -26,7 +29,7 @@ export const GridView = () => {
|
|||
columns: 'auto',
|
||||
size: { width: explorerSettings.gridItemSize, height: itemHeight },
|
||||
padding: {
|
||||
bottom: PADDING + (explorerView.scrollPadding?.bottom ?? 0),
|
||||
bottom: BOTTOM_PADDING + (explorerView.scrollPadding?.bottom ?? 0),
|
||||
x: PADDING,
|
||||
y: PADDING
|
||||
},
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { getItemFilePath, useSelector, type ExplorerItem } from '@sd/client';
|
||||
import { flexRender, type Cell } from '@tanstack/react-table';
|
||||
import clsx from 'clsx';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { getItemFilePath, useSelector, type ExplorerItem } from '@sd/client';
|
||||
|
||||
import { TABLE_PADDING_X } from '.';
|
||||
import { useExplorerContext } from '../../Context';
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
import {
|
||||
getExplorerItemData,
|
||||
getIndexedItemFilePath,
|
||||
getItemFilePath,
|
||||
getItemObject,
|
||||
humanizeSize,
|
||||
useExplorerLayoutStore,
|
||||
useSelector,
|
||||
type ExplorerItem
|
||||
} from '@sd/client';
|
||||
import {
|
||||
CellContext,
|
||||
functionalUpdate,
|
||||
|
@ -9,15 +19,6 @@ import clsx from 'clsx';
|
|||
import dayjs from 'dayjs';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { stringify } from 'uuid';
|
||||
import {
|
||||
getExplorerItemData,
|
||||
getIndexedItemFilePath,
|
||||
getItemFilePath,
|
||||
getItemObject,
|
||||
humanizeSize,
|
||||
useSelector,
|
||||
type ExplorerItem
|
||||
} from '@sd/client';
|
||||
import { useLocale } from '~/hooks';
|
||||
|
||||
import { useExplorerContext } from '../../Context';
|
||||
|
@ -48,6 +49,7 @@ const NameCell = memo(({ item, selected }: { item: ExplorerItem; selected: boole
|
|||
|
||||
const explorer = useExplorerContext();
|
||||
const explorerSettings = explorer.useSettingsSnapshot();
|
||||
const explorerLayout = useExplorerLayoutStore();
|
||||
|
||||
return (
|
||||
<div className="flex">
|
||||
|
@ -66,15 +68,41 @@ const NameCell = memo(({ item, selected }: { item: ExplorerItem; selected: boole
|
|||
selected={selected}
|
||||
allowHighlight={false}
|
||||
style={{ fontSize: LIST_VIEW_TEXT_SIZES[explorerSettings.listViewTextSize] }}
|
||||
className="absolute top-1/2 z-10 max-w-full -translate-y-1/2"
|
||||
idleClassName="!w-full"
|
||||
className="absolute top-1/2 z-10 -translate-y-1/2"
|
||||
idleClassName={clsx(explorerLayout.showTags ? '!w-4/5' : '!w-full')}
|
||||
editLines={3}
|
||||
/>
|
||||
{explorerLayout.showTags && (
|
||||
<Tags item={item}/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const Tags = ({item}: {item: ExplorerItem}) => {
|
||||
const object = getItemObject(item);
|
||||
const filePath = getItemFilePath(item);
|
||||
const data = object || filePath;
|
||||
const tags = data && 'tags' in data ? data.tags : [];
|
||||
return (
|
||||
<div
|
||||
className='relative flex size-full flex-row items-center justify-end self-center'
|
||||
style={{
|
||||
marginLeft: tags.length * 4
|
||||
}}
|
||||
>
|
||||
{tags.map(({tag}, i: number) => (
|
||||
<div key={tag.id} className='relative size-2.5 rounded-full border border-app' style={{
|
||||
backgroundColor: tag.color || 'transparent',
|
||||
right: i * 4
|
||||
}}/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
const KindCell = ({ kind }: { kind: string }) => {
|
||||
const explorer = useExplorerContext();
|
||||
const explorerSettings = explorer.useSettingsSnapshot();
|
||||
|
|
|
@ -1,7 +1,3 @@
|
|||
import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio';
|
||||
import { z } from 'zod';
|
||||
import type {
|
||||
ExplorerItem,
|
||||
ExplorerLayout,
|
||||
|
@ -12,6 +8,10 @@ import type {
|
|||
Tag
|
||||
} from '@sd/client';
|
||||
import { ObjectKindEnum, type Ordering, type OrderingKeys } from '@sd/client';
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { proxy, snapshot, subscribe, useSnapshot } from 'valtio';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { createDefaultExplorerSettings } from './store';
|
||||
import { uniqueId } from './util';
|
||||
|
|
|
@ -5,9 +5,10 @@ import {
|
|||
ToastDefautlColor,
|
||||
useLibraryMutation,
|
||||
usePlausibleEvent,
|
||||
useRspcLibraryContext,
|
||||
useZodForm
|
||||
} from '@sd/client';
|
||||
import { Dialog, InputField, useDialog, UseDialogProps, z } from '@sd/ui';
|
||||
import { Dialog, InputField, UseDialogProps, useDialog, z } from '@sd/ui';
|
||||
import { ColorPicker } from '~/components';
|
||||
import { useLocale } from '~/hooks';
|
||||
|
||||
|
@ -22,10 +23,12 @@ export type AssignTagItems = Array<
|
|||
|
||||
export function useAssignItemsToTag() {
|
||||
const submitPlausibleEvent = usePlausibleEvent();
|
||||
const rspc = useRspcLibraryContext();
|
||||
|
||||
const mutation = useLibraryMutation(['tags.assign'], {
|
||||
onSuccess: () => {
|
||||
submitPlausibleEvent({ event: { type: 'tagAssign' } });
|
||||
rspc.queryClient.invalidateQueries(['search.paths']);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -614,6 +614,7 @@
|
|||
"show_object_size": "إظهار حجم الكائن",
|
||||
"show_path_bar": "إظهار شريط المسار",
|
||||
"show_slider": "إظهار المنزلق",
|
||||
"show_tags": "أضهر العلامات",
|
||||
"size": "الحجم",
|
||||
"size_b": "بايت",
|
||||
"size_bs": "ب",
|
||||
|
|
|
@ -605,6 +605,7 @@
|
|||
"show_object_size": "Памер аб'екта",
|
||||
"show_path_bar": "Адрасны радок",
|
||||
"show_slider": "Паказаць паўзунок",
|
||||
"show_tags": "Паказаць тэгі",
|
||||
"size": "Памер",
|
||||
"size_b": "Б",
|
||||
"size_bs": "Б",
|
||||
|
|
|
@ -602,6 +602,7 @@
|
|||
"show_object_size": "Objektgröße anzeigen",
|
||||
"show_path_bar": "Pfadleiste anzeigen",
|
||||
"show_slider": "Slider anzeigen",
|
||||
"show_tags": "Tags anzeigen",
|
||||
"size": "Größe",
|
||||
"size_b": "B",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -614,6 +614,7 @@
|
|||
"show_object_size": "Show Object size",
|
||||
"show_path_bar": "Show Path Bar",
|
||||
"show_slider": "Show slider",
|
||||
"show_tags": "Show Tags",
|
||||
"size": "Size",
|
||||
"size_b": "B",
|
||||
"size_bs": "Bs",
|
||||
|
|
|
@ -604,6 +604,7 @@
|
|||
"show_object_size": "Mostrar Tamaño del Objeto",
|
||||
"show_path_bar": "Mostrar Barra de Ruta",
|
||||
"show_slider": "Mostrar deslizador",
|
||||
"show_tags": "Mostrar etiquetas",
|
||||
"size": "Tamaño",
|
||||
"size_b": "B",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -604,6 +604,7 @@
|
|||
"show_object_size": "Afficher la taille de l'objet",
|
||||
"show_path_bar": "Afficher la barre de chemin",
|
||||
"show_slider": "Afficher le curseur",
|
||||
"show_tags": "Voir les étiquettes",
|
||||
"size": "Taille",
|
||||
"size_b": "o",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -604,6 +604,7 @@
|
|||
"show_object_size": "Mostra la dimensione dell'oggetto",
|
||||
"show_path_bar": "Mostra barra del percorso",
|
||||
"show_slider": "Mostra slider",
|
||||
"show_tags": "Mostra tag",
|
||||
"size": "Dimensione",
|
||||
"size_b": "B",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -598,6 +598,7 @@
|
|||
"show_object_size": "ファイルサイズを表示",
|
||||
"show_path_bar": "パスバーを表示",
|
||||
"show_slider": "スライダーを表示",
|
||||
"show_tags": "タグを表示",
|
||||
"size": "サイズ",
|
||||
"size_b": "バイト",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -602,6 +602,7 @@
|
|||
"show_object_size": "Toon Object grootte",
|
||||
"show_path_bar": "Padbalk Tonen",
|
||||
"show_slider": "Toon schuifregelaar",
|
||||
"show_tags": "Toon labels",
|
||||
"size": "Grootte",
|
||||
"size_b": "B",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -605,6 +605,7 @@
|
|||
"show_object_size": "Размер объекта",
|
||||
"show_path_bar": "Адресная строка",
|
||||
"show_slider": "Показать ползунок",
|
||||
"show_tags": "Показать теги",
|
||||
"size": "Размер",
|
||||
"size_b": "Б",
|
||||
"size_bs": "Б",
|
||||
|
|
|
@ -602,6 +602,7 @@
|
|||
"show_object_size": "Nesne Boyutunu Göster",
|
||||
"show_path_bar": "Yol Çubuğunu Göster",
|
||||
"show_slider": "Kaydırıcıyı Göster",
|
||||
"show_tags": "Etiketleri göster",
|
||||
"size": "Boyut",
|
||||
"size_b": "B",
|
||||
"size_bs": "B",
|
||||
|
|
|
@ -598,6 +598,7 @@
|
|||
"show_object_size": "显示对象大小",
|
||||
"show_path_bar": "显示路径栏",
|
||||
"show_slider": "显示滑块",
|
||||
"show_tags": "显示标签",
|
||||
"size": "大小",
|
||||
"size_b": "乙",
|
||||
"size_bs": "乙",
|
||||
|
|
|
@ -598,6 +598,7 @@
|
|||
"show_object_size": "顯示對象大小",
|
||||
"show_path_bar": "顯示路徑條",
|
||||
"show_slider": "顯示滑塊",
|
||||
"show_tags": "顯示標籤",
|
||||
"size": "大小",
|
||||
"size_b": "乙",
|
||||
"size_bs": "乙",
|
||||
|
|
|
@ -7,6 +7,7 @@ export const explorerLayout = createPersistedMutable(
|
|||
'sd-explorer-layout',
|
||||
createMutable({
|
||||
showPathBar: true,
|
||||
showTags: true,
|
||||
showImageSlider: true,
|
||||
// might move this to a store called settings
|
||||
defaultView: 'grid' as ExplorerLayout
|
||||
|
|
Loading…
Reference in a new issue