mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-20 22:50:11 -04:00
* Make Prettier and ESLint work together - Resolve conflicts between Prettier and ESLint regarding indentation and Tailwind rules order - Add `.editorconfig` to standardize basic formatting options across tools and editors - Add `.gitattributes` to hide `pnpm-lock.yaml` in `git diff` output - Include EditorConfig in the recommended extensions for VSCode - Replace some instances of `pnpm exec <command>` with `pnpm <command>` - Remove superfluous Tauri config for Linux * Revert Prettier changes (it was working correctly before) - Update ESLint to read Tailwind config from absolute path - Remove redundant Prettier dependency from subprojects - Specify the source folder for the lint script in subprojects * use mobile's tailwind config with eslint * pnpm format + pnpm lint:fix --------- Co-authored-by: Utku Bakir <74243531+utkubakir@users.noreply.github.com>
252 lines
6.8 KiB
TypeScript
252 lines
6.8 KiB
TypeScript
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
import clsx from 'clsx';
|
|
import { DotsThree, Eye, Key as KeyIcon } from 'phosphor-react';
|
|
import { PropsWithChildren, useState } from 'react';
|
|
import { animated, useTransition } from 'react-spring';
|
|
import { useLibraryMutation } from '@sd/client';
|
|
import { Button, Tooltip } from '@sd/ui';
|
|
|
|
// TODO: Replace this with Prisma type when integrating with backend
|
|
export interface Key {
|
|
id: string;
|
|
name: string;
|
|
queue: Set<string>;
|
|
mounted?: boolean;
|
|
locked?: boolean;
|
|
stats?: {
|
|
objectCount?: number;
|
|
containerCount?: number;
|
|
};
|
|
default?: boolean;
|
|
memoryOnly?: boolean;
|
|
automount?: boolean;
|
|
// Nodes this key is mounted on
|
|
nodes?: string[]; // will be node object
|
|
}
|
|
|
|
interface Props extends DropdownMenu.MenuContentProps {
|
|
trigger: React.ReactNode;
|
|
transformOrigin?: string;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export const KeyDropdown = ({
|
|
trigger,
|
|
children,
|
|
transformOrigin,
|
|
className
|
|
}: PropsWithChildren<Props>) => {
|
|
const [open, setOpen] = useState(false);
|
|
|
|
const transitions = useTransition(open, {
|
|
from: {
|
|
opacity: 0,
|
|
transform: `scale(0.9)`,
|
|
transformOrigin: transformOrigin || 'top'
|
|
},
|
|
enter: { opacity: 1, transform: 'scale(1)' },
|
|
leave: { opacity: -0.5, transform: 'scale(0.95)' },
|
|
config: { mass: 0.4, tension: 200, friction: 10 }
|
|
});
|
|
|
|
return (
|
|
<DropdownMenu.Root open={open} onOpenChange={setOpen}>
|
|
<DropdownMenu.Trigger>{trigger}</DropdownMenu.Trigger>
|
|
{transitions(
|
|
(styles, show) =>
|
|
show && (
|
|
<DropdownMenu.Portal forceMount>
|
|
<DropdownMenu.Content forceMount asChild>
|
|
<animated.div
|
|
// most of this is copied over from the `OverlayPanel`
|
|
className={clsx(
|
|
'flex flex-col',
|
|
'z-50 m-2 space-y-1 px-4 py-2',
|
|
'cursor-default select-none rounded-lg',
|
|
'text-left text-sm text-ink',
|
|
'bg-app-overlay/80 backdrop-blur',
|
|
// 'border border-app-overlay',
|
|
'shadow-2xl shadow-black/60 ',
|
|
className
|
|
)}
|
|
style={styles}
|
|
>
|
|
{children}
|
|
</animated.div>
|
|
</DropdownMenu.Content>
|
|
</DropdownMenu.Portal>
|
|
)
|
|
)}
|
|
</DropdownMenu.Root>
|
|
);
|
|
};
|
|
|
|
export const Key = ({ data }: { data: Key }) => {
|
|
const mountKey = useLibraryMutation('keys.mount');
|
|
const unmountKey = useLibraryMutation('keys.unmount');
|
|
const deleteKey = useLibraryMutation('keys.deleteFromLibrary');
|
|
const setDefaultKey = useLibraryMutation('keys.setDefault');
|
|
const changeAutomountStatus = useLibraryMutation('keys.updateAutomountStatus');
|
|
const syncToLibrary = useLibraryMutation('keys.syncKeyToLibrary');
|
|
|
|
if (data.mounted && data.queue.has(data.id)) {
|
|
data.queue.delete(data.id);
|
|
}
|
|
|
|
return (
|
|
<div
|
|
className={clsx(
|
|
'flex items-center justify-between rounded-lg bg-app-box px-2 py-1.5 text-sm shadow-lg shadow-app-shade/10'
|
|
)}
|
|
>
|
|
<div className="flex items-center">
|
|
<KeyIcon
|
|
className={clsx(
|
|
'ml-1 mr-3 h-5 w-5',
|
|
data.mounted
|
|
? data.locked
|
|
? 'text-accent'
|
|
: 'text-accent'
|
|
: 'text-gray-400/80'
|
|
)}
|
|
/>
|
|
<div className="flex flex-col ">
|
|
<div className="flex flex-row items-center">
|
|
<div className="font-semibold">{data.name}</div>
|
|
{data.mounted && (
|
|
<div className="ml-2 inline rounded bg-gray-500 px-1 text-[8pt] font-medium text-gray-300">
|
|
{data.nodes?.length || 0 > 0
|
|
? `${data.nodes?.length || 0} nodes`
|
|
: 'This node'}
|
|
</div>
|
|
)}
|
|
{data.default && (
|
|
<div className="ml-2 inline rounded bg-gray-500 px-1 text-[8pt] font-medium text-gray-300">
|
|
Default
|
|
</div>
|
|
)}
|
|
</div>
|
|
{/* <div className="text-xs text-gray-300 opacity-30">#{data.id}</div> */}
|
|
{data.stats ? (
|
|
<div className="mt-[1px] flex flex-row space-x-3">
|
|
{data.stats.objectCount && (
|
|
<div className="text-[8pt] font-medium text-ink-dull opacity-30">
|
|
{data.stats.objectCount} Objects
|
|
</div>
|
|
)}
|
|
{data.stats.containerCount && (
|
|
<div className="text-[8pt] font-medium text-ink-dull opacity-30">
|
|
{data.stats.containerCount} Containers
|
|
</div>
|
|
)}
|
|
</div>
|
|
) : (
|
|
!data.mounted && (
|
|
<div className="text-[8pt] font-medium text-ink-dull opacity-30">
|
|
{data.queue.has(data.id) ? 'Key mounting...' : 'Key not mounted'}
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="space-x-1">
|
|
{data.mounted && (
|
|
<Tooltip label="Browse files">
|
|
<Button size="icon">
|
|
<Eye className="h-4 w-4 text-ink-faint" />
|
|
</Button>
|
|
</Tooltip>
|
|
)}
|
|
<KeyDropdown
|
|
trigger={
|
|
<Button size="icon">
|
|
<DotsThree className="h-4 w-4 text-ink-faint" />
|
|
</Button>
|
|
}
|
|
>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
unmountKey.mutate(data.id);
|
|
}}
|
|
hidden={!data.mounted}
|
|
value="Unmount"
|
|
/>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
syncToLibrary.mutate(data.id);
|
|
}}
|
|
hidden={!data.memoryOnly}
|
|
value="Sync to library"
|
|
/>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
data.queue.add(data.id);
|
|
mountKey.mutate(data.id);
|
|
}}
|
|
hidden={data.mounted || data.queue.has(data.id)}
|
|
value="Mount"
|
|
/>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
deleteKey.mutate(data.id);
|
|
}}
|
|
value="Delete from Library"
|
|
/>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
setDefaultKey.mutate(data.id);
|
|
}}
|
|
hidden={data.default}
|
|
value="Set as Default"
|
|
/>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
changeAutomountStatus.mutate({ uuid: data.id, status: false });
|
|
}}
|
|
hidden={!data.automount || data.memoryOnly}
|
|
value="Disable Automount"
|
|
/>
|
|
<KeyDropdownItem
|
|
onClick={() => {
|
|
changeAutomountStatus.mutate({ uuid: data.id, status: true });
|
|
}}
|
|
hidden={data.automount || data.memoryOnly}
|
|
value="Enable Automount"
|
|
/>
|
|
</KeyDropdown>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const KeyDropdownItem = (props: {
|
|
value: string;
|
|
hidden?: boolean | undefined;
|
|
onClick: () => void;
|
|
}) => {
|
|
return (
|
|
<DropdownMenu.DropdownMenuItem
|
|
className="!cursor-default select-none py-0.5 text-menu-ink focus:outline-none active:opacity-80"
|
|
onClick={props.onClick}
|
|
hidden={props.hidden}
|
|
>
|
|
{props.value}
|
|
</DropdownMenu.DropdownMenuItem>
|
|
);
|
|
};
|
|
|
|
export const DummyKey = (props: { text: string }) => {
|
|
return (
|
|
<div className="flex items-center justify-between rounded-lg bg-app-box p-2 py-1.5 text-sm shadow-lg shadow-app-shade/10">
|
|
<div className="flex items-center">
|
|
<KeyIcon className="ml-1 mr-3 h-5 w-5 text-gray-400/80" />
|
|
<div className="flex flex-col ">
|
|
<div className="flex flex-row items-center">
|
|
<div className="font-medium">{props.text}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|