mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-14 05:24:04 +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-error-boundary": "^3.1.4",
|
||||||
"react-hotkeys-hook": "^3.4.4",
|
"react-hotkeys-hook": "^3.4.4",
|
||||||
"react-json-view": "^1.21.3",
|
"react-json-view": "^1.21.3",
|
||||||
|
"react-loading-icons": "^1.0.8",
|
||||||
"react-portal": "^4.2.2",
|
"react-portal": "^4.2.2",
|
||||||
"react-query": "^3.34.19",
|
"react-query": "^3.34.19",
|
||||||
"react-router": "6.3.0",
|
"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(
|
className={clsx(
|
||||||
'border-2 border-transparent rounded-lg text-center w-[100px] h-[100px] mb-1',
|
'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 React, { useState } from 'react';
|
||||||
|
import { Device } from '../components/device/Device';
|
||||||
import FileItem from '../components/file/FileItem';
|
import FileItem from '../components/file/FileItem';
|
||||||
|
|
||||||
interface StatItemProps {
|
interface StatItemProps {
|
||||||
|
@ -9,7 +11,7 @@ interface StatItemProps {
|
||||||
|
|
||||||
const StatItem: React.FC<StatItemProps> = (props) => {
|
const StatItem: React.FC<StatItemProps> = (props) => {
|
||||||
return (
|
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-sm text-gray-400">{props.name}</span>
|
||||||
<span className="text-2xl font-bold">
|
<span className="text-2xl font-bold">
|
||||||
{props.value}
|
{props.value}
|
||||||
|
@ -23,17 +25,16 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
||||||
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
const [selectedFile, setSelectedFile] = useState<null | string>(null);
|
||||||
|
|
||||||
function handleSelect(key: string) {
|
function handleSelect(key: string) {
|
||||||
// if (selectedFile === key) setSelectedFile(null);
|
if (selectedFile === key) setSelectedFile(null);
|
||||||
// else setSelectedFile(key);
|
else setSelectedFile(key);
|
||||||
setSelectedFile(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full h-screen">
|
<div className="flex flex-col w-full h-screen">
|
||||||
<div data-tauri-drag-region className="flex flex-shrink-0 w-full h-7" />
|
<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 flex-col w-full h-screen px-3 overflow-scroll">
|
||||||
<div className="flex items-center w-full px-2">
|
<div className="flex items-center w-full ml-4">
|
||||||
<div className="flex flex-wrap space-x-4">
|
<div className="flex flex-wrap p-2 space-x-6">
|
||||||
<StatItem name="Total capacity" value="26.5" unit="TB" />
|
<StatItem name="Total capacity" value="26.5" unit="TB" />
|
||||||
<StatItem name="Index size" value="103" unit="MB" />
|
<StatItem name="Index size" value="103" unit="MB" />
|
||||||
<StatItem name="Preview media" value="23.5" unit="GB" />
|
<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" />
|
<StatItem name="Total backed up" value="25.3" unit="TB" />
|
||||||
</div>
|
</div>
|
||||||
</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
|
<FileItem
|
||||||
selected={selectedFile == 'assets'}
|
selected={selectedFile == 'assets'}
|
||||||
onClick={() => handleSelect('assets')}
|
onClick={() => handleSelect('assets')}
|
||||||
|
@ -132,7 +149,7 @@ export const OverviewScreen: React.FC<{}> = (props) => {
|
||||||
folder
|
folder
|
||||||
/>
|
/>
|
||||||
</div>
|
</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" /> */}
|
{/* <hr className="my-5 dark:border-gray-600" /> */}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -280,6 +280,7 @@ importers:
|
||||||
react-error-boundary: ^3.1.4
|
react-error-boundary: ^3.1.4
|
||||||
react-hotkeys-hook: ^3.4.4
|
react-hotkeys-hook: ^3.4.4
|
||||||
react-json-view: ^1.21.3
|
react-json-view: ^1.21.3
|
||||||
|
react-loading-icons: ^1.0.8
|
||||||
react-portal: ^4.2.2
|
react-portal: ^4.2.2
|
||||||
react-query: ^3.34.19
|
react-query: ^3.34.19
|
||||||
react-router: 6.3.0
|
react-router: 6.3.0
|
||||||
|
@ -321,6 +322,7 @@ importers:
|
||||||
react-error-boundary: 3.1.4_react@18.0.0
|
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-hotkeys-hook: 3.4.4_react-dom@18.0.0+react@18.0.0
|
||||||
react-json-view: 1.21.3_761282c47ea27a9f40e1344337910648
|
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-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-query: 3.34.19_react-dom@18.0.0+react@18.0.0
|
||||||
react-router: 6.3.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==}
|
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
|
||||||
dev: false
|
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:
|
/react-portal/4.2.2_react-dom@18.0.0+react@18.0.0:
|
||||||
resolution: {integrity: sha512-vS18idTmevQxyQpnde0Td6ZcUlv+pD8GTyR42n3CHUQq9OHi1C4jDE4ZWEbEsrbrLRhSECYiao58cvocwMtP7Q==}
|
resolution: {integrity: sha512-vS18idTmevQxyQpnde0Td6ZcUlv+pD8GTyR42n3CHUQq9OHi1C4jDE4ZWEbEsrbrLRhSECYiao58cvocwMtP7Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
|
Loading…
Reference in a new issue