mirror of
https://github.com/spacedriveapp/spacedrive.git
synced 2026-04-22 23:48:26 -04:00
* Update pnpm version
* Fix autoformat
* Improve autoformat msg
* Attempt to fix autoformat 2
* Fix autoformat
* Ignore deleted files in auto-format
* Fix diff filter
* Autoformat whole codebase
* Improve error message for autoformat CI
* Test autoformat CI
* Revert "Test autoformat CI"
This reverts commit 0bf2f46d1a.
250 lines
7.5 KiB
TypeScript
250 lines
7.5 KiB
TypeScript
import { CheckCircle, XCircle } from '@phosphor-icons/react';
|
|
import { Suspense, useMemo } from 'react';
|
|
import {
|
|
auth,
|
|
CloudInstance,
|
|
CloudLibrary,
|
|
HardwareModel,
|
|
useLibraryContext,
|
|
useLibraryMutation,
|
|
useLibraryQuery
|
|
} from '@sd/client';
|
|
import { Button, Card, Loader, tw } from '@sd/ui';
|
|
import { Icon } from '~/components';
|
|
import { AuthRequiredOverlay } from '~/components/AuthRequiredOverlay';
|
|
import { LoginButton } from '~/components/LoginButton';
|
|
import { useLocale, useRouteTitle } from '~/hooks';
|
|
import { hardwareModelToIcon } from '~/util/hardware';
|
|
|
|
const DataBox = tw.div`max-w-[300px] rounded-md border border-app-line/50 bg-app-lightBox/20 p-2`;
|
|
const Count = tw.div`min-w-[20px] flex h-[20px] px-1 items-center justify-center rounded-full border border-app-button/40 text-[9px]`;
|
|
|
|
export const Component = () => {
|
|
useRouteTitle('Cloud');
|
|
|
|
const authState = auth.useStateSnapshot();
|
|
|
|
const authSensitiveChild = () => {
|
|
if (authState.status === 'loggedIn') return <Authenticated />;
|
|
if (authState.status === 'notLoggedIn' || authState.status === 'loggingIn')
|
|
return (
|
|
<div className="flex size-full items-center justify-center">
|
|
<DataBox className="flex flex-col items-center gap-5 !p-6">
|
|
<div className="flex flex-col items-center gap-1">
|
|
<Icon name="Sync" size={60} />
|
|
<p className="max-w-[75%] text-center text-sm">
|
|
To access cloud related features, please login
|
|
</p>
|
|
</div>
|
|
<LoginButton />
|
|
</DataBox>
|
|
</div>
|
|
);
|
|
|
|
return null;
|
|
};
|
|
|
|
return <div className="flex size-full flex-col items-start p-4">{authSensitiveChild()}</div>;
|
|
};
|
|
|
|
// million-ignore
|
|
function Authenticated() {
|
|
const { library } = useLibraryContext();
|
|
const cloudLibrary = useLibraryQuery(['cloud.library.get'], { suspense: true, retry: false });
|
|
const createLibrary = useLibraryMutation(['cloud.library.create']);
|
|
const { t } = useLocale();
|
|
|
|
const thisInstance = useMemo(() => {
|
|
if (!cloudLibrary.data) return undefined;
|
|
return cloudLibrary.data.instances.find(
|
|
(instance) => instance.uuid === library.instance_id
|
|
);
|
|
}, [cloudLibrary.data, library.instance_id]);
|
|
|
|
return (
|
|
<Suspense
|
|
fallback={
|
|
<div className="flex size-full items-center justify-center">
|
|
<Loader />
|
|
</div>
|
|
}
|
|
>
|
|
{cloudLibrary.data ? (
|
|
<div className="flex flex-col items-start gap-10">
|
|
<Library thisInstance={thisInstance} cloudLibrary={cloudLibrary.data} />
|
|
{thisInstance && <ThisInstance instance={thisInstance} />}
|
|
<Instances instances={cloudLibrary.data.instances} />
|
|
</div>
|
|
) : (
|
|
<div className="relative flex size-full flex-col items-center justify-center">
|
|
<AuthRequiredOverlay />
|
|
<DataBox className="flex min-w-[400px] flex-col items-center gap-5 p-6">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<Icon name="CloudSync" size={60} />
|
|
<p className="max-w-[60%] text-center text-sm text-ink">
|
|
{t('cloud_connect_description')}
|
|
</p>
|
|
</div>
|
|
<Button
|
|
className="h-8"
|
|
disabled={createLibrary.isLoading}
|
|
variant="accent"
|
|
onClick={() => {
|
|
createLibrary.mutateAsync(null);
|
|
}}
|
|
>
|
|
{createLibrary.isLoading ? (
|
|
<div className="flex h-4 flex-row items-center gap-2">
|
|
<Loader className="w-5" color="white" />
|
|
<p className="text-xs">{t('Connecting' + '...')}</p>
|
|
</div>
|
|
) : (
|
|
t('Connect')
|
|
)}
|
|
</Button>
|
|
</DataBox>
|
|
</div>
|
|
)}
|
|
</Suspense>
|
|
);
|
|
}
|
|
|
|
// million-ignore
|
|
const Instances = ({ instances }: { instances: CloudInstance[] }) => {
|
|
const { library } = useLibraryContext();
|
|
const filteredInstances = instances.filter((instance) => instance.uuid !== library.instance_id);
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
<div className="flex flex-row items-center gap-3">
|
|
<p className="text-medium font-bold">Instances</p>
|
|
<Count>{filteredInstances.length}</Count>
|
|
</div>
|
|
<div className="flex flex-row flex-wrap gap-2">
|
|
{filteredInstances.map((instance) => (
|
|
<Card
|
|
key={instance.id}
|
|
className="flex-col items-center gap-4 bg-app-box/50 !p-5"
|
|
>
|
|
<div className="flex flex-col items-center gap-2">
|
|
<Icon
|
|
name={
|
|
hardwareModelToIcon(
|
|
instance.metadata.device_model as HardwareModel
|
|
) as any
|
|
}
|
|
size={70}
|
|
/>
|
|
<p className="max-w-[250px] truncate text-xs font-medium">
|
|
{instance.metadata.name}
|
|
</p>
|
|
</div>
|
|
<div className="flex flex-col gap-1.5">
|
|
<DataBox>
|
|
<p className="truncate text-xs font-medium">
|
|
Id:{' '}
|
|
<span className="font-normal text-ink-dull">{instance.id}</span>
|
|
</p>
|
|
</DataBox>
|
|
<DataBox>
|
|
<p className="truncate text-xs font-medium">
|
|
UUID:{' '}
|
|
<span className="font-normal text-ink-dull">
|
|
{instance.uuid}
|
|
</span>
|
|
</p>
|
|
</DataBox>
|
|
<DataBox>
|
|
<p className="truncate text-xs font-medium">
|
|
Public Key:{' '}
|
|
<span className="font-normal text-ink-dull">
|
|
{instance.identity}
|
|
</span>
|
|
</p>
|
|
</DataBox>
|
|
</div>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface LibraryProps {
|
|
cloudLibrary: CloudLibrary;
|
|
thisInstance: CloudInstance | undefined;
|
|
}
|
|
|
|
// million-ignore
|
|
const Library = ({ thisInstance, cloudLibrary }: LibraryProps) => {
|
|
const syncLibrary = useLibraryMutation(['cloud.library.sync']);
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
<p className="text-medium font-bold">Library</p>
|
|
<Card className="flex-row items-center gap-6 !px-2">
|
|
<p className="font-medium">
|
|
Name: <span className="font-normal text-ink-dull">{cloudLibrary.name}</span>
|
|
</p>
|
|
<Button
|
|
disabled={syncLibrary.isLoading || thisInstance !== undefined}
|
|
variant={thisInstance === undefined ? 'accent' : 'gray'}
|
|
className="flex flex-row items-center gap-1 !text-ink"
|
|
onClick={() => syncLibrary.mutateAsync(null)}
|
|
>
|
|
{thisInstance === undefined ? (
|
|
<XCircle weight="fill" size={15} className="text-red-400" />
|
|
) : (
|
|
<CheckCircle weight="fill" size={15} className="text-green-400" />
|
|
)}
|
|
{thisInstance === undefined ? 'Sync Library' : 'Library synced'}
|
|
</Button>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
interface ThisInstanceProps {
|
|
instance: CloudInstance;
|
|
}
|
|
|
|
// million-ignore
|
|
const ThisInstance = ({ instance }: ThisInstanceProps) => {
|
|
return (
|
|
<div className="flex flex-col gap-3">
|
|
<p className="text-medium font-bold">This Instance</p>
|
|
<Card className="flex-col items-center gap-4 bg-app-box/50 !p-5">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<Icon
|
|
name={
|
|
hardwareModelToIcon(
|
|
instance.metadata.device_model as HardwareModel
|
|
) as any
|
|
}
|
|
size={70}
|
|
/>
|
|
<p className="max-w-[160px] truncate text-xs font-medium">
|
|
{instance.metadata.name}
|
|
</p>
|
|
</div>
|
|
<div className="flex flex-col gap-1.5">
|
|
<DataBox>
|
|
<p className="truncate text-xs font-medium">
|
|
Id: <span className="font-normal text-ink-dull">{instance.id}</span>
|
|
</p>
|
|
</DataBox>
|
|
<DataBox>
|
|
<p className="truncate text-xs font-medium">
|
|
UUID: <span className="font-normal text-ink-dull">{instance.uuid}</span>
|
|
</p>
|
|
</DataBox>
|
|
<DataBox>
|
|
<p className="truncate text-xs font-medium">
|
|
Public Key:{' '}
|
|
<span className="font-normal text-ink-dull">{instance.identity}</span>
|
|
</p>
|
|
</DataBox>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
};
|