mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-07 05:33:27 +00:00
redesigned home screen
This commit is contained in:
parent
5a1d8fc174
commit
edcd011986
10
docs/product/ideas.md
Normal file
10
docs/product/ideas.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
# Core view
|
||||
The primary screen in the Spacedrive app is of your entire virtual network, every "core" is a device under your full command. It is presented in a large panel list view, featuring bold visuals and quick interactions with your fleet.
|
||||
|
||||
Recent files, recent locations and settings are part of these panels, that can be customized at will.
|
||||
Visual indicators report the live status of this device.
|
||||
|
||||
Key statistics of this devices are also present on these panels.
|
||||
|
||||
A "Core" represents a device in your "Space".
|
|
@ -37,6 +37,7 @@
|
|||
"react-error-boundary": "^3.1.4",
|
||||
"react-hotkeys-hook": "^3.4.4",
|
||||
"react-json-view": "^1.21.3",
|
||||
"react-loading-icons": "^1.0.8",
|
||||
"react-portal": "^4.2.2",
|
||||
"react-query": "^3.34.19",
|
||||
"react-router": "6.3.0",
|
||||
|
|
89
packages/interface/src/components/device/Device.tsx
Normal file
89
packages/interface/src/components/device/Device.tsx
Normal file
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
Desktop,
|
||||
DeviceMobileCamera,
|
||||
DotsSixVertical,
|
||||
Laptop,
|
||||
Phone,
|
||||
PhoneX
|
||||
} from 'phosphor-react';
|
||||
import React, { useState } from 'react';
|
||||
import FileItem from '../file/FileItem';
|
||||
import { Button } from '@sd/ui';
|
||||
import ProgressBar from '../primitive/ProgressBar';
|
||||
import { CogIcon } from '@heroicons/react/solid';
|
||||
import { KeyIcon } from '@heroicons/react/outline';
|
||||
import LoadingIcons, { Rings } from 'react-loading-icons';
|
||||
|
||||
export interface DeviceProps {
|
||||
name: string;
|
||||
size: string;
|
||||
type: 'laptop' | 'desktop' | 'phone';
|
||||
locations: { name: string }[];
|
||||
runningJob?: { amount: number; task: string };
|
||||
}
|
||||
|
||||
export function Device(props: DeviceProps) {
|
||||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||
|
||||
function handleSelect(key: string) {
|
||||
if (selectedFile === key) setSelectedFile(null);
|
||||
else setSelectedFile(key);
|
||||
}
|
||||
return (
|
||||
<div className="w-full bg-gray-600 border rounded-md border-gray-550 ">
|
||||
<div className="flex flex-row items-center px-4 pt-2 pb-2">
|
||||
<DotsSixVertical weight="bold" className="mr-3 opacity-30" />
|
||||
{props.type === 'phone' && <DeviceMobileCamera weight="fill" size={20} className="mr-2" />}
|
||||
{props.type === 'laptop' && <Laptop weight="fill" size={20} className="mr-2" />}
|
||||
{props.type === 'desktop' && <Desktop weight="fill" size={20} className="mr-2" />}
|
||||
<h3 className="font-semibold text-md">{props.name}</h3>
|
||||
<div className="flex flex-row space-x-1.5 mt-0.5">
|
||||
<span className="font-semibold h-[19px] ml-3 py-0.5 px-1.5 text-[10px] rounded-md text-gray-400 bg-gray-550">
|
||||
Primary
|
||||
</span>
|
||||
<span className="font-semibold h-[19px] py-0.5 px-1.5 text-[10px] rounded-md text-gray-400 bg-gray-550">
|
||||
{props.size}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex flex-grow" />
|
||||
{props.runningJob && (
|
||||
<div className="flex flex-row ml-5 bg-opacity-50 border border-gray-500 rounded bg-gray-550">
|
||||
<Rings
|
||||
stroke="#2599FF"
|
||||
strokeOpacity={4}
|
||||
strokeWidth={10}
|
||||
speed={0.5}
|
||||
className="ml-0.5 mt-0.5 -mr-1 w-7 h-7"
|
||||
/>
|
||||
<div className="flex flex-col p-1.5">
|
||||
<span className="mb-[3px] -mt-0.5 truncate text-gray-450 text-tiny">
|
||||
{props.runningJob.task}...
|
||||
</span>
|
||||
<ProgressBar value={props.runningJob?.amount} total={100} />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex flex-row ml-3 space-x-1">
|
||||
<Button className="!p-1 ">
|
||||
<KeyIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
<Button className="!p-1 ">
|
||||
<CogIcon className="w-5 h-5" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="border-gray-700" />
|
||||
<hr className="border-gray-550" />
|
||||
<div className="px-4 pb-3 mt-3">
|
||||
{props.locations.map((location) => (
|
||||
<FileItem
|
||||
selected={selectedFile == location.name}
|
||||
onClick={() => handleSelect(location.name)}
|
||||
fileName={location.name}
|
||||
folder
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -31,7 +31,7 @@ export default function FileItem(props: Props) {
|
|||
className={clsx(
|
||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
||||
{
|
||||
'bg-gray-50 dark:bg-gray-750': props.selected
|
||||
'bg-gray-50 dark:bg-gray-650': props.selected
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import { DotsSixVertical, Laptop, LineSegments } from 'phosphor-react';
|
||||
import React, { useState } from 'react';
|
||||
import { Device } from '../components/device/Device';
|
||||
import FileItem from '../components/file/FileItem';
|
||||
|
||||
interface StatItemProps {
|
||||
|
@ -9,7 +11,7 @@ interface StatItemProps {
|
|||
|
||||
const StatItem: React.FC<StatItemProps> = (props) => {
|
||||
return (
|
||||
<div className="flex flex-col p-4 mt-2 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
|
||||
<div className="flex flex-col px-4 py-3 duration-75 transform rounded-md cursor-default hover:bg-gray-50 hover:dark:bg-gray-600">
|
||||
<span className="text-sm text-gray-400">{props.name}</span>
|
||||
<span className="text-2xl font-bold">
|
||||
{props.value}
|
||||
|
@ -23,17 +25,16 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
|||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||
|
||||
function handleSelect(key: string) {
|
||||
// if (selectedFile === key) setSelectedFile(null);
|
||||
// else setSelectedFile(key);
|
||||
setSelectedFile(key);
|
||||
if (selectedFile === key) setSelectedFile(null);
|
||||
else setSelectedFile(key);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col w-full h-screen">
|
||||
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-7" />
|
||||
<div className="flex flex-col w-full h-screen px-5 pb-3 overflow-scroll">
|
||||
<div className="flex items-center w-full px-2">
|
||||
<div className="flex flex-wrap space-x-4">
|
||||
<div className="flex flex-col w-full h-screen px-3 overflow-scroll">
|
||||
<div className="flex items-center w-full ml-4">
|
||||
<div className="flex flex-wrap p-2 space-x-6">
|
||||
<StatItem name="Total capacity" value="26.5" unit="TB" />
|
||||
<StatItem name="Index size" value="103" unit="MB" />
|
||||
<StatItem name="Preview media" value="23.5" unit="GB" />
|
||||
|
@ -42,9 +43,25 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
|||
<StatItem name="Total backed up" value="25.3" unit="TB" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-5" />
|
||||
<div className="flex flex-col space-y-4">
|
||||
<Device
|
||||
name="Spacedad"
|
||||
size="1.4TB"
|
||||
runningJob={{ amount: 65, task: 'Generating preview media' }}
|
||||
locations={[{ name: 'Pictures' }, { name: 'Downloads' }, { name: 'Minecraft' }]}
|
||||
type="laptop"
|
||||
/>
|
||||
<Device
|
||||
name="Jamies iPhone"
|
||||
size="47.7GB"
|
||||
locations={[{ name: 'Camera Roll' }, { name: 'Notes' }]}
|
||||
type="phone"
|
||||
/>
|
||||
</div>
|
||||
{/* <hr className="my-5 border-gray-50 dark:border-gray-600" /> */}
|
||||
|
||||
<hr className="my-5 border-gray-50 dark:border-gray-600" />
|
||||
<div className="mt-2 space-x-1">
|
||||
{/* <div className="mt-2 space-x-1">
|
||||
<FileItem
|
||||
selected={selectedFile == 'assets'}
|
||||
onClick={() => handleSelect('assets')}
|
||||
|
@ -132,7 +149,7 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
|||
folder
|
||||
/>
|
||||
</div>
|
||||
<hr className="my-5 border-gray-50 dark:border-gray-600" />
|
||||
<hr className="my-5 border-gray-50 dark:border-gray-600" /> */}
|
||||
|
||||
{/* <hr className="my-5 dark:border-gray-600" /> */}
|
||||
</div>
|
||||
|
|
|
@ -280,6 +280,7 @@ importers:
|
|||
react-error-boundary: ^3.1.4
|
||||
react-hotkeys-hook: ^3.4.4
|
||||
react-json-view: ^1.21.3
|
||||
react-loading-icons: ^1.0.8
|
||||
react-portal: ^4.2.2
|
||||
react-query: ^3.34.19
|
||||
react-router: 6.3.0
|
||||
|
@ -321,6 +322,7 @@ importers:
|
|||
react-error-boundary: 3.1.4_react@18.0.0
|
||||
react-hotkeys-hook: 3.4.4_react-dom@18.0.0+react@18.0.0
|
||||
react-json-view: 1.21.3_761282c47ea27a9f40e1344337910648
|
||||
react-loading-icons: 1.0.8
|
||||
react-portal: 4.2.2_react-dom@18.0.0+react@18.0.0
|
||||
react-query: 3.34.19_react-dom@18.0.0+react@18.0.0
|
||||
react-router: 6.3.0_react@18.0.0
|
||||
|
@ -4834,6 +4836,10 @@ packages:
|
|||
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||
dev: false
|
||||
|
||||
/react-loading-icons/1.0.8:
|
||||
resolution: {integrity: sha512-j0myUwDUPoo3qaPkbgnA7U2RNHqLLC+wXcpMWe+rtk3Iw+mGHltZ3QitPSHFKtsFKlpM9UlMmZGZ6sw6WVVW7w==}
|
||||
dev: false
|
||||
|
||||
/react-portal/4.2.2_react-dom@18.0.0+react@18.0.0:
|
||||
resolution: {integrity: sha512-vS18idTmevQxyQpnde0Td6ZcUlv+pD8GTyR42n3CHUQq9OHi1C4jDE4ZWEbEsrbrLRhSECYiao58cvocwMtP7Q==}
|
||||
peerDependencies:
|
||||
|
|
Loading…
Reference in a new issue